Varanleg gagnageymsla og Linux skrá API

Ég, sem rannsakaði stöðugleika gagnageymslu í skýjakerfum, ákvað að prófa mig áfram til að ganga úr skugga um að ég skilji grunnatriðin. ég byrjaði á því að lesa NVMe spec til að skilja hvaða tryggingar varðandi gagnaþol (þ.e. tryggir að gögn verði tiltæk eftir kerfisbilun) gefðu okkur NMVe diska. Ég dró eftirfarandi meginniðurstöður: þú þarft að íhuga að gögnin séu skemmd frá því augnabliki sem gagnaskrifskipunin er gefin og þar til þau eru skrifuð á geymslumiðilinn. Hins vegar, í flestum forritum, eru kerfissímtöl nokkuð örugglega notuð til að skrifa gögn.

Í þessari grein kanna ég þrautseigjuaðferðirnar sem Linux skráarforritaskilin veita. Það virðist sem allt ætti að vera einfalt hér: forritið kallar á skipunina write(), og eftir að aðgerð þessarar skipunar er lokið verða gögnin geymd á öruggan hátt á disknum. En write() afritar aðeins forritsgögn í skyndiminni kjarna sem staðsett er í vinnsluminni. Til þess að þvinga kerfið til að skrifa gögn á diskinn verður að nota nokkrar viðbótaraðferðir.

Varanleg gagnageymsla og Linux skrá API

Almennt séð er þetta efni safn athugasemda sem tengjast því sem ég hef lært um efni sem ég hef áhuga á. Ef við tölum mjög stuttlega um það mikilvægasta kemur í ljós að til að skipuleggja sjálfbæra gagnageymslu þarftu að nota skipunina fdatasync() eða opna skrár með fána O_DSYNC. Ef þú hefur áhuga á að læra meira um hvað verður um gögn á leiðinni frá kóða til disks skaltu skoða þetta grein.

Eiginleikar þess að nota write() aðgerðina

Kerfiskall write() skilgreint í staðlinum IEEE POSIX sem tilraun til að skrifa gögn í skráarlýsingu. Eftir farsæla vinnu write() gagnalestraraðgerðir verða að skila nákvæmlega þeim bætum sem áður voru skrifaðar, og gera það jafnvel þó að gögnin séu aðgengileg frá öðrum ferlum eða þráðum (hér samsvarandi hluta POSIX staðalsins). Hér, í kaflanum um samspil þráða við venjulega skráaraðgerðir, er athugasemd sem segir að ef tveir þræðir kalla hver á þessar aðgerðir, þá verður hvert símtal annað hvort að sjá allar tilgreindar afleiðingar sem framkvæmd hins símtalsins leiðir til, eða alls ekki sjá engar afleiðingar. Þetta leiðir til þeirrar niðurstöðu að allar skráa I/O aðgerðir verða að halda læsingu á auðlindinni sem unnið er með.

Þýðir þetta að aðgerðin write() er atóm? Frá tæknilegu sjónarmiði, já. Gagnalestraraðgerðir verða að skila annað hvort öllu eða engu af því sem skrifað var með write(). En aðgerðin write(), í samræmi við staðalinn, þarf ekki að enda, að hafa skrifað niður allt sem hún var beðin um að skrifa niður. Það er leyfilegt að skrifa aðeins hluta af gögnunum. Til dæmis gætum við haft tvo strauma sem hvor um sig bæta 1024 bætum við skrá sem lýst er með sama skráarlýsingu. Frá sjónarhóli staðalsins verður niðurstaðan ásættanleg þegar hver skrifaðgerð getur aðeins bætt einu bæti við skrána. Þessar aðgerðir verða áfram atómar, en eftir að þeim er lokið verða gögnin sem þeir skrifa í skrána ruglað saman. Hér mjög áhugaverð umræða um þetta efni á Stack Overflow.

fsync() og fdatasync() aðgerðir

Auðveldasta leiðin til að skola gögn yfir á disk er að kalla á aðgerðina fsync(). Þessi aðgerð biður stýrikerfið um að færa allar breyttar blokkir úr skyndiminni yfir á disk. Þetta felur í sér öll lýsigögn skráarinnar (aðgangstími, breytingatími skráar og svo framvegis). Ég tel að það sé sjaldan þörf á þessum lýsigögnum, þannig að ef þú veist að það er ekki mikilvægt fyrir þig geturðu notað aðgerðina fdatasync(). Í hjálp á fdatasync() þar segir að á meðan á þessari aðgerð stendur sé slíkt magn af lýsigögnum vistað á disk, sem er "nauðsynlegt fyrir rétta framkvæmd eftirfarandi gagnalestraraðgerða." Og þetta er einmitt það sem flestum forritum er sama um.

Eitt vandamál sem getur komið upp hér er að þessi kerfi tryggja ekki að skráin finnist eftir hugsanlega bilun. Sérstaklega, þegar ný skrá er búin til, ætti maður að hringja fsync() fyrir möppuna sem inniheldur það. Annars, eftir hrun, getur komið í ljós að þessi skrá er ekki til. Ástæðan fyrir þessu er sú að undir UNIX, vegna notkunar á hörðum hlekkjum, getur skrá verið til í mörgum möppum. Því þegar hringt er fsync() það er engin leið fyrir skrá að vita hvaða möppugögn ættu líka að vera skoluð á diskinn (hér þú getur lesið meira um þetta). Það lítur út fyrir að ext4 skráarkerfið sé fær um það sjálfkrafa sækja um fsync() í möppur sem innihalda samsvarandi skrár, en það er kannski ekki raunin með önnur skráarkerfi.

Þetta fyrirkomulag er hægt að útfæra á mismunandi hátt í mismunandi skráarkerfum. ég notaði blktrace til að fræðast um hvaða diskaaðgerðir eru notaðar í ext4 og XFS skráarkerfum. Báðir gefa út venjulegar skrifskipanir á diskinn fyrir bæði innihald skránna og skráarkerfisdagbókina, skola skyndiminni og hætta með því að framkvæma FUA (Force Unit Access, skrifa gögn beint á disk, framhjá skyndiminni) skrifa í dagbókina. Þeir gera það líklega til að staðfesta staðreyndir viðskiptanna. Á drifum sem styðja ekki FUA veldur þetta tveimur skyndiminni skolun. Tilraunir mínar hafa sýnt það fdatasync() aðeins hraðar fsync(). Gagnsemi blktrace bendir til þess fdatasync() skrifar venjulega minna gögn á diskinn (í ext4 fsync() skrifar 20 KiB, og fdatasync() - 16 KiB). Einnig komst ég að því að XFS er aðeins hraðari en ext4. Og hér með hjálpina blktrace gat komist að því fdatasync() skolar minni gögn á diskinn (4 KiB í XFS).

Óljósar aðstæður þegar fsync() er notað

Ég get hugsað mér þrjár óljósar aðstæður varðandi fsync()sem ég hef rekist á í reynd.

Fyrsta slíka atvikið átti sér stað árið 2008. Á þeim tíma „frysti“ Firefox 3 viðmótið ef verið var að skrifa mikinn fjölda skráa á diskinn. Vandamálið var að innleiðing viðmótsins notaði SQLite gagnagrunn til að geyma upplýsingar um ástand þess. Eftir hverja breytingu sem varð á viðmótinu var aðgerðin kölluð fsync(), sem gaf góða tryggingu fyrir stöðugri gagnageymslu. Í ext3 skráarkerfinu sem þá var notað er aðgerðin fsync() skolaði á diskinn allar „óhreinu“ síðurnar í kerfinu, en ekki bara þær sem tengdust samsvarandi skrá. Þetta þýddi að það að smella á hnapp í Firefox gæti valdið því að megabæt af gögnum voru skrifuð á seguldisk, sem gæti tekið margar sekúndur. Lausnin á vandanum, eftir því sem ég skildi af af þessu efni, átti að færa vinnuna með gagnagrunninn yfir í ósamstillt bakgrunnsverkefni. Þetta þýðir að Firefox notaði til að innleiða strangari kröfur um geymsluþol en raunverulega var nauðsynlegt og ext3 skráarkerfiseiginleikarnir ýttu aðeins á þetta vandamál.

Annað vandamálið kom upp árið 2009. Síðan, eftir kerfishrun, komust notendur nýja ext4 skráarkerfisins að því að margar nýstofnaðar skrár voru núll-lengdar, en það gerðist ekki með eldra ext3 skráarkerfinu. Í fyrri málsgrein talaði ég um hvernig ext3 henti of miklum gögnum á diskinn, sem hægði mjög á hlutunum. fsync(). Til að bæta ástandið skolar ext4 aðeins þessar „óhreinu“ síður sem eiga við tiltekna skrá. Og gögn annarra skráa eru í minni í miklu lengri tíma en með ext3. Þetta var gert til að bæta árangur (sjálfgefið haldast gögnin í þessu ástandi í 30 sekúndur, þú getur stillt þetta með dirty_expire_centisecs; hér þú getur fundið frekari upplýsingar um þetta). Þetta þýðir að mikið magn gagna getur tapast óafturkallanlega eftir hrun. Lausnin á þessu vandamáli er að nota fsync() í forritum sem þurfa að veita stöðuga gagnageymslu og vernda þau eins og hægt er fyrir afleiðingum bilana. Virka fsync() virkar mun skilvirkari með ext4 en með ext3. Ókosturinn við þessa nálgun er að notkun hennar, eins og áður, hægir á sumum aðgerðum, svo sem uppsetningu forrita. Sjá nánari upplýsingar um þetta hér и hér.

Þriðja vandamálið varðandi fsync(), upprunnið árið 2018. Síðan, innan ramma PostgreSQL verkefnisins, kom í ljós að ef fallið fsync() lendir í villu, það merkir "óhreinar" síður sem "hreinar". Þar af leiðandi kallar eftirfarandi fsync() gera ekkert með svona síður. Vegna þessa eru breyttar síður geymdar í minni og aldrei skrifaðar á disk. Þetta er algjör hörmung, vegna þess að forritið mun halda að sum gögn séu skrifuð á disk, en í raun verður það ekki. Þvílík mistök fsync() eru sjaldgæfar, forritið við slíkar aðstæður getur nánast ekkert gert til að berjast gegn vandamálinu. Þessa dagana, þegar þetta gerist, hrynja PostgreSQL og önnur forrit. Hér, í greininni "Geta forrit endurheimt frá fsync bilunum?", er þetta vandamál kannað í smáatriðum. Eins og er er besta lausnin á þessu vandamáli að nota Direct I/O með fánanum O_SYNC eða með fána O_DSYNC. Með þessari nálgun mun kerfið tilkynna villur sem geta komið upp þegar framkvæmt er sérstakar gagnaskrifunaraðgerðir, en þessi aðferð krefst þess að forritið stjórni sjálft biðminni. Lestu meira um það hér и hér.

Að opna skrár með O_SYNC og O_DSYNC fánum

Snúum okkur aftur að umræðunni um Linux kerfin sem veita viðvarandi gagnageymslu. Við erum nefnilega að tala um notkun fánans O_SYNC eða fána O_DSYNC þegar skrár eru opnaðar með kerfiskalli opna(). Með þessari nálgun er hver gagnaritunaraðgerð framkvæmd eins og eftir hverja skipun write() kerfið fær skipanir í sömu röð fsync() и fdatasync(). Í POSIX upplýsingar þetta er kallað "Synchronized I/O File Integrity Completion" og "Data Integrity Completion". Helsti kosturinn við þessa nálgun er að aðeins þarf að framkvæma eitt kerfiskall til að tryggja gagnaheilleika, en ekki tvö (til dæmis − write() и fdatasync()). Helsti ókosturinn við þessa nálgun er að allar skrifaðgerðir sem nota samsvarandi skráarlýsingu verða samstilltar, sem getur takmarkað getu til að skipuleggja forritskóðann.

Notkun Direct I/O með O_DIRECT fánanum

Kerfiskall open() styður fánann O_DIRECT, sem er hannað til að komast framhjá skyndiminni stýrikerfisins, framkvæma I / O aðgerðir, hafa samskipti beint við diskinn. Þetta þýðir í mörgum tilfellum að skrifskipanirnar sem forritið gefur út verða þýddar beint í skipanir sem miða að því að vinna með diskinn. En almennt kemur þetta vélbúnaður ekki í staðinn fyrir aðgerðir fsync() eða fdatasync(). Staðreyndin er sú að diskurinn sjálfur getur seinkun eða skyndiminni viðeigandi skipanir til að skrifa gögn. Og, jafnvel verra, í sumum sérstökum tilfellum, I / O aðgerðirnar sem framkvæmdar eru þegar fáninn er notaður O_DIRECT, útsending inn í hefðbundna biðminni starfsemi. Auðveldasta leiðin til að leysa þetta vandamál er að nota fánann til að opna skrár O_DSYNC, sem mun þýða að hverri skrifaðgerð verður fylgt eftir með símtali fdatasync().

Það kom í ljós að XFS skráarkerfið hafði nýlega bætt við „hraðleið“ fyrir O_DIRECT|O_DSYNC-gagnaskrár. Ef kubburinn er skrifaður yfir með því að nota O_DIRECT|O_DSYNC, þá mun XFS, í stað þess að skola skyndiminni, framkvæma FUA skrifa skipunina ef tækið styður það. Ég staðfesti þetta með því að nota tólið blktrace á Linux 5.4/Ubuntu 20.04 kerfi. Þessi aðferð ætti að vera skilvirkari, þar sem hún skrifar lágmarksmagn gagna á diskinn og notar eina aðgerð, ekki tvær (skrifa og skola skyndiminni). Ég fann tengil á plástur 2018 kjarna sem útfærir þetta kerfi. Það er nokkur umræða um að beita þessari fínstillingu á önnur skráarkerfi, en eftir því sem ég best veit er XFS eina skráarkerfið sem styður það hingað til.

sync_file_range() virka

Linux er með kerfiskall sync_file_range(), sem gerir þér kleift að skola aðeins hluta af skránni á diskinn, ekki alla skrána. Þetta símtal kemur af stað ósamstilltri skolun og bíður ekki eftir að henni ljúki. En í tilvísun til sync_file_range() þessi skipun er sögð vera "mjög hættuleg". Ekki er mælt með því að nota það. Eiginleikar og hættur sync_file_range() mjög vel lýst í þetta efni. Sérstaklega virðist þetta símtal nota RocksDB til að stjórna því hvenær kjarninn skolar „óhreinum“ gögnum á diskinn. En á sama tíma þar, til að tryggja stöðuga gagnageymslu, er það líka notað fdatasync(). Í kóða RocksDB hefur nokkrar áhugaverðar athugasemdir um þetta efni. Til dæmis lítur það út eins og símtalið sync_file_range() þegar ZFS er notað skolar ekki gögn á diskinn. Reynslan segir mér að sjaldan notaður kóði gæti innihaldið villur. Þess vegna myndi ég ráðleggja því að nota þetta kerfiskall nema brýna nauðsyn beri til.

Kerfissímtöl til að tryggja gagnaþol

Ég hef komist að þeirri niðurstöðu að það eru þrjár aðferðir sem hægt er að nota til að framkvæma viðvarandi I/O aðgerðir. Þeir þurfa allir aðgerðarkall fsync() fyrir möppuna þar sem skráin var búin til. Þetta eru aðferðirnar:

  1. Virkjakall fdatasync() eða fsync() eftir aðgerð write() (betra að nota fdatasync()).
  2. Vinna með skráarlýsingu opnuð með fána O_DSYNC eða O_SYNC (betra - með fána O_DSYNC).
  3. Skipunarnotkun pwritev2() með fána RWF_DSYNC eða RWF_SYNC (helst með fána RWF_DSYNC).

Athugasemdir um árangur

Ég mældi ekki vandlega árangur hinna ýmsu aðferða sem ég rannsakaði. Munurinn sem ég tók eftir á hraða vinnu þeirra er mjög lítill. Þetta þýðir að ég get haft rangt fyrir mér og að við aðrar aðstæður getur það sama sýnt mismunandi niðurstöður. Fyrst mun ég tala um það sem hefur meiri áhrif á frammistöðu og síðan um það sem hefur minni áhrif á frammistöðu.

  1. Að skrifa yfir skráargögn er hraðari en að bæta gögnum við skrá (afköst geta verið 2-100%). Að hengja gögn við skrá krefst viðbótarbreytinga á lýsigögnum skráarinnar, jafnvel eftir kerfiskallið fallocate(), en umfang þessara áhrifa getur verið mismunandi. Ég mæli með því að hringja til að ná sem bestum árangri fallocate() að úthluta tilskildu rými fyrirfram. Þá verður að fylla þetta rými sérstaklega með núllum og kalla það fsync(). Þetta mun valda því að samsvarandi blokkir í skráarkerfinu verða merktar sem "úthlutað" í stað "óúthlutað". Þetta gefur litla (um 2%) frammistöðubót. Einnig geta sumir diskar verið með hægari fyrstu aðgangsaðgerð en aðrir. Þetta þýðir að það að fylla plássið með núllum getur leitt til verulegrar (um 100%) frammistöðubata. Sérstaklega getur þetta gerst með diskum. AWS EBS (þetta eru óopinber gögn, ég gat ekki staðfest þau). Sama á við um geymslu. GCP viðvarandi diskur (og þetta eru nú þegar opinberar upplýsingar, staðfestar með prófum). Aðrir sérfræðingar hafa gert slíkt hið sama athuguntengjast mismunandi diskum.
  2. Því færri kerfissímtöl, því meiri árangur (ábatinn getur verið um 5%). Það lítur út eins og símtal open() með fána O_DSYNC eða hringdu pwritev2() með fána RWF_SYNC hraðari símtal fdatasync(). Mig grunar að punkturinn hér sé sá að með þessari nálgun spili það inn í að það þurfi að framkvæma færri kerfissímtöl til að leysa sama verkefnið (eitt símtal í stað tveggja). En frammistöðumunurinn er mjög lítill, svo þú getur auðveldlega hunsað hann og notað eitthvað í forritinu sem leiðir ekki til flækjustigs rökfræði þess.

Ef þú hefur áhuga á efni sjálfbærrar gagnageymslu, hér eru nokkur gagnleg efni:

  • I/O aðgangsaðferðir — yfirlit yfir grunnatriði inntaks/úttaksaðferða.
  • Tryggja að gögn berist til disks - saga um hvað verður um gögnin á leiðinni frá forritinu á diskinn.
  • Hvenær ættir þú að fsync möppuna sem inniheldur - svarið við spurningunni um hvenær eigi að sækja um fsync() fyrir möppur. Í hnotskurn kemur í ljós að þú þarft að gera þetta þegar þú býrð til nýja skrá og ástæðan fyrir þessum tilmælum er sú að í Linux geta verið margar tilvísanir í sömu skrána.
  • SQL Server á Linux: FUA Internals - hér er lýsing á því hvernig viðvarandi gagnageymsla er útfærð í SQL Server á Linux pallinum. Það er áhugaverður samanburður á Windows og Linux kerfissímtölum hér. Ég er næstum viss um að það hafi verið þökk sé þessu efni sem ég lærði um FUA hagræðingu XFS.

Hefur þú einhvern tíma týnt gögnum sem þú hélst að væru tryggilega geymd á disknum?

Varanleg gagnageymsla og Linux skrá API

Varanleg gagnageymsla og Linux skrá API

Heimild: www.habr.com