Ruajtje e qëndrueshme të të dhënave dhe API skedarësh Linux

Unë, duke eksperimentuar qëndrueshmërinë e ruajtjes së të dhënave në sistemet në re, vendosa të provoj veten, të sigurohem që kuptoj gjërat bazike. Unë fillova me leximin e specifikimeve NVMe për të kuptuar se cilat garanci, në lidhje me ruajtjen e qëndrueshme të të dhënave (domethënë - garantimi që të dhënat do të jenë të aksesueshme pas një dështimi të sistemit), na ofrojnë diskët NMVe. Unë bëra këto përfundime kryesore: duhet të mendohet se të dhënat janë dëmtuar nga momenti kur jepet komanda për të shkruar të dhënat, dhe deri në momentin kur përfundon shkrimi i tyre në pajisjen e informacionit. Megjithatë, në shumicën e programeve për shkrimin e të dhënave, përdoren thirrje për sistemin pa ndonjë problem.

Në këtë material, unë studioj mekanizmat e ruajtjes së qëndrueshme të të dhënave, që ofrohen nga API-të e skedave të Linux. Duket se këtu gjithçka duhet të jetë e thjeshtë: programi thërret komandën write(), dhe pas përfundimit të punës së kësaj komande, të dhënat do të ruhen sigurt në disk. Por write() vetëm kopjon të dhënat e aplikacionit në cache-in e bërthamës, që ndodhet në memorien operative. Për të detyruar sistemin të shkruajë të dhënat në disk, duhen përdorur disa mekanizma shtesë.

Ruajtje e qëndrueshme të të dhënave dhe API skedarësh Linux

Në përgjithësi, ky material përbën një grumbull shënimesh, në lidhje me atë që kam mësuar mbi temën që më intereson. Nëse do të fliste shumë shkurt për më të rëndësishmen, do të rezultonte se për të organizuar ruajtjen e qëndrueshme të të dhënave, duhet të përdorim komandën fdatasync() ose të hapim skedarët me flamurin O_DSYNC. Nëse jeni të interesuar të mësoni në detaje se çfarë ndodh me të dhënat në rrugën nga kodi programor te disku, shikoni artikull. artikullin.

Veçoritë e përdorimit të funksionit write()

Thirrja sistemike write() shtjellohet në standardin IEEE POSIX si një përpjekje për të shkruar të dhënat në një descriptor skedari. Pas përfundimit me sukses write() të dhënat e leximit duhet të kthejnë pikërisht ato byte që ishin shkruar më parë, duke bërë këtë edhe në rast se të dhënat qasën nga procese ose rrjedha të tjera (ja pjesa përkatëse e standardit POSIX). Këtu, në seksionin që i kushtohet ndërveprimit të rrjedhave me operacionet e zakonshme të skedarëve, ka një shënim që thotë se nëse secila nga dy rrjedhat thërret këto funksione, çdo thirrje duhet të shohë ose të gjitha pasojat e specifikuara që vijnë si rezultat i ekzekutimit të thirrjes tjetër, ose të mos shohë fare asnjë pasojë. Kjo lejon përfundimin se të gjitha operacionet e skedarit për futje/dalje duhet të mbajnë bllokimin e burimit me të cilin punojnë.

A do të thotë kjo se operacioni write() është atomar? Nga ana teknike - po. Operacionet e leximit të të dhënave duhet të kthejnë ose gjithçka, ose asgjë nga ajo që është shkruar me write(). Por operacioni write(), sipas standardit, nuk është domosdoshmërisht i detyruar të përfundojë duke shkruar gjithçka që i është propozuar të shkruajë. I lejohet të kryejë vetëm një pjesë të të dhënave. Për shembull, mund të kemi dy rrjedha, secila prej të cilave bashkangjit 1024 byte në një skedar, i cili përshkruhet nga një descriptor i të njëjtit skedar. Nga pikëpamja e standardit, rezultati kur secili nga operacionet e shkruar arrin të bashkangjisë vetëm një byte në skedarin është i pranueshëm. Këto operacione do të mbeten atomare, por pasi të përfundojnë, të dhënat e shkruara prej tyre në skedar do të jenë të përziera. Ja një diskutim shumë interesant në këtë temë në Stack Overflow.

Funksionet fsync() dhe fdatasync()

Mënyra më e thjeshtë për të rikthyer të dhënat në disk është duke thirrur funksionin fsync(). Ky funksion kërkon nga sistemi operativ që të transferojë të gjitha blloqet e modifikuara nga cache në disk. Kjo përfshin gjithashtu të gjithë metadata e skedarit (koha e qasjes, koha e modifikimit të skedarit dhe kështu me radhë). Unë mendoj se nevoja për këto metadata ndodh rrallë, prandaj, nëse e dini se ato nuk janë të rëndësishme për ju, mund të përdorni funksionin fdatasync(). Në ndihmës sipër fdatasync() thotë se gjatë punës së këtij funksioni, ruhet në disk një volum metadata që "është e nevojshme për përfundimin e saktë të operacioneve të ardhshme të leximit të të dhënave." Dhe kjo është ajo që lidhet me shumicën e aplikacioneve.

Një nga problemet që mund të lindin këtu është se këto mekanizma nuk garantojnë se skedari do të jetë i zbulueshëm pas një dështimi të mundshëm. Në veçanti, kur krijohet një skedar i ri, është e nevojshme të thirret fsync() për katalogun që e përmban atë. Përndryshe, pas një dështimi, mund të ndodhte që ky skedar të mos ekzistojë. Arsyeja e kësaj është se në UNIX, për shkak të përdorimit të lidhjeve të forta, një skedar mund të ekzistojë në disa katalogë. Prandaj, kur thirret fsync() për skedarin nuk ka mënyrë për të ditur se të dhënat e cilit katalog gjithashtu duhet të shkarkohen në disk (këtu për këtë mund të lexoni më shumë). Duket se sistemi i skedarëve ext4 është në gjendje automatikisht aplikohen fsync() për katalogët që përmbajnë skedarët përkatës, por në rastin e sistemeve të tjera të skedarëve, kjo mund të mos jetë e vërtetë.

Ky mekanizĂ«m mund tĂ« implementohet nĂ« mĂ«nyra tĂ« ndryshme nĂ« sisteme tĂ« ndryshme tĂ« skedarĂ«ve. UnĂ« pĂ«rdora blktrace pĂ«r tĂ« mĂ«suar se cilat operacione diskesh pĂ«rdoren nĂ« sistemet e skedarĂ«ve ext4 dhe XFS. TĂ« dy sistemet japin komanda standarde pĂ«r shkrimin nĂ« disk pĂ«r pĂ«rmbajtjen e skedarĂ«ve dhe pĂ«r regjistrin e sistemit tĂ« skedarĂ«ve, shkarkojnĂ« cache-n dhe pĂ«rfundojnĂ« punĂ«n duke kryer njĂ« shkrim FUA (Force Unit Access, shkrimi i tĂ« dhĂ«nave direkt nĂ« disk, duke anashkaluar cache) nĂ« regjistĂ«r. Probabilisht, ato veprojnĂ« kĂ«shtu pĂ«r tĂ« konfirmuar faktin e kryerjes sĂ« operacionit. NĂ« disqet qĂ« nuk mbĂ«shtesin FUA, kjo shkakton dy shkarkime tĂ« cache-it. Eksperimentet e mia treguan se fdatasync() disi mĂ« shpejt fsync(). Vegla blktrace tregon se fdatasync() zakonisht shkruan nĂ« disk mĂ« pak tĂ« dhĂ«na (nĂ« ext4 fsync() shkruan 20 KiB, dhe fdatasync() – 16 KiB). PĂ«r mĂ« tepĂ«r, unĂ« zbuloja se XFS Ă«shtĂ« pak mĂ« i shpejtĂ« se ext4. Dhe kĂ«tu, me ndihmĂ«n e blktrace arriti tĂ« kuptoj se fdatasync() shkĂ«put nga disku mĂ« pak tĂ« dhĂ«na (4 KiB nĂ« XFS).

