Olen uurinud andmete salvestamise usaldusvÀÀrsust pilvesĂŒsteemides ja olen otsustanud end proovile panna, et aru saada, kas ma mĂ”istan pĂ”hilisi aspekte. Ma , et mĂ”ista, milliseid tagatisi pĂŒsiva andmete salvestamise kohta (st tagatisi, et andmed on saadaval pĂ€rast sĂŒsteemi riket) pakuvad NMVe kettad. JĂ”udsin jĂ€rgmistele peamistele jĂ€reldustele: andmeid tuleb pidada rikutuks hetkel, kui on antud andmete kirjutamise kĂ€sk, ja kuni hetkeni, mil nende kirjutamine andmekandjale lĂ”petatakse. Siiski, enamikus andmete kirjutamise programmides kasutatakse vĂ€ga rahulikult sĂŒsteemi vĂ€ljakutseid.
KĂ€esolevas materjalis uurin pĂŒsivate andmete salvestamise mehhanisme, mida pakuvad Linuxi failide API-d. Tundub, et siin peaks kĂ”ik olema lihtne: programm kutsub vĂ€lja kĂ€su write(), ja pĂ€rast selle kĂ€su tĂ€itmist peaksid andmed olema usaldusvÀÀrselt salvestatud kettale. Kuid write() see lihtsalt kopeerib rakenduse andmed tuuma vahemĂ€llu, mis asub aktiivses mĂ€lus. Et sundida sĂŒsteemi andmeid kettale kirjutama, tuleb kasutada mĂ”ningaid tĂ€iendavaid mehhanisme.
Ăldiselt on see materjal kogumik mĂ€rkmeid, mis kĂ€sitlevad seda, mida ma huvitaval teemal Ă”ppisin. Kui vĂ€ga lĂŒhidalt rÀÀkida kĂ”ige olulisemast, siis selgub, et pĂŒsiva andmete salvestamise korraldamiseks tuleb kasutada kĂ€sku fdatasync() vĂ”i avada faile flagi O_DSYNCabil. Kui teid huvitab ĂŒksikasjalik ĂŒlevaade sellest, mis juhtub andmetega teel programmikoodist kettale, vaadake artiklit.
write() funktsiooni kasutamise eripÀrad
SĂŒsteemi vĂ€ljakutse write() on mÀÀratletud standardis kui katse andmete kirjutamiseks failide deskiptorisse. PĂ€rast operatsiooni edukat lĂ”petamist write() peavad andmete lugemise toimingud tagastama tĂ€pselt need baitid, mis olid eelnevalt kirjutatud, isegi juhul, kui andmetele pÀÀsevad ligi teised protsessid vĂ”i lĂ”imed ( vastav POSIXi standardi osa). , jaotises, mis kĂ€sitleb voogude suhtlemist tavaliste failitegevustega, on mĂ€rge, kus öeldakse, et kui iga voog kutsub neid funktsioone vĂ€lja, peab iga vĂ€ljakutse nĂ€gema kas kĂ”iki mÀÀratud tagajĂ€rgi, mida pĂ”hjustab muu vĂ€ljakutse tĂ€itmine, vĂ”i ei nĂ€e mitte ĂŒhtegi tagajĂ€rge. See vĂ”imaldab jĂ€reldada, et kĂ”ik sisendi/vĂ€ljundi failitegevused peavad hoidma ressursi lukus, millega nad töötavad.
Kas see tĂ€hendab, et operatsioon write() on aatomaarne? Tehniliselt â jah. Andmete lugemise operatsioonid peaksid tagastama kas kĂ”ik vĂ”i mitte midagi sellest, mis on kirjutatud funktsiooni write(). Kuid operatsioon write(), vastavalt standardile, ei pea tingimata lĂ”pule viima, kirjutades kĂ”ik, mis tal oli ette nĂ€htud kirjutada. Tal on lubatud kirjutada ainult osa andmeid. NĂ€iteks vĂ”ib meil olla kaks voogu, igaĂŒks, kes lisab 1024 baiti faili, millel on sama failihaldur. Standardi kohaselt on vastuvĂ”etav tulemus, kui iga kirjutamisoperatsioon suudab faili lisada vaid ĂŒhe baiti. Need operatsioonid jÀÀvad aatomaarseks, kuid nende lĂ”puleviimisel on neile kirjutatud andmed failis segamini. Sellel teemal on vĂ€ga huvitav arutelu Stack Overflow's.
Funktsioonid fsync() ja fdatasync()
Lihtsaim viis andmete kettale kirjutamiseks on funktsiooni vĂ€ljakutse . See funktsioon taotleb operatsioonisĂŒsteemilt kĂ”igi muudetud plokkide edastamist vahemĂ€lust kettale. Siia kuuluvad ka kĂ”ik faili metaandmed (jĂ”udmisaja, faili muutmise aeg jne). Ma usun, et nende metaandmete vajadus tekib harva, seega kui teate, et need ei ole teile olulised, vĂ”ite kasutada funktsiooni fdatasync(). Failis 6TB fdatasync() ĂŒtleb, et selle funktsiooni töö kĂ€igus salvestatakse kettale nii palju metaandmeid, kui on "vajalik jĂ€rgmiste andmete lugemise operatsioonide korrektseks teostamiseks". Ja see on tĂ€pselt see, mis enamiku rakenduste jaoks muret tekitab.
Ăks probleem, mis siit vĂ”ib tekkida, on see, et need mehhanismid ei garanteeri, et faili saab pĂ€rast vĂ”imalikku riket leida. Eriti siis, kui luuakse uus fail, tuleb vĂ€ljakutse teha fsync() katalooge, mis seda sisaldab. Vastasel juhul vĂ”ib pĂ€rast tĂ”rget juhtuda, et seda faili ei eksisteeri. Selle pĂ”hjuseks on asjaolu, et UNIXis, tugevate linkide kasutamise tĂ”ttu, vĂ”ib fail eksisteerida mitmes kataloogis. SeetĂ”ttu kutsutakse esile fsync() faili jaoks pole vĂ”imalust teada, milliste kataloogide andmeid tuleb samuti kettale kirjutada ( sellest saab lugeda lĂ€hemalt). Tundub, et failisĂŒsteem ext4 on vĂ”imeline rakendama fsync() kataloogidesse, mis sisaldavad vastavaid faile, kuid teiste failisĂŒsteemide puhul ei pruugi see nii olla.
Seda mehhanismi vĂ”ib erinevates failisĂŒsteemides eri viisil rakendada. Ma kasutasin selgitamaks, milliseid kettategevusi kasutatakse failisĂŒsteemides ext4 ja XFS. MĂ”lemad annavad tavalised kettale kirjutamise kĂ€sud nii failide sisu kui ka failisĂŒsteemi ĆŸurnali jaoks, kirjutavad vahemĂ€lud tĂŒhjaks ja lĂ”petavad töötamise, teostades FUA-kirjutamise (Force Unit Access, andmete kirjutamine otse kettale, vahe mĂ€lu vahele kĂŒsimata) ĆŸurnali. TĂ”enĂ€oliselt kĂ€ituvad nad nii, et kinnitada operatsiooni teostamise fakti. Kettadel, mis ei toeta FUA-d, tekitab see kaks vahemĂ€lu tĂŒhjendust. Minu katsed nĂ€itasid, et fdatasync() veidi kiiremini fsync(). Utiliit blktrace'i jĂ€tab mulje, et fdatasync() kirjutab tavaliselt 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 sain blktrace'i teada, et fdatasync() kirjutab kettale vĂ€hem andmeid (4 KiB XFS-is).
Ebamugavad olukorrad, mis tekivad fsync() kasutamisel
Mulle tuleb meelde kolm ebamugavat olukorda, mis on seotud fsync(), millega ma olen praktikas kokku puutunud.
Esimene selline juhtum toimus 2008. aastal. Sel ajal âjĂ€iâ Firefox 3 kasutajaliides seisma, kui kirjutati kettale palju faile. Probleem oli selles, et liidese oleku teabe salvestamiseks kasutati SQLite andmebaasi. PĂ€rast iga muudatust, mis toimus liideses, kutsuti vĂ€lja funktsioon fsync(), mis pakkus head garantiid andmete pĂŒsiva hoidmise osas. Tol ajal kasutatud failisĂŒsteemis ext3 funktsioon fsync() salvestas sĂŒsteemis kĂ”ik ârĂ€pasedâ lehed, mitte ainult need, mis olid seotud vastava failiga. See tĂ€hendas, et hiireklĂ”ps Firefoxis vĂ”is algatada megabaidise andmete salvestamise magnetketta peale, mis vĂ”is vĂ”tta palju sekundeid. Probleemi lahendus, nii nagu ma aru sain, oli materjalist, et andmebaasihalduse viimine asĂŒnkroonsetesse taustatöödesse oli vajalik. See tĂ€hendab, et varem olid Firefoxis rakendatud rangemad andmete salvestamise vastupidavuse nĂ”uded, kui see tegelikult vajalik oli, ja ext3 failisĂŒsteemi omadused halvendavad seda probleemi veelgi.
Teine vastuolu tekkis 2009. aastal. Siis, pĂ€rast sĂŒsteemi nurjumist, leidsid uue ext4 failisĂŒsteemi kasutajad, et paljud hiljuti loodud failid on nullpikkusega, kuid vanema ext3 failisĂŒsteemi puhul ei toimunud sellist asja. Eelmises lĂ”igus rÀÀkisin, et ext3 salvestas kettale liiga palju andmeid, mis aeglustas oluliselt fsync()tööd. Situatsiooni parandamiseks salvestab ext4 kettale ainult need ârĂ€pasedâ lehed, mis on seotud konkreetse failiga. Teiste failide andmed jÀÀvad mĂ€lu sisse palju pikemaks ajaks kui ext3 kasutamisel. See tehti jĂ”udluse parandamiseks (vaikimisi on andmed sellises olekus 30 sekundit, seda saab seadistada ; leiate tĂ€iendavat teavet selle kohta). See tĂ€hendab, et suurel andmemahtude puhul vĂ”ivad andmed olla pĂ€rast sĂŒsteemiviga pöördumatult kadunud. Selle probleemi lahendus seisneb selle kasutamises fsync() rakendustes, mis peavad tagama usaldusvÀÀrse andmesalvestuse ja maksimaalselt kaitsta neid tĂ”rgete tagajĂ€rgede eest. Funktsioon fsync() toimib ext4 kasutamisel palju tĂ”husamalt kui ext3 kasutamisel. Selle lĂ€henemise miinus on see, et selle rakendamine aeglustab, nagu ennegi, teatud toimingute tĂ€itmist, nĂ€iteks programmide installimist. Ăksikasjad selle kohta leiate ja .
Kolmas probleem, mis fsync(), kerkis esile 2018. aastal. Sel ajal, PostgreSQL projekti raames, selgitati vĂ€lja, et kui funktsioon fsync() kohtab viga, mĂ€rgib see ârĂ€pasedâ lehed kui âpuhtadâ. SeetĂ”ttu jĂ€rgmised vĂ€ljakutsed fsync() Selliseid lehti ei tehta midagi. SeetĂ”ttu sĂ€ilivad muudetud lehed mĂ€lu piires ja neid ei salvestata kunagi kettale. See on tĂ”eline katastroof, kuna rakendus usub, et teatud andmed on kettale salvestatud, kuigi tegelikult see ei ole nii. Sellised vead fsync() esinevad harva, rakendus sellistes olukordades peaaegu ei saa probleemi lahendamiseks midagi ette vĂ”tta. TĂ€napĂ€eval, kui see juhtub, katkestavad PostgreSQL ja teised rakendused oma töö. , artiklis âKas rakendused saavad fsynci tĂ”rgetest taastuda?â, uuritakse seda probleemi ĂŒksikasjalikult. Praegu on parim lahendus selle probleemi jaoks kasutada Direct I/O koos lipuga O_SYNC vĂ”i lipuga O_DSYNC. Sellise lĂ€henemise korral teatab sĂŒsteem vigu, mis vĂ”ivad tekkida andmete kirjutamise konkreetsete operatsioonide tĂ€itmisel, kuid see lĂ€henemine nĂ”uab, et rakendus haldaks puhverdamist ise. Lisateavet selle kohta lugege ja .
Failide avamine O_SYNC ja O_DSYNC lippudega
Naaseme arutelu juurde Linuxi mehhanismide kohta, mis tagavad andmete pĂŒsiva salvestamise. Nimelt rÀÀgime me lipu kasutamisest O_SYNC vĂ”i lipu O_DSYNC failide avamisel sĂŒsteemi sĂŒsteemikĂ”nes . Sellise lĂ€henemise korral toimub iga andmete kirjutamise toiming nii, nagu oleks pĂ€rast iga kĂ€sku write() sĂŒsteemile antud vastavalt kĂ€sud fsync() ja fdatasync(). Failis nii öeldakse "SĂŒnkroniseeritud I/O Faili Terviklikkuse TĂ€itmine" ja "Andmete Terviklikkuse TĂ€itmine". Selle lĂ€henemise peamine eelis on see, et andmete terviklikkuse tagamiseks peab toimuma ainult ĂŒks sĂŒsteemi kĂ”ne, mitte kaks (nĂ€iteks â write() ja fdatasync()). Selle lĂ€henemise peamine puudus on see, et kĂ”ik kirjutamisoperatsioonid, mis kasutavad vastavat failikirjet, sĂŒnkroniseeritakse, mis vĂ”ib piirata rakenduse koodi struktureerimise vĂ”imalusi.
Direct I/O kasutamine O_DIRECT lipuga
SĂŒsteemi vĂ€ljakutse open() toetab lippu O_DIRECT, mis on mĂ”eldud selleks, et mööda minna operatsioonisĂŒsteemi vahemĂ€lust ning teostada sisendi-vĂ€ljundi toiminguid, suheldes otse ketastega. See tĂ€hendab paljudes olukordades, et programmi poolt antud kirjutamiskĂ€sked tĂ”lgitakse otse ketastele suunatud kĂ€skudeks. Kuid ĂŒldiselt ei asenda see mehhanism funktsioone fsync() vĂ”i fdatasync(). Asi on selles, et ise kettas vĂ”ib vastavaid andmete salvestamise kĂ€sklusi. Ja mis veel hullem, mĂ”ningatel erijuhtudel sisendi-vĂ€ljundi toimingud, mida viiakse lĂ€bi lipu kasutamisel O_DIRECT, traditsioonilistesse puhvrifunktsioonidesse. Selle probleemi lihtsaim lahendus on kasutada failide avamisel ka lippu O_DSYNC, mis tĂ€hendab, et iga salvestamistoiminguga kaasneb kutse fdatasync().
Leiti, et XFS failisĂŒsteemisse on hiljuti lisatud "kiire tee" O_DIRECT|O_DSYNC-andmete salvestamiseks. Kui bloke peatatakse, kasutades O_DIRECT|O_DSYNC, siis XFS, selle asemel et vahemĂ€lu tĂŒhjendada, viib lĂ€bi FUA-salvestamise kĂ€su, kui seade seda toetab. Ma olen seda veendunud, kasutades tööriista blktrace'i Linuxis 5.4 / Ubuntu 20.04. Selline lĂ€henemine peaks olema efektiivsem, kuna selle kasutamisel salvestatakse kettale minimaalne andmemahu hulk ja rakendatakse ĂŒhte operatsiooni, mitte kahte (salvestamine ja vahemĂ€lu tĂŒhjendamine). Leidsin viite 2018. aasta tuumale, milles see mehhanism on rakendatud. Seal on arutelu selle optimeerimise rakendamise kohta ka teistes failisĂŒsteemides, kuid niipalju kui ma tean, on XFS praegu ainus failisĂŒsteem, mis seda toetab.
funktsioon sync_file_range()
Linuxis on sĂŒsteemikutsumus , mis vĂ”imaldab salvestada kettale vaid osa failist, mitte kogu faili. See kutsung algatab asĂŒnkroonse andmete tĂŒhjendamise ja ei oota selle lĂ”petamist. Kuid juhendis sync_file_range() öeldakse, et see kĂ€sk on "vĂ€ga ohtlik". Selle kasutamist ei soovitata. Omadused ja ohud sync_file_range() on vĂ€ga hĂ€sti kirjeldatud materjalis. EelkĂ”ige nĂ€ib, et see kutse kasutab RocksDB-d selleks, et hallata, millal tuum tĂŒhjendab "mustad" andmed kettale. Kuid samal ajal kasutatakse seal andmete pĂŒsimise tagamiseks ka fdatasync(). Failis RocksDB-s on selle teema kohta huvitavad kommentaarid. NĂ€iteks tundub, et kutse sync_file_range() kasutades ZFS ei viibi andmete tĂŒhjendamiseni kettale. Kogemus vihjab, et harva kasutatav kood vĂ”ib sisaldada vigu. SeetĂ”ttu soovitaksin seda sĂŒsteemikutsumust mitte kasutada ilma ÀÀrmise vajaduseta.
SĂŒsteemikutsumised, mis aitavad tagada andmete pĂŒsivuse
Olen jĂ”udnud jĂ€reldusele, et andmete pĂŒsivaks salvestamiseks mĂ”eldud sisendi/vĂ€ljundi toiminguteks on olemas kolm lĂ€henemist. KĂ”ik need nĂ”uavad funktsiooni vĂ€ljakutset fsync() kausta, kus fail on loodud. Need lĂ€henemised on:
- Funktsiooni vÀljakutse
fdatasync()vÔifsync()pÀrast funktsiooniwrite()(on parem kasutadafdatasync()). - Faili descriptoriga töötamine, mis on avatud lipuga
O_DSYNCvĂ”iO_SYNC(on parem â lipugaO_DSYNC). - KĂ€su kasutamine
pwritev2()lipugaRWF_DSYNCvĂ”iRWF_SYNC(on eelistatavam â lipugaRWF_DSYNC).
JÔudluse mÀrkmed
Ma ei ole pĂ”hjalikult mÔÔtnud erinevate lĂ€henemiste jĂ”udlust, mida olen uurinud. MĂ€rgatud kiirusvahed on ĂŒsna vĂ€ikesed. See tĂ€hendab, et vĂ”in eksida ja teistes tingimustes vĂ”ivad samad asjaolud anda teistsuguseid tulemusi. Esiteks rÀÀgin sellest, mis mĂ”jutab jĂ”udlust enam, ja seejĂ€rel sellest, mis mĂ”jutab jĂ”udlust vĂ€hem.
- Faili andmete ĂŒlekatte tĂ€itmine on kiirem kui andmete failiga liitmine (jĂ”udluse kasu vĂ”ib olla 2-100%). Andmete failiga liitmine nĂ”uab tĂ€iendavate muudatuste tegemist faili metaandmetes, isegi pĂ€rast sĂŒsteemikutsumist
fallocate(), kuid selle efekti ulatus vĂ”ib varieeruda. Soovitan, et parima jĂ”udluse tagamiseks kutsudafallocate()vajaliku ruumi eelnevalt reserveerimiseks. SeejĂ€rel tuleb see ruum selgelt nullidega tĂ€ita ja kutsudafsync(). Nii mĂ€rgitakse vastavad plokid failisĂŒsteemis kui 'reserveeritud', mitte kui 'reserveerimata'. See toob kaasa vĂ€ikese (umbes 2%) jĂ”udluse paranemise. Lisaks vĂ”ivad mĂ”ned kettad esmakordsel plokiga juurdepÀÀsutegemisel töötada aeglasemalt kui teised. See tĂ€hendab, et ruumi nullidega tĂ€itmine vĂ”ib viia mĂ€rkimisvÀÀrse (umbes 100%) jĂ”udluse paranemiseni. Eriti vĂ”ib see juhtuda ketastega (need andmed on mitteametlikud, ma ei suutnud neid kinnitada). Sama kehtib ka salvestuste kohta (see on juba ametlik teave, mis on kinnitatud katsetega). Teised spetsialistid on teinud samasuguseid , mis on seotud erinevate ketastega. - Mida vĂ€hem on sĂŒsteemikutsungeid, seda kĂ”rgem on jĂ”udlus (kasu vĂ”ib olla umbes 5%). Tundub, et
open()lipugaO_DSYNCvĂ”i kutsepwritev2()lipugaRWF_SYNCkiiremini kutsefdatasync(). Kahtlen, et asi on selles, et sellise lĂ€henemise korral on oluline, et sama ĂŒlesande lahendamiseks on vaja teha vĂ€hem sĂŒsteemikĂ”nesid (ĂŒks kĂ”ne kahe asemel). Kuid jĂ”udluse erinevus on vĂ€ga vĂ€ike, seetĂ”ttu vĂ”ite selle tĂ€helepanuta jĂ€tta ja kasutada rakenduses seda, mis ei too kaasa selle loogika keerukust.
Kui teid huvitab andmete pĂŒsivuse teema â siin on mĂ”ned kasulikud materjalid:
- â ĂŒlevaade sisendi/vĂ€ljaande mehhanismide pĂ”himĂ”tetest.
- â jutt sellest, mis juhtub andmetega teel rakendusest kettale.
- â vastus kĂŒsimusele, millal tuleks rakendada
fsync()kaustadele. Kui rÀÀkida sellest lĂŒhidalt, siis tuleb seda teha uue faili loomisel, ning soovituse pĂ”hjus seisneb selles, et Linuxis vĂ”ib olla palju viiteid ĂŒhe ja sama faili kohta. - â siin on toodud kirjeldus selle kohta, kuidas andmete pĂŒsivust rakendatakse SQL Serveris Linuxi platvormil. Siin on mĂ”ned huvitavad vĂ”rdlused Windowsi ja Linuxi sĂŒsteemikĂ”nede vahel. Olen peaaegu kindel, et just tĂ€nu sellele materjalile sain teada FUA optimeerimisest XFS-il.
Kas olete kunagi kaotanud andmeid, mida pidasite usaldusvÀÀrselt kettale salvestatuks?
Allikas: habr.com
