UsaldusvÀÀrne andmete ja failide salvestamine Linuxis

Uurides andmete sĂ€ilitamise usaldusvÀÀrsust pilvesĂŒsteemides, otsustasin end proovile panna ja veenduda, et mĂ”istan pĂ”hiasju. Ma alustasin NVMe spetsifikatsiooni lugemisega et mĂ”ista, milliseid garantiisid pakuvad NMVe kettad seoses andmete usaldusvÀÀrse sĂ€ilitamisega (st garanteerides, et andmed on sĂŒsteemi rikke korral kergesti kĂ€ttesaadavad). Minu pĂ”hijĂ€reldused olid jĂ€rgmised: andmeid tuleb pidada kahjustatuks hetkest, kui on antud andmete kirjutamise kĂ€sk, kuni nende tĂ€ieliku kirjutamiseni salvestusseadmesse. Kuid enamikus andmete kirjutamise programmides kasutatakse sĂŒsteemi kutseid tĂ€iesti rahulikult.

Selles materjalis uurin Linuxi failide API-de kaudu pakutavaid andmete usaldusvÀÀrse sÀilitamise mehhanisme. Tundub, et siin peaks kÔik olema lihtne: programm kutsub vÀlja kÀsu write(), ja kui selle kÀsu töö on lÔpetatud, on andmed usaldusvÀÀrselt salvestatud kettale. Kuid write() ainult kopeerib rakenduse andmed tuuma vahemÀllu, mis asub juhuslikus mÀlus. Kettale andmete kirjutamise sundimiseks on vaja kasutada mÔningaid tÀiendavaid mehhanisme.

UsaldusvÀÀrne andmete ja failide salvestamine Linuxis

KokkuvĂ”ttes on see materjal mĂ€rkmete kogum, mis kĂ€sitleb, mida olen Ă”ppinud mind huvitavast teemast. Kui rÀÀkida kĂ”ige olulisemast vĂ€ga lĂŒhidalt, siis tuleb andmete usaldusvÀÀrse salvestamise organiseerimiseks kasutada kĂ€sku fdatasync() vĂ”i avada faile lipuga O_DSYNC. Kui olete huvitatud, et teada saada, mis tĂ€pselt juhtub andmetega programmikoodist kuni ketaseni, vaadake seda artiklit.

Funktsiooni write() kasutamise omadused

SĂŒsteemikĂ”ne write() on mÀÀratletud standardis IEEE POSIX kui katse andmete kirjutamiseks failikirjeldajasse. PĂ€rast edukat tĂ€itmist write() peavad andmete lugemise operatsioonid tagastama tĂ€pselt need baitid, mis olid enne kirjutatud, isegi juhul, kui andmetele pÀÀsevad juurde teised protsessid vĂ”i lĂ”imud (siin on vastav jaotis POSIX standardist). Siit, jaotises, mis kĂ€sitleb voogude suhete loomist tavaliste failitoimetamistega, on mĂ€rge, mille kohaselt kui iga voog kutsub neid funktsioone, peab iga kutse nĂ€gema kas kĂ”iki mÀÀratud tagajĂ€rgi, mis tulenevad teise kutse tĂ€itmisest, vĂ”i mitte nĂ€gema ĂŒldse mingeid tagajĂ€rgi. See toetab jĂ€reldust, et kĂ”ik sisendi/vĂ€ljundi failitoimingud peavad hoidma ressurssi, millega nad töötavad, lukustatud.

