hexojs / hexo

A fast, simple & powerful blog framework, powered by Node.js.
https://hexo.io
MIT License
38.84k stars 4.76k forks source link

Enhancement of fragment_cache - enhance_cache #3897

Open SukkaW opened 4 years ago

SukkaW commented 4 years ago

Check List

Please check followings before submitting a new feature request.

Feature Request

Aggressive Cache for Partial

Fragment cache was introduced in #637. It stores the function's output (usually partial()'s) to a big Object and later read value from it.

Before we could completely understand hexo's rendering & generation process, fragment cache was probably the best way to solve the issue like #2164 (binding locals again and again for thousands of times) and #2991 (renders everything and only do comparison when writing files into disk).

Let's take a look at #1769 again. Before the theme utilizing fragment_cache(), although files are loaded less than 5 seconds, but rendering process has lasted for 4 min and no files has changed and written to drive. This issue is definitely the result of #2164 — rendering everything but nothing generated. But after just enable fragment_cache for sidebar partial the rendering process drops from 4 min to 6.41s.

Users could get significant performance boost if theme developer could utilize fragment_cache() correctly, but currently most of the theme developers don't know how to use fragment_cache() correctly: #3071 (fragment_cache can be enabled directly in partial()) & #3738 (fragment cache shouldn't be enabled in a partial when its locals could change).

Recently I have noticed https://github.com/ppoffice/hexo-theme-icarus/commit/958138f2140010e4953cbecb923945fce85b642c : every partial will be cached in fragment cache with cacheId as md5 of locals. Then the output of a partial can be found in the fragment cache if its locals has not changed since the first time call. In this way every partial could enable fragment cache without theme developers enables it manually.

And there is a problem: if a partial has its locals changed frequently, fragment cache will consume memory very, very quickly every time this partial is called. So if we are going to introduce this feature to hexo, we have to first refactoring fragment cache from a big Object to LFU.

I suggest if enhance_cache is enabled, all partial() should be cached by fragment_cache unless { cache:false } is set.

The current problem is how we handle { cache: true } and { cache: [theme developer specific cache id] }. In some cases, theme developer want to cache a partial even locals has been changed.

The current problem is nearly no theme developers are using locals, while config & page contains to much changeable things across different pages. We need a way to recognize what value from site, config or page will be used during rendering the partial. If we have to let theme developer to support enhance_cache by passing locals, the feature will become useless.

Cache output for Helpers

Since it is hard to address aggressive cache for partial, another idea is to enable cache for other helpers. For example, url_for() should output the same result when inputted paths are the same (unless relative option is enabled). Based on this idea we could enable lfu cache for helpers likes url_for(), full_url_for(), gravatar(), meta_generator() and list_[type]().

cc @hexojs/core

SukkaW commented 4 years ago

I have bring up the implementation of this proposal in #3901 & #3092.

In #3902, I add enhance_cache option (default: false) option.

When a theme developer has specified { cache: true } or { cache: [cacheId] }, the fragment_cache will be enabled no matter enhance_cache is enabled or not. For the case a theme developer want to cache this partial even the locals has changed, I believe this approach will be helpful.

When a theme developer doesn't utilize fragment_cache (no specific { cache }), the fragment_cache will still be enabled if enhance_cache is enabled. The cache id will be the md5 of the current locals to make sure there will be different cache across different locals.

When a theme developer has specified { cache: false }, the fragment_cache will be disabled no matter enhance_cache is enabled or not. For the case a theme developer want to disable caching through { cache } option, the enhance_cache will not break current behavior.

When a partial will have different locals across pages and posts, there will be multiple cache inside fragment_cache, causing huge memory consumption. To avoid this, the LFU cache has been introduced in #3901. The called-once cache will be considered as "cold" and be removed from fragment_cache.