Quantcast
Channel: Issues for Drupal core
Viewing all articles
Browse latest Browse all 293241

Add a cache prewarm API

$
0
0

Problem/Motivation

After a full cache clear, Drupal has to build up various caches before it can serve a request. Element info, route preloading, menu links, library info, various plugin registries, theme registry, twig templates, CSS assets (#1014086: Stampedes and cold cache performance issues with css/js aggregation) etc.

This both takes a long time, and massively increases the memory usage of a request.

Steps to reproduce

drush cr, load a heavy-ish page, watch that spinny wheel / check mem_get_usage()

Proposed resolution

PHP 8.1 adds Fibers https://php.watch/versions/8.1/fibers / https://wiki.php.net/rfc/fibers

In DrupalKernel::handle(), execute HttpKernerl::handleRequest()in a fiber. This allows any code, anywhere in core, to suspend itself using Fiber::suspend(), the place we suspend is for now after entering lock wait or getting a cache miss in particular services - this should only be done for cache items that are used to build most pages - plugins and registries, but not render caching or anywhere like that. But it could also be after executing an async query once we have a database driver that supports it.

At the same time, services can tag themselves using the cache_prewarmable service tag and implement PreWarmableInterface. This is not mutually exclusive with calling suspend on a cache miss, some services can do both.

When a fiber has suspended, DrupalKernel is then able to call the cache_prewarmer service, which gets a list of tagged service IDs. It calls the ::prewarm() method of one of these services at random, then resumes the original fiber and the request continues as normal. The prewarm method will usually just call a different method on the service, this needs to be one that both checks and sets the cache - we always want to assume it might have been set before we got there.

If the fiber gets suspended again later in the request, we'll go back to the cache prewarmer service and pick a different one.

This is complementary to #3377570: Add PHP Fibers support to BigPipe and in general is designed to speed up the part of request handling that precedes having bigpipe placeholders to render, but since different pages hit different caches at different times, sometimes the 'same' Fiber::suspend() call will result in going back to DrupalKernel and warming a cache, sometimes it will result in moving onto a different BigPipe placheholder. This is fine and by design, the code inside the fiber doesn't care what happens in between suspending itself and being resumed, it's just anticipating that something might be able to happen and then bigpipe and the prewarmer service take care of what that 'something' is.

The big advantage of Fibers is that as long as the code executing a fiber handles them correctly, and as long as the code that might be executing inside a fiber checks its inside a fiber first before suspending, they don't need to know anything about each other at all. The 'parent' starts and resumes, and the 'child' suspends, and it doesn't need to be any more entangled than that.

Here's a very simplified visual indication of what stampedes look like in terms of service cache get misses, building, and setting:

Each letter represents a service.
Aget (cache get)
A-set (build and cache set).

Extra hyphens means a longer period of time building the cache item.

C and D are nested services with one calling the other, equivalent to the views examples above. When C is warm, D doesn't get requested.

When we have one request, it just builds each service sequentially.

AgetA-set[NO-PRE-WARMING-]BgetB--------setCgetDgetD-setC--setEgetE----setFget-Fset

In HEAD, when we get a stampede. All the requests build and set each cache sequentially. The requests with Z and F at the end represent different pages (node vs. front vs. user or whatever), which may have different caches to build.

[NO-PRE-WARMING-] is a space filler because suspend and resume take up space on the line..

[NO-PRE-WARMING-]
[SUSPEND][RESUME]
<code>
AgetA-set[NO-PRE-WARMING-]BgetB--------setCgetDgetD-setC--setEgetE----setFgetF-set
AgetA-set[NO-PRE-WARMING-]BgetB--------setCgetDgetD-setC--setEgetE----setFgetF-set
AgetA-set[NO-PRE-WARMING-]BgetB--------setCgetDgetD-setC--setEgetE----setFgetF-set
AgetA-set[NO-PRE-WARMING-]BgetB--------setCgetDgetD-setC--setEgetE----setZgetZ-set
AgetA-set[NO-PRE-WARMING-]BgetB--------setCgetDgetD-setC--setEgetE----setZgetZ-set

With the patch, if we get two requests at the same time, we start to see a benefit (without any async):

AgetA-set[SUSPEND]BgetB--------set[RESUME]BgetCgetDgetC--setEgetFgetF-set
AgetA-set[SUSPEND]DgetD-set[RESUME]BgetCgetDgetC--setEgetE---setFgetF-set

Now with five requests, it's making much more difference:

AgetA-set[SUSPEND]BgetB--------set[RESUME]BgetCgetEgetZget
AgetA-set[SUSPEND]FgetF-set[RESUME]BgetCgetEgetZgetZ-set
AgetA-set[SUSPEND]FgetF-set[RESUME]BgetCgetEgetFget
AgetA-set[SUSPEND]CgetDgetD-setC--Set[RESUME]CgetEgetFget
AgetA-set[SUSPEND]EgetE---set[RESUME]BgetCgetEgetFget

Note also that because B and D take different amounts of time, in this case even though Z isn't prewarmed, the two requests that need it get there at different times, so only one builds it and the other gets a cache hit - this is an effect of the offsetting/shuffling that the prewarmer does. Say one prewarmable cache takes 800ms to build and the other takes 8ms, the one that's done with prewarming after 8ms will get a lot more done before the 800ms prewarming is finished.

Remaining tasks

Once there are enough prewarmable services defined, we should be able to manually test this by clearing the cache then using ab -n1 -c10, probably with the standard profile, to see if there's a measurable performance improvement.

Once the API is in core, we can potentially add full async cache prewarming support to drush. I think drush could then build all the prewarmable caches at the same time immediately after a cache clear.
https://github.com/drush-ops/drush/issues/5724

User interface changes

API changes

Adds PreWarmableInterface

Data model changes

Release notes snippet


Viewing all articles
Browse latest Browse all 293241

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>