Kas see tĂ€hendab, et operatsioon write() on atomaarne? Tehnilisest vaatepunktist - jah. Andmete lugemise operatsioonid peavad tagastama kas kĂ”ik vĂ”i mitte midagi, mis on kirjutatud kasutades write(). Kuid operatsioon write(), vastavalt standardile ei pea see tingimata lĂ”petama, kirjutades kogu, mis on talle ette nĂ€htud. Tal on lubatud salvestada ainult osa andmetest. NĂ€iteks vĂ”ib meil olla kaks voogu, igaĂŒks neist lisab 1024 baidi faili, mis on kirjeldatud sama failihandleriga. Standardi seisukohalt on vastuvĂ”etav tulemus, kui iga salvestusoperatsioon suudab faili lisada vaid ĂŒhe baidi. Need operatsioonid jÀÀvad aatomaarseteks, kuid pĂ€rast nende lĂ”petamist on failis salvestatud andmed omavahel segamini. Siin on selle teema kohta on vĂ€ga huvitav arutelu Stack Overflow's.

Funktsioonid fsync() ja fdatasync()

Lihtsaim viis andmete diskile kirjutamiseks on funktsiooni vĂ€ljakutse fsync(). See funktsioon palub operatsioonisĂŒsteemil viia kĂ”ik muudetud plokid vahemĂ€lust diskile. See hĂ”lmab ka kĂ”iki failide metaandmeid (juurdepÀÀsu aeg, faili muutmise aeg jne). Arvan, et nende metaandmete vajadus tekib harva, seega kui teate, et need ei ole teie jaoks olulised, vĂ”ite kasutada funktsiooni fdatasync(). Dokumendihalduses abi kohta fdatasync() On vĂ€idetud, et selle funktsiooni töötamise kĂ€igus salvestatakse kettale sellises mahus metaandmeid, mis on vajalik jĂ€rgmiste andmelugemise toimingute Ă”igeaegseks tĂ€itmiseks. See on just see, mis enamikku rakendusi huvitab.

Üks probleem, mis siin vĂ”ib tekkida, on see, et need mehhanismid ei garanteeri, et faili saab pĂ€rast vĂ”imalikke tĂ”rkeid leida. Eriti siis, kui luuakse uus fail, tuleb kutsuda fsync() kausta, mis seda sisaldab. Vastasel juhul vĂ”ib pĂ€rast tĂ”rget selguda, et see fail ei eksisteeri. Selle pĂ”hjuseks on see, et UNIXis, jĂ€igade linkide kasutamise tĂ”ttu, vĂ”ib fail eksisteerida mitmes kaustas. SeetĂ”ttu kutsudes fsync() faili puhul ei ole vĂ”imalik teada saada, millise kausta andmeid tuleb samuti kettale salvestada (siin selle kohta saab lugeda rohkem). Tundub, et failisĂŒsteem ext4 on suuteline automaatselt rakendama fsync() kaustadele, mis sisaldavad vastavaid faile, kuid teiste failisĂŒsteemide puhul ei pruugi see nii olla.

See mehhanismi rakendamine vĂ”ib erinevates failisĂŒsteemides erineda. Kasutasin blktrace , et teada saada, milliseid kettaoperatsioone kasutatakse failisĂŒsteemides ext4 ja XFS. MĂ”lemad vĂ€ljastavad tavapĂ€rased ketta kirjutamise kĂ€sud, nii failisisu osas kui ka failisĂŒsteemi logis, tĂŒhjendavad vahemĂ€lu ja lĂ”petavad töö, tehes FUA-kirjutuse (Force Unit Access, andmete kirjutamine otse kettale, mööda vahemĂ€lu) logisse. TĂ”enĂ€oliselt kĂ€ituvad nad nii operatsiooni kinnitamise tagamiseks. Ketastel, mis ei toeta FUA-d, pĂ”hjustab see kahe vahemĂ€lu tĂŒhjendamise. Minu katsed on nĂ€idanud, et fdatasync() veidi kiiremini fsync(). Tööriist blktrace nĂ€itab, et fdatasync() tavaliselt kirjutab kettale vĂ€hem andmeid (ext4 fsync() kirjutab 20 KiB, samas kui fdatasync() — 16 KiB). Lisaks avastasin, et XFS on veidi kiirem kui ext4. Ja siin abil blktrace sain teada, et fdatasync() tĂŒhjendab kettale vĂ€hem andmeid (4 KiB XFS).

Kahtlased olukorrad, mis tekivad fsync() kasutamisel

MĂ€letan kolme kahtlast olukorda, millega olen praktikas silmitsi seisnud. fsync(), millega ma praktikas kokku puutusin.

Esimene selline juhtum leidis aset 2008. aastal. Sel ajal 'riputas' Firefox 3 liides, kui salvestati suure arvu failide andmeid kettale. Probleem oli seotud sellega, et liidese olekuandmete talletamiseks kasutati SQLite andmebaasi. Iga kord, kui liideses toimus muutus, kutsuti vĂ€lja funktsioon fsync(), mis pakkus head garantiid andmete stabiilse salvestamise kohta. Toona kasutusel olnud ext3 failisĂŒsteemis funktsioon fsync() salvestas kettale kĂ”ik 'mĂ€danenud' lehed sĂŒsteemis, mitte ainult need, mis olid seotud vastava failiga. See tĂ€hendas, et Firefoxi nuppudele klĂ”psamine vĂ”is alustada megabaitide andmete salvestamist magnetkettale, mis vĂ”is kesta mitu sekundit. Probleemi lahendus, nii nagu ma aru sain sellest materjalist, seisnes andmebaasiga töötamise ĂŒleviimises asĂŒnkroonsetesse taustategevustesse. See tĂ€hendab, et varem rakendati Firefoxis rangemaid nĂ”udeid andmete stabiilse salvestamise osas, kui see reaalselt vajalik oli, ja ext3 failisĂŒsteemi eripĂ€ra sĂŒvendas seda probleemi.

Teine probleem tekkis 2009. aastal. PĂ€rast sĂŒsteemi riket sattusid uue ext4 failisĂŒsteemi kasutajad olukorda, kus paljud hiljuti loodud failid olid nullpikkusega, samas kui vanema ext3 failisĂŒsteemi puhul seda ei juhtunud. Eelnevas lĂ”igus rÀÀkisin, et ext3 salvestas kettale liiga palju andmeid, mis aeglustas tööd mĂ€rkimisvÀÀrselt. fsync()Selle olukorra parandamiseks salvestatakse ext4 failile kettale vaid need 'mustad' lehekĂŒljed, mis on seotud konkreetse failiga. ÜlejÀÀnud failide andmed jÀÀvad mĂ€llu palju pikemaks ajaks kui ext3 kasutamisel. See tehti jĂ”udluse parandamise nimel (vaikimisi jÀÀvad andmed sellesse olekusse 30 sekundiks, seda saab seadistada koos dirty_expire_centisecs; siin leiate tĂ€iendavaid materjale selle kohta). See tĂ€hendab, et suur hulk andmeid vĂ”ib pĂ€rast rikete tekkimist pöördumatult kaduma minna. Selle probleemi lahenduseks on fsync() rakendustes, mis peavad tagama andmete stabiilse sĂ€ilitamise ja maksimaalselt nende kaitsmise rikete tagajĂ€rgede eest. Funktsioon fsync() ext4 töötab mĂ€rksa tĂ”husamalt kui ext3. Selle lĂ€henemise miinus on aga see, et selle kasutamine aeglustab teatud toimingute, nĂ€iteks programmide installimise, teostamist. Lisainfot leiate siit siit ja siit.

Kolmas probleem, mis puudutab fsync(), tekkis 2018. aastal. Siis selgus PostgreSQL projekti raames, et kui funktsioon fsync() kohtub veaga, mĂ€rgib ta 'mustad' lehed kui 'puhtad'. Selle tulemusena ei tehta jĂ€rgmiste kĂ”nede fsync() selliste lehtedega midagi. Selle tĂ”ttu jÀÀvad muudetud lehed mĂ€llu ja neid ei kirjutata kunagi kettale. See on tĂ”eline katastroof, kuna rakendus arvab, et andmed on kettale kirjutatud, kuid tegelikult see nii ei ole. Sellised tĂ”rked fsync() on haruldased, rakendus ei suuda sellistes olukordades peaaegu midagi teha probleemi lahendamiseks. TĂ€napĂ€eval, kui see juhtub, PostgreSQL ja teised rakendused lĂ”petavad Ă€kki töö. Siit, artiklis 'Kas rakendused saavad fsync tĂ”rgetest taastuda?', uuritakse seda probleemi kĂ”igis detailides. Praegu on parim lahendus selle probleemi lahendamiseks Direct I/O kasutamine koos lipuga O_SYNC vĂ”i lipuga O_DSYNC. Selle lĂ€henemise korral teatab sĂŒsteem vigadest, mis vĂ”ivad tekkida konkreetsete andmete kirjutamise toimingute kĂ€igus, kuid see nĂ”uab, et rakendus haldaks puhverdusi ise. Lisateavet leiate siit siit ja siit.

Failide avamine O_SYNC ja O_DSYNC lipukestega

Naaseme arutelule Linuxi mehhanismide ĂŒle, mis tagavad andmete jĂ€rjepideva salvestamise. EelkĂ”ige rÀÀgime me lipu kasutamisest O_SYNC vĂ”i lipust O_DSYNC failide avamisel sĂŒsteemikĂ”ne kaudu open(). Selle lĂ€henemisega viiakse iga andme kirjutamise toiming lĂ€bi nagu pĂ€rast iga kĂ€sku antaks sĂŒsteemile vastavad kĂ€sud write() POSIX spetsifikatsioonide fsync() ja fdatasync(). Dokumendihalduses seda nimetatakse „SĂŒnkroonitud I/O Faili Terviklikkuse TĂ€itmine“ ja „Andmete Terviklikkuse TĂ€itmine“. Selle lĂ€henemise peamine eelis on see, et andmete terviklikkuse tagamiseks tuleb teha vaid ĂŒks sĂŒsteemikĂ”ne, mitte kaks (nĂ€iteks — ). Selle lĂ€henemise peamine puudus on see, et kĂ”ik kirjutamisoperatsioonid, mis kasutavad vastavat failide kirjeldajat, synchroniseeritakse, mis vĂ”ib piirata rakenduse koodi struktureerimise vĂ”imalusi. write() ja fdatasync()). Selle lĂ€henemise peamine puudus on see, et kĂ”ik kirjutamise operatsioonid, mis kasutavad vastavat faili deskriptorit, saavad olema sĂŒnkroniseeritud, mis vĂ”ib piirata rakenduse koodistruktuuri loomise vĂ”imalusi.

Direct I/O kasutamine O_DIRECT lipuga

SĂŒsteemikĂ”ne open() toetab lippu O_DIRECT, mis on mĂ”eldud selleks, et vĂ€ltida operatsioonisĂŒsteemi vahemĂ€lu, teostades sisendi-vĂ€ljundi toiminguid otse kettaga. See tĂ€hendab, et paljusid juhtumeid, kirjutamiskĂ€sklusi, mis on antud programmile, edastatakse otse kettale suunatud kĂ€sklusteks. Kuid ĂŒldiselt ei asenda see mehhanism funktsioone fsync() vĂ”i fdatasync(). Asi on selles, et ise ketas vĂ”ib edasi lĂŒkata vĂ”i vahemĂ€luda vastavaid andmete kirjutamise kĂ€ske. Ja mis veelgi hullem, mĂ”ningatel erijuhtudel, sisendi-vĂ€ljundi toimingud, mis teostatakse lippu kasutades O_DIRECT, tĂ”lgitakse traditsioonilisteks puhverdatud toiminguteks. Selle probleemi lahendamine on lihtsaim viis avada failid ka lipuga O_DSYNC, mis tĂ€hendab, et iga kirjutamistoiminguga kaasneb kutse fdatasync().

Leiti, et failisĂŒsteemis XFS on hiljuti lisatud "kiire tee" O_DIRECT|O_DSYNC-andmete kirjutamiseks. Kui kirjutatakse ĂŒle plokk, kasutades O_DIRECT|O_DSYNC, siis XFS, asemel vahemĂ€lu tĂŒhjendamise, tĂ€idab FUA-kirje kĂ€sku, kui seade seda toetab. Olen selle kinnitanud tööriista kasutamisega blktrace Linuxi sĂŒsteemis 5.4/Ubuntu 20.04. See lĂ€henemine peaks olema efektiivsem, kuna selle kasutamisel kirjutatakse kettale minimaalne kogus andmeid ja kasutatakse ĂŒhte toimingut, mitte kahte (kirjutamine ja vahemĂ€lu tĂŒhjendamine). Leidsin viite plekk 2018. aasta tuuma, kus see mehhanism on rakendatud. Seal on arutelu, mis kĂ€sitleb selle optimeerimise rakendamist ka teistes failisĂŒsteemides, kuid niipalju kui mina tean, on XFS praegu ainus failisĂŒsteem, mis seda toetab.

sync_file_range()

Linuxis on sĂŒsteemi kĂ”ne sync_file_range(), mis vĂ”imaldab kirjutada kettale ainult osa failist, mitte kogu faili. See kĂ”ne algatab asĂŒnkroonse andmete kirjutamise ja ei oota selle lĂ”petamist. Kuid juhendis sync_file_range() öeldakse, et kĂ€sk on "vĂ€ga ohtlik". Selle kasutamine ei ole soovitatav. EripĂ€ra ja ohtlikud aspektid sync_file_range() on vĂ€ga hĂ€sti kirjeldatud selles materjalis. Eriti paistab silma, et see kutse kasutab RocksDB-d selleks, et hallata, millal sĂŒdamik "rĂ€pane" teave kettale kirjutab. Kuid selleks, et andmed oleksid stabiilselt salvestatud, kasutatakse ka fdatasync(). Dokumendihalduses koodis RocksDB-s on selle teema kohta huvitavad kommentaarid. NĂ€iteks tundub, et kutse sync_file_range() ZFS-i kasutamisel ei pĂ”hjusta andmete kirjutamist kettale. Minu kogemus ĂŒtleb, et harva kasutatav kood vĂ”ib sisaldada vigu. SeetĂ”ttu soovitaksin seda sĂŒsteemikutsumist vĂ€ltida, kui see ei ole hĂ€davajalik.

SĂŒsteemikutsed, mis aitavad tagada andmete stabiilse salvestamise

JÔudsin jÀreldusele, et andmete stabiilse salvestamise tagamiseks saab kasutada kolme lÀhenemisviisi. KÔik need nÔuavad funktsiooni vÀljakutset fsync() kaustas, kus fail on loodud. Siin on need lÀhenemisviisid:

  1. Funktsiooni vÀljakutse fdatasync() vÔi fsync() pÀrast funktsiooni write() (soovitatav on kasutada fdatasync()).
  2. Failideskirpti kasutamine, mis on avatud lipuga O_DSYNC vĂ”i O_SYNC (parem — lipuga O_DSYNC).
  3. KĂ€skluse kasutamine pwritev2() lipu RWF_DSYNC vĂ”i RWF_SYNC (eelistatult — lipuga RWF_DSYNC).

Toimivuse mÀrkmed

Ma ei ole teadlikult mÔÔtnud erinevate uuritud mehhanismide jĂ”udlust. Minu tĂ€hele pandud kiiruseread on ĂŒsna vĂ€iksed. See tĂ€hendab, et ma vĂ”in eksida ja et samade tingimuste korral vĂ”ivad tulemused olla erinevad. Esiteks rÀÀgin sellest, mis mĂ”jutab jĂ”udlust kĂ”ige enam, ja seejĂ€rel, mis mĂ”jutab jĂ”udlust vĂ€hem.

  1. Faili andmete ĂŒle kirjutamine on kiirem kui andmete lisamine failile (jĂ”udluse kasum vĂ”ib ulatuda 2–100%). Failile andmete lisamine nĂ”uab lisamuudatusi faili metaandmetes, isegi pĂ€rast sĂŒsteemikĂ”net, fallocate(), kuid selle efekti ulatus vĂ”ib varieeruda. Soovitan parima jĂ”udluse tagamiseks kutsuda fallocate() vĂ€lja vajaliku ruumi eelnevalt eraldamiseks. Siis tuleb see ruum selgesĂ”naliselt nullidega tĂ€ita ja kutsuda fsync(). TĂ€nu sellele mĂ€rgistatakse vastavad plokid failisĂŒsteemis kui "valitud", mitte kui "mittevalitud". See toob kaasa vĂ€ikese (umbes 2%) jĂ”udluse paranemise. Lisaks sellele vĂ”ivad mĂ”ned kettad esimesel ploki ligipÀÀsu operatsioonil olla aeglasemad kui teised. See tĂ€hendab, et ruumi tĂ€itmine nullidega vĂ”ib tuua kaasa mĂ€rkimisvÀÀrse (umbes 100%) jĂ”udluse paranemise. EelkĂ”ige vĂ”ib see juhtuda ketastega AWS EBS (need on mitteformaalne andmed, neid ei ole vĂ”imalik kinnitada). Sama kehtib ka salvestuste kohta. GCP Persistent Disk (see on juba ametlik teave, kinnitatud katsetega). Teised spetsialistid on teinud sarnaseid tĂ€heldusi, mis on seotud erinevate ketastega.
  2. Mida vĂ€hem on sĂŒsteemikĂ”nesid, seda kĂ”rgem on jĂ”udlus (kasv vĂ”ib olla umbes 5%). Tundub, et kĂ”ne open() lipu O_DSYNC vĂ”i kĂ”ne pwritev2() lipu RWF_SYNC on kiiremini kui kĂ”ne fdatasync()Kahtlen, et asi on selles, et sellise lĂ€henemise puhul mĂ€ngib rolli, et ĂŒhe ja sama ĂŒlesande tĂ€itmiseks tuleb teha vĂ€hem sĂŒsteemi kĂ”nesid (ĂŒks kĂ”ne kahe asemel). Kuid jĂ”udluse erinevus on vĂ€ga vĂ€ike, seega vĂ”ite selle tĂ€iesti tĂ€helepanuta jĂ€tta ja kasutada rakenduses seda, mis ei too kaasa selle loogika keerukuse suurenemist.

Kui teid huvitab andmete usaldusvÀÀrne salvestamine, siis siin on mÔned kasulikud materjalid:

  • I/O sisenemismeetodid — ĂŒlevaade sisenemise ja vĂ€ljumise mehhanismidest.
  • Andmete jĂ”udmine kettale — jutt sellest, mis juhtub andmetega rakendusest ketta suunas.
  • Millal peaksite fsync'i rakendama sisaldavale kaustale — vastus kĂŒsimusele, millal tuleb seda rakendada fsync() kaustade jaoks. Kui seda kahe sĂ”naga rÀÀkida, selgub, et seda tuleks teha uue faili loomisel, ja pĂ”hjuseks on see, et Linuxis vĂ”ib olla palju linke ĂŒhe ja sama faili jaoks.
  • SQL Server Linuxil: FUA sisemised seadmed — siin on kirjeldus, kuidas andmete pĂŒsiv hoidmine on realiseeritud SQL Serveris Linuxi platvormil. Siin on tehtud mĂ”ned huvitavad vĂ”rdlused Windowsi ja Linuxi sĂŒsteemikutsungite vahel. Olen peaaegu kindel, et just selle materjali kaudu Ă”ppisin FUA optimeerimisest XFS-s.

Kas olete kunagi kaotanud andmeid, mida pidasite kindlalt kettale salvestatuks?

UsaldusvÀÀrne andmete ja failide salvestamine Linuxis

UsaldusvÀÀrne andmete ja failide salvestamine Linuxis

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster