Hur fungerar cache i WordPress?


Artikel av Thomas Audunhus. Published on april 19, 2018

Cache. Det kan tyckas vara ”den heliga graalen” för alla prestationsfrågor. Så det är inte konstigt att folk höjer ögonbrynen när jag säger ”Sluta använda cache” i mina föreläsningar, möten eller workshops. För vissa, särskilt i WordPress-gemenskapen, har jag blivit ”han som hatar cache”.

Så, nu är det dags att klargöra vad jag tycker om caching, när jag anser det bör användas och vad det ska användas till. Och kanske viktigast, när du inte ska använda cache utan istället se till andra lösningar.

När ska du använda helsidescache?

Låt oss börja med det viktigaste. Helsidescache, eller Full Page Caching. Ett sätt att tillfälligt lagra en genererad sida på servern för att leverera exakt samma kod (HTML) till besökare inom en begränsad tid. Så här fungerar helsidescache:

  1. Besökare A besöker abc.se/sida. Denna sida är inte cachad, så den genereras från databasen via PHP. Innan den levereras till besökare A så lagras även sidan i cacheminnet med en utgångstid om 10 minuter.
  2. Besökare B besöker abc.se/sida, två minuter efter besökare A och får därför samma sida levererad från cacheminnet. Sidan som levereras till besökare B och A är helt identisk.
  3. Besökare C besöker abc.se/sida, 15 minuter efter besökare A. Eftersom den sidan från cacheminnet har löpt ut så måste den åter genereras från databasen via PHP, lagras i cachen osv.

Den som besöker sidan under den giltiga cacheperioden (besökare B) får den cachade versionen, som levereras snabbt. Beroende på hur mycket resurser som finns för att hämta sidan från cacheminnet så kan helsidescache hjälpa för prestanda och skalning. Även om det låter lockande – helsidescache har även några nackdelar.

  1. Om du ska leverera dynamiskt, personligt eller varierande innehåll till olika användare måste det lösas med t ex JavaScript (AJAX) som körs efter att HTML-dokumentet har levererats till webbläsaren. I många fall är det helt ok, men med AJAX sker ändå förfrågningar mot servern. Så om du använder caching för att undvika att skriva snabb kod, så har du ändå problem om du ska leverera dynamiskt innehåll.
  2. Även om du uppfattar sidan som snabb så betyder det inte att alla andra gör det. Hemsidor är idag inte ”enkla inträdespunkter”, utan människor landar på väldigt många olika och slumpmässiga sidor. Det gäller särskilt för nätbutiker där människor går direkt till produktsidor, eller om du kör annonser i t ex Google Shopping på hela produktkatalogen med länkar direkt till produkten. För att lösa detta finns det människor som försöker ”förbereda” cachen för hela sidan, dvs använder en spindel som kryper igenom hela sajter så att allt är cachat hela tiden. Det fungerar dock sällan, det är sårbart, svårt att installera ordentligt, tar mycket tid att upprätthålla och är inte minst onödigt.
  3. Du förlorar känslan av den faktiska hastigheten och hur koden fungerar. Genom att använda helsidescache förlorar du möjligheten att i många fall se via svarstiden hur status är, hur bra din kod är osv. All erfarenhet visar också att bristen på fokus på den faktiska hastigheten är den främsta orsaken till att webbplatser kraschar på Black Friday, mellandagsrea och vid stora utskickningskampanjer.

Men cachetillägg då, som W3 Total Cache?

WordPresstillägg som W3 Total Cache fungerar i grund och botten som all annan sidcache. De sparar en version av en färdigproducerad sida till disk(fil) eller minne och serverar det till användare tills utgångstiden är uppnådd. Även om W3 Total Cache används av över 1 miljoner sidor betyder det inte att det är en bra idé, särskilt om du har en snabb leverantör/webbhotell.

W3 Total Cache är ett stort tillägg, som ofta kör en stor mängd onödig kod. Mer kod betyder mer saker som kan gå fel. Om du absolut måste köra sidcache bör detta inte göras i PHP, eftersom PHP är långsamt. Det bör istället ske innan trafiken träffar PHP (Apache). Och om du ställer in W3 Total Cache felaktigt så är det en falsk säkerhet.

W3 Total Cache pratar om sidor som laddar på 2 sekunder, med en standardsida med temat Twenty Sixteen som ett exempel. Bara för att ha det sagt, samma sida hos oss laddar på millisekunder, inte sekunder.

Så när ska du använda helsidescache och varför?

Helsidescache skapades för att enkelt kunna skala hemsidor med mycket trafik, samt för att enklare kunna hantera spikar i trafik till vissa sidor. Helsidescache kom till när människor använde Internet främst för att läsa nyheter och se uppdaterade väderprognoser, när tidningar hade ett stort tryck på några artiklar. Att generera all HTML för varje enskild besökare var onödigt då sidorna sällan ändrades. För detta scenario används fortfarande helsidescache. Men dagens hemsidor liknar mer applikationer.

