
St-Pete'ist
Tere kõigile! Mina olen Mons Anderson, platvormi arhitekt , räägin, kuidas me ehitasime oma S3-lao, kuidas see töötab, millised lahendused osutusid õnnestunuks ja mida oleks pidanud muutma, kui me oleksime sellise projekti nullist uuesti alustanud.
Artikkel on koostatud Mail.ru Cloud Solutions & Tarantooli ettekande põhjal. Artiklis käsitleme: kuidas oli Mail.ru salvestusstruktuur üles ehitatud, mille peal me S3-lao ehitasime;
- mida me lisasime, et luua Mail.ru pilvesalvestus;
- kuidas töötab objektide salvestamise mudel ja millised sammud tehti tootmisseminekuks;
- töösüsteemi täiustamist: failide taastamine ja skaleerimine;
- kuidas me rakendasime shardimist ja shardi ümberjaotamist;
- ning SSL-sertifikaatidega seotud tööd.
- Kui te ei soovi lugeda, siis saate
Kuidas oli Mail.ru salvestusstruktuur üles ehitatud, mille peal me S3-lao ehitasime .
Meie S3 arendus algas Mail.ru pilvesalvestusel, seega tasub alguses rääkida, kuidas see on üles ehitatud ja mida see suudab.
Разработка нашего S3 началась поверх хранилища Облака Mail.ru, поэтому изначально стоит рассказать, как оно устроено и что умеет.
Mail.ru pilveteenus koosneb serveritest, mis on varustatud ketastega. Tänapäeva modernse salvestusserveri keskmine suurus on 36 ketast, igaühe mahuga 12–14 terabaiti. Varasemalt olid kettad väiksemad, kuid viimase kolme aasta jooksul on kettamaht suurenenud ja täna on need peaaegu pool petabaiti tooreid andmeid.
Erinevate serverite ketastest moodustatakse nn «paare» (pair). Paar on ainus failide salvestamise üksus. Sisuliselt on see disk, mis on monteeritud kindlasse sektsiooni kindlal teel, kus saavad asuda failid, mida tuvastatakse hash'i abil.
Paar on ajalooline nimetus, mis on tänapäevani säilinud, kuigi nüüd ei pea paaris olema ainult kaks ketast. Ketasid võib olla kolm, samuti võivad olla erinevad hübriidsalvestused, näiteks 3/2.

Paared (pair) — objektide salvestamise üksused
Kõik paarid salvestatakse PairDB-s — see on rakendus Tarantooli baasil. Kõik andmebaasid meie salvestuses, alates kõige varasematest, on Tarantool ja me ei kasuta teisi andmebaase.
PairDB salvestab kõik paarid, nende seisukohad, vaba ruum, tõrked ja viimased vead. Samuti suudab see ise paaridele ligipääsu luua, värskendada nende olekut, kontrollida, kas need töötavad või mitte. Ehk siis PairDB on üldine hetkeseis kõigist meie süsteemi ketastest.

Pair DB: paaride oleku andmebaas
Paarides hoitakse faile, ja et teada saada, millises paaris asub mingi fail, on vajalik veel üks andmebaas — FileDB. See hoiab mapitamise ehk vaste määratlemise: fail selline talletatakse paaris selline, samuti väikese koguse vajalikke atribuute.
File DB: koht, kus fail asub
Veel üks oluline lüli on teenus Nylon, andmebaasidega töötamise ruuter. See on ainus sissepääsupunkt, mis võimaldab töötada nii PairDB kui ka FileDB-ga ühise liidese kaudu. See on stateless-teenus, see tasakaalustab päringuid, mõistab, millisele shardile FileDB suunata, ja teab, millised paarid on aktiivsed ja millised mitte.