Situatat e paqartë, që ndodhin gjatë përdorimit të fsync()

Mund të kujtoj tre situata të paqarta që lidhen me fsync(), me të cilat u përballa në praktikë.

Rasti i parë ndodhi në 2008. Atëherë, ndërfaqja Firefox 3 "ngadalësohej" në rast se po bëhej shkrimi në disk i një numri të madh skedarësh. Problemi qëndronte në faktin se në implementimin e ndërfaqes për ruajtjen e informacionit mbi gjendjen e saj përdorej një bazë të dhënash SQLite. Pas çdo ndryshimi që ndodhte në ndërfaqe, thirrej funksioni fsync(), duke ofruar garanci të mira për ruajtjen e qëndrueshme të të dhënave. Në sistemin e skedarëve ext3 që përdorej atëherë, funksioni fsync() përmbante të gjitha faqet "e pista" në sistem, jo vetëm ato që kishin lidhje me skedarin përkatës. Kjo do të thoshte se një klikim në butonin në Firefox mund të inicjonte shkrimin e megabajtave të të dhënave në disqet magnetik, gjë që mund të zgjaste shumë sekonda. Zgjidhja e problemit, sa kam kuptuar nga këtë materiali, consistonte në zhvendosjen e punës me bazën e të dhënave në detyra asinkrone në sfond. Kjo do të thotë se më parë në Firefox ishin realizuar kërkesa më të forta për qëndrueshmërinë e ruajtjes së të dhënave, sesa kishte nevojë për të, dhe veçoritë e sistemit të skedarëve ext3 vetëm sa e kishin përkeqësuar këtë problem.

E dyta ndodhi në vitin 2009. Atëherë, pas një dështimi të sistemit, përdoruesit e sistemit të ri të skedarëve ext4 përballeshin me faktin se shumë skedarë të sapo krijuar kishin gjatë zero, ndërsa me sistemin më të vjetër ext3 kjo nuk ndodhi. Në paragrafin e mëparshëm, unë thashë se ext3 shkarkonte shumë të dhëna në disk, e cila ngadalësoi shumë punën fsync(). Për të përmirësuar situatën, në ext4 shkarkohen në disk vetëm ato faqe "e pista" që i përkasin një skedari specifik. Të dhënat e skedarëve të tjerë mbeten në kujtesë për një periudhë shumë më të gjatë, sesa me përdorimin e ext3. Kjo u bë për të përmirësuar performancën (në mënyrë të paracaktuar, të dhënat qëndrojnë në një gjendje të tillë për 30 sekonda, kjo mund të konfiguroni me dirty_expire_centisecs; këtu , mund të gjeni materiale të tjera mbi këtë). Kjo do të thotë se një volum i madh të dhënash mund të humbasë përfundimisht pas një dështimi. Zgjidhja e këtij problemi është përdorimi i fsync() në aplikacionet që duan të garantojnë ruajtjen e qëndrueshme të të dhënave dhe të sigurtojnë ato nga pasojat e dështimeve. Funksioni fsync() funksionon shumë më efektivisht në përdorimin e ext4, sesa në përdorimin e ext3. Një disavantazh i këtij qasje është se aplikimi i saj, si më parë, ngadalëson ekzekutimin e disa operacioneve, si instalimi i programeve. Detajet mbi këtë shikoni këtu dhe këtu.