Hemsidor idag är mer komplexa, mer dynamiska och gradvis mer personliga. Koden har i allmänhet blivit mer komplex, och det är inte alltid en bra idé att lägga på ännu fler komplexa mekanismer (läs: olika cachelösningar) på komplex kod.

Vad du gör i praktiken är att lägga på ytterligare ett lager av teknik som ska upprätthållas, vilket kan skapa problem för utvecklare och ge en onödig single point of failure. Om du kodar en lösning där prestanda baseras på helsidescache så kan jag nästan garantera att lösningen kollapsar fullständigt om cachningen slutar fungera.

Svaret på frågan ovan är därför; skriv bra kod och kom ihåg att koden ska skalas, samt använd helsidescache för att hantera trafiktopparna. Precis som tidningarna gjorde tidigare, och förresten, ännu gör det.

Du ska kunna stänga av helsidescachen en vanlig dag, med vanlig trafik, utan att bli nervös. Och om du har gjort ett bra jobb kan du stänga av helsidescachen även en dag med mycket trafik, utan att lösningen kollapsar.

Hos Servebolt kör de flesta webbshopar utan helsidescache även på högtrafikdagar som Black Friday.

WordPress Object Cache – du använder den utan att känna till det

Väldigt många vet inte att Object Cache används i nästan alla WordPress-installationer. Och ja, du använder Object Cache även om du inte kör ytterligare programvara som MemCached, REDIS eller liknande.

Så fungerar WordPress Object Cache

  1. Du ställer en fråga efter t.ex. postmeta-värden med get_post_meta()
  2. WordPress kör frågan och får ett resultat från databasen och sparar automatiskt resultatet i objektcachen (minne, RAM)
  3. Resultatet använder du någonstans i din kod
  4. Längre ner i koden ställs samma fråga igen
  5. WordPress vet att resultatet av den frågan finns i objektcachen och levererar resultatet blixtsnabbt
  6. Sidan har laddats klart och objektcachen tömts (minnet återgår)

Det är inte alla funktioner i WordPress som lagrar sina resultat i Objekt Cache, men get_post_meta() gör så. Det beror på att _postmeta-tabellen i databasen snabbt kan bli enorm och frågorna därför kan bli krävande (kräver mycket datorkraft, tar lång tid etc.).

Som utvecklare kan du själv lägga in resultat i objektcachen, för senare användning vid samma sidvisning. Om du skriver egna frågor med till exempel WP_Query måste du lägga till resultaten i objektcachen själv. Men det är viktigt att komma ihåg att WordPress Object Cache är som all annan cache. Du kan inte lita på den, så basera inte din kod på att objektcachen hjälper till.

Du kan kontrollera hur många värden som hämtas från Object Cache och hur mycket som hämtas från databasen med WordPress-tillägget Query Monitor. I mina tester har en vanlig WooCommerce-butik en träff-ratio om 95-98 %.

Så använder du Object Cache för dina egna frågor

$result = wp_cache_get( 'some_unique_name' ); 

if ( false === $result ) { 
   $result = $wpdb->get_results( $query );
   wp_cache_set( 'some_unique_name', $result );
}
// Do something with $result;

Vad detta kodexempel gör är att först sätta $result, för att sedan kontrollera om $result finns. wp_cache_get() returnerar false om det inte finns. Om det inte finns kör vi en fråga för att få önskvärt resultat, som sedan lagras i objektcachen så vi snabbt kan hämta resultatet med senare frågor.

Men extern objektcache som Memcached eller REDIS då?

Med en extern objektcache kan du få en bestående objekt cache (persistent object cache). Det innebär att objektcachen inte frigörs när en sida är klar, utan består och kan användas kors och tvärs över olika sidvisningar. Bra idé, eller hur? Men som vanligt när det gäller cache är allt inte guld och gröna skogar här heller.

  1. Den potentiella prestandavinsten begränsas till de frågor som inte redan träffar den vanliga objektcachen. Med en träff-ratio om 95-98 %, som tidigare noterat, kan prestandan förbättras för 2-5 % av frågorna. Dessutom kommer en extern cache skapa ytterligare tröghet, då det är en extern lösning. Erfarenheten visar att summan av tröghet + vinsten från en extern objektcache ofta ger ett negativt resultat för webbshopar.
  2. Även med en extern objektcache så kan du inte lita på att cachen faktiskt existerar när du skriver din kod.
  3. Om du använder en extern objektcache för att få sidorna att ladda snabbare, betyder det att din kod är dålig eller att databasfrågorna är för långsamma. Ditt problem är i koden, inte hur snabbt databasen svarar. Därför bör du inte ”plåstra” med MemCached eller Redis.
  4. Om du använder en snabb databas som är optimerad och använder index på rätt sätt så behöver du inte en extern objektcache.

Om du inte behöver en extern objektcache så kan det vara direkt skadligt att använda det

Skadligt kan vara lite överdrivet, men i slutändan är det så. Det gäller all den teknik du har i «stacken» som du inte behöver. Har du det finns en risk att du blir beroende av det, även om du inte behöver det. Är du beroende av det så ökar risken att något går fel. Dessutom blir det mer att underhålla och konfigurera ordentligt samt ytterligare en potentiell felkälla.

Transienter kan rädda, men även ta sönder din hemsida

Transienter används av WordPress och ett stort antal plugins, och konceptet är ganska enkelt. Du gör en fråga och sparar resultatet, eller en del av det, i databasen för att enkelt använda resultatet senare. Till skillnad från objektcache behövs ingen extra teknik för att använda transienter mellan olika sidvisningar. Transienter kan ha en utgångstid, eller inte. Det väljer du själv.

Personligen är jag inte glad i överdriven användning av transienter, av flera orsaker.

  1. Transienter lagras i _options-tabellen. En tabell som redan har mycket trafik. Att skriva till _options i hög utsträckning kan skapa låsningsproblem (vilket orsakar kö).
  2. Överdriven användning av transienter ger en stor _options-tabell. Absolut alla sidor är beroende av denna tabell och ett stort _options-tabell kan även bidra till sämre prestanda för alla sidvisningar.
  3. Transienter som inte löper ut laddas automatiskt när WordPress laddar alla alternativ via wp_load_alloptions(). Detta kommer också att resultera i sämre prestanda för alla sidvisningar.

Men, med det sagt, så kan rätt användning av transienter vara mycket bra för prestanda. Om du har en tung fråga som körs ofta och ändras sällan så är det mycket bra att cachelagra resultatet i en transient. Det är mycket lättare för WordPress att hämta värdet från nycke X i _options-tabellen än att köra select eller liknande från praktiskt taget alla andra tabeller.

Men om du använder transienter så vill jag rekommendera dig att ha koll på _options-tabellen, så den inte växer för mycket, samt implementera logik för att ta bort transienter som inte längre behövs, används eller har gått ut. Och använd inte transienter för data som du behöver uppdatera kontinuerligt.

Fragmenterad cache är bra, om det används rätt

Fragmenterad cache är en typ av cache där du sparar ett element, en del av en sida eller något annat som är resurskrävande att generera och/eller används ofta. Många väljer att implementera en version av Mark Jaquiths funktion för fragmenterad cache i WordPress.

Fragmenterad cache innebär att resultatet (genererad HTML) sparas, så det kan levereras blixtsnabbt nästa gång. Tanken med fragmenterad cache är lika enkel som det är fantastisk. Du väljer själv vilka element eller delar som ska cachelagras, så du behöver inte spara hela sidor med helsidescache.

I WordPress hanteras detta på samma sätt som objektcache, eftersom det inte finns någon begränsning av vilken data som kan cachelagras. Därför gäller även samma principer. Du kan inte lita på att cacheminnet existerar, så gör dig inte beroende av den utan fokusera istället på koden framför genvägen med fragmenterad cache.

Många väljer att köra minnesbaserad fragmenterad cache. Men för att det ska fungera måste man ha en extern objektcache, så att cachen lagras mellan sidvisningar, vilket jag inte rekommenderar (läs ovan). Jag vill snarare rekommendera försiktig användning av fragmenterad cache, med lagring av data i transienter. Funktionellt sett är det som minnesbaserad lagring, om än lite långsammare, men du behöver inte lägga på mer teknik vilket ger en mer stabil lösning.

Så sparar du transienter enkelt

$output = get_transient('some_unique_key');

if( $output === false ){

$output = 'Some data';
set_transient('some_unique_key', $output, 3600);

}
// Do something with $output

Vad detta kodexempel gör är att först ange $output och kontrollera om $output existerar. get_transient() returnerar false om det inte existerar. Så om det inte finns, kör vi frågan som krävs för att få den data vi ska använda. Därefter sparas detta i en transient för senare användning.

Vilken cachelösning ska man då använda?

Använd aldrig cache för prestandaförbättring. Helsidescache kan användas för skalning – för att hantera de största trafiktopparna, men gör dig inte beroende av det. Du ska kunna stänga av helsidescachen utan att få en märkbar prestandaförsämring.

Använd objektcache flitigt, särskilt med WooCommerce. Men undvik extern objektcache. Det orsakar mer problem än det löser. Fokusera på din kod och skaffa dig en leverantör med blixtsnabb databashantering såsom Servebolt.

Använd transienter för vanligt förekommande frågor, där resultatet sällan ändras. Tänk på att sätta en rimlig utgångstid baserat på hur ofta värdet uppdateras. Kom även ihåg att radera utgångna transienter. Använd fragmenterad cache i transienter för element som är resurskrävande och används ofta.