HTTP-caching met Varnish: een webserver versnellen zonder hem aan te passen
Kort antwoord
Varnish is een reverse proxy cache. Het staat voor je webserver en bewaart kopieën van responses, zodat een herhaald verzoek voor dezelfde content direct uit het geheugen komt in plaats van bij de applicatie erachter. Dat is sneller voor de bezoeker en minder belastend voor de origin server, en het vereist geen aanpassingen aan de applicatie zelf.
Wat Varnish precies doet
Varnish staat tussen het internet en je webserver. Een verzoek voor een pagina komt eerst bij Varnish binnen. Heeft Varnish al een geldige gecachte kopie van precies die response, dan geeft het die direct terug: het verzoek bereikt je applicatie, je database of wat er verder achter staat helemaal niet. Heeft Varnish geen gecachte kopie, of is de kopie die het heeft verlopen, dan haalt het een nieuwe versie op bij de origin server, levert die aan de bezoeker en bewaart een kopie voor de volgende keer.
Dit is een reverse proxy, geen browsercache. Een browsercache helpt alleen dezelfde bezoeker bij een volgend bezoek. Varnish helpt elke bezoeker, omdat de gecachte kopie aan de serverkant staat en door iedereen wordt gedeeld. Het is ook geen CDN in de zin van content verspreiden over meerdere geografische locaties: Varnish draait doorgaans als één laag dicht bij je origin server. Zie Wat is een CDN, en hoe content delivery networks werken voor hoe dat zich verhoudt tot caching die wereldwijd over edge-locaties is verspreid.
Waar het van nature goed in is
Varnish werkt het best voor content die er voor elke bezoeker hetzelfde uitziet: een statische marketingpagina, een blogpost, een productoverzicht op een webshop, een JSON-response van een API-endpoint die niet verschilt per aanvrager. Die content is niet afhankelijk van wie het verzoek doet, dus kan één gecachte kopie veilig duizenden verschillende bezoekers bedienen. Dit is precies het type workload dat er het meest van profiteert: pagina's die duur zijn om te genereren (een productcatalogus die op een database draait, bijvoorbeeld) maar niet bij elk verzoek veranderen.
Het voordeel stapelt zich op naarmate de belasting toeneemt. Een pagina die pakweg een paar honderd milliseconden nodig heeft om vanuit de applicatie te renderen, hoeft dat maar één keer per cache-periode te doen, niet één keer per bezoeker. Elk ander verzoek in die periode wordt in een fractie van de tijd uit het geheugen bediend, en de origin server merkt nauwelijks iets van het verkeer.
De echte complicatie: content die niet voor iedereen hetzelfde is
De moeilijkheid bij elke cachelaag ontstaat zodra content niet meer identiek is voor elke bezoeker. Een accountpagina van een ingelogde gebruiker, een winkelwagen, een pagina met "Welkom terug, [naam]": niets daarvan kun je cachen en aan de volgende bezoeker tonen zonder de content van de ene persoon naar de andere te lekken. Ga hier de mist mee in en het gevolg is niet zomaar een verouderde pagina, maar een serieus privacyprobleem: de ene bezoeker die de sessiecontent van een andere bezoeker te zien krijgt.
Varnish raadt dit niet zelf. Het moet via zijn configuratietaal (VCL, Varnish Configuration Language) verteld worden welke verzoeken veilig te cachen zijn en welke niet. Veelgebruikte patronen zijn:
- Nooit verzoeken cachen die een sessiecookie of een authenticatieheader bevatten.
- Nooit specifieke paden cachen, zoals
/account/,/checkout/of/wp-admin/. - Cookies uit het verzoek verwijderen voordat de cache wordt gecheckt, voor content die echt voor iedereen hetzelfde is, zodat een ongerelateerd trackingcookie caching niet per ongeluk blokkeert terwijl dat verder wel veilig zou zijn.
- De gecachte response laten variëren op een specifieke header, zoals
Accept-Language, wanneer dezelfde URL terecht andere content teruggeeft afhankelijk van die header.
Een basale VCL-regel om de cache te omzeilen voor alles met een sessiecookie ziet er ongeveer zo uit:
sub vcl_recv {
if (req.http.Cookie ~ "session_id") {
return (pass);
}
}
return (pass) vertelt Varnish om direct bij de origin op te halen en de response niet op te slaan, precies wat je wilt voor alles wat gepersonaliseerd is. Deze regelset goed krijgen voor een specifieke applicatie, zeker een die niet is ontworpen met een cache ervoor, is meestal het grootste deel van het configuratiewerk.
Het klassieke lastige probleem: cache-invalidatie
Ook voor content die echt veilig te cachen is, is er een tweede probleem: ervoor zorgen dat Varnish een gecachte kopie verwijdert of ververst zodra de onderliggende content daadwerkelijk verandert. Wordt de prijs van een product aangepast in de applicatie, maar serveert Varnish nog steeds de gecachte pagina van gisteren, dan zien bezoekers verouderde data totdat die cache-entry verloopt of expliciet wordt gewist.
Er zijn twee brede benaderingen, en de meeste praktijkopstellingen combineren ze allebei:
| Aanpak | Hoe het werkt | Afweging |
|---|---|---|
| Tijdgebaseerd verlopen (TTL) | Elke gecachte response wordt een vaste periode bewaard, en daarna automatisch als verouderd beschouwd en bij het volgende verzoek opnieuw opgehaald bij de origin | Eenvoudig te configureren, maar er is altijd een venster waarin bezoekers verouderde content kunnen zien |
| Expliciet purgen | De applicatie (of een beheerder) vertelt Varnish direct om een specifieke gecachte entry te verwijderen zodra de onderliggende content verandert, meestal via een HTTP PURGE-verzoek naar de betreffende URL | Content is direct vers, maar het betekent dat je een purge-aanroep moet inbouwen op elke plek waar content wordt bijgewerkt |
Dit wordt vaak omschreven als een van de twee echt lastige problemen in de informatica, en het verdient serieuze aandacht in plaats van een bijzaak te zijn. Een korte TTL verkleint het risico op verouderde content, maar vermindert ook hoeveel belasting Varnish daadwerkelijk van de origin afneemt, omdat gecachte kopieën vaker verlopen en opnieuw worden opgehaald. Een langere TTL levert betere cache-efficiëntie op, maar een langer venster van mogelijke veroudering, tenzij purgen goed is ingericht. Er is geen universeel juist antwoord: het hangt af van hoe vaak de betreffende content daadwerkelijk verandert en hoeveel veroudering acceptabel is voor die specifieke pagina.
Waar Varnish past in de rest van de stack
Varnish opereert op de HTTP-laag, voor wat je applicatie ook serveert: een gewone webserver, een WordPress- of WooCommerce-installatie, of een eigen applicatieserver. Zie WordPress en WooCommerce hosten op een dedicated server voor een workload waarbij dit patroon vaak voorkomt: product- en categoriepagina's zijn ideale kandidaten voor caching, winkelwagen- en afrekenpagina's niet.
Omdat Varnish zelf ook extra werk verzet, door gecachte objecten in het geheugen te houden en bij elk verzoek VCL-regels te evalueren, heeft de server waarop het draait nog steeds genoeg CPU en RAM nodig om dat comfortabel te doen naast de origin-applicatie, zeker bij een hoog aantal verzoeken. Zie Een CPU kiezen voor CI/CD, virtualisatie en Kubernetes-nodes voor algemene richtlijnen om compute-resources af te stemmen op wat een workload daadwerkelijk vraagt.