Tämä kirjoitus on kolmas osa kirjoitussarjassa – aiemmat osat: yksi ja kaksi.
Tästä lähtien ei enää sanaakaan komponenttien tehostamisesta eikä vertikaalisesta skaalautumisesta. Jatkossa kaikki mitä tehdään, liittyy joko uusien skaalautuvien rakenteiden, komponenttien tai käytettävien koneiden määrän kasvattamiseen.
ABC
Edellisessä osassa annoin jo viitteitä siitä, että monet skaalautuvuutta ja sitä kautta suorituskykyä parantavat toimenpiteet eivät ole yhtä haastavia. Jatkossa jaan nämä keinotekoisesti ja varsin hatusta vedettynä kolmeen eri luokkaan eli A-, B- ja C-luokkiin pohjautuen niihin kuuluvien ratkaisujen haastavuuteen ja arkkitehtuurillisiin vaikutuksiin. Skaalautuvuutta kasvattaessa on järkevää aloittaa helpommista toimenpiteistä ja edetä haastavampiin vasta tarpeiden kasvaessa – tästä siis nyrkkisäännöksi, että ensin A, sitten B ja vasta lopulta C.
Huomaa tosin, ettei osajärjestelmistä ja komponenteista koostuvassa kokonaisuudessa ole välttämätöntä skaalata kaikkia osia samaan tahtiin. Tarve ratkaisee.
- A-luokka lähtee liikkeelle järjestelmän roolien jakamisesta eri koneisiin – tämä sisältää myös kokonaan uusia komponentteja – mutta ei vielä yksittäisten komponenttien monistamista. Eli mikään yksittäinen järjestelmän osa ei vielä skaalaudu, vaikka järjestelmä jo kokonaisuutena alkaa skaalautumaan. A-luokan toimenpiteisiin kuuluu esimerkiksi tietokantojen siirto omiin koneisiin, esitys- ja taustakerrosten erottaminen, käyttäjä- ja datajoukkojen triviaali segmentointi sekä monta muuta.
- B-luokka sisältää ne toimenpiteet joissa järjestelmää skaalataan lisäämällä koneita. Tämä toimenpiteiden joukko on laaja alkaen komponenttien sisäänrakennettujen skaalautumisominaisuuksien hyödyntämisestä (esimerkiksi tietokantojen klusterointi) päätyen aina järjestelmän arkkitehtuurin läpikotaiseen uusimiseen. Kuitenkin kaikki B-luokan toimenpiteet seuraavat vakiintuneita käytäntöjä ja kaavoja (“scalability patterns”, sekä sisältävät todellista komponenttitason skaalautumista.
- C-luokassa siirrytään olemassaolevien kaavojen ja komponenttien ulkopuolelle. Käytännössä tällä alueella on kaikki mikä edellyttää kokonaan uusien osajärjestelmien toteuttamista alusta asti ja siihen liittyvää (tieteellistäkin) tutkimustyötä. Esimerkkejä yrityksistä ja palveluista jotka ovat tällä alueella ovat Google, Amazon, Yahoo, Twitter ja Facebook. Monet näiden yritysten kehittämät järjestelmät ja käytännöt ovat siirtyneet joko sellaisenaan käyttöön muualle tai ainakin toimineet malleina myöhemmälle kehitykselle.
Tässä kirjoituksessa aloitan A-luokan toimenpiteillä. B- ja C-luokkiin pääsemme myöhemmin.
Lähden liikkeelle LöydäSukkasi.com-palvelun kanssa tilanteesta, jossa järjestelmän tehostustoimenpiteet on tehty ja vanha nuhapumppukone on vaihdettu moderniin (katso edellinen osa). Järjestelmän suorituskyky yhdellä koneella on nyt 100 000 sukkaparia. Järjestelmä koostuu tällä hetkellä seuraavista selkeästi erotettavista osista:
- Webipalvelimesta, joka tarjoaa käyttäjille mahdollisuuden seurata sukkiensa tilannetta, lisätä uusia ja niin edelleen. Palvelun käyttö edellyttää sisäänkirjautumista.
- Sukkien sijaintikannan päivittäjästä, joka vastaanottaa tilapäivityksiä RFID-lukijoilta ja vie ne kantaan. Oletamme RFID-lukijoiden olevan varsin älykkäitä ja verkotettuja, ja ne lähettävät itsenäisesti tiedot luetuista RFID-tunnisteista seurantajärjestelmälle.
- Tietokannasta jonne on talletettu käyttäjätiedot, tiedot sukista ja sukkien sijaintitiedot.
Tietokannan erottelu
Tämä kaikki toimii aluksi yhdessä tietokoneessa. Aivan ensimmäinen tehtävä asia on siirtää tietokanta omaan koneeseensa (kuvissa uudet tai merkittävästi muuttuneet osat on merkitty magentalla, lisäksi nuolet esittävät kontrollin eivätkä datan siirtymistä – palvelin ottaa yhteyden tietokantaan eikä toisinpäin):

Tietokanta erotettuna muusta palvelusta omalle koneelleen. Katkoviiva esittää "verkon reunaa" eli kuvaan on tuotu "sisäisen verkon" käsite.
Roolien lisäerottaminen
Jatko riippuu paljon siitä, miten ja millaisin rajapinnoin järjestelmän eri osat on toteutettu. Esimerkissämme RFID-päivityksiä vastaanottava osa kirjoittaa päivitykset tietokantaan eikä sillä ole suoria riippuvuuksia web-käyttöliittymään. Toiminnallisuuden jako vastaa tässä hyvin ajatusta kerrostuneista verkkopalveluista joissa on loogisesti vähintään kolme kerrosta:
- Front-end eli FE, joka hoitaa liittymät ulkopuolisiin järjestelmiin kuten käyttäjille.
- Back-end eli BE, joka toteuttaa “bisneslogiikan” mutta ei keskustele suoraan käyttäjien kanssa. Laajemmasti voidaan tulkita, ettei back-endin tulisi keskustella minkään ulkopuolisen järjestelmän kanssa, mutta rajanveto on tällöin helpompaa. Helpointa on sanoa, ettei back-end keskustele suoraan käyttäjien kanssa ja tulkita muut ulkopuoliset yhteydet tapauskohtaisesti.
- Tietokanta tai muu tietovarasto joka voi olla myös levyjärjestelmä.
Tästä huomaamme, että RFID-päivitysten käsittelijä on erillinen webiliittymästä ja voimme sijoittaa sen erilliseen tietokoneeseen:
-

RFID-tilapäivitysten ja käyttäjien palvelu erotetaan eri koneille.
Nyt käytössä on kolme konetta. Pikaisesti voisi kuvitella tästä saavutettavan suoraan 3× suorituskyvyn kasvun, mutta todellisuudessa siitä saa helposti enemmän, eli 5× suorituskyvyn. Tähän on kaksi syytä:
- Eri palvelut eivät joudu jakamaan toisten kanssa samoja resursseja. Tämä parantaa prosessorin, L1/L2-cachen, levyn ja muistin käyttöä.
- Eri komponenttien koneiden investoinnit voidaan suhteuttaa niiden kuormituksen mukaan, jolloin järjestelmä on myös paremmin tasapainossa eli kaikki osat ovat suhteellisesti yhtä kuormitettuja suhteessa käytössä oleviin resursseihin. Jos tietokanta kantaa 50% kuormasta, FE 35% ja BE 15%, lienee selkeää että tietokantakoneeseen kannattaa laittaa enemmän rahaa.
Staattinen data
Voimme myös lisätä järjestelmään joitain uusia komponentteja. LöydäSukkasi.com -palvelussa on jokaisesta sukasta kuva (miten muuten erottaisit nalle- ja prinsessasukat toisistaan?) joita esitellään palvelussa monissa paikoissa. Nämä kuvat ovat varsin isoja ja niitä on paljon, josta aiheutuu kaksi ongelmaa: 1) niiden näyttäminen käyttäjälle kuormittaa webipalvelinta ja 2) koko sivun lataus hidastuu, koska kaikki kuvat tulevat samasta osoitteesta.
Tarvitaan vähän tarkempaa järjestelmän tuijotusta jotta pystymme näkemään tämän eron, eli on olemassa erityyppistä dataa, joita käyttävät palvelun eri roolit. Tällä hetkellä FE hoitaa siis kaksi eri roolia yhdessä komponentissa ja koneessa – voimme siirtää toisen roolin (eli staattisen datan palvelemisen) toiseen komponenttiin ja saman tien tuon komponentin toiseen koneeseen.
-

