Cache-contexten
Cache-contexten = (request) contextafhankelijkheden
Cache-contexten zijn vergelijkbaar met de HTTP-header Vary.
Waarom?
Cache-contexten definiëren hoe contextafhankelijke varianten worden gemaakt van iets dat gecachet moet worden. De code die caches aanmaakt wordt leesbaarder, en dezelfde logica hoeft niet telkens herhaald te worden waar dezelfde contextvariaties nodig zijn.
Voorbeelden:
- De uitvoer van bepaalde gegevens hangt af van het actieve thema en geeft verschillende resultaten voor verschillende thema’s. Dan gebruik je een cache afhankelijk van de context theme.
- Bij het bouwen van een render-array die een gepersonaliseerd bericht toont, hangt de weergave af van de gebruiker. Dan is de cache afhankelijk van de context user.
- Algemeen: wanneer bepaalde kostbare informatie afhangt van de serveromgeving, wordt daar ook een cache-context voor gebruikt.
Hoe?
Een cache-context is een string die verwijst naar één van de beschikbare cache-contextservices (zie hieronder).
Cache-contexten worden doorgegeven als sets strings (volgorde maakt niet uit), dus ze worden weergegeven in string[]. Het zijn sets omdat één cache-item van meerdere cache-contexten kan afhangen.
Meestal worden cache-contexten bepaald door de requestcontext (dus uit de HTTP-request). Veel van de omgeving voor een webapplicatie komt voort uit de requestcontext. Uiteindelijk worden HTTP-responses grotendeels gegenereerd afhankelijk van eigenschappen van de HTTP-request die ze aanriepen.
Maar cache-contexten hoeven niet uit de request te komen – ze kunnen ook afhangen van uitgerolde code, zoals de deployment_id-context.
Daarnaast zijn cache-contexten georganiseerd in een hiërarchie. Het eenvoudigste voorbeeld: wanneer iets varieert per gebruiker, hoeven we niet enkel permissies te gebruiken, want verschillen bestaan voor elke gebruiker. Voor een set permissies wordt er per permissie gecachet. Als een deel van de pagina per gebruiker varieert en een ander deel per permissie, moet Drupal slim genoeg zijn om enkel de verschillen per gebruiker te hanteren. Daarvoor gebruikt Drupal hiërarchie-informatie, zodat er geen onnodige cachevarianten worden gemaakt.
Syntaxis
- Punten scheiden ouder- en kindelementen.
- Meervoudsvormen geven aan dat er een parameter kan worden gebruikt: voeg een dubbele punt toe en specificeer de gewenste parameter (als geen parameter wordt gegeven, worden alle mogelijke waarden meegenomen, bijv. alle query-argumenten).
Cache-contexten in Drupal 8 core
Drupal 8 core levert de volgende hiërarchie van cache-contexten:
cookies
:name
headers
:name
ip
languages
:type
protocol_version // Beschikbaar in 8.9.x of hoger.
request_format
route
.book_navigation
.menu_active_trails
:menu_name
.name
session
.exists
theme
timezone
url
.path
.is_front // Beschikbaar in 8.3.x of hoger.
.parent
.query_args
:key
.pagers
:pager_id
.site
user
.is_super_user
.node_grants
:operation
.permissions
.roles
:role
Opmerking: om de cache-context url.path.is_front in oudere versies te gebruiken, zie de change record.
Overal waar cache-contexten worden gebruikt, wordt de volledige hiërarchie aangegeven. Dat heeft 3 voordelen:
- geen dubbelzinnigheid: duidelijk waarop de oudercache-context is gebaseerd
- vergelijking (en reductie) van cache-contexten wordt eenvoudiger: als zowel a.b.c als a.b aanwezig zijn, dan dekt a.b ook a.b.c, dus kan a.b.c worden weggelaten
- geen vereiste dat elke boomlaag uniek is in de volledige boom
Voorbeelden uit de hiërarchie:
- theme (varieert per ingesteld thema)
- user.roles (varieert per combinatie van rollen)
- user.roles:anonymous (varieert afhankelijk van of de gebruiker anoniem is of niet)
- languages (verschilt voor alle taalsoorten: interface, content…)
- languages:language_interface (varieert per interfacetaal)
- languages:language_content (varieert per contenttaal)
- url (varieert per volledige URL)
- url.query_args (varieert per querystring)
- url.query_args:foo (varieert per query-argument
?foo) - protocol_version (verschilt tussen HTTP/1 en HTTP/2)
Optimalisatie/reductie/samenvoeging van cache-contexten
Drupal gebruikt de hiërarchie om cache-contexten te vereenvoudigen. Als een deel van de pagina varieert per gebruiker (user) en een ander deel per permissie (user.permissions), dan is user voldoende, want dat dekt ook permissies. Dus: optimize([user, user.permissions]) = [user].
Cache-contexten die afhankelijk zijn van configuratie die kan veranderen, kunnen cachemetadata binden: cache-tags en max-age. Wanneer een context geoptimaliseerd wordt, worden de tags geassocieerd, zodat wijzigingen in permissies alsnog de cache ongeldig maken.
Een complexer voorbeeld: node grants. Ze gelden voor specifieke gebruikers, dus de context is user.node_grants. Maar node grants kunnen zeer dynamisch zijn, dus vaak met max-age = 0 (nooit cachen). Dus: optimize([user, user.node_grants]) = [user, user.node_grants].
Hoe herkennen, bepalen en maken?
Cache-contexten zijn services met de tag cache.context. Elke module kan eigen cache-contexten toevoegen. Ze implementeren \Drupal\Core\Cache\Context\CacheContextInterface of \Drupal\Core\Cache\Context\CalculatedCacheContextInterface (voor contexten die parameters accepteren).
Alle implementaties vind je door CacheContextInterface te zoeken in je IDE, of via de Drupal Console:
$ 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-adres Drupal\Core\Cache\Context\IpCacheContext languages Language Drupal\Core\Cache\Context\LanguagesCacheContext request_format Requestformat Drupal\Core\Cache\Context\RequestFormatCacheContext route Route Drupal\Core\Cache\Context\RouteCacheContext route.book_navigation Boeknavigatie Drupal\book\Cache\BookNavigationCacheContext route.menu_active_trails Actief menupad Drupal\Core\Cache\Context\MenuActiveTrailsCacheContext
In de klasse zie je een commentaar zoals:
Cache context ID: 'user'.
Dat betekent dat 'user' de context is die je in code kan gebruiken. Je kan ook de *.services.yml bekijken om de service-ID te vinden.
De service-ID is gestandaardiseerd: altijd cache_context. + parent + child. Bijvoorbeeld:
cache_context.route.book_navigation:
class: Drupal\book\Cache\BookNavigationCacheContext
arguments: ['@request_stack']
tags:
- { name: cache.context }
Dit definieert de context route.book_navigation.
Debugging
Een cache-item met keys ['foo', 'bar'] en contexten ['languages:language_interface', 'user.permissions', 'route'] resulteert in een CID:
foo:bar:[languages:language_interface]=en:[user.permissions]=HASH:[route]=myroute.ROUTE_PARAMS_HASH
- Cachekeys eerst, in opgegeven volgorde
- Daarna cache-contexten in alfabetische volgorde, als
[context]=waarde - Alles gescheiden door dubbele punten
Zo kan je caches makkelijker analyseren en debuggen!
Headers (debug)
Je kan de header X-Drupal-Cache-Contexts bekijken om te zien van welke contexten een response afhangt.
Dynamische page cache
Dankzij cache-contexten levert Drupal 8 standaard de Dynamic Page Cache module mee (vroeger "Smart Cache").
Interne page cache
De Internal Page Cache module gaat ervan uit dat alle pagina’s voor anonieme gebruikers identiek zijn. Als je cache-contexten wilt gebruiken voor contentvariatie bij anonieme gebruikers, moet je deze module uitschakelen, wat impact kan hebben op de prestaties.