- Gyors weboldalakat mindenkinek
- Kliens oldali cachelés – lebegj mint a lepke
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.
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 :)