Sekä tietokannasta tulevien sukkakuvien että muun staattisen datan välittäminen siirretään omalle palvelimelleen.
Oletan tässä, että kuvat on tallennettu tietokantaan eli tietorakenne on täysin normalisoitu. Denormalisointi – kuten kuvien siirto levylle – on B-luokan toimenpiteitä joten siitä ei tässä. Toinen tarpeellinen huomautus on, että selainten rinnakkaisten yhteyksien rajoituksia voidaan helposti kiertää CNAME-aliaksien käytöllä – mutta kirjoitan koetun suorituskyvyn parantamisesta myöhemmin, joten ei siitä tässä tämän enempää.
Välimuistit
Webipalveluissa tehdään monesti toistuvia tietokantakyseljä. Joukossa on tavallisesti paljon kyselyitä joiden tulokset eivät muutu kovinkaan usein. Lisäksi palveluissa on monenlaista tilatietoa jonka häviäminen ei ole maailmaloppu, joten niitä oikeasti tarvitse tallettaa tietokantaan, kunhan ne eivät katoa ihan seuraavalla sekunnillakaan. Näihin molempiin käytetään cache- eli välimuistiratkaisuja.
Välimuistiratkaisuja voidaan käyttää sekä komponentin tehostamiseen (no, tulihan se sana sittenkin esille) jolloin se on A-luokan toimenpide, tai mahdollistamassa skaalautuvan järjestelmän toteutusta jolloin se kuuluu B-luokkaan. Nyt käsiteltävässä tapauksessa puhun nimenomaan ensimmäisestä käyttötarpeesta, eli cachea käytetään komponentin suorituskyvyn nostamiseen, eikä yksittäistä cachea siis jaeta muiden komponenttien kanssa. (Oleellinen ero on “local cache” vs. “shared” tai “distributed cache.”)
Voimme ottaa välimuistin käyttöön kolmessa paikassa:
- Webikäyttäjien istuntotietoja ei talleteta tietokantaan. (Mikään pakkauksesta otettu web-palvelun kehitysympäristö ei oletuksena talleta istuntotietoja tietokantaan, joten tämä ongelma tulee harvoin vastaan. Varoituksena kuitenkin, ettei istuntotietoja pitäisi tallettaa tietokantaan ilman todella hyviä perusteluita.)
- Käyttäjäpalvelun tekemiä tietokantakyselyjä voidaan cacheta. Yleensä löytyy monia vähemmän aikakriittisiä asioita joita haetaan jatkuvasti. Nämä ovat helposti laitettavissa välimuistiin.
- Koska oletimme normalisoidun skeeman jossa myös sukkien kuvat ovat kannassa, voimme vähentää tietokantakyselyjä tallentamalla tietokannasta haetut kuvat välimuistiin staattisen datan palvelimessa.
-

