Wim Leers |
Fabian Franz |
Front end
↕
Back end
Front end: CSS, JS, images, fonts … (assets)
↕
Back end: HTML
80–90% of time is spent on front end
⬇
Bigger speed gains on front end than on back end
Performance
↕
Perceived performance
First, optimize front end in typical way:
… but if the back end is already slow, the front end alone cannot make it fast.
Intersection of front-end & back-end performance
BigPipe | Traditional | |
50 ms | Initial HTML | … |
---|---|---|
100 ms | More HTML | … |
150 ms | More HTML | … |
200 ms | More HTML | … |
250 ms | Final HTML | ALL HTML |
⬇
Page header visible after ~150 ms instead of ~350 ms
⬇
Changed back end improves perceived performance
Comparing
Drupal needs to lots of things before replying
TTFB, TTAP, TTI: typically high 100s of ms (~ back end)
Drupal needs to do almost nothing before replying
Drupal needs to several things before replying
Drupal needs to do almost nothing before replying, BUT continues to do things afterwards
At a high level
+
Cacheability bubbled during rendering!
Cache tags: efficient invalidation of Varnish/CDN
Page cache = reverse proxy ⇒ tested!
http://wimleers.com/talk/caching-at-the-edge-cdns-for-everyone
renderer.config:
auto_placeholder_conditions:
max-age: 0
contexts: ['session', 'user']
tags: []
renderable in isolation
+
poor cacheability
⬇
auto-placeholdered: deferred rendering via BigPipe
How to use BigPipe on your Drupal site
Install BigPipe module — zero configuration
Try BigPipe demo
?big_pipe=off
, observe differenceJust try
"Jumpiness"? → make CSS more robustalso great for 3G!
Slow code not BigPipe'd? → simulate in BigPipe demo then fix
So:
How Drupal is able to do this
#lazy_builder
callbacks (guarantee isolation)BigPipe sends:
d.o/developing/api/8/render/pipeline + wimleers.com/talk/drupal-8-render-pipeline
No infrastructure needed!
(Just the ability to stream responses: no buffering.)
⬇
For hobbyist & enterprise!
⬇
Replaced without JavaScript
⬇
BigPipe always is an improvement
How you can make your code BigPipe-compatible
To know when something is uncacheable/personalized.
Try to make this thought process a habit:
I'm rendering something. That means I must think of cacheability!
Is this something that's expensive to render, and therefore is worth caching?
↪︎ If "yes": cache keys.
$build['#cache']['keys'] = ['node', 5, 'teaser'];
Does the representation of the thing I'm rendering vary by permissions, by URL, by interface language, by … something?
↪︎ If "yes": cache contexts.
$build['#cache']['contexts'][] = 'user.permissions';
$build['#cache']['contexts'][] = 'url';
~ HTTP's Vary
header
What causes the representation of the thing I'm rendering become outdated?
↪︎ If "yes": cache tags.
$build['#cache']['tags'][] = 'node:5';
$build['#cache']['tags'][] = 'user:3';
$build['#cache']['tags'][] = 'taxonomy_term:23';
When does the representation of the thing I'm rendering become outdated?
↪︎ If "yes": cache max-age.
$build['#cache']['max-age'] = Cache::PERMANENT;
~ HTTP's Cache-Control: max-age
header
All relevant objects provide cacheability metadata!
interface CacheableDependencyInterface {
public function getCacheContexts();
public function getCacheTags();
public function getCacheMaxAge();
}
Implemented by:
To be able to defer rendering via BigPipe.
// Drupal 7.
function current_temperature() {
return [
'#markup' => temp_from_satellite(new LatLong('US', 'Boston')),
];
}
temp_from_satellite()
runs always
// Drupal 8: now with cacheability metadata.
function current_temperature() {
return [
'#cache' => ['max-age' => 5], //→ cacheable for 5 seconds
'#markup' => temp_from_satellite(new LatLong('US', 'Boston')),
];
}
Still runs always
function current_temperature() {
return [
'#cache' => [
'keys' => ['temperature'], //→ triggers render caching
'max-age' => 5,
],
'#latlong' => new LatLong('US', 'Boston'),//→ used in #pre_render
'#pre_render' => [
['current_temp_pre_render'], //→ does not run on cache hit
],
];
}
function current_temp_pre_render($build) {
$build['#markup'] = get_temp_from_satellite($build['#latlong']);
return $build;
}
temp_from_satellite()
runs only if cache miss
But not isolated (can use anything in $build
)
function current_temperature() {
return [
'#cache' => [
'keys' => ['temperature'],
'max-age' => 5,
],
'#lazy_builder' => [
'current_weather_lazy_builder', //→ callback
['US', 'Boston'], //→ primitive args for callback
],
];
}
function current_temp_lazy_builder($country, $city) {
$latlong = new LatLong($country, $city));
return ['#markup' => get_temp_from_satellite($latlong)];
}
All calculations happen in the callback (including LatLong
)
Isolated ⇒ deferable ⇒ BigPipe
Visualizes cacheability metadata
d.o/project/renderviz + wimleers.com/blog/renderviz-prototype
RefreshLess
Turbolinks-but-better
(automatic, loads only parts that change)
Thanks to cacheability metadata!