Nylon: andmebaasidega töötamise ruuter
Lisaks tuleb sisu ka mingil moel salvestusse panna. Selleks on teenus — Streamer. See pakub kahte HTTP meetodit: PUT meetod, et sisu salvestusse laadida, ja GET meetod, et sealt seda tagasi saada. HTTP on üsna populaarne ja mugav protokoll andmete edastamiseks.
Kui me pöördume Streamer’i poole, siis see küsib Nylon’i kaudu PairDB'lt, millisele paarile saab faili laadida, seejärel edastab andmed WebDAV abil sellele paarile.
Sisuliselt on iga storage server — see on nginx pluss kettad, mis on monteeritud määratud radadele. Me saame Streamer'ist laadida salvestusse faili, kustutada selle, nimetada ümber või kontrollida selle terviklikkust. See on mugav liides madala taseme suhtlemiseks salvestusega.

Streamer: sisenemispunkt salvestusse
Mida me lisasime, et S3 salvestust teha
Nii oleme uurinud salvestusseadmise üldist alust, kui olime käivitamas S3 salvestust. PUT meetodi abil saime sinna paigutada suvalist sisu ja saada selle andme identifikaatoriks räsi. Selle identifikaatoriga oli hiljem võimalik tagasi tulla ja originaalfail kätte saada. Kuid see ei piisa S3 rakendamiseks. S3 protokollis, lisaks objektide salvestamisele, on:
- metainfo salvestamine — objekti täiendavad omadused;
- juurdepääsu korraldamine objektidele HTTP kaudu;
- objektide grupeerimine kogudesse — ämbrid;
- HTTP-S3 lõpp-punkt. S3 korraldab andmed kindlatesse struktuuridesse — ämbrid, millest igaüks pakub sissepääsufunktsiooni failide salvestamiseks.
Selle loogika rakendamiseks oli vajalik eraldi teenus. Hakkasime kohe kavandama arhitektuuri teenuse edasise kasvu jaoks, et tagada lineaarne skaleeritavus.
Esimesed komponendid
Demon, mis rakendab S3 API-d. See on Amazoni standardne S3 API, mis toetab XML-i kasutamist metainfo jaoks ja võimaldab sisu otse edastada. Me ei pidanud midagi leiutama, kõik on kirja pandud ja dokumenteeritud.
Lisaks teenusele oleme paigaldanud Nginxi. Kasutasime seda SSL-i terminatsiooniks, koormuse tasakaalustamiseks ning teatud loogika jaoks Luas (meetmed, logimine ja jälgimine).
Metandmete S3 talletamiseks valisime samuti Tarantooli. Esimeses versioonis läks S3 deemon nende metandmete jaoks sellesse andmebaasi, sisu talletati aga suures ladustamises Streameri kaudu.

Nginx + S3 API + metandmed
Objektide hoidmise mudel
Vaadakem, kuidas S3 töötab. Kasutaja võib luua ämbri — objekti kogumi. Ämbrit adresseeritakse hosti nimega ja see on teenuse alamdomeen. Ämbri raames võib kasutaja luua objekte. Objekti identifikaatoriks on URL. Objekti sisu on blob, binaarsete andmete massiiv, mida me ladustame. Samuti on objektil atribuute: nimi — see sama URL, ACL (juurdepääsuhaldusloend), muud täiendavad või vabatahtlikud atribuudid — kõik see talletatakse metandmetes.
Korrigeeritud andmeskeem võib välja näha järgmine: on projekte, millele kuuluvad bucket'id, millele kuuluvad objektid ning objektid võivad olla komplekssetest. Kuna üheks viisiks objekti üles laadimiseks on osade kaupa, on laadimise jaoks kaks abita tabelit: uploads ja chunks. Samuti on projektidel ligipääsude andmed ja arveldamine.

Andmeskeem
Kuna me tegime b2b-teenust tasulise ligipääsuga, siis oli selle skeemi jaoks vajalik arveldamine.
Arveldamise teenust rakendasime samuti Tarantoolis.

S3-hoidla täiustamine: sammud tootmisse
Meil on juba töötav mudel, mida saab kasutada: objektid ja metaandmed on salvestatud, kuid tootmisse minekuks puudus veel mõned Esemed.
Esiteks süsteem määrangute piiramiseks. Kui käivitada teenus ilma selleta, siis tipnõudluse korral võiksime mõne süsteemi osa ettearvamatult üle koormata. Määrangute piiramine peaks toimima nii: iga S3-päring jõuab konkreetsele hostile, see host on bucket'i identifikaator ja bucket kuulub mõnele kliendile. Me peame määrama funktsiooni bucket'ist, mis võimaldaks arvutada määrangute piiri.
Lisaks peab reitingu piiride süsteem olema piisavalt jõudlusvõimeline, et taluda S3-le suunduvat koormust.
Siin kasutasime jälle Tarantooli. Reitingu piirid on 21 instantsist koosnev klaster, instantsid on jagatud gruppidesse, laiali kolmele füüsilisele sõlmele ning ühendatud suureks topoloogiliseks klasstriks. Konfiguratsioonimuudatused levivad automaatselt: määratakse reitingu piirid, vaikeseaded ja konfiguratsioon. Iga ämber teenindatakse rangelt ühe instantsi poolt. Kui konkreetse ämber esitab päringu, arvutatakse välja, milline instants vastutab selle ämber eest. Selles sõlmes jälgitakse praegust päringu määratlemise määra algoritmi järgi, mis sarnaneb Token Bucket'iga. Edasi ütleb reitingu piirsüsteem, lähtudes praegustest koormustest ja saidile seatud omadustest, kas päringut saab teostada või mitte. Limiteerimise kontrollimist teostatakse S3 päringu täitmise varases staadiumis, kaitstes kõiki muid süsteemi elemente liialdava koormuse eest.

Koormuse all on päris keeruline ilma vahemäluta hakkama saada. S3 puhul eeldatakse korduvat juurdepääsu samadele objektidele, mistõttu on see kuum salvestus. Tavalises olukorras teenindab üksikule failile pääsemine täieliku ahelaga: Streamer, FileDB, PairDB, Storage. Kuid kui failile pääseb korduvalt, optimeerime juurdepääsu sellele sisule kohalikku vahemälu kasutades.
Vahemälu on mitmekihiline ja realiseeritud nginx'i, kohalike, SSD- ja RAM-kettade abil. Me ei kasutanud Tarantool'i, kuna objektide edastamine failisüsteemist on mugavam, nii saame teha vahemälu tiering'i. Lisaks on meil suured objektid, mille maksimaalne suurus on 32 gigabaiti, aga Tarantool'is saab vahemällu salvestada ainult väikseid objekte.

See oli esimene süsteem, millega me alustasime, ja sellel oli mõningane kalkuleeritud mahutavus, mis oli piisav uuringute ja toote mõistmise jaoks, et teada saada, et see töötab.
Taktikalise süsteemi täiustused: failover ja skaleerimine
Süsteem oli juba tegevuses, kuid alguses jätsime mõningaid asju tähelepanuta - oleks pidanud lisama failover'i ja skaleerimise.
Meie S3-deemon edastas metaandmeid Tarantooli protokolli kaudu. Originaalbaasi asemel paigaldasime Tarantooli, mis toimis metaandmete päringute proksina. Rakenduse, mis rakendab API, seisukohalt ei muutunud midagi — see jätkas andmebaasile Tarantooli protokolli kaudu pöördumist, kuid proksi suutis tagada aktiivse veakaitse. See tähendab, et suutsime kontrollida sõlme saadavust, teha vahepausi vahetuste ja rikete ajal jne. Samas ei muutnud me rakendust ise.

Lisateave selle kohta, kuidas me shardimise rakendasime
Järgmine küsimus, millega tuli tegeleda, oli shardimine. Süsteem kasvas, objektide arv kasvas ning tuli tagada võimalused edasiseks kasvuks.
Naasume andmete skeemi juurde: seal on projektid, on mahutid, krediid ja arveldamine. Need on objektid, mis tõenäoliselt ei ületa lähitulevikus ühe instantsi piire ei mahu ega päringute osas. Seega pole mõtet neid shardida ja oleme need viinud eraldi instantsi, mis jääb shardimata. See võimaldab projektide ja mahutite haldamist ühtlasemalt, kuna on olemas ühtne shardimata punkt.

Samuti on skeemis objekte, mis kasvavad lineaarselt – alguses oli neid sadu tuhandeid, nüüd mõõdetakse nende arvu mitme miljardi kaupa. Sellised objektid koos nende osadega tuli viia sharditud klastrisse.

Skeemi oleme jaganud, kuid objektid peavad töötama mahutitega: objekt kuulub alati konkreetsele mahutile, pluss mahutil on ACL. Seetõttu hoiame iga objektide shardiga kaasas igas mahutis varukoopia. Lisaks, objekti modifitseerimise ja päringute täitmise ajal tuleb arvestada mahtu arveldamiseks, seega on igas shardis arveldamise loendurid.
Oleme lisanud veel mõned tabelid ja komponendid:
- kast, et vanade projektide kustutamiseks või külmutamiseks, mida kõrvaldatakse;
- järjekord taustatöid jaoks, st peamine salvestus võib teha taustatöid, mida tuleb klastris teha;
- elutsükli toetamine — mehhanism, mis võimaldab töötada objektidega ja hallata nende eluiga.

Kuna osa andmeid viidi shardidele, oli vajalik shardimise vaheproksi. Selle rolli täitmiseks oleks saanud uuesti kasutada marsruuterit, kuid eraldi shardimise vaheproksi, mis vastutab ainult andmete shardimise eest, võimaldab marsruuterist andmete kogumiseks minna, mõtlema shirtingule.

Rääkige eraldi, miks me ei võtnud valmis lahendust, vaid soovisime luua kohandatud shardimise funktsiooni.
Vaata, kuidas see on üles ehitatud. Meil on 256 saadaval shardit. Iga ämbri jaoks määrame vahemiku abil mingit järjepidevat funktsiooni. See on lihtne — sama nagu määrate järjepideva funktsiooniga ühte shardisse kuulumise, määrate algse shard'i ja määrate vahemiku:
f(ämber, shardid) = alamkogum
Seega, kui võtta bukett, võib öelda, et see ja tema andmed asuvad alati konkreetse alamhulga peal kõigist shardidest. See aitab vähendada ühe buketi mõju teistele ning lihtsustab map-reduce päringute töötlemist, kui näiteks on vajalik buketi objektide loetlemine. Selleks tuleb küsida kõikide shardide kohta, kuhu need objektid on salvestatud. Kui objektid asuksid kõigis shardides, mõjutaks iga loetlemine kogu süsteemi, kuid siin mõjutab see ainult konkreetset alamhulka.
Edasi — iga objekt kuulub konkreetsesse buketti, seega kui me pöördume objekti poole, siis me pöördume objekti poole tema nime alusel konkreetses buketis. See tähendab, et me saame määratleda objekti funktsiooni mitte kõigi saadaval olevate shardide hulgast, vaid ainult oma buketi alamhulgast:
f(object, subset) = shard
Me võtame konkreetse objekti ja funktsiooni argumentidena edastame sinna mitte kõik shardid, vaid ainult tema buketi alamhulga — ja saame konkreetse shard.

Nii, sharding on rakendatud ja meil on sharding-proksi. Järgmine samm on routerist ja metadatabaasist sharding-proksisse suunamine. Näiteks varjatud koopiate loomise jaoks — kui loome ämber, peab peamine salvestus looma selle ämber esindaja kõikides shardides, kus see peab olema.

Kuidas me shardingut ümber korraldasime
Suurim probleem shardingul on ümberkorraldamine. Meie jaoks oli oluline teostada see ilma seisakuteta, kuna süsteem oli juba tootmises. Näitan, kuidas me probleemi lahendasime sarnase ülesande näitel, kus andmete elav migreerimine toimus ühelt projektilt teisele.
Allpool on meie klastrite skeem, mis kujunes pärast shardingut. Meil on nginx, S3 API, router, peamine andmebaas projektide jaoks, sharding-proksi ja otse sharded.

Ülal mainisin, et teatud etapis projekti jooksul oli tooteülesanne: 'Käivitada veel üks salvestus, Icebox, — nagu Hotbox, ainult külmade andmete jaoks.' Sisuliselt sama salvestus, kuid erineva URL-i ja ilma vahemägedeta.

Icebox'i kasutati vähem kui Hotbox'i, seetõttu suutis see üsna kaua hakkama saada ilma igasuguse jagamiseta. Lõpuks otsustasime sellest loobuda ja ühendada Hotbox ja Icebox üheks teenuseks, lihtsalt jagades salvestusklassi.
Konteinerid salvestustes ei kattunud, neid oli lihtne sulandada ja liigutada, kuid kliendid kasutasid mõlemaid salvestusi, seega oli vaja lahendada katkestuste puudumise probleem. Oli võimatu lihtsalt välja lülitada ja kopeerida. Tegime migratsiooni mitmes etapis.
Esmalt sünkroniseerisime esmased salvestused. Meil oli Tarantool ja me saime objekti loomisel teha nii:
- andmebaasi tuleb päring konteineri loomiseks, näiteks Hotbox'is;
- Tarantool kontrollib teises andmebaasis (selles juhul — Icebox'is), et sellist konteinerit ei ole;
- kui konteiner on olemas, ütleb andmebaas, et seda ei saa luua, ja see sünkroniseeritakse kui olemasolev.

Konteinerite sünkroniseerimine
Selles salvestuses, mis pidi vastu võtma kõik andmed, määrati projektide ja konteinerite jaoks märk, mis näitab, kus see objekt asub. See võis olla lokaalselt, st Hotboxis, Iceboxis — siis ei olnud uues salvestuses selle kohta mingeid andmeid, või võis olla migreerimise olekus.
Kui projektil või konteineril oli märk Migrating, siis migreerimise ajal tehti päring esmalt uude salvestusse, kus andmed pidid olema, ja kui seal ei olnud, siis suunati päringud alternatiivsele salvestusele.
Seejärel suunati meedet. Kuna API võis teenindada nii Iceboxi kui ka Hotboxi päringuid, suutsime meedet edasi suunata ilma seiskamiseta, lihtsalt kandes hostid üle ja lisades vastavad kirjed Nginxi.
Pärast liikluse suunamist sai Nginxi ja Icebox API eemaldada.
Seejärel eemaldasime Icebox Nginxi ja S3 API — ning kõik hakkas toimima:

Seejärel käivitasime taustprotsessi, mis töötab andmebaasis — läbib elementide kaupa kõik projektid ja nende konteinerid, määrab neile märgi Migrating, kannab andmed üle ja pärast ülekande lõpetamist määrab märgi Local.

Andmete üleviimise järel ei ole meil enam vaja vana salvestusruumi ning eemaldame vana süsteemi jäänused ja eemaldame koodist migratsiooni staatuse toe.

Sama põhimõtete kohaselt viidi läbi ka ümberdisainimine vanast salvestusruumist killustatud:
- Märgistasime kõik ämbrid kui
Nicht-zerlegt. Kõik päringud suunati algsesse, mitte-killustatud salvestusruumi. - Uued ämbrit loodi kohe staatusega
Killustatud. - Võtsime ämbreid järjestikku, määrasime selle staatuseks
Migratsioonja kandsime andmed üle.
Päringuid teenindati põhimõtte kohaselt:
- Loeme uuest, siis vanast.
- Loome ainult uuest.
- Uuendame kahes faasis: kui uues ei ole, kanname vanast uude ja siis uuendame.
SSL-sertifikaatidega töötamine
Eesliinil kasutame Nginx'i. Meie puhul ei ole see tavaline Nginx, vaid OpenResty, Nginx, mis toetab LuaJIT'i.
Veel üks süsteemi osa — SSL-sertifikaatidega töötamine. S3 salvestusruumis võite määrata oma domeeni konkreetsele ämbri juurde juurdepääsuks, lihtsalt kasutades CNAME. Kuid tänapäeval ei saa ilma HTTPS'ita hakkama: oma domeen eeldab oma SSL-sertifikaati.
Nagu juba mainisin, vastutab meie SSL-i tasakaalustamise ja lõpetamise eest Nginx. Meie puhul ei ole see lihtsalt tavaline Nginx, vaid OpenResty, Nginx, mis toetab LuaJIT-i.
See võimaldas meil üsna lihtsalt õpetada oma Nginx'i andma vabalt valitud sertifikaate. Samuti oli vajalik sertifikaate anda dünaamiliselt (ilma et neid oleks vaja konfigureerimisfaili kirjutada). Me kasutasime laiendust ssl_certificate_by_lua, mis võimaldab lugeda sertifikaate mistahes allikast otse TLS-i käepideme ajal. Sertifikaatide hoidmiseks kasutasime ka Tarantooli: see võimaldab sertifikaate hallata väljastpoolt ja tagab äärmiselt kiire tarnimise.
Samuti rakendati eraldi daemon, mille ülesanne on regulaarselt värskendada sertifikaate, mis on välja antud Let’s Encrypti abil.

Mida ma säilitaksin ja mida teeksin teisiti, kui arendaksin hoidlat nullist.
Mida oleks tulnud alates algusest peale kasutada.
Shardimine kohe.. Probleemide hulk, mis põhjustas sharding, oli piisavalt suur. Seda on lihtne teostada, kuid kui alustada projektidega, mis vajavad skaleerimist, on parem kohe valida sharditud klaster, isegi kui see koosneb minimaalsetest sõlmedest. Shardimise rakendamine alguses on peaaegu tasuta võrreldes sharding'u integreerimisega töötavasse süsteemi.
Tarantooliga töötamine läbi koormuse tasakaalustajate. Hetkel ühendame kõik uued andmebaasid kohe koormuse tasakaalustajate kaudu töösse. See võimaldab funktsionaalsust laiendada ja saavutada suuremat tõrke taluvust.
Automaatne failover. Paigaldasin kõik tööriistad, mis on vajalikud automaatse failover'i jaoks, kuna esimesed ebaõnnestumised pärast käivitamist olid seotud selle puudumisega. Pärast S3 kogemust käivitati kõik järgmised tooted selle arvesse võtmisega.
S3 funktsioon 'Versioonimine'. Alguses tundus, et see ei ole väga nõutud funktsionaalsus. Selle võimaluse integreerimine töötava süsteemi arhitektuuri on äärmiselt keeruline.
Erinev arveldus. Kuidas me integreerisime arvelduse meie süsteemi, toetas see alguses hästi, kuid hiljem muutus see takistuseks, oleks parem, kui see oleks täiesti eraldi teenus.
Mis oli edukas lahendus
Andmemudel. Ajalugu näitas, et teenuse arendamise käigus ühtisime üsna täpselt Amazon'i andmemudeliga, seega suudame rakendada seal olevaid funktsioone.
Shardimise skeem. Toetaksin selliseid vahemiku põhiseid shardimisi konteinerite kaupa, kuna see võimaldab hästi jaotada päringud erinevate konteinerite vahel suurel klastril.
Tarantooli kasutamine. Tarantool aitas oluliselt teenuse arendamisel ja selle kohandumisel, saime andmetega hõlpsasti töötada, transformeerida ja shardida salvestust, samas ei olnud tarvis minna rakenduse kihile.
See ettekannet kõlas esmakordselt by Mail.ru Cloud Solutions& Tarrantool. Vaata teisi ettekandeid ja tellige ürituste teateid Telegramis .
Samuti saate vaadata minu vana ettekannet S3-st või lugeda minu kolleegi artiklit plokk salvestuse kohta.
- .
- .
Allikas: habr.com

