🌴 Barcelona edition 🌴
I have installed Drupal. However, it's running very, very slowly. Hitting the front page or clicking on a link such as administer can result in delays of 15-30 seconds. Caching is enabled.
There is almost nothing in my installation. Very few users, very little content.
d.o/node/63722 — complaining about Drupal 4.7!
November 2007Why is my drupal site so slow?
February 2013[…] navigating between admin pages is slower than an arthritic snail.
November 2011Drupal 7 is unnaceptably slow
Poor Drupal…
???
function cache_get() {
global $user, $REQUEST_URI, $REQUEST_METHOD;
if (!$user->id && $REQUEST_METHOD == "GET") {
if ($cache = db_fetch_object(db_query("SELECT * FROM cache WHERE url = '". check_input($REQUEST_URI) ."'"))) {
cache_clear(variable_get("cache_clear", 30));
}
else {
ob_start();
}
}
return $cache->data ? $cache->data : 0;
}
… since June 30, 2001!
[...] generating a page in Drupal 5 is 3% slower than in Drupal 4.7. [...] serving a cached page [...] Drupal 5 is 73% faster [...] and 268% faster when the aggressive database cache is used.
However Drupal 4.7 back then looked like this.
But overall ...
We are starting to see a trend here.
September 2015Drupal 8 is the fastest Drupal ever.
Optimize to do:
expire
/ varnish
module in D7.The theory of how we make Drupal fast.
drupal_add_css()
, drupal_add_js()
…
#attached
asset libraries solve thaturl()
's output depended on:
<front>
configuration
+
Cacheability bubbled during rendering!
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:
To make it easier:
Renderer::addCacheableDependency($build, $dependency)
$site_config = $this->config->get('system.site');
$build = [
'#markup' => t('Welcome to @site, @user!', $site_config->get('name')),
];
$this->renderer->addCacheableDependency($build, $site_config)
(Drupal rendering a page ~ building a ship)
So the remaining problem we faced was ...
... that even with all those dependencies ...
... the cacheability was not good.
... and things were slower than they could be. :-(
But why?
So let's make the pages less dynamic
Fortunately!
Drupal 8 has a solution for that!
- and it is already in Core!
#lazy_builder
$build['complex_thing'] = [
'#lazy_builder' => [$callback, $args]
];
$output['comment_form'] = [
'#lazy_builder' => ['comment.lazy_builders:renderForm', [
$entity->getEntityTypeId(),
$entity->id(),
$field_name,
$this->getFieldSetting('comment_type'),
]],
];
throw new \DomainException("A #lazy_builder callback's context may
only contain scalar values or NULL.");
throw new \DomainException(sprintf('When a #lazy_builder callback
is specified, no children can exist; all
children must be generated by the
#lazy_builder callback. You specified
the following children: %s.',
implode(', ', $children)));
A #lazy_builder
:
⇒ able to render in isolation!
renderer.config:
auto_placeholder_conditions:
max-age: 0
contexts: ['session', 'user']
tags: []
Configurable!
$output['comment_form'] = [
'#lazy_builder' => ['comment.lazy_builders:renderForm', …]
];
⬇︎
$output['comment_form'] = [
'#lazy_builder' => ['comment.lazy_builders:renderForm', …]
'#cache' => [
'contexts' => ['user'],
],
];
$output['comment_form'] = [
'#lazy_builder' => ['comment.lazy_builders:renderForm', …]
'#cache' => [
'contexts' => ['user'],
],
];
⬇︎
$output['comment_form'] = [
'#lazy_builder' => ['comment.lazy_builders:renderForm', …]
'#cache' => [
'contexts' => ['user'],
],
'#create_placeholder' => TRUE,
];
$output['comment_form'] = [
'#lazy_builder' => ['comment.lazy_builders:renderForm', …]
'#cache' => ['contexts' => ['user']],
'#create_placeholder' => TRUE,
];
⬇︎
$output['comment_form'] = [
'#markup' => ' ',
'#attached' => [
'placeholders' => [
' ' => [
'#lazy_builder' => ['comment.lazy_builders:renderForm', …]
'#cache' => ['contexts' => ['user']],
],
],
],
];
placeholder_strategy:
class: Drupal\Core\Render\Placeholder\ChainedPlaceholderStrategy
tags:
- { name: service_collector, tag: placeholder_strategy, call: addPlaceholderStrategy }
placeholder_strategy.single_flush:
class: Drupal\Core\Render\Placeholder\SingleFlushStrategy
tags:
- { name: placeholder_strategy, priority: -1000 }
class SingleFlushStrategy implements PlaceholderStrategyInterface {
public function processPlaceholders(array $placeholders) {
// Return all placeholders as is; they should be rendered directly.
return $placeholders;
}
}
class BigPipeStrategy implements PlaceholderStrategyInterface {
public function processPlaceholders(array $placeholders) {
foreach ($placeholders as $placeholder => $placeholder_elements) {
$return[$placeholder] = [
'#markup' => '',
'#cache' => [
'max-age' => 0,
],
'#attached' => [
'library' => ['big_pipe/big_pipe'],
],
];
}
return $return;
}
}
Don't forget to add its cache tags!
Renderer::addCacheableDependency($build, $config)
Don't forget to add its cache tags!
Renderer::addCacheableDependency($build, $entity)
Don't forget to add the entity's cache tags!
Renderer::addCacheableDependency($build, $entity)
Don't forget to set max-age to zero!
$build['#cache']['max-age'] = 0;
Either:
{{ link(something.title, something.url) }}
#type => link
:
$build['link'] = [
'#type' => 'link',
'#title' => 'Drupal',
'#url' => Url::fromUri('https://drupal.org'),
];
$args = ['%username' => $_COOKIE['username']];
$build['#markup'] = $this->t('Hi, %username', $args);
$build['#cache']['contexts'][] = 'cookies:username';
In sites/yoursite.com/services.yml
:
parameters:
renderer.config:
required_cache_contexts:
# The two default required cache contexts.
- 'languages:language_interface'
- 'theme'
# The one we added!
- 'cookies:device'
wimleers.com/talk/making-drupal-fly-fastest-drupal-ever-here
d.o/developing/api/8/cache/contexts
d.o/developing/api/8/render/arrays/cacheability