- We want D8's authenticated user page loads to be fast.
- Some parts of the page are cacheable across users. To actually cache that across users, we have .
- Other parts need to be dynamically calculated per user, or are simply uncacheable.
- Those dynamic parts should not prevent us from showing the rest of the page first.
(Drupal 8's anonymous user page loads already are fast since.)
We take inspiration from Facebook's BigPipe rendering strategy.
BigPipe demo: https://www.drupal.org/project/big_pipe_demo
- Drupal renders HTML pages using render arrays.
- Render arrays are a tree data structure that abstractly describe the structure (semantical+visual) of a HTML document. They're really render trees, which are a commonly used abstraction for software that needs to render something. Think of them like a browser's DOM+CSSOM, but with a higher level of abstraction.
- Render arrays now contain cacheability metadata: https://www.drupal.org/developing/api/8/render/arrays/cacheability.
- Thanks to that cacheability metadata, we can automatically infer which parts of the render tree are too dynamic to be cached. We can automatically detect these "islands of dynamicness":
- Subtrees that have
max-age = 0are not cacheable at all.
- Subtrees that have "high-cardinality cache contexts" (such as the
'user'cache context, which caches per user, or
'route', which caches per route=route name+params) are cacheable, but would cause such enormous amounts of variations, that the cache hit ratios would be very low.
In either case, we want to replace these "islands of dynamicness" with placeholders, that we render separately, after rendering the ("main") render tree.
If we wouldn't do that, then these "islands of dynamicness" would "infect" the entire render tree, causing the entire render tree to become uncacheable, or to have so many variations that cache hit ratios would be very low.
- Subtrees that have
- Note that because rendering a render array ("the render tree") is a matter of traversing the tree depth-first, we can automatically discover the parts of the render array that are "too dynamic", and replace them with placeholders. That means we're able to not only replace blocks with placeholders, but anything, i.e. any subtree! It could be something tiny!
For example: it's perfectly possible to render cache an entire block, but let that block contain a placeholder that we need to render after the page has loaded, because only a small part of that block actually is too dynamic to render cache!
- To ensure we are aware of all placeholders on a page, we add "placeholders" as another type of "bubbleable metadata" to
\Drupal\Core\Render\BubbleableMetadata, next to
#attached. That way, after rendering the entire render tree, we'll know all placeholders that need to be replaced, just like we know all assets that need to be loaded (thanks to
#attachedhaving bubbled up the render tree).
- There are many possible methods to replace those placeholders with their final values (markup): Single-Flush (which is what HEAD does), ESI, BigPipe, ESI-in-JS … Drupal needs a selection mechanism to determine which method should be used. Selection mechanism: TBD.
- With a method selected, the placeholders will be replaced with method-specific placeholders: ESI needs
<esi:include>tags, BigPipe needs something else, and Single-Flush just renders the final markup immediately (because, as the name says, it wants to deliver the document in a single flush, which is HEAD's behavior).
Let's look at a simple example:
PAGE |- BLOCK A (max-age = infinite, tags = [x], contexts = ['user.permissions']) |- BLOCK B (max-age = infinite, tags = [y], contexts = ['user.permissions']) |- BLOCK C (max-age = 0) |- BLOCK D (max-age = infinite, tags = , contexts = ['user']) |- BLOCK E (max-age = infinite, tags = [z], contexts = ['user.roles'])
In this example page, blocks A, B and E are cacheable just fine. But blocks C and D aren't: block C is not cacheable at all (needs to be calculated on every page load), block D varies per user. If we'd bubble their cacheability metadata as we normally would, then the entire page would not be cacheable. But the majority of the page is cacheable! So, instead, we want to serve the page with the cacheable blocks A, B and E first, and then render blocks C and D after the page has loaded.
Note that we could cache block D: caching a block per user is probably acceptable, but caching a route's response per permissions, roles and user is definitely not: that'll cause too many variations. We'll just have to load block D after the page has already loaded, so that the output that is cacheable across authenticated users is visible ASAP.
In other words: we don't want to let blocks C or D "infect" the cacheability of the entire page.
- Step 1: "placeholders:
- Step 2: auto-placeholdering:
- Step 3: render strategies: abstraction + SingleFlush (=== HEAD):
- Step 4: BigPipe render strategy (this issue)
Remaning task list:
SessionExistsCacheContextto Drupal 8 core; it's useful to more than just BigPipe, and should therefore not live in the BigPipe module:
Test coverage: #210, see Refine README — see https://www.drupal.org/documentation/modules/big_pipe + https://www.drupal.org/documentation/modules/big_pipe/environment, done in #244 Make it easy to set up a demo, and provide a live demo:
- Test coverage/hardening against #212.4, follow-up created: .
User interface changes
Original problem/motivation section by Fabianx
blocked by(or will be, after the proof of concept phase)
- We want core to be fast
- We want to ensure we can cache pages that have blocks with max-age=0 (set directly from the config) [IMPLEMENTED]
- We want to ensure we can cache pages that have blocks with a bubbled max-age=0 [@todo]
- We can optionally stream data via JS after the fact
- We want to replace high-cardinality cache contexts (e.g. user) with a placeholder (set directly from config)
- We want to replace high-cardinality cache contexts (e.g. user) that come bubbled up with a placeholder
|#274||Screen Shot 2016-06-01 at 4.21.50 PM.png||97.46 KB||RavindraSingh|
|#264||big_pipe-2469431-263.patch||121.41 KB||Wim Leers|
PASSED: [[SimpleTest]]: [PHP 5.4 MySQL] 91,923 pass(es). View