Wim Leers

  1. Introduction
  2. How to use
  3. Architecture
  4. Code
  5. Other possibilities

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


Perceived performance

  1. TTFB = Time to First Byte
  2. TTAP = Time to Assets Prefetching
  3. TTI = Time to Interact
  4. Page load time = total load time: until finished loading

First, optimize front end in typical way:

  • optimize images
  • minify & aggregate CSS/JS
  • fewer HTTP requests (even for HTTP/2 in 2016)

… but if the back end is already slow, the front end alone cannot make it fast.

Intersection of front-end & back-end performance


  1. Uncached/regular
  2. Page Cache
  3. Dynamic Page Cache
  4. BigPipe

0. Uncached/regular

Drupal needs to lots of things before replying

TTFB, TTAP, TTI: typically 100s of ms (~ back end)

1. Page cache

Drupal needs to do almost nothing before replying

  • TTFB: ms (<10 ms)
  • TTAP: low 10s of ms
  • TTI: 100s of ms (~ front-end code)

  • For anonymous users (unpersonalized pages)
  • Enabled by default in Drupal 8

2. Dynamic Page cache

Drupal needs to several things before replying

  • TTFB: 10s to low 100s of ms (~ back-end code)
  • TTAP: 100s of ms
  • TTI: high 100s of ms (~ back-end + front-end code)

  • For authenticated users (personalized pages)
  • Enabled by default in Drupal 8

3. BigPipe

Drupal needs to do almost nothing before replying, BUT continues to do things afterwards

  • TTFB: low 10s of ms (~ server, typical: 15-40)
  • TTAP: 10s of ms
  • TTI: low 100s of ms (~ front end)

  • For authenticated users (personalized pages)
  • Candidate for Drupal 8.1 or 8.2


At a high level

Cacheability metadata in Drupal 8

  • Cache tags (data dependencies)
  • Cache contexts (context dependencies)
  • Cache max-age (time dependencies)


Cacheability bubbled during rendering!

Cache tags + reverse proxy

Cache tags: efficient invalidation of Varnish/CDN

Page cache = reverse proxy ⇒ tested!

Improved Render API

  • rendering in isolation: "lazy builders"
  • auto-placeholdering
           max-age: 0
           contexts: ['session', 'user']
           tags: []

renderable in isolation
poor cacheability

auto-placeholdered: deferred rendering via BigPipe

Animation by @shawnmmccabe

2. How to use

How to use BigPipe on your Drupal site

Install BigPipe module — zero configuration


Try BigPipe demo

  • Locally or on sandbox
  • Simulate slow blocks
  • ?big_pipe=off, observe difference


Just try

"Jumpiness"? → make CSS more robustalso great for 3G!

Slow code not BigPipe'd? → simulate in BigPipe demo then fix


  1. Play
    observe the difference
  2. Analyze
    site has uncacheable/personalized content?
  3. Simulate
    install BigPipe (demo)
  4. Adopt
    fix cacheability metadata/render logic

3. Architecture

How Drupal is able to do this

  1. Cacheability metadata
    tags, contexts, max-age + bubbling
  2. Isolated rendering
    #lazy_builder callbacks
  3. Auto-placeholdering
    if poorly cacheable and renderable in isolation
  4. Placeholder strategies
    default = replace before sending complete response (1 flush)
    BigPipe = replace after sending unpersonalized response (N flushes)

BigPipe sends:

  1. initial page directly from Dynamic Page Cache
  2. rendered BigPipe placeholders, in DOM order

d.o/developing/api/8/render/pipeline +

Edge cases

Page is sent in multiple flushes for:

  • Non-HTML placeholders form action URL …
  • All placeholders if JavaScript off

BigPipe always is an improvement

4. Code

How you can make your code BigPipe-compatible

4.A Cacheability metadata

To know when something is uncacheable/personalized.

In practice

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 per combination of permissions, per URL, per interface language, per … 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:

  • configuration + entities (content & config)
  • access results
  • block, context, condition plugins

4.B Renderable in isolation

To be able to defer rendering via BigPipe.

Typical: uncacheable, always slow

// 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

Better: render cacheable

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)

Best: render cacheable & isolated

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

Developer tool: renderviz

Visualizes cacheability metadata

d.o/project/renderviz +

5. Other possibilities

  • More placeholder strategies, such as ESI
  • Turbolinks load difference between current & next page
  • Serviceworkers client-side proxy updated based on cacheability metadata


Try BigPipe