Skaalautuviin järjestelmiin kohdistuu muitakin vaatimuksia – skaalautuvuudesta ei hirveästi ole hyötyä ilman hyväksyttävää suorituskykyä ja lisäksi kokonaisuuden pitäisi vielä olla vikasietoinen ja ylläpidettävä. Toisin kuin toiminnalliset vaatimukset, skaalautuvuuden tapaiset ei-toiminnalliset vaatimukset ovat usein käänteisesti riippuvaisia toisistaan. Yhden vaatimuksen naiivi ratkaisu heikentää tai hankaloittaa toisen vaatimuksen saavuttamista.

Palvelimellasi saattaa olla paha olo.
Osittain tästä syystä skaalautuvien järjestelmien rakentaminen on vähän kuin tukkisi vuotavaa patoa sormilla – vuotoja poksahtelee aina vain lisää. Tämä on jo nähty edellisissä tämän sarjan kirjoituksissa:
- Yhdessä koneessa toimineen palvelun osajärjestelmät sijoitettiin eri koneisiin.
- Webpalvelimesta muodostuu pullonkaula, joka korjataan monistamalla se ja ottamalla LB ja tahmeat istunnot käyttöön.
- Tahmeissa istunnoissa on taas ongelmia vikaantumisten ja kuorman tasauksen kanssa, jota korjataan jaetuilla istunnoilla.
- Tietokannasta muodostuu pullonkaula, jota korjataan webpalvelimen hajautetulla tai jaetulla välimuistilla.

Epäkonsistentti näkymä
Käsittelin välimuisteja jo aiemmin jolloin rajoittauduttiin yksittäisessä palvelimessa paikallisesti käytettyyn välimuistiin. Paikallinen välimuisti toimii hyvin tilanteissa, joissa suurin osa välimuistissa olevasta tiedosta on vain ja ainoastaan kyseisessä välimuistissa.
Jos käyttäjien kuorma tasataan palvelimien kesken dynaamisesti ja yritetään käyttää vain paikallisia välimuisteja tulee hyvin nopeasti vastaan epäkonsistentin näkymän ongelma. Tällöin käyttäjä näkee epäkonsistenttia tietoa, koska sitä ei ole päivitetty ajan tasalle kaikkien palvelimien välimuisteihin.

Epäkonsistentti tila voi muodostua välimuisteihin esimerkiksi seuraavasti: 1) Alussa palvelimien välimuistissa on sama tieto. 2) Käyttäjä tekee jotain, joka päivittää palvelimen FE1 paikallista välimuistia. 3) Käyttäjää palvellaan palvelimesta FE2, joka välimuistissa on vanhentunut tieto.
Käyttäjälle tilanne voi olla hämäävä jos hänen toimenpiteillään ei ole näennäistä vaikutusta palvelun tilaan. Palvelun kannalta tilanteessa ei kuitenkaan välttämättä ole mitään vaarallista – vanha välimuistitieto päivittyy ajan kanssa, ja tietokannassa oleva pysyvä tila on myös oikein. Voidaan kuitenkin helposti – vahingossa – rakentaa ratkaisu, joka väärään välimuistin tilaan luottaessaan tekeekin jotain peruuttamatonta, joten epäkonsistentin tilan ongelma koskettaa paljon muutakin kuin sekaantuneita käyttäjiä.
Hajautettu, jaettu välimuisti
Periaatteessa jaettu välimuisti voitaisiin toteuttaa siten, että jokaisessa palvelimessa olisi paikallinen välimuisti, jota pidettäisiin yhteisesti ajan tasalla. Kun yksi palvelin suorittaa päivityksen, se lähettää päivitystiedot myös muille palvelimille jotka päivittävät paikallisen välimuistinsa. Käytännössä näin ei koskaan tehdä, koska se ei skaalaudu.
Toinen tapa on siirtää ongelma “muualle” eli käytännössä levyjärjestelmälle. Jos käytössä on luotettava ja riittävän tehokas jaettu verkkolevy (NAS-ratkaisu) niin mikään ei estä jakamasta levypohjaista välimuistia NFS:llä kaikille koneille. Tämä ei silti ole myöskään yleinen ratkaisu.