Problemi i tretë, lidhur me fsync(), ndodhi në vitin 2018. Atëherë, në kuadër të projektit PostgreSQL, u zbuluar se nëse funksioni fsync() përballet me një gabim, ai shënon faqet "e pista" si "të pastra". Si pasojë, thirrjet e ardhshme fsync() nukë bëhet asgjë me këto faqe. Për këtë arsye, faqet e modifikuara ruajnë në memorie dhe kurrë nuk shkruhen në disk. Kjo është një katastrofë e vërtetë, pasi aplikacioni do të mendojë se disa të dhëna janë shkruar në disk, ndërsa në të vërtetë nuk është kështu. Të tilla dështime fsync() ndodhin rrallë, aplikacioni në këto situata pothuajse nuk mund të bëjë asgjë për të luftuar problemin. Në ditët e sotme, kur ndodh kjo, PostgreSQL dhe aplikacione të tjera përfundojnë me dështim. Këtu, në materialin "Mund të Rikuperohen Aplikacionet nga Dështimet e fsync?", kjo problematike shqyrtohet në të gjitha detajet. Aktualisht, zgjidhja më e mirë për këtë problem është përdorimi i Direct I/O me flamurin O_SYNC ose me flamurin O_DSYNC. Me këtë qasje, sistemi do të raportojë për gabimet që mund të ndodhin gjatë kryerjes së operacioneve të caktuara të shkruarjes së të dhënave, por kjo qasje kërkon që aplikacioni të menaxhojë vetë bufferat. Detajet rreth kësaj lexoni këtu dhe këtu.

Hapja e skedareve me përdorimin e flamujve O_SYNC dhe O_DSYNC

Le tĂ« kthehemi nĂ« diskutimin e mekanizmave Linux qĂ« sigurojnĂ« ruajtjen e qĂ«ndrueshme tĂ« tĂ« dhĂ«nave. NĂ« veçanti, bĂ«het fjalĂ« pĂ«r pĂ«rdorimin e flamurit O_SYNC ose flamurit O_DSYNC kur hapen skedarĂ«t duke pĂ«rdorur thirrjen sistemike open(). Me kĂ«tĂ« qasje, çdo operacion i shkruarjes sĂ« tĂ« dhĂ«nave kryhet sikur pas çdo urdhri write() sistemi jep, pĂ«rkatĂ«sisht, komanda fsync() dhe fdatasync(). NĂ« specifikimit POSIX kjo quhet "PĂ«rfundimi i Integritetit tĂ« SkedarĂ«ve tĂ« Shkruarjes tĂ« Sinkronizuar" dhe "PĂ«rfundimi i Integritetit tĂ« TĂ« DhĂ«nave". Avantazhi kryesor i kĂ«saj qasjeje Ă«shtĂ« se pĂ«r tĂ« siguruar integritetin e tĂ« dhĂ«nave nevojitet tĂ« kryhen vetĂ«m njĂ« thirrje sistemike, jo dy (pĂ«r shembull — write() dhe fdatasync()). Disavantazhi kryesor i kĂ«saj qasjeje Ă«shtĂ« se tĂ« gjitha operacionet e shkruarjes qĂ« pĂ«rdorin skedarin e pĂ«rkatĂ«sshĂ«m do tĂ« jenĂ« tĂ« sinkronizuara, tĂ« cilat mund tĂ« kufizojnĂ« mundĂ«sitĂ« pĂ«r strukturimin e kodit tĂ« aplikacionit.

Përdorimi i Direct I/O me flamurin O_DIRECT

Thirrja sistemike open() mbështet flamurin O_DIRECT, i cili është menduar për të kryer operacione hyrjeje-dali, duke anashkaluar cache-në e sistemit operativ dhe duke komunikuar drejtpërdrejt me diskun. Kjo, në shumë raste, do të thotë se komandat e shkruarjes të lëshuara nga programi do të translen në mënyrë të drejtpërdrejtë në komandat që i drejtohen diskut. Por, në përgjithësi, ky mekanizëm nuk është një zëvendësim për funksionet fsync() ose fdatasync(). Kjo është për faktorë se disku vetë mund të vonojë ose të cache-ojë komandat përkatëse për regjistrimin e të dhënave. Dhe, çka është edhe më keq, në disa raste të veçanta operacionet e hyrjes-daljes, që kryhen kur përdoret flagu O_DIRECT, përcillen në operacione tradicionale të tamponuara. Më lehtë se sa të zgjidhësh këtë problem është të përdorësh për hapjen e skedarëve gjithashtu një flag O_DSYNC, çka do të thotë se pas çdo operacioni regjistrimi do të ketë një thirrje fdatasync().

Doli se në sistemin e skedarëve XFS kohët e fundit është shtuar një "rrugë e shpejtë" për O_DIRECT|O_DSYNC-regjistrimin e të dhënave. Nëse ri-regjistron një bllok duke përdorur O_DIRECT|O_DSYNC, atëherë XFS, në vend që të pastrojë cache-n, do të kryejë komandën e regjistrimit FUA nëse pajisja e mbështet këtë. Unë e kam verifikuar këtë duke përdorur utilitarin blktrace në sistemin Linux 5.4/Ubuntu 20.04. Ky qasje duhet të jetë më efikase, pasi me të shkruhet një sasi minimale e të dhënave në disk dhe gjithashtu aplikohet një operacion, jo dy (regjistrimi dhe pastrimi i cache-it). Kam gjetur një lidhje për patch në kernelin e vitit 2018, ku është realizuar ky mekanizëm. Aty ka një diskutim në lidhje me aplikimin e kësaj optimizimi edhe në sisteme të tjera skedari, por, për aq sa di, XFS është për momentin e vetmja sistem skedari që e mbështet këtë.

Funksioni sync_file_range()

Në Linux ka një thirrje sistemike sync_file_range(), e cila lejon të pastrohet në disk vetëm një pjesë e skedarit, e jo tërë skedari. Kjo thirrje iniciaton një pastrim asinkron të të dhënave dhe nuk pret për përfundimin e tij. Por në dokumentacionin për sync_file_range() thuhet se kjo komandë "është shumë e rrezikshme". Nuk rekomandohet përdorimi i saj. Karakteristikat dhe rreziqet sync_file_range() janë përshkruar shumë mirë në këtë material. Sidomos, duket se kjo thirrje përdor RocksDB për të menaxhuar kur bërthama pastron të dhënat "të ndotura" në disk. Por në të njëjtën kohë, për të siguruar ruajtjen e qëndrueshme të të dhënave, përdoret gjithashtu fdatasync(). Në kode RocksDB ka komente interesante në këtë temë. Për shembull, duket se thirrja sync_file_range() kur përdoret ZFS nuk e çon në pastrimin e të dhënave në disk. Eksperienca më tregon se kodi, i cili përdoret rrallë, ndoshta përmban gabime. Prandaj, do t'ju rekomandoja të mos përdoroni këtë thirrje sistemike pa një nevojë të madhe.

Thirrjet sistemike që ndihmojnë për të siguruar ruajtjen e qëndrueshme të të dhënave

Kam er në përfundimin se për të kryer operacione input/output që sigurojnë ruajtjen e qëndrueshme të të dhënave, mund të përdoren tre qasje. Të gjitha ato kërkojnë thirrjen e funksionit fsync() për direktorinë ku është krijuar skedari. Këto janë qasjet:

  1. Thirrja e funksionit fdatasync() ose fsync() pas funksionit write() (është më mirë të përdoret fdatasync()).
  2. Puna me descriptorin e skedarit, i hapur me flag O_DSYNC ose O_SYNC (mĂ« mirĂ« — me flag O_DSYNC).
  3. PĂ«rdorimi i komandĂ«s pwritev2() me flamurin RWF_DSYNC ose RWF_SYNC (preferohet — me flag RWF_DSYNC).

Shënime mbi performancën

Nuk kam bërë matje të kujdesshme të performancës së mekanizmave të ndryshëm që kam studiuar. Diferencat e vërejtura nga unë në shpejtësinë e punës së tyre janë mjaft të vogla. Kjo do të thotë se mund të kem gabuar dhe se, në kushte të tjera, të njëjtat gjëra mund të tregojnë rezultate të tjera. Fillimisht, do të flas për atë që ndikon më shumë në performancë, e më pas për atë që ndikon më pak në performancë.

  1. Rikthimi i të dhënave të skedarit është më i shpejtë se bashkimi i të dhënave në skedar (fitimi në performancë mund të arrijë 2-100%). Bashkimi i të dhënave në skedar kërkon ndryshime të tjera në metadatën e skedarit, madje pas thirrjes së sistemit fallocate(), por shkallët e këtij efekti mund të ndryshojnë. Unë rekomandoj, për të siguruar performancën më të mirë, të thirret fallocate() për rezervimin e hapësirës së nevojshme. Më pas, kjo hapësirë duhet të plotësohet qartë me zero dhe të thirret fsync(). Falë kësaj, blloket përkatëse në sistemin e skedarëve do të shënohen si "rezervuar", dhe jo si "në dispozicion". Kjo ofron një përmirësim të vogël (rreth 2%) në performancë. Për më tepër, disa disqe mund të kenë operacionin e parë të aksesit në bllok që mund të realizohet më ngadalë se të tjerët. Kjo do të thotë se plotësimi i hapësirës me zero mund të sjellë një përmirësim të ndjeshëm (rreth 100%) në performancë. Në veçanti, diçka e tillë mund të ndodhë me disqet AWS EBS (kjo janë të dhëna jozyrtare, nuk kam arritur t'i konfirmoj ato). E njëjta gjë vlen për ruajtjet GCP Persistent Disk (dhe kjo është informacion zyrtar, e konfirmuar nga provat). Specialistë të tjerë kanë bërë vëzhgime të ngjashme në lidhje me disqe të ndryshme.Sa më pak thirrje sistemesh - aq më e lartë është performanca (fitimi mund të arrijë rreth 5%). Duket se thirrja
  2. ose thirrja open() me flamurin O_DSYNC është më e shpejtë se thirrja pwritev2() me flamurin RWF_SYNC thirrja më e shpejtë fdatasync()Dyshoj se kjo ka të bëjë me faktin se me këtë qasje, luan rol fakti që për të zgjidhur të njëjtën detyrë nevojiten më pak thirrje sistemike (një thirrje në vend të dyve). Por ndryshimi në performancë është shumë i vogël, prandaj ju mund ta injoroni atë dhe të përdorni në aplikacion atë që nuk do ta komplikoje logjikën e tij.

NĂ«se jeni tĂ« interesuar pĂ«r temĂ«n e ruajtjes sĂ« qĂ«ndrueshme tĂ« tĂ« dhĂ«nave — kĂ«tu janĂ« disa materiale tĂ« dobishme:

  • Metoda tĂ« qasjes I/O — njĂ« pĂ«rmbledhje e mekanizmave bazĂ« tĂ« hyrjes/daljes.
  • Sigurimi qĂ« tĂ« dhĂ«nat arrijnĂ« nĂ« disk — njĂ« tregim se çfarĂ« ndodh me tĂ« dhĂ«nat nĂ« rrugĂ«n nga aplikacioni nĂ« disk.
  • Kur duhet ta bĂ«ni fsync pĂ«r direktorinĂ« pĂ«rkatĂ«se — njĂ« pĂ«rgjigje nĂ« pyetjen se kur duhet ta aplikoni fsync() pĂ«r direktorĂ«t. NĂ«se e shpjegojmĂ« nĂ« mĂ«nyrĂ« tĂ« thjeshtĂ«, duhet ta bĂ«ni kĂ«tĂ« kur krijoni njĂ« skedar tĂ« ri, dhe arsyeja pĂ«r kĂ«tĂ« rekomandim Ă«shtĂ« se nĂ« Linux mund tĂ« ketĂ« shumĂ« lidhje mbi tĂ« njĂ«jtin skedar.
  • SQL Server nĂ« Linux: FUA Brenda — kĂ«tu pĂ«rshkruhet se si ruajtja e qĂ«ndrueshme e tĂ« dhĂ«nave zbatohet nĂ« SQL Server nĂ« platformĂ«n Linux. JanĂ« disa krahasime interesante midis thirrjeve sistemike Windows dhe Linux. Jam pothuajse i sigurt se pikĂ«risht falĂ« kĂ«tij materiali kam mĂ«suar pĂ«r optimizimin FUA tĂ« XFS.

A keni humbur ndonjëherë të dhëna që mendonit se ishin ruajtur në mënyrë të besueshme në disk?

Ruajtje e qëndrueshme të të dhënave dhe API skedarësh Linux

Ruajtje e qëndrueshme të të dhënave dhe API skedarësh Linux

Burimi: habr.com

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster