Cache API
Cache API
Explore Drupal's Cache API features in-depth
This documentation needs work. See "Help improve this page" in the sidebar.
Drupal's Cache API enables the storage of data that may require a significant amount of time to calculate. This cached data can have various lifespans; it can be kept indefinitely, set to expire after a specified duration, or configured to expire when related data changes. The following sections go into more detail on each feature.
For a brief run-down, see also the Cache API page from the API documentation.
Cacheability metadata
Cacheability metadata, a fundamental concept within Drupal, ensures efficient caching. It is associated with all elements that can be rendered or influence the rendering process, encompassing a wide range of items from access results to entities and URLs. This metadata includes the information and rules that guide the caching and management of cached data, determining when and how content can be cached and ensuring effective cache invalidation. As a result, it leads to improved website performance and efficiency.
Cacheability metadata comprises three key properties:
These represent dependencies on data managed by Drupal, such as entities and configuration. When any of these dependencies change, Drupal knows to invalidate or update the cached content, ensuring that your site's data remains up-to-date.
These handle variations based on the request context. For instance, they account for variations in content that might be influenced by factors like the user's role or language. Cache contexts enable Drupal to serve personalized content efficiently.
This property is responsible for time-sensitive caching. It defines the duration for which content remains valid in the cache before it needs to be re-fetched. Cache max-age ensures that content remains relevant and reduces the load on your server by minimizing unnecessary recalculations.
Practical: how you'll typically use the Cache API
Typically, your code will end up rendering things (blocks, entities, and so on) and your controllers will return render arrays or responses. So, usually, you won't be interacting with the Cache API directly. Instead you'll be using:
Render caching (aka fragment caching)
The Render API uses cacheability metadata embedded in render arrays to perform caching (aka render caching). Therefore, the Cache API should not be used to interact with the render cache (neither to retrieve cache items nor to create new ones).
Response caching
The cacheability metadata used by the Render API (see the previous section) bubbles all the way up to Response objects (usually a
HtmlResponse) that implementCacheableResponseInterface.The cacheability metadata on these Response objects is what allows Drupal 8 to ship with Page Cache and Dynamic Page Cache enabled by default, because it allows them to work transparently: they are always up-to-date and always vary appropriately.
Cache tags
Cache tags = data dependencies
This documentation needs review. See "Help improve this page" in the sidebar.
Cache tags = data dependencies
Cache tags describe dependencies on data managed by Drupal
Why?
Cache tags provide a declarative way to track which cache items depend on some data managed by Drupal.
This is essential for a content management system/framework like Drupal because the same content can be reused in many ways. In other words: it is impossible to know ahead of time where some content is going to be used. In any of the places where the content is used, it may be cached. Which means the same content could be cached in dozens of places. Which then brings us to the famous quote "There are only two hard things in Computer Science: cache invalidation and naming things." — that is, how are you going to invalidate all cache items where the content is being used?
Note: Drupal 7 offered 3 ways of invalidating cache items: invalidate a specific CID, invalidate using a CID prefix, or invalidate everything in a cache bin. None of those 3 methods allow us to invalidate the cache items that contain an entity that was modified, because that was impossible to know!
What?
A cache tag is a string.
Cache tags are passed around in sets (order doesn't matter) of strings, so they are typehinted to string[]. They're sets because a single cache item can depend on (be invalidated by) many cache tags.
Syntax
By convention, identifiers follow the format thing:identifier. When there is no concept of multiple instances of a thing, the format is simply thing. The only rule is that identifiers cannot contain spaces.
There is no strict syntax.
Examples:
node:5— cache tag forNodeentity 5 (invalidated whenever it changes)user:3— cache tag forUserentity 3 (invalidated whenever it changes)node_list— list cache tag forNodeentities (invalidated whenever anyNodeentity is updated, deleted or created, i.e., when a listing of nodes may need to change). Applicable to any entity type in following format:{entity_type}_list.node_list:article— list cache tag for thearticlebundle (content type). Applicable to any entity + bundle type in following format:{entity_type}_list:{bundle}.config:node_type_list— list cache tag forNode typeentities (invalidated whenever any content types are updated, deleted or created). Applicable to any entity type in the following format:config:{entity_bundle_type}_list.config:system.performance— cache tag for thesystem.performanceconfigurationlibrary_info— cache tag for asset libraries
Common cache tags
The data that Drupal manages fall in 3 categories:
- entities — these have cache tags of the form
<entitytypeID>:<entityID>as well as<entitytypeID>_listand<entitytypeID>_list:<bundle>to invalidate lists of entities. Config entity types use the cache tag of the underlying configuration object. - configuration — these have cache tags of the form
config:<configurationname> - custom (for example
library_info)
Drupal provides cache tags for entities & configuration automatically — see the Entity base class and the ConfigBase base class. (All specific entity types and configuration objects inherit from those.)
Although many entity types follow a predictable cache tag format of <entity type ID>:<entity ID>, third-party code shouldn't rely on this. Instead, it should retrieve cache tags to invalidate for a single entity using its::getCacheTags() method, e.g., $node->getCacheTags(), $user->getCacheTags(), $view->getCacheTags() etc.
In addition, it may be necessary to invalidate listings-based caches that depend on data from the entity in question (e.g., refreshing the rendered HTML for a listing when a new entity for it is created): this can be done using EntityTypeInterface::getListCacheTags(), then invalidating any returned by that method along with the entity's own tag(s). Starting with Drupal 8.9 (change notice), entities with bundles also automatically have a more specific cache tag that includes their bundle, to allow for more targetted invalidation of lists.
It is also possible to define custom, more specific cache tags based on values that entities have, for example a term reference field for lists that show entities that have a certain term. Invalidation for such tags can be put in custom presave/delete entity hooks:
function yourmodule_node_presave(NodeInterface $node) {
$tags = [];
if ($node->hasField('field_category')) {
foreach ($node->get('field_category') as $item) {
$tags[] = 'mysite:node:category:' . $item->target_id;
}
}
if ($tags) {
Cache::invalidateTags($tags);
}
}
These tags can then be used in code and in views using the contributed module Views Custom Cache Tag.
Note: There is currently no API to get per-bundle and more specific cache tags from an entity or other object. That is because it is not the entity that decided which list cache tags are relevant for a certain list/query, that depends on the query itself. Future Drupal core versions will likely improve out of the box support for per-bundle cache tags and for example integrate them into the entity query builder and views.
How
Setting
Any cache backend should implement CacheBackendInterface, so when you set a cache item with the ::set() method, provide third and fourth arguments e.g:
$cache_backend->set(
$cid, $data, Cache::PERMANENT, ['node:5', 'user:7']
);This stores a cache item with ID $cid permanently (i.e., stored indefinitely), but makes it susceptible to invalidation through either the node:5 or user:7 cache tags.
Invalidating
Tagged cache items are invalidated via their tags, using cache_tags.invalidator:invalidateTags() (or, when you cannot inject the cache_tags.invalidator service: Cache::invalidateTags()), which accepts a set of cache tags (string[]).
Note: this invalidates items tagged with given tags, across all cache bins. This is because it doesn't make sense to invalidate cache tags on individual bins, because the data that has been modified, whose cache tags are being invalidated, can have dependencies on cache items in other cache bins.
Debugging
All of the above is helpful information when debugging something that is being cached. But, there's one more thing: let's say something is being cached with the cache tags ['foo', 'bar']. Then the corresponding cache item will have a tags column (assuming the database cache back-end for a moment) with the following value:
bar foo
In other words:
- cache tags are separated by space
- cache tags are sorted alphabetically
That should make it much easier to analyze & debug caches!
Headers (debugging)
Finally: it is easy to see which cache tags a certain response depends on (and thus is invalidated by): one must only look at the X-Drupal-Cache-Tags header!
(This is also why spaces are forbidden: because the X-Drupal-Cache-Tags header, just like many HTTP headers, uses spaces to separate values.)
Note: If you're not seeing those headers, you will want to set up your Drupal instance for development.
Integration with reverse proxies
Rather than caching responses in Drupal and invalidating them with cache tags, you could also cache responses in reverse proxies (Varnish, CDN …) and then invalidate responses they have cached using cache tags associated with those responses. To allow those reverse proxies to know which cache tags are associated with each response, you can send the cache tags along with a header.
Just like Drupal can send an X-Drupal-Cache-Tags header for debugging, it can also send a Surrogate-Keys header with space-separated values as expected by some CDNs or a Cache-Tag header with comma-separated values as expected by other CDNs. And it could also be a reverse proxy you run yourself, rather than a commercial CDN service.
As a rule of thumb, it's recommended that both your web server and your reverse proxy support response headers with values of up to 16 KB.
- HTTP is text-based. Cache tags are therefore also text-based. Reverse proxies are free to represent cache tags in a different data structure internally. The 16 KB response header value limit was selected based on 2 factors: A) to ensure it works for the 99% case, B) what is practically achievable. Typical web servers (Apache) and typical CDNs (Fastly) support 16 KB response header values. This means roughly 1000 cache tags, which is enough for the 99% case.
- The number of cache tags varies widely by site and the specific response. If it's a response that depends on many other things, there will be many cache tags. More than 1000 cache tags on a response will be rare.
- But, of course, this guideline (~1000 tags/response is sufficient) may and will evolve over time, as we A) see more real-world applications use it, B) see systems specifically leverage/build on top of this capability.
Finally, anything beyond 1000 cache tags probably indicates a deeper problem: that the response is overly complex, that it should be split up. Nothing prevents you going beyond that number in Drupal, but it may require manual finetuning. Which is acceptable for such extremely complex use cases. Arguably, that's the case even for far less than 1000 cache tags.
Read documentation for using Varnish with cache tags.
CDNs known to support tag-based invalidation/purging:
Internal Page Cache
Comprehensive use of cache tags across Drupal allows Drupal to ship with its Internal Page Cache enabled by default. This is nothing more than a built-in reverse proxy.
See also
Cache contexts
Cache contexts provide a declarative way to create context-dependent variations of something that needs to be cached.
This documentation needs review. See "Help improve this page" in the sidebar.
Cache contexts = (request) context dependencies
Cache contexts are analogous to HTTP's
Varyheader.
Why?
Cache contexts provide a declarative way to create context-dependent variations of something that needs to be cached. By making it declarative, code that creates caches becomes easier to read, and the same logic doesn't need to be repeated in every place where the same context variations are necessary.
Examples:
- Some expensive-to-calculate data depends on the active theme: different results for different themes. Then you'd vary by the
themecache context. - When creating a render array that shows a personalized message, the render array varies per user. Then you'd vary (the render array) by the
usercache context. - Generally: when some expensive-to-calculate information varies by some environment context: vary by a cache context.
Please note that cache contexts are not applied when serving cached pages to anonymous users with the Internal Page Cache (page_cache) module enabled.
What?
A cache context is a string that refers to one of the available cache context services (see below).
Cache contexts are passed around in sets (order doesn't matter) of strings, so they are typehinted to string[]. They're sets because a single cache item can depend on (vary by) many cache contexts.
Typically, cache contexts are derived from the request context (i.e., from the Request) object. Most of the environment for a web application is derived from the request context. After all, HTTP responses are generated in large part depending on the properties of the HTTP requests that triggered them.
But, this doesn't mean cache contexts have to originate from the request — they could also depend on deployed code, e.g., a deployment_id cache context.
Second, cache contexts are hierarchical in nature. The clearest example: when varying something per user, it's pointless to also vary that per permissions (i.e., the set of permissions that a user has), because per-user is already more granular. A user has a set of permissions, so per-user caching implies per-permissions caching.
Now for the most interesting aspect: if one part of the page is varied per user and another per permissions, then Drupal needs to be smart enough to make the combination of the two: only vary per user. That is where Drupal can exploit the hierarchy information to not create unnecessary variations.
Syntax
- periods separate parents from children
- a plurally named cache context indicates a parameter may be specified; to use: append a colon, then specify the desired parameter (when no parameter is specified, all possible parameters are captured, e.g., all query arguments)
Drupal core's cache contexts
Drupal core ships with the following hierarchy of cache contexts:
cookies
:name
headers
:name
ip
languages
:type
protocol_version // Available in 8.9.x or higher.
request_format
route
.book_navigation
.menu_active_trails
:menu_name
.name
session
.exists
theme
timezone
url
.path
.is_front // Available in 8.3.x or higher.
.parent
.query_args
:key
.pagers
:pager_id
.site
user
.is_super_user
.node_grants
:operation
.permissions
.roles
:role
Note: To use the url.path.is_front cache context in prior branches/releases, see the change record.
Everywhere cache contexts are used, that entire hierarchy is listed, which has 3 benefits:
- no ambiguity: it's clear what parent cache context is based on wherever it is used
- comparing (and folding) cache contexts becomes simpler: if both
a.b.canda.bare present, it's obvious thata.bencompassesa.b.c, and thus it's clear why thea.b.ccan be omitted, why it can be "folded" into the parent - no need to deal with ensuring each level in a tree is unique in the entire tree
So, examples of declarative cache contexts from that hierarchy:
theme(vary by negotiated theme)user.roles(vary by the combination of roles)user.roles:anonymous(vary by whether the current user has the 'anonymous' role or not, i.e., "is anonymous user")languages(vary by all language types: interface, content …)languages:language_interface(vary by interface language —LanguageInterface::TYPE_INTERFACE)languages:language_content(vary by content language —LanguageInterface::TYPE_CONTENT)url(vary by the entire URL)url.query_args(vary by the entire given query string)url.query_args:foo(vary by the?fooquery argument)protocol_version(vary by HTTP 1 vs 2)
Optimizing/folding/combining/simplifying of cache contexts
Drupal automatically uses the hierarchy information to simplify cache contexts as much as possible. For example, when one part of the page is varied per user (user cache context) and another part of the page is varied per permissions (user.permissions cache context), then it doesn't make sense to vary the final result (e.g.,: the page) per permissions, since varying per user is already more granular.
In other words: optimize([user, user.permissions]) = [user].
However, that is oversimplifying things a bit: even though user indeed implies user.permissions because it is more specific, if we optimize user.permissions away, any changes to permissions no longer cause the user.permissions cache context to be evaluated on every page load. Which means that if the permissions change, we still continue to use the same cached version, even though it should change whenever permissions change.
That is why cache contexts that depend on configuration that may change over time can associate cacheability metadata: cache tags and a max-age. When such a cache context is optimized away, its cache tags are associated with the cache item. Hence whenever the assigned permissions change, the cache item is also invalidated.
(Remember that "caching" is basically "avoiding unnecessary computations". Therefore, optimizing a context away can be thought of as caching the result of the context service's getContext() method. In this case, it's an implicit cache (the value is discarded rather than stored), but the effect is the same: on a cache hit, the getContext() method is not called, hence: computations avoided. And when we cache something, we associate the cacheability of that thing; so in the case of cache contexts, we associate tags and max-age.)
A similar, but more advanced example are node grants. Node grants apply to a specific user, so the node grants cache context is user.node_grants Except that node grants can be extremely dynamic (they could, e.g., be time-dependent, and change every few minutes). It depends on the node grant hook implementations present on the particular site. Therefore, to be safe, the node grants cache context specifies max-age = 0, meaning that it can not be cached (i.e., optimized away). Hence optimize([user, user.node_grants]) = [user, user.node_grants].
Specific sites can override the default node grants cache context implementation and specify max-age = 3600 instead, indicating that all their node grant hooks allow access results to be cached for at most an hour. On such sites, optimize([user, user.node_grants]) = [user].
Uncacheable contexts
Drupal core treats cache contexts with poor cacheability as uncacheable. The definition of what constitutes "poor cacheability" depends on the renderer.config.auto_placeholder_conditions service container parameter (defined in core.services.yml), which can be overridden by sites.
How to recognize, discover and create?
Cache contexts are cache.context-tagged services. Any module can thus add more cache contexts. They implement \Drupal\Core\Cache\Context\CacheContextInterface or \Drupal\Core\Cache\Context\CalculatedCacheContextInterface (for cache contexts that accept parameters — i.e., cache contexts that accept a :parameter suffix).
Hence, all you have to do to find all cache contexts you have available for use, is to go to the CacheContextInterface and CalculatedCacheContextInterface and use your IDE to find all of its implementations. (In PHPStorm: Type Hierarchy → Subtypes Hierarchy, in NetBeans: right-click on Interface name → Find Usages → Find All Subtypes.)
Alternatively you can use Drupal Console (drupal debug:cache:context) to display all current cache contexts for your site or application:
$ drupal debug:cache:context
Context ID Label Class path
cookies HTTP-Cookies Drupal\Core\Cache\Context\CookiesCacheContext
headers HTTP-Header Drupal\Core\Cache\Context\HeadersCacheContext
ip IP-Adresse Drupal\Core\Cache\Context\IpCacheContext
languages Language Drupal\Core\Cache\Context\LanguagesCacheContext
request_format Anfrageformat Drupal\Core\Cache\Context\RequestFormatCacheContext
route Route Drupal\Core\Cache\Context\RouteCacheContext
route.book_navigation Buchnavigation Drupal\book\Cache\BookNavigationCacheContext
route.menu_active_trails Aktiver Menüpfad Drupal\Core\Cache\Context\MenuActiveTrailsCacheContextIn every class you find, you will see a comment like this one in \Drupal\Core\Cache\Context\UserCacheContext:
Cache context ID: 'user'.
This means that 'user' is the actual cache context you can specify in code. (Alternatively, find where this class is being used in a *.services.yml file and look at the service ID. More about that below.)
Tip: You can get an up-to-date, complete listing of all cache contexts in Drupal core only by looking at the services tagged with
cache.context!
The service ID is standardized. It always begins with cache_context., followed by the parents of the cache context, finally followed by the name of the cache context. So, for example: cache_context (mandatory prefix) + route (parents) + book_navigation (this cache context's name):
cache_context.route.book_navigation:
class: Drupal\book\Cache\BookNavigationCacheContext
arguments: ['@request_stack']
tags:
- { name: cache.context }
This defines the route.book_navigation cache context.
Debugging
All of the above is helpful information when debugging something that is being cached. But, there's one more thing: let's say something is being cached with the cache keys ['foo', 'bar'] and the cache contexts ['languages:language_interface', 'user.permissions', 'route']. Then the corresponding cache item will be cached in a particular cache bin with a CID (cache ID) of:
foo:bar:[languages:language_interface]=en:[route]=myroute.ROUTE_PARAMS_HASH:[user.permissions]=A_QUITE_LONG_HASH
In other words:
- cache keys are listed first, in the order provided
- cache contexts are listed second, alphabetically, and result in CID parts of the form
[<cachecontextname>]=<cachecontextvalue> - all these CID parts are concatenated together using colons
That should make it much easier to analyze & debug caches!
Headers (debugging)
Finally: it is easy to see which cache contexts a certain response depends on (and thus is varied by): one must only look at the X-Drupal-Cache-Contexts header!
Note: If you're not seeing those headers, you will want to set up your Drupal instance for development.
Dynamic Page Cache
Comprehensive use of cache contexts across Drupal allows Drupal to ship with its Dynamic Page Cache enabled by default. (Previously known as 'Smart Cache')
Internal Page Cache
Note the Internal Page Cache assumes that all pages served to anonymous users will be identical, regardless of the implementation of cache contexts. If you want to use cache contexts to vary the content served to anonymous users, this module must be disabled, and the performance impact that entails incurred.
See also
Cache max-age
Cache max-age = time dependencies
Cache max-age = time dependencies
Cache max-age is analogous to HTTP's
Cache-Controlheader'smax-agedirective
Why?
Cache max-age provides a declarative way to create time-dependent caches.
Some data is only valid for a limited period of time, in that case, you want to specify a corresponding maximum age. However, in Drupal core's case, we don't have any data that is valid for only a limited period of time; we typically cache permanently (see below) and rely entirely on cache tags for invalidation.
What?
A cache max-age is a positive integer, expressing a number of seconds.
Cache max-ages are passed around as individual integers, because a given cache item can only logically have a single max-age.
Examples:
60means cacheable for 60 seconds100means cacheable for 100 seconds0means cacheable for zero seconds, i.e. not cacheable\Drupal\Core\Cache\Cache::PERMANENT(value-1) meanscacheable forever, i.e. this will only ever be invalidated due to cache tags. (In other words: ∞, or infinite seconds.)
So if you for example want to prevent a rendered block from being cached, you should specify max-age=0 on it.
Example for most render arrays:
$build['#cache']['max-age'] = 0;
Example in a function:
\Drupal::cache()->set('my_cache_item', $school_list, \Drupal::time()->getRequestTime() + (86400));If you want to change block max-age to 0 then you must implement getCacheMaxAge method.
Limitations of max-age
Unfortunately, max-agedoes not work for anonymous users and the Drupal core Page Cache module. For example, see these issues:
- #2352009: [pp-3] Bubbling of elements' max-age to the page's headers and the page cache
- #2449749: Add #cache['downstream-ttl'] to force expiration after a certain time and fix #cache['max-age'] logic by adding #cache['age']
- #2835068: PageCache caching uncacheable responses (violating HTTP/1.0 spec) + D8 intentionally disabling HTTP/1.0 proxies = WTF
#2951814: Improve X-Drupal-Cache and X-Drupal-Dynamic-Cache headers, even for responses that are not cacheable
Until these (and perhaps other) issues are resolved, beware that setting max-age on a render array included in a page is insufficient to ensure that anonymous users will see a new version after the max-age as elapsed. In the meanwhile, the Cache Control Override contributed module tries to mitigate the problems. You might also have more luck setting a custom cache tag on pages with time-dependent content and invalidating those cache tags manually via hook_cron(). Good luck!
Cache tags + Varnish
Varnish Cache is a web application accelerator also known as a caching HTTP reverse proxy. Varnish is used on thousands of Drupal sites to
CacheableDependencyInterface & friends
To make dealing with cacheability metadata (cache tags, cache contexts and max-age) easier, Drupal 8 has CacheableDependencyInterface.
CacheableResponseInterface
This interface lives at the intersection of the Cache API and Responses. Since it is first and foremost a response, that's where the
Access checkers + cacheability
Route access checkers, hook_entity_access() and anything that needs to return an AccessResultInterface object should add the appropriate