Esimerkki kolmeen palvelimeen hajautetusta jaetusta välimuistista. Tässä esimerkissä hajautusalgoritmina on chr(substr(sha1(avain), -1, 1) mod 3, joten uusi arvo "karhukopla" päätyisi cache1:een (indeksi 0).
Eniten käytetty ratkaisu on jakaa kuhunkin välimuistipalvelimeen vain osa kaikista arvoista – tavoitteena tietysti mahdollisimman tasainen jakauma. Jos jokaisesta webipalvelimesta varataan osa kapasiteettia jaetulle välimuistille, skaalautuu välimuistin kapasiteetti palvelinten lukumäärän mukaan. Koska nykyään sekä muisti, että paikallinen verkkokapasiteetti ovat halpaa voidaankin melko “halvalla” rakentaa koko palvelun tarpeita vastaava tarpeeksi iso jaettu muistinvarainen välimuisti. Verkon yli toisen koneen muistista tiedon hakeminen on nopeampaa kuin paikalliselta levyltä!
Oleellista on kuitenkin se, että yhdelle hakuavaimelle tallennettua tietoa haetaan koko klusterissa vain yhdestä paikasta kullakin ajan hetkellä. Kaikki välimuistia käyttävät palvelimet saavat siis saman arvon koska se on niiden näkökulmasta saatavilla vain yhdestä paikasta.
Haasteita
Välimuistien käyttö voi olla ongelmatonta ja kehittäjälle helppoa. Näin on esimerkiksi Hibernaten ja vastaavien ORM:ien kanssa jotka tukevat hajautettuja välimuisteja. Välimuistiin tallennetaan vain oliot – itse relaatiot määritellään edelleen kannasta, tosin niiden koko on paljon pienempi. Kyselyt ja niiden tulokset voidaan sijoittaa myös välimuistiin, joskin sen tehokas hyödyntäminen vaatii jo enemmän työtä kehittäjältä.
Välimuistin käyttö on sovellukselle läpinäkyvää vain jos dataa muokataan yhdessä paikassa. Jos samaa tietoa päivitetään taustalla olevassa tietokannassa useaa eri kautta, pitää tiedon päivittäjien osata invalidoida muiden käyttäjien väärät tiedot. Esimerkiksi viestien määrä palvelussa voi pienentyä käyttäjän toimesta (poistaa viestejä) ja lisääntyä järjestelmän kautta (vastaanottaa viestejä.)
Välimuistit kategorisesti eivät myöskään toteuta transaktioita, joten niistä on turha odottaa ACID-semantiikkaa. Jos teet put/get/get/get sarjan samalle avaimelle, voit saada jokaisella get:llä eri arvon – eikä mikään niistä ole välttämättä se mitä put:lla laitoit. Paljon on siis kiinni siitä, miten ohjelmoija välimuistia käyttää joko suoraan välimuisti-API:n kautta tai epäsuorasti ORM:n tai muun middlewaren kautta.
Jaettu välimuisti ei myöskään aina ole oikea vaihtoehto. Jos datan käyttö on pääasiassa lokalisoitunutta, mutta mukana on silti myös globaalia dataa voi ratkaisuna olla sekoittaa paikallisia ja jaettuja välimuisteja.
Nyrkkisääntö on kuitenkin, ettei suorituskykyistä ja skaalautuvaa verkkopalvelua saa tehtyä ilman välimuistien käyttöä. (Välimuistin sijainnista ja mallista voidaan sitten keskustella paljonkin.)
Välimuistien variantteja

Välimuisteista on paljon erilaisia variantteja seuraavien ominaisuuksien suhteen:
- Miten ne reagoivat välimuistipalvelimien hallittuun (ylläpidolliseen) lisäämiseen ja poistoon? Jotkut vaativat koko välimuistin tyhjentämistä.
- Miten ne reagoivat palvelinten kaatumiseen? Jotkin kestävät yhden tai useamman palvelimen kaatumisen hukkaamatta välimuistin arvoja. (Tällöin lähestytään jo hajautettua tietovarastoa…)
- Ovatko ne muistipohjaisia, levypohjaisia vai molempia? Mitä pidetään muistissa ja mitä levyllä?
- Käytetäänkö levyä tilapäistilan laajennuksena, vai pysyväistalletukseen? Välimuistin sisältö saatetaan tallettaa pysyvästi, jotta esimerkiksi uudelleenkäynnistyksessä välimuisti saadaan täytettyä nopeasti aiemmilla arvoilla.
- Mitkä hakuavaimet ylipäänsä on sallittu? Onko mahdollista käyttää binääritietoa (myös NUL-merkki) avaimena?
- Miten isoja arvoja voidaan tallettaa? Voiko tallettaa vaikkapa isoja kuvatiedostoja?
- Onko niissä lisäominaisuuksia, kuten atomisia “test-and-set” tai “atomic-increment” operaatioita?
- Millä perusteella välimuisti valitsee poistettavat (evicted) arvot, jos sen maksimikapasiteetti saavutetaan?
Tunnettuja jaettuja välimuisteja ovat Memcached, Oracle Coherence, Terracotta EHCache ja Windows Azuren AppFabric. Lähempänä hajautettuja tietovarastoja, mutta myös välimuistina käytettäviä ovat redis ja Cassandra.
Seuraavassa osassa käsittelen “perinteisten” tietokantojen skaalaamista.
Linkkejä:
- Twitter käytti ainakin 2009 paljon memcachedia.
- Miksei DHT:t (esim. memcached) ole aina sopiva ratkaisu: Why you won’t be building your killer app on a distributed hash table











