Goal
Improve performance
How
Store the result of slow calculations so that they do not need to be
executed again
Documentation: api.drupal.org caching topic
$key = 'my-unique-cache-key';
if ($cache = \Drupal::cache()->get($key)) {
$data = $cache->data;
}
else {
$data = my_slow_calculation();
\Drupal::cache()->set($key, $data);
}
// Alternative for multiple items
\Drupal::cache()->getMultiple($keys);
\Drupal::cache()->setMultiple($items);
Static caching refers to the concept doing in-memory, per-request caching
In Drupal 7 and non-OOP code in Drupal 8, the function drupal_static() is often used
In Drupal 8, using a non-static property on a service works in the same way
Cache backends usually do not use static cache, requesting the same item twice results in two queries
protected $data;
protected function getData() {
if ($this->data === NULL) {
$key = 'my-unique-cache-key';
if ($cache = $this->cacheBackend->get($key)) {
$this->data = $cache->data;
}
else {
$this->data = $this->buildData();
$this->cacheBackend->set($key, $this->data);
}
}
return $this->data;
}
Storage for cache items
Problem: APCu is fast but not shared across multiple servers/with drush
Limited use cases, no non-test uses right now in core
Can be used as an alternative to the static cache pattern shown before
$chain = new BackendChain('noop');
$chain
->appendBackend(\Drupal::cache('static'))
->appendBackend(\Drupal::cache('default'));
$chain->get('my-unique-cache-key');
$chain->get('my-unique-cache-key');
A container for cache entries with a configurable backend
\Drupal::cache($bin)->get()
$container->get("cache.$bin")
yourmodule.services.yml:
cache.your_bin:
class: Drupal\Core\Cache\CacheBackendInterface
tags:
- { name: cache.bin }
factory: cache_factory:get
arguments: [your_bin]
// Use redis by default.
$settings['cache']['default'] = 'cache.backend.redis';
// Use the null backend to disable caching for certain bins.
$settings['cache']['bins']['render'] = 'cache.backend.null';
How to configure Redis: See README.md
Never develop without using sites/example.settings.local.php
There are only two hard things in Computer Science: cache invalidation, naming things and off-by-one errors.http://martinfowler.com/bliki/TwoHardThings.html
In case there is a single or only a few cache item with known keys, delete them directly
\Drupal::cache()->delete('my-key');
\Drupal::cache()->deleteMultiple(['my-key:de', 'my-key:en']);
\Drupal::cache()->deleteAll();
Faster than cache tags, no runtime overhead. If possible, just write new caches instead of using delete()
Same methods as delete, except invalidate*(). Does not actually delete just marks records as invalidated.
\Drupal::cache()->invalidate('my-key');
\Drupal::cache()->invalidateMultiple(['my-key:de', 'my-key:en']);
\Drupal::cache()->invalidateAll();
Extremely rarely used. Core has a single invalidate call and that is likely wrong*.
* Might be my fault.
Set a fixed expiration time for a cache item on write.
Examples
// Cache for 10 minutes, not known if/when it changes.
$cache->set($key, $data, REQUEST_TIME + 600);
// $data is only valid today.
$cache->set($key, $data, strtotime('tomorrow midnight'));
Note: Expiration is a timestamp, not an age
Dependencies of your cache: entities, configuration, custom
// Data must be invalidated if node 1 or the node settings change.
$cache->set($key, $data, ..., ['node:1', 'config:node.settings']);
// Tag invalidation is across all bins.
$cache_tag_invalidator->invalidateTags(['my_tag']);
Cache::invalidateTags(['my_tag']);
Entity and Config cache tags are invalidated automatically. Use custom cache tags for unknown amount of cache item variations.
Runtime overhead on every cache read. Limit cache tags to the required minimum.
Invalid data can still be used.
$cache = \Drupal::cache()->get('my-key', TRUE);
if ($cache && $cache->valid) {
return $cache->data;
}
elseif (\Drupal::lock()->acquire('my-key')) {
// Rebuild and set new data.
}
elseif ($cache) {
// Someone else is rebulding, work with stale data.
return $cache->data;
}
else {
// Wait or rebuild.
}
Other uses: Rebuild in background (queue), min-age
$build = $view_builder->view($node, 'full');
www.drupal.org/docs/drupal-apis/render-api/cacheability-of-render-arrays
Is my data worth caching? If yes, what identifies my data?
Does my output vary by something?
Examples:
theme, language, user roles, permissions, URL, query
arguments, timezone, ...
Default cache contexts:
theme, languages:language_interface, user.permissions
What things does my output depend on?
What changes require that my data is regenerated?
When does my output become outdated?
{cache_render}
cid: entity_view:node:4:full:[languages:language_interface]=en
:[theme]=bartik:[timezone]=Europe/Berlin:
[url.query_args:key]=:[user.permissions]=da0b1c64....:
[user.roles]=is-super-user
data: a:3:{s:7:"#markup";O:25:"Drupal\Core\Render\Markup....
expire: -1
tags: config:filter.format.basic_html config:image.style.large
file:1 node:4 node_view rendered taxonomy_term:1 user:1
user_view
checksum: 17
Every node view on your site has the node_list cache tag
Example: "Promoted events" block in sidebar
Every time any node is saved, the block and every page containing that block is invalidated
Allows to use a configurable cache, e.g. event:promoted
Placeholders: news:section:{{ raw_arguments.arg1 }}
Drupal 9+ now has built-in bundle cache tags, like node_list:article
More specific use cases requires code to define/invalidate cache tags in a hook_entity_presave()
For self-contained parts of a page
Examples: Blocks, Comment form, Poll, CSRF tokens, Flag links
A callback with arguments (scalar values only)
Can be put as a placeholder in cached data if uncacheable/many variations
$output['comment_form'] = [
'#lazy_builder' => ['comment.lazy_builders:renderForm', [
$entity->getEntityTypeId(),
$entity->id(),
$field_name,
$this->getFieldSetting('comment_type'),
]],
'#create_placeholder' => TRUE,
];
Cache contexts can bubble up.. but they are part of the key that needs to be known initially
The secret is that two cache entries are stored, one just contains all the cache contexts
Video by @WimLeers
None of those features are exclusive, they work best when combined together. And Drupal 8 does that automatically, based on cacheability metadata, with almost zero configuration.
Hitchhikers Guide to the Galaxy, http://geekifyinc.com/product/hitchhikers-guide-to-the-galaxy-tablet-ereader-cover/
Knowing some of these concepts can help, but...
* Remember to test with caching enabled before deploying.