Cache eli välimuisti on lisätty istuntotietojen, tietokantakyselyjen ja kuvien tilapäiseen tallentamiseen. Yhtenäinen viiva kuvaa yhden tietokoneen sisäisiä komponentteja. (Otso Kivekäs ehdotti juustoa kuvaamaan välimuistia - "cache" on myös eläinten ruokapiilo.)
Välimuisturatkaisuja on monia. Kaikissa webiohjelmointiin käytetyistä kielistä löytyy valmiit kirjastot ainakin paikallisen välimuistin käyttöön. Paikallinen välimuisti voi olla levy- tai muistipohjainen. Myöhemmin B-luokan toimenpiteissä keskustelemme lisää jaetuista sekä hajautetuista välimuisteista. Tämän hetken tarkasteluun liittyen riittää, että LöydäSukkasi.com-palvelun eri komponentit käyttävät paikallista tiedostopohjaista välimuistia.
Viestijonot
Viimeisenä – jo vähän B-luokkaa liippaavasti – huomaamme että RFID-käsittelijä toteuttaa itseasiassa kahta eri tehtävää: se sekä vastaanottaa päivitykset että käsittelee ne.
Erotamme päivitysten vastaanoton ja niiden käsittelyn laittamalla näiden väliin jonotusmekanismin. Tällöin RFID-muutosten vastaanottajan tehtävä on suoraviivainen: hyväksy yhteys, lue päivitystiedot, laita tiedot jonoon, kuittaa päivitys vastaanotetuksi ja sulje yhteys. RFID-viestien dekoodaus ja tietokannan päivitys tapahtuu eri komponentissa, joka purkaa viestijonoa ja käsittelee viestejä muuten samalla tavalla kuin ennenkin – vastaanottomekanismi on vain vaihtunut.
-

Lisäämällä jono RFID-päivitysten vastaanoton ja käsittelyn väliin saadaan viestin vastaanotto riippumattomaksi tietokannan nopeudesta.
Jonotusjärjestelmissä on paljon vaihtoehtoja. Yksinkertainen tapa on tehdä ad hoc jono laittamalla tiedot joko levylle tai tietokantaan. En kuitenkaan suosittele renkaan uudelleen keksimistä, vaan kannattaa ennemmin käyttää olemassaolevia jonotusjärjestelmätoteutuksia (“message queue”.)
Mitä tämä hyödyttää? Aiempi RFID-päivitysten synkroninen käsittely on muutettu asynkroniseksi. Aiemmassa synkronisessa käsittelyssä oli tilanteita, jossa tuotaessa laitospesulaan kärrykaupalla sukkia lukijan päivitysten käsittely kesti kauemmin kuin mikä oli suurin sallittu viive. Tällöin hukattiin sukkien sijaintipäivityksiä. Irrottamalla viestin nopea vastaanotto ja hitaampi käsittely toisistaan voidaan tämä tilanne välttää.
Suorituskyvyn arviointi
Millainen järjestelmä meillä nyt sitten on?
Ensinnäkin, järjestelmän suorituskykyä ei pysty mittaamaan enää yhdellä luvulla. Olen vähän huijannut viittaamalla sukkien määrään. Todellisuudessa emme niinkään voi puhua palvelun suorituskyvystä, vaan erilaisista suorituskykyarvoista jotka riippuvat siitä, miten sitä kuormitetaan.
Seuraavassa lasken muutamia suorituskykyarvoja sukkapalvelulle. Aivan ensimmäiseksi joudun kuitenkin määrittelemään kuormitusprofiilin joihin laskelmat perustan.
Ensin oletukset:
- Yhdellä “taloudella” (oli se sairaala, sinkkutalous tai jotain siltä väliltä) on keskimäärin 100 seurattavaa sukkaparia.
- Kullakin “taloudella” on yksi käyttäjä ja käyttäjätunnus.
- Kukin talous käy kerran viikossa etsimässä kadonneita sukkiaan.
- 10% koko sukkapareista käytetään päivittäin, eli niiden sijainti muuttuu tavalla joka aiheuttaa keskimäärin yhden RFID-tapahtuman.
- Keskivertokäynti palvelussa aiheuttaa 100 sivulatausta ja 1000 sukkakuvan latausta.
- Yksi kuva sukkaparista on 100 kilotavua.
Näiden oletusten pohjalta voimme laskea järjestelmän muut ominaisarvot vaikkapa 5 miljoonan sukkaparin tapaukselle:
- Järjestelmässä on 50000 taloutta.
- Sukista tulee 1 miljoonaa sijaintipäivitystä päivässä, eli keskimäärin 12 päivitystä sekunnissa.
- Keskivertopäivänä verkkopalvelussa käy 7200 käyttäjää, joista tulee 8 sivulatausta sekunnissa ja 80 kuvalatausta sekunnissa.
- Kuvalataukset vievät keskimäärin 8 megatavua verkkokaistaa sekunnissa pahimmassa tapauksessa, luottaen selaimen cacheukseen vain 0,8 Mt/s – todellisuudessa siltä väliltä.
- Kirjautumisia palveluun tapahtuu 0,08 sekunnissa eli 5 kappaletta minuutissa.
- Tietokannassa on 500 gigatavua kuvia sukista.
Huomaa, että “keskivertopäivä” tässä olettaa 24×7 -mallin, eli käyttö jakaantuu tasan jokaisen viikon päivän ja jokaisen päivän tunnin yli. Realistisempi malli olisi 12×5 eli kuorma jakaantuu viiden päivän yli (arkipäivät) joina kunakin 12 tunnin yli. Tämä tarkoittaisi 3× lisäystä yllä oleviin kuormalukuihin. Lisäksi todellisissa kuormahuipuissa voi olla aivan eri luvut – tosin eri asiakassegmenttien kuormahuiput voivat osua eri aikoihin. Teollisuuspesulassa tehtäneen töitä päivällä, kun taas (monissa) yksityistalouksissa pyykätään vasta töistä tultua.
Näillä oletuksilla kokemuksesta sanoa, että yllä oleva palvelu on enimmäkseen mahdollinen (“feasible”) annetuin oletuksin, mutta varsinkin jos huomioimme kuormahuiput olemme tuskallisen lähellä tai jo ylittäneet A-luokan operaatioiden tuoman suorituskyvyn ylärajan. Todellisuudessa järjestelmää pitäisi suorituskykytestata, mutta puhtaasti teoreettisena harjoituksena suosittelisin yllä olevalle palvelulle siirtymistä B-luokan skaalautumistoimenpiteisiin. (Tai vähentää tavoitettaan alle 1 miljoonan sukkaparin.)
Tietokannasta vielä: 10 miljoona riviä (eli sukkaa) ei ole rivimääränä merkittävä. Sensijaan puolta teratavua kuvadataa ei ole järkevää laittaa tietokantaan, koska se hidastaa merkittävästi vikatilanteista palautumista. Yksinkertainen denormalisointi eli kuvien siirto muualle kuin päätietokantaan on yksinkertaisin ratkaisu, mutta kuten aiemmin sanoin, se kuuluu B-luokan toimenpiteisiin joista lisää myöhemmin.
Lopuksi: Yllä olen kuvannut monia toimenpiteitä jotka olen listannut “A-luokkaan” kuuluviksi. Vaikka listasin ne eräänlaisessa loogisessa järjestyksessä, tulee jokaisen järjestelmäarkkitehdin itse arvioida aina eri menetelmien soveltuvuus ja järkevä vaiheistus juuri työn alla olevaan projektiin.
Sitten vielä vähän linkkejä: