Kuten muuallakin kapitalistisessa yhteiskunnassamme, myös tietojenkäsittelyssä aika on rahaa. Hommat pitää tehdä mahdollisimman nopeasti, jotta urakan kannattavuus saadaan maksimoitua ja entistä enemmän duunia päästään sullomaan samaan aikatauluslottiin. Tehokkuutta on revittävä sieltä, mistä sitä saadaan.

Kuluttaja, tarvitset sipulia!

TV-kokit ovat loistava reaalimaailman esimerkki välimuistin käytöstä. Sen sijaan, että ohjelmassa näytettäisiin, kuinka kokki pilkkoo sipulia, mittaa öljyä ja kuorii raparperia, on kaikki tämä tehty etukäteen valmiiksi ja kokki vain heittelee valmiit ainesosat sopivassa välissä mukaan sotkuihinsa. Säästetty ruutuaika voidaan käyttää entistä pidempiin mainoskatkoihin. Koska mainosaika halutaan tietysti maksimoida (ja ehkä lisätä ohjelmaan mukaan pieni tuotepromootiopätkä), voidaan valtaosa kokkailusta oikeastaan skipata kokonaan ja tehdä ohjelman loppupuoliskolla jo genreklassikoksi muodostunut "tässä minulla onkin valmis kesäinen sipuliraparperivuoka…"-temppu, eli napata kokonaan valmis tuotos jemmasta pöydän alta. Lusikallinen suuhun (yök!) ja mainoskatkolle!

Tietojenkäsittelyssä välimuistia käytetään täsmälleen samalla tavalla kuin TV-kokki käyttää esivalmistelua: jos prosessin tulos on entuudestaan tiedossa, voidaan prosessi jättää suorittamatta ja korvata se suoraan tuloksella. Niin ihanan taloudellista ja kannattavaa, että kokkiohjelmatuotantoyhtiön talousosaston Excel-graafienkin mukaan tällä menolla päästäisiin melkein tavoitteeseen!

Mitä isompi sipuli, sitä enemmän kerroksia

Brendan Gregg on kirjassaan Systems Performance: Enterprise and the Cloud havainnollisesti siirtänyt tietojenkäsittelyn latenssit ihmiselle hieman helpommin ymmärrettävään mittaluokkaan:

Toiminto Oikea latenssi Skaalattu latenssi
Yksi prosessorisykli 0.4 ns 1 s
Prosessorin L1-välimuisti 0.9 ns 2 s
Prosessorin L2-välimuisti 2.8 ns 7 s
Prosessorin L3-välimuisti 28 ns 1 min
Käyttömuisti (DDR DIMM) ~100 ns 4 min
Intel Optane -muisti <10 μs 7 h
NVMe SSD-massamuisti ~25 μs 17 h
SSD-massamuisti 50–150 μs 1.5–4 pv
Kiintolevy 1–10 ms 1–9 kk
Internet-pyyntö: USA:n rannikolta toiselle 65 ms 5 v
Internet-pyyntö: San Franciscosta Hong Kongiin 141 ms 11 v

Koska latenssit kasvavat näin valtavasti siirryttäessä prosessorin sisältä kerroksissa ulospäin, ei ole mikään ihme, että välimuisteja käytetään tietojenkäsittelyssä ihan joka paikassa. Niitä käytetään prosessoreiden sisällä (L1-, L2-, ja L3-välimuistit), tiedonsiirrossa tietokoneen eri osien välillä, käyttöjärjestelmissä ja ohjelmistoissa, verkkolaitteissa… aivan kaikkialla. Erityisen tärkeää välimuistin käyttö on silloin, kun sillä pystytään vähentämään tarvetta siirtyä ulompaan kerrokseen, eli voidaan käyttää yllä olevan taulukon ylempää riviä alemman asemesta.

Verkkosivustoilla hyödynnetään välimuistia selkeästi etenkin seuraavissa kohdissa:

  • Sivun eri palasten luominen

  • Palasten kokoaminen valmiiksi HTML-sivuksi

  • Kokonaisen HTTP-vastauksen muodostaminen

  • HTTP-pyynnön tekeminen selaimesta palvelimelle

  • Sisällön esittäminen selaimessa

Riippuen siitä, katsotaanko verkkosivuston käyttöä palvelimen vai selaimen näkökulmasta, on suunniteltava erilainen välimuististrategia. Molemmat strategiat kuitenkin tähtäävät yhteiseen maaliin ja molempia on pakko soveltaa, jotta sivusto saadaan toimimaan järkevästi.

Mikään työhän ei ole niin nopeaa, etteikö olisi nopeampaa jättää sitä tekemättä.

Slicing onion - Caroline Attwood / Unsplash

Palvelinpään välimuisti yhden verkkosivun esittämisessä

Palvelimen hommana tässä tapauksessa on ottaa vastaan asiakkaan (yleensä selaimen) tekemä HTTP-pyyntö, muodostaa sopiva HTTP-vastaus ja lähettää se takaisin asiakkaalle. Jos asiakas esimerkiksi pyytää kokkiohjelman julkaisujärjestelmän päällä pyörivältä verkkosivustolta kesäisen sipuliraparperivuoan reseptiä, on tarkoituksena, että palvelin palauttaa vastauksena HTML-sivun, jossa on paitsi ko. resepti, myös paljon muuta sisältöä, kuten esimerkiksi:

  • Navigaatio, jolla kävijä pääsee liikkumaan sivuston sisällä

  • Ristiinmarkkinointinostot, joissa voidaan esittää muita yhtä… öhömm... kiinnostavia reseptejä

  • Mainosbannereita kokkiohjelman tulevasta syyskiertueesta

  • Reseptiä koskevat (positiiviset, tietysti) kävijäkommentit

Mikäli palvelin lähtee rakentamaan HTML-sivua tyhjästä, on palvelimen käytävä läpi sivuston rakenne navigaation toteuttamiseksi, etsittävä sopivat reseptit ristiinmarkkinointia varten, muodostettava mainosbannerit kulloinkin voimassa olevan markkinointilogiikan mukaan sekä ladattava ja jäsenneltävä kaikki kyseiseen reseptiin liittyvät kävijäkommentit. Tämä kaikki vie huikeita määriä palvelinresursseja sekä kallisarvoista aikaa, eikä sivun sisältö ainakaan pääosin vaikuta olevan sellaista, että se muuttuisi kovin usein. Tämä voisi siis olla erittäin hyvä kandidaatti välimuistin käytölle!

Välimuisti sivun osille

Kyseistä sivua rakennettaessa kannattaisi ensin miettiä sitä, mitkä osat sivusta muuttuvat erittäin harvoin. Tällaisia ovat oletettavasti ainakin itse reseptin sisältö sekä navigaatio. Nämä osat sivusta voitaisiin jättää palvelimen välimuistiin valmiissa asussaan hyvin pitkäksi ajaksi. Jos ne joskus muuttuvat, ne voidaan muutoshetkellä luoda uudestaan jo valmiiksi välimuistiin, josta niitä voidaan sitten käyttää taas seuraavaan muutokseen asti.

Ristiinmarkkinointinostot päivittyvät harvoin, joten myös ne voidaan jättää pitkäkestoiseen välimuistiin. Tässä tapauksessa olisi varmastikin hyvä idea pitää reseptikohtaiset ristiinmarkkinointinostot välimuistissa esimerkiksi noin päivän verran. Tuskinpa tuotantoyhtiö pystyy tuottamaan reseptejä sivustolle nopeammin kuin yhden reseptin päivätahdilla.

Mainosbannerien puolestaan on todennäköisesti tarkoitus muuttua hieman useammin ja ehkä jopa jonkinlaisella satunnaislogiikalla, joten niitä ei voida jättää välimuistiin kovinkaan pitkäksi aikaa. Vielä suurempi ongelma muodostuu kävijäkommenteista, sillä niiden päivitystahtia on mahdotonta arvioida. Pahimmassa (tai parhaassa?) tapauksessa kommentteja tulee useita minuutissa! Miten ihmeessä näissä voidaan hyödyntää välimuistia? Sulaako palvelin sipulinkatkuiseksi kasaksi?

Fiksulla välimuistitoteutuksella ja ei se sellaiseksi sula.

Välimuisti kokonaiselle sivulle

Sivun rakentaminen nopeutuu jo huomattavasti, kun jotkin sivun osat voidaan ottaa suoraan välimuistista ja niitä ei tarvitse rakentaa jokaista sivupyyntöä varten uusiksi. Sivu pitää silti kuitenkin koostaa valmiiksi, mikä edellyttää osien yhteenliittämisen lisäksi esimerkiksi sivun tarvitsemien JS- ja CSS-tiedostojen viittausten sekä sivun metatietojen liittämistä osaksi dokumenttia. Tässäkin on oma hommansa, joten välimuistista voisi olla hyötyä.

Valmiin, koostetun HTML-sivun tallentaminen välimuistiin nopeuttaisi prosessia vielä enemmän. Kokonaisen sivun tallentaminen välimuistiin vaatii kokkiohjelman käyttämältä julkaisujärjestelmältä ainoastaan sitä, että se osaa yhdistää pyydetyn sivun oikeaan välimuistikohteeseen ja muodostaa siitä vastauksen - koostamisprosessi voidaan jättää kokonaan välistä ja sivu voidaan tarjoilla niin nopeasti kuin se vain suinkin kyseiseltä julkaisujärjestelmältä onnistuu.

Suurimpana ongelmana sivuvälimuistissa kuitenkin on se, että kokonainen sivu on usein liian iso kokonaisuus yhden välimuististrategian käyttämiseksi. Jos sivu pidetään välimuistissa esim. kuusi tuntia, voi sivun sisältö päivittyä kävijöiden näkökulmasta tiheimmillään kuuden tunnin välein. Tämä aiheuttaisi ongelmia kokkiohjelman sivustolla, sillä tuotantoyhtiön markkinointiosasto vaatii bannereiden vaihtuvan jokaisella sivulla paljon nopeammalla tahdilla ja käyttäjät ovat turhautuneita, jos heidän kirjoittamansa "Eka!"-kommentit tulevat näkyviin kuuden tunnin viiveellä. Tätä ongelmaa voidaan taklata kahdella tavalla: ESI-tyyppisellä palvelinpään toteutuksella tai dynaamisella näyttötoteutuksella asiakaspäässä.

ESI:ssä sekä sitä vastaavissa toteutuksissa sivun sisältö ladataan ensin kokonaisuudessaan välimuistista, minkä jälkeen osat siitä korvataan dynaamisesti. Tämä voisi olla kohtuullisen hyvä tapa toteuttaa kokkiohjelman sivustolle banneritoiminnallisuus, sillä bannerit voisivat päivittyä huomattavasti tiheämmin kuin sivun muu sisältö. Kommentointiongelmaa se ei kuitenkaan korjaa täydellisesti, sillä optimaalisessa toteutuksessa kommentit päivittyisivät sivuille reaaliajassa.

Kaiken tämän lisäksi yhtenä ongelmana säilyy se, että julkaisujärjestelmä joutuu kuitenkin tekemään töitä periaatteessa muuttumattoman sivun tarjoamiseksi vastauksena asiakkaalle. Mikään työhän ei ole niin nopeaa, etteikö olisi nopeampaa jättää sitä tekemättä. Voitaisiinko jotakin välimuistia hyödyntää vielä näiden kaikkien lisäksi?

Palvelinpään HTTP-välimuisti

Palvelimen näkökulmasta olisi täydellistä, jos kukin sivu voitaisiin aina tarjoilla prikulleen samanlaisena jokaiselle kävijälle ikuisesti. Ihan näin mukavaan tilanteeseen ei normaalisti päästä, mutta hyvin lähelle kylläkin. Jos sivu voidaan tarjoilla palvelimelta kaikille kävijöille samanlaisena mahdollisimman pitkään, voidaan tehokkaasti hyödyntää HTTP-välimuistia. HTTP-välimuistiin tallennetaan kokonaisia HTTP-vastauksia ja ajatuksena on, että vastaukset voidaan aina lähettää sellaisenaan asiakkaalle heti, kun pyyntö tulee palvelimelle. Tällöin voidaan kokonaan ohittaa paitsi julkaisujärjestelmä, myös julkaisujärjestelmän edessä nököttävä HTTP-palvelinsovellus. Tunnetuin tässä käytössä oleva ohjelmisto on varmastikin Varnish Cache, jota myös me Karhulla käytämme.

HTTP-välimuisti toimii yhteistyössä sivuvälimuistin kanssa, sillä sivuvälimuisti kertoo HTTP-välimuistille, kuinka pitkään sivu voidaan tarjoilla välimuistista muuttumattomana. Kun ilmoitettu aika on kulunut, HTTP-välimuisti päästää seuraavan pyynnön läpi julkaisujärjestelmälle asti ja nappaa vastauksen itselleen talteen taas seuraavia pyyntöjä varten. Erittäin fiksua ja huomattavan tehokasta!

Ihan ongelmatonta tämä HTTP-välimuistin kanssa eläminen ei ole, sillä HTTP-välimuistille pitää erikseen ilmoittaa, mikäli jotkin pyynnöt pitäisi saada päivitettyä etuajassa. Toisinaan välimuistiin myös saattaa mennä jotakin, jota sinne ei olisi todellakaan pitänyt mennä (kuten versio kesäisestä sipuliraparperivuoasta, jossa muskottipähkinä on vahingossa korvautunut maksalla), jolloin virheiden korjaaminen saattaa olla erittäin hankalaa.

Ehdottomasti suurin ongelma HTTP-välimuistin kanssa muodostuu kuitenkin yleensä siitä, että sivut eivät yksinkertaisesti voi käyttää sitä. Mikäli sivun sisältöön vaikuttavat perustavanlaatuisella tavalla esimerkiksi käyttäjäkohtaiset asetukset tai vaikkapa aikaisemmin sivustolla tehdyt valinnat, ei sivua käytännössä voida tarjoilla ikinä sellaisenaan HTTP-välimuistista. Ongelma on tismalleen sama kuin kokonaisen HTML-sivunkin välimuistin kanssa - ja samat ovat myös ratkaisut. Jo aikaisemmin mainittu Varnish Cache esimerkiksi tarjoaa oman ESI-toteutuksensa, joten sekä ESI että dynaaminen asiakaspään näyttötoteutus voisivat ainakin jossain määrin olla ratkaisuna tähän ongelmaan. Kummankaan ratkaisun toteuttaminen ei ole ihan yksinkertaista, joten useimmiten ratkaisuna tosielämässä toimii se, että HTTP-välimuisti jätetään vain yksinkertaisesti kokonaan välistä ja ongelmalliset pyynnöt ohjataan eteenpäin aina julkaisujärjestelmälle asti. Kompromissit ovat tylsiä, mutta usein toimivia.

Asiakaspään välimuisti

Siinä missä palvelinpään välimuistit pyrkivät antamaan vastauksen asiakkaan pyyntöön mahdollisimman nopeasti, tähdätään asiakaspäässä ensisijaisesti siihen, että pyyntöjä ei edes tarvitsisi tehdä. Koska verkkosivut ovat lisäksi nykyään paljon enemmän kuin pelkkiä digitaalisia vastineita A4-arkeille, tarvitaan panostusta myös asiakaspään ajonaikaisen suorituskyvyn parantamiseen. Ei siis ole kovinkaan yllättävää, että myös asiakaspäässä välimuistit ovat erittäin suuressa roolissa.

Asiakaspään HTTP-välimuisti

Tämän kirjoituksen alkupuolella olevasta latenssitaulukosta on helppo huomata, että tiedonsiirto Internetin yli on nopeilla runkoverkkoyhteyksilläkin oikeasti älyttömän hidasta. Tästä syystä kaikki ylimääräiset pyynnöt asiakkaalta palvelimelle on ehdottomasti järkevää eliminoida. Tämä onnistuu näppärästi tietenkin asiakaspään HTTP-välimuistilla.

Asiakaspään ja palvelinpään HTTP-välimuistin oikeastaan erottaa ainoastaan se, kummassa päässä Internetin isoa letkua ne ovat. Näihin molempiin nimittäin voidaan vaikuttaa samoilla HTTP-vastausten mukana menevillä välimuistiotsakkeilla, jotka kertovat, kuinka pitkäksi aikaa kyseistä osoitetta koskevan pyynnön vastaus voidaan tallentaa välimuistiin. Molemmat myös ohjaavat pyynnöt eteenpäin, mikäli välimuistissa ollut vastaus on vanhentunut tai sitä ei vielä löydy välimuistista ollenkaan.

Toisin kuin palvelinpään HTTP-välimuistista, ei palvelimella ajettavalla julkaisujärjestelmällä ole mahdollisuutta poistaa tietoja asiakaspään HTTP-välimuistista. Tästä syystä verkkosivustoilla saattaakin toisinaan näkyä vanhentuneita tietoja tai sivut voivat tilapäisesti näyttää olevan hiukan rikki. Useimmiten asioihin auttaa sivun lataaminen toisella osoitteella (esim. lisäämällä osoitteen perään ?a-tyyppinen kyselyosa), selaimen välimuistin tyhjentäminen tai sivun lataaminen ilman selaimen välimuistia. Seuraava taulukko koittaa selittää sitä, miten nämä kikat toimivat. Esimerkkinä toimii kokkiohjelman sivustolta kesäisen sipuliraparperivuoan resepti, joka majailee kuvitteellisessa osoitteessa stafylokokki.tv/resepti/12.

Toiminto Vaikutus
Sivun lataaminen eri osoitteella: stafylokokki.tv/resepti/12?a Sivun sisältöön tehdyt muutokset tulevat näkyviin. Jos itse reseptin sisältö olisi muuttunut, näkyisivät muutokset nyt. Muutokset esim. tyylitiedostoihin jäisivät päivittymättä.
Sivun lataaminen ilman HTTP-välimuistia (esim. näppäinyhdistelmällä ctrl+F5) Sivun sisältö ja kaikki sivuun liitetyt resurssit ladataan palvelimelta. Palvelimelle pyynnössä ei näy eroa, joten palvelin voi tarjoilla vastauksen HTTP-välimuististaan.
Selaimen välimuistin tyhjentäminen Välimuistin tyhjentämisen jälkeen niin haluttu reseptisivu kuin kaikki muutkin sivut sekä kuvat ja muut resurssit ladataan palvelimelta Internetin yli pyydettäessä.

Sillä on siis merkitystä, miten HTTP-välimuistin sivuvaikutuksista koittaa päästä eroon. Mikäli törmäät ongelmiin, joiden epäilet johtuvan selaimen HTTP-välimuistista, koita komentaa selaimesi lataamaan sivu ilman HTTP-välimuistia. Windows-selaimissa tämä tapahtuu useimmiten näppäinyhdistelmällä ctrl+F5 ja macOS:ssä näppäinyhdistelmällä cmd+shift+R.

Asiakaspään ajonaikaiset välimuistit

Koska verkkosivustot ovat hyvin usein sovellusmaisia, on asiakaspäässä käytettävä vastaavanlaisia välimuistitoteutuksia kuin palvelinpäässäkin. Näiden ns. ajonaikaisten välimuistien tarkoituksena on parantaa asiakaspään toteutuksen suorituskykyä ja mahdollisuuksien mukaan vähentää riippuvuutta palvelimesta.

Ajonaikaiset välimuistitoteutukset vaihtelevat erittäin paljon kunkin sovelluksen tarpeiden mukaan, joten niiden yleisluontoinen kuvailu ja kategorisointi on mahdotonta. Ensisijaisesti ajonaikaisten välimuistien tavoitteena yleensä on verkkoliikenteen vähentäminen, sillä tiedonsiirto on etenkin mobiilikäytössä hitautensa ja epäluotettavuutensa vuoksi suurin yksittäinen käyttökokemukseen vaikuttava tekijä.

Jonkin verran latensseja voidaan asiakaspäässä vähentää myös näyttövälimuistilla, johon palvelimelta dynaamisesti ladattu data tallennetaan valmiiksi näytettävässä muodossa. Tämä on erittäin käytetty tekniikka mm. erilaisten listausten rakentamisessa. Tällaisesta toteutuksesta esimerkkinä voi toimia vaikkapa Olympia Kaukomatkatoimiston matkakalenteri, jossa kukin näytettävä matka tallennetaan ensimmäisen esittämisen yhteydessä näyttövälimuistiin, josta se sitten noudetaan seuraavalla näyttökerralla, kun listaus muuttuu käyttäjän valintojen mukaan. Matkakalenterissa on toki käytössä muitakin ajonaikaisia välimuistikerroksia, minkä ansiosta kalenteri reagoi käyttäjän tekemiin valintoihin varsin nopeasti.

Ja sitten vielä ne reaaliaikaisesti päivittyvät kommentit

Niin, esimerkkinämme toimineella kokkiohjelman reseptisivulla oli tosiaan myös kommentointimahdollisuus. Ideanahan oli, että kommentit ilmestyisivät reseptisivulle reaaliaikaisesti, joten pitkäkestoinen välimuisti lienee näiden kohdalla poissuljettu ajatus. Mitenköhän nämä kannattaisi toteuttaa?

Kommentit ovat loistava esimerkki siitä, miten jotkin osa-alueet kannataa ulkoistaa kokonaan muualle. Kävijäkommenttien tapauksessa voisi olla järkevää käyttää esim. Disqus-palvelua, jolla koko kommentointisysteemi saadaan ulkoistettua jonkun muun päänvaivaksi. Disqusia käytetään hyvin yleisesti niin Suomessa kuin muuallakin maailmalla ja se tarjoaa paitsi hyvän käyttökokemuksen kommentoijille, myös loistavat moderointiominaisuudet. Koska Disqus liitetään sivuille vasta asiakaspäässä, voidaan sivut tarjoilla palvelimen HTTP-välimuistista muuttumattomina, vaikka kommentteja putkahtelisi resepteihin kuin sieniä sateella.

Yhteenveto

Välimuisti on erittäin perustavanlaatuinen käsite tietojenkäsittelyssä. Verkkosivustojen yhteydessä välimuistiin liittyvät ainakin seuraavat piirteet:

  • Palvelin voi pitää joitakin tietoja välimuistissa, jolloin dynaamisten sivujen rakentaminen nopeutuu.

  • Palvelin voi pitää rakennettuja HTML-dokumentteja välimuistissa, jolloin dynaamisia sivuja ei rakenneta uudelleen jokaisella sivupyynnöllä.

  • Palvelin voi pitää kokonaisia HTTP-vastauksia välimuistissa, jolloin pyyntöjä ei tarvitse käsitellä palvelimen sovelluskerroksessa.

  • Selain voi pitää kokonaisia HTTP-vastauksia välimuistissa, jolloin toistuvia pyyntöjä ei tarvitse tehdä verkon yli ollenkaan.

  • Jos välimuistia ei voi jostain syystä käyttää, kannattaa siitä tehdä jonkun muun ongelma.

Välimuisti todella on kuin sipuli: kerroksia on lähes loputtomasti ja pahimmassa tapauksessa kyyneliltä ei voi välttyä.