Kliens oldali cachelés – lebegj mint a lepke

This entry is part 2 of 2 in the series Kliens oldali teljesítmény

A sorozat második részében tekintsük át hogy milyen szabványos lehetőségeink vannak a kliens oldali cachelésre (gyorsítótárazás) weboldalaink esetében. Szó lesz az alábbi kulcsszavakról: Expires, Last-Modfiied, If-Modified-Since, Etag, Cache-Control, Redirect (átirányítások).

Ahogy az előző részben láttuk, egy weboldal sebessége erősen meghatározza a felhasználói élményt, a sebességét pedig számos tényező befolyásolja és a ebből a szempontból sok fontos dolog kliens oldalon történik. Nézzük hát, hogy mit tehetünk weboldalunk betöltésének gyorsítása érdekében a cachelés segítségével – a kliens oldali sebesség javítása a cél.

Mi az a cachelés és mi köze a kliens oldali sebességhez?

A cache, vagy gyorsítótár általánosságban gyors elérésű, átmeneti tárolóhelyet jelent, tehát egy olyan területet ahova azokat az elemeket tesszük, amikről tudjuk hogy valószínűleg sűrűn használva lesznek és nem szeretnénk kivárni amíg be- vagy letöltődnek, legenerálódnak minden alkalommal. A kliens oldali cachelés gyakorlatilag azt jelenti, hogy a böngésző a weboldalak gyakran használt elemeit (képek, css-ek, js-ek, stb.) memóriában/lemezen tartja, és ha nem szükséges, nem tölti le újra a szerverről minden oldalletöltésnél. Ennek nyilvánvaló jótékony hatása hogy nem foglalnak az ilyen elemek hálózati szálakat és nem kell megvárni a szerver válaszát a renderelés elkezdéséhez.

Miért nem cachelünk mindent, ha ez ilyen jó dolog?

Jogos a kérdés és elvben mindent cachelhetünk, akár a teljes weboldalunkat is képes eltárolni gyorsítótárban a böngésző. Egy buktató azért van: a weboldalak nagy részén dinamikusan változó részek, tartalmak is találhatók (felhasználók ki- belépése, egy blogon új cikkek, stb.), így ezeket időről időre újra le kell kérnie a böngészőnek. Ugyanígy ha szerveroldali naplózásra van szükség (access- vagy egyéb logok), fontos hogy minden látogatás eljusson a szerverhez.

A cachelés dimenziói

A kliens oldali cachelés egyik dimenziója, hogy logikusan csak akkor működik, ha a felhasználó már járt az oldalon korábban, tehát az első oldalletöltést nem tudja gyorsítani. Ezzel nincs nagy baj hiszen tipikusan egy funkciókban gazdag oldal használata nem ér véget az első oldalletöltéssel – és persze a sorozat további részeiben látjuk majd, hogy hogyan optimizáljuk az első oldalmegtekintést is.

A másik fontos tudnivaló, hogy a cache kulcs mindig a teljes url, a hashmarkos in-page navigációt leszámítva. Így

http://www.domain.em/oldal  != http://www.domain.em/oldal?param=1

http://www.domain.em/oldal#alma == http://www.domain.em/oldal

Ez fontos szabály, ne felejtsük el, egyben jó fegyver is lesz majd amikor a cache érvénytelenítés problémaköréhez érünk.

A kliens oldali cachelés természetesen a HTTP protokolban nyújtott eszköztárra épül. Alapvetően két megközelítés van cachelésnél: az egyik azt figyeli hogy időben meddig tekintheti nyugodan a kliens változatlannak az adott url tartalmát, a másik pedig egyedi azonosítót rendel a tartalomhoz és ennek változását figyeli. Nagy általánosságban elmondható hogy az első technológia statikus assetekre jóval hatékonyabb, a második pedig az elsőt kiegészítve vagy önmagában jó lehet dinamikus oldalak tartalom-érzékeny gyorstárazására. Nézzük meg a két irány eszközeit.

Idő-alapú cachelés

Itt alapvetően az alábbi fejlécekről van szó:

Expires és Cache-Control.

Expires: A szerver adja a válaszban és azt jelenti, hogy mely időpontig nem kell a böngészőnek újra lekérdeznie az adott url tartalmát. Ha a Cache-Control megfelelő beállítása mellett ez az érték ki van töltve (és jövőbeli), akkor addig a böngésző normál esetben nem indít GET kérést a szerver felé ha egy weboldal az adott URL-re hivatkozik, hanem a saját gyorstárából olvassa be a tartalmat – már persze ha nem ürítjük addig a gyorstárat.

Cache-Control: A szerver adja a válaszban – ez egy kicsit komplex jószág. Először is felülbírálja az Expires-t, másodszor pedig több dologra is használható (kombinációban akár):

  • max-age direktívájának segítségével Expires-hez hasonló működést érhetünk el, megmondja hogy a letöltéstől számított hány másodpercig cachelhető a tartalom, tehát az Expires-el időpontot állítunk be, a max-age-el pedig relatív időt adunk meg.
  • public direktívájának használata akkor szokás, amikor HTTP authentikáció mögött lévő tartalmat szeretnénk minden felhasználó számára cacheltetni (pl. egy js-t, css-t) , az ilyen tartalom automatikusan nem cachelhető mindenki számára, csak az adott felhasználónak (private), ezzel felülbírálhatjuk ezt a működést.
  • no-cache: megtévesztő, mert valójában nem titlja a cachelést, csak azt írja elő a böngészőnek, hogy küldjön a szervernek érvényesítési kérelmet (pl. authentikáció) minden alkalommal, mielőtt cache-ből előhúzná a tartalmat.
  • must-revalidate: Azt írja elő a böngészőnek, hogy amint lejár a cachelt tartalom (max-age, expires), kötelező újra érvényesítenie azt a szerveren. Ez elvileg minden böngésző alapértelmezett működése.
Ha a kliens úgy látja, hogy még érvényes a tartalom (nem járt le Expires vagy max-age alapján, és egyébként cachelhető újra-validálás nélkül), akkor nem küld lekérést a szerver felé, hanem szó nélkül a cache-ben talált tartalmat használja.

Tartalom/azonosító alapú cachelés

Nem ennyire éles a két technika között a határvonal, itt is vannak idő-alapú ellenőrzések.

Last-Modified: A szerver adja válaszban. Azt mondja meg a böngésző számára, hogy mikor változott legutóbb az url tartalma. Amikor webszerverre hagyjuk ennek a generálását, tipikusan a file módosítási dátum kerül ide statikus assetek esetén. Természetesen dinamikusan is generálható szerver oldali scriptből. A böngészők megjegyzik ezt az értéket és általában elküldik a további lekérdezésekben, ekkor If-Modified-Since a fejléc neve. Ez a kérdés-válasz séma arra jó, hogy a szervernek is legyen információja arról hogy a böngészőben milyen régi verzió van cachelve a tartalomból és tudjon update-et küldeni ha szükséges.

Etag: Kicsit a Last-Modified és az If-Modified-Since páros automatizálásának vagy általánosításának tekinthető: egy db. egyedi azonosító amit a tartalomhoz rendelhetünk. Akkor hasznos, ha tudunk ilyet egyszerűen generálni. Ilyen lehet pl. egy statikus asset md5 sum-ja. A böngésző szintén küldi lekérdezésekben, ekkor If-None-Match a neve.

Ha a szerver egyező Last-Modified  - If-Modified-Since vagy Etag – If-None-Match párost lát, 304 Not Modified választ ad, jelezve a kliensnek, hogy a tartalom nem változott az ő verziójához képest, nyugodtan használhatja azt – ilyenkor lekérdezés megy minden esetben ettől függetlenül a szerverhez, csak a válasz méretén tudunk nyerni, hiszen a 304 csak fejléc, a tartalom nem jön vele, így nyilván gyorsabb a művelet.

Hogyan érdemes hát használni ezeket?

Ökölszabály, hogy használjunk mindkét irányból egy-egy megoldást, tehát:

Expires + Last-Modified

vagy Cache-Control:max-age + Last-Modified

vagy Expires + Etag

vagy Cache-Control:max-age + Etag

max-age + Expires értelmetlen, ugyanarra valók, ugyanígy Last-Modified + Etag általában értelmetlen (nem mindig!).

Azért fontos lefedni a cachelést mindkét irányból, mert könnyen lehet hogy amikor pl. az Expires értéke már lejár, akkor sem kell újra letölteni a tartalmat, mert valójában nem változott, ezt pedig a Last-Modified megmondja majd (304), így érjük el a legjobb cachelést. Ha csak Expires-t állítunk, akkor a tartalom lejárta után a teljes lekérés lefut a szerveren.

Én személy szerint az Expires + Last-Modified kombinációt használom statikus assetekre.

Szintén ökölszabály, hogy minél hosszabb Expires-t érdemes megadni, a Google szerint minimum +1 hónapot. Ez viszont felveti a következő témát:

Cache invalidálás (gyorsítótár érvénytelenítése)

Képzeljünk el egy olyan helyzetet, hogy 1 hónapos Expires-t állítunk be az oldalunk css file-jára, aztán rájövünk hogy el van szúrva benne valami, szétcsúszik az oldal, gyorsan javítjuk, hátradőlünk, de… mi van azokkal a felhasználókkal akik már jártak a siteon? Ők egy hónapig még a régi verziót kapják cache-ből, benne a hibával. Mivel az Expires alapján a böngésző rá sem néz a szerverre, nincs lehetőségünk a Last-Modfied-el vagy mással játszani.

Emlékezzünk hogy a cachelés a teljes url alapján történik! Nincs más dolgunk mint pl. egy query paramétert betenni a css linkjébe az oldal fejlécében:

http://site.om/assets/main.css —> http://site.om/assets/main.css?v=2

Így máris új, cacheletlen elemnek tekinti a böngésző és letölti az új verziót. Lehet ezt automatizálni is szoftveresen, pl. a file md5 sum-ját használni paraméternek.

Némi nginx config – a gyakorlati oldal

Mivel apache-ot rég nem használok, nginx config-ban mutatom meg a példákat.

location ~* ^.+\.(?:jpg|png|css|gif|jpeg|js|swf) {
  expires max;
  #expires 30d;
 add_header Cache-Control public;
  #add_header Cache-Control "public, must-revalidate";
 }

Ez néhány statikus filetípusra a szabvány szerinti maximális expires headert állítja be (1 év),  a kommentezett verzió pedig 30 napot.

Láthatjuk a Cache-Control fejléc beállítási lehtőségét is

Az nginx a Last-Modified mezőt automatikusan, a file dátuma alapján adja, ha dinamikusan generált tartalomról van szó, akkor pedig az aktuális dátumot teszi ide.

Természetesen az nginx tiszteletben tartja a generált tartalmak által beállított fejléceket.

Ha valamilyen okból szeretnénk letiltani a Last-Modified mechanizmust:

if_modified_since off;

Ha nem szertnénk Last-Modified fejlécet küldeni:

add_header Last-Modified "";

Alapvetően ez a kliens oldali cachelés lényege – köszönöm hogy idáig olvastad a cikket és szívesen veszek minden hozzászólást :)

Series Navigation<< Gyors weboldalakat mindenkinek
0saves
Ha tetszett a cikk, kérlek szánj rá pár másodpercet hogy megoszd vagy like-old, illetve szólj hozzá!