PĂŒsiv andmete ja failide API Linux

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 alustasin NVMe spetsifikatsiooni lugemisest , 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.

PĂŒsiv andmete ja failide API Linux

Ü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 seda artiklit.

write() funktsiooni kasutamise eripÀrad

SĂŒsteemi vĂ€ljakutse write() on mÀÀratletud standardis IEEE POSIX 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 (siin on vastav POSIXi standardi osa). Siin, 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. Siin Sellel teemal on vĂ€ga huvitav arutelu Stack Overflow's.

Funktsioonid fsync() ja fdatasync()

Lihtsaim viis andmete kettale kirjutamiseks on funktsiooni vĂ€ljakutse fsync(). 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 abi 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 (siit sellest saab lugeda lĂ€hemalt). Tundub, et failisĂŒsteem ext4 on vĂ”imeline automaatne 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 blktrace'i 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 selle 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 dirty_expire_centisecs; siit 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 siin ja siin.

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öö. Siin, 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 siin ja siin.

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 open(). 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 POSIXi spetsifikatsioon 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 tĂ€htsustada vĂ”i vahemĂ€lu vastavaid andmete salvestamise kĂ€sklusi. Ja mis veel hullem, mĂ”ningatel erijuhtudel sisendi-vĂ€ljundi toimingud, mida viiakse lĂ€bi lipu kasutamisel O_DIRECT, edastatakse 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 patĆĄ 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 sync_file_range(), 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 selles 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 koodis 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:

  1. Funktsiooni vÀljakutse fdatasync() vÔi fsync() pÀrast funktsiooni write() (on parem kasutada fdatasync()).
  2. Faili descriptoriga töötamine, mis on avatud lipuga O_DSYNC vĂ”i O_SYNC (on parem — lipuga O_DSYNC).
  3. KĂ€su kasutamine pwritev2() lipuga RWF_DSYNC vĂ”i RWF_SYNC (on eelistatavam — lipuga RWF_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.

  1. 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 kutsuda fallocate() vajaliku ruumi eelnevalt reserveerimiseks. SeejĂ€rel tuleb see ruum selgelt nullidega tĂ€ita ja kutsuda fsync(). 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 AWS EBS (need andmed on mitteametlikud, ma ei suutnud neid kinnitada). Sama kehtib ka salvestuste kohta GCP Persistent Disk (see on juba ametlik teave, mis on kinnitatud katsetega). Teised spetsialistid on teinud samasuguseid vaatlusi, mis on seotud erinevate ketastega.
  2. Mida vĂ€hem on sĂŒsteemikutsungeid, seda kĂ”rgem on jĂ”udlus (kasu vĂ”ib olla umbes 5%). Tundub, et open() lipuga O_DSYNC vĂ”i kutse pwritev2() lipuga RWF_SYNC kiiremini kutse fdatasync(). 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:

  • I/O juurdepÀÀsumeetodid – ĂŒlevaade sisendi/vĂ€ljaande mehhanismide pĂ”himĂ”tetest.
  • Kuidas tagada, et andmed jĂ”uavad kettale – jutt sellest, mis juhtub andmetega teel rakendusest kettale.
  • Millal tuleks fsync'da sisaldav kaust – 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.
  • SQL Server Linuxil: FUA sisemised detailid – 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?

PĂŒsiv andmete ja failide API Linux

PĂŒsiv andmete ja failide API Linux

Allikas: habr.com

Osta usaldusvÀÀrne veebimajutus DDoS-kaitsega veebisaitidele, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebimajutus DDoS-kaitsega veebisaitidele, VPS VDS serverid - ProHoster