Izturīgas datu glabāŔanas un Linux failu API

Es, pētot datu uzglabāŔanas stabilitāti mākoņsistēmās, nolēmu sevi pārbaudÄ«t, lai pārliecinātos, ka saprotu pamata lietas. es sākās, izlasot NVMe spec lai saprastu, kādas garantijas attiecÄ«bā uz datu noturÄ«bu (tas ir, garantijas, ka dati bÅ«s pieejami pēc sistēmas kļūmes) dod mums NMVe diskus. Es izdarÄ«ju Ŕādus galvenos secinājumus: dati ir jāuzskata par bojātiem no datu rakstÄ«Å”anas komandas doÅ”anas brīža un lÄ«dz brÄ«dim, kad tie tiek ierakstÄ«ti datu nesējā. Tomēr lielākajā daļā programmu datu ierakstÄ«Å”anai diezgan droÅ”i izmanto sistēmas zvanus.

Å ajā rakstā es izpētÄ«Å”u Linux failu API nodroÅ”inātos noturÄ«bas mehānismus. Å Ä·iet, ka Å”eit visam jābÅ«t vienkārÅ”am: programma izsauc komandu write(), un pēc Ŕīs komandas pabeigÅ”anas dati tiks droÅ”i saglabāti diskā. Bet write() tikai kopē lietojumprogrammas datus kodola keÅ”atmiņā, kas atrodas RAM. Lai piespiestu sistēmu ierakstÄ«t datus diskā, ir jāizmanto daži papildu mehānismi.

Izturīgas datu glabāŔanas un Linux failu API

Kopumā Å”is materiāls ir piezÄ«mju kopums par to, ko esmu iemācÄ«jies par mani interesējoÅ”u tēmu. Ja mēs ļoti Ä«si runājam par svarÄ«gāko, izrādās, ka, lai organizētu ilgtspējÄ«gu datu glabāŔanu, jums ir jāizmanto komanda fdatasync() vai atveriet failus ar karogu O_DSYNC. Ja vēlaties uzzināt vairāk par to, kas notiek ar datiem ceļā no koda uz disku, apskatiet Å”is rakstu.

RakstīŔanas () funkcijas izmantoŔanas iezīmes

Sistēmas zvans write() definēts standartā IEEE POSIX kā mēģinājums ierakstÄ«t datus faila deskriptorā. Pēc veiksmÄ«gas darba pabeigÅ”anas write() datu lasÄ«Å”anas operācijām ir jāatgriež tieÅ”i tie baiti, kas tika ierakstÄ«ti iepriekÅ”, to darot pat tad, ja datiem tiek piekļūts no citiem procesiem vai pavedieniem (Å”eit atbilstoŔā POSIX standarta sadaļa). Å eit, sadaļā par to, kā pavedieni mijiedarbojas ar parastajām failu operācijām, ir piezÄ«me, kurā teikts, ka, ja divi pavedieni izsauc Ŕīs funkcijas, katram izsaukumam ir jāredz vai nu visas otra izsaukuma noteiktās sekas, vai arÄ« nekādas. sekas. Tas liek secināt, ka visām failu I/O operācijām ir jābÅ«t bloķētai resursam, ar kuru tās darbojas.

Vai tas nozÄ«mē, ka operācija write() ir atoms? No tehniskā viedokļa, jā. Datu nolasÄ«Å”anas operācijām ir jāatgriež viss vai nekas no tā, kas tika rakstÄ«ts write(). Bet operācija write(), saskaņā ar standartu, nav jābeidzas, pierakstot visu, kas viņai tika lÅ«gts pierakstÄ«t. Ir atļauts ierakstÄ«t tikai daļu datu. Piemēram, mums var bÅ«t divas straumes, kas katra pievieno 1024 baitus failam, ko apraksta tas pats faila deskriptors. No standarta viedokļa rezultāts bÅ«s pieņemams, ja katra no rakstÄ«Å”anas operācijām failam var pievienot tikai vienu baitu. Å Ä«s darbÄ«bas paliks atomāras, taču pēc to pabeigÅ”anas failā ierakstÄ«tie dati tiks sajaukti. Å”eit ir ļoti interesanta diskusija par Å”o tēmu vietnē Stack Overflow.

fsync() un fdatasync() funkcijas

VienkārŔākais veids, kā izskalot datus diskā, ir izsaukt funkciju fsync(). Å Ä« funkcija pieprasa operētājsistēmai pārsÅ«tÄ«t visus modificētos blokus no keÅ”atmiņas uz disku. Tas ietver visus faila metadatus (piekļuves laiku, faila modifikācijas laiku un tā tālāk). Es uzskatu, ka Å”ie metadati ir reti nepiecieÅ”ami, tāpēc, ja zināt, ka tie jums nav svarÄ«gi, varat izmantot funkciju fdatasync(). Uz palÄ«dzēt par fdatasync() tajā teikts, ka Ŕīs funkcijas darbÄ«bas laikā diskā tiek saglabāts tāds metadatu daudzums, kas "nepiecieÅ”ams turpmāko datu nolasÄ«Å”anas darbÄ«bu pareizai izpildei". Un tas ir tieÅ”i tas, kas rÅ«p lielākajai daļai lietojumprogrammu.

Viena problēma, kas var rasties Å”eit, ir tāda, ka Å”ie mehānismi negarantē, ka fails bÅ«s atrodams pēc iespējamās kļūmes. Jo Ä«paÅ”i, veidojot jaunu failu, jums ir jāzvana fsync() direktorijam, kurā tas ir. Pretējā gadÄ«jumā pēc avārijas var izrādÄ«ties, ka Å”is fails neeksistē. Iemesls tam ir tas, ka UNIX sistēmā cieto saiÅ”u izmantoÅ”anas dēļ fails var pastāvēt vairākos direktorijos. Tāpēc, zvanot fsync() fails nevar zināt, kura direktorija dati ir arÄ« jāizskalo diskā (Å”eit JÅ«s varat lasÄ«t vairāk par to). Å Ä·iet, ka ext4 failu sistēma to spēj automātiski piemērot fsync() uz direktorijiem, kas satur atbilstoÅ”os failus, bet tas var nenotikt citās failu sistēmās.

Å o mehānismu dažādās failu sistēmās var ieviest atŔķirÄ«gi. ES izmantoju blktrace lai uzzinātu, kādas diska darbÄ«bas tiek izmantotas ext4 un XFS failu sistēmās. Abi izdod parastās ierakstÄ«Å”anas komandas diskā gan failu saturam, gan failu sistēmas žurnālam, iztÄ«ra keÅ”atmiņu un iziet, veicot FUA (Force Unit Access, datu ierakstÄ«Å”ana tieÅ”i diskā, apejot keÅ”atmiņu) ierakstÄ«Å”anu žurnālā. Viņi, iespējams, tieÅ”i to dara, lai apstiprinātu darÄ«juma faktu. Diskos, kas neatbalsta FUA, tas izraisa divas keÅ”atmiņas izskaloÅ”anas. Mani eksperimenti to ir parādÄ«juÅ”i fdatasync() mazliet ātrāk fsync(). LietderÄ«ba blktrace norāda uz to fdatasync() parasti diskā ieraksta mazāk datu (ext4 fsync() raksta 20 KiB, un fdatasync() - 16 KiB). Es arÄ« uzzināju, ka XFS ir nedaudz ātrāks nekā ext4. Un Å”eit ar palÄ«dzÄ«bu blktrace varēja to noskaidrot fdatasync() diskā izskalo mazāk datu (4 KiB XFS).

Neskaidras situācijas, izmantojot fsync()

Es varu iedomāties trīs neskaidras situācijas saistībā ar fsync()ar ko esmu saskāries praksē.

Pirmais Ŕāds incidents notika 2008. gadā. Tajā laikā Firefox 3 saskarne ā€œiesaldējaā€, ja diskā tika ierakstÄ«ts liels skaits failu. Problēma bija tāda, ka saskarnes ievieÅ”ana izmantoja SQLite datu bāzi, lai saglabātu informāciju par tās stāvokli. Pēc katras izmaiņas, kas notika saskarnē, funkcija tika izsaukta fsync(), kas deva labas garantijas par stabilu datu glabāŔanu. Toreiz izmantotajā ext3 failu sistēmā funkcija fsync() izskalo visas sistēmas "netÄ«rās" lapas, nevis tikai tās, kas bija saistÄ«tas ar attiecÄ«go failu. Tas nozÄ«mēja, ka, noklikŔķinot uz pogas pārlÅ«kprogrammā Firefox, magnētiskajā diskā var tikt ierakstÄ«ti datu megabaiti, kas var ilgt daudzas sekundes. Problēmas risinājums, cik es sapratu tā materiālu, bija pārcelt darbu ar datubāzi uz asinhroniem fona uzdevumiem. Tas nozÄ«mē, ka Firefox agrāk ieviesa stingrākas krātuves noturÄ«bas prasÄ«bas, nekā patiesÄ«bā bija nepiecieÅ”ams, un ext3 failu sistēmas lÄ«dzekļi Å”o problēmu tikai saasināja.

Otrā problēma radās 2009. gadā. Pēc tam pēc sistēmas avārijas jaunās ext4 failu sistēmas lietotāji atklāja, ka daudziem jaunizveidotajiem failiem ir nulles garums, taču tas nenotika ar vecāku ext3 failu sistēmu. IepriekŔējā rindkopā es runāju par to, kā ext3 diskā ievietoja pārāk daudz datu, kas ievērojami palēnināja darbÄ«bu. fsync(). Lai uzlabotu situāciju, ext4 izskalo tikai tās "netÄ«rās" lapas, kas attiecas uz konkrētu failu. Un citu failu dati paliek atmiņā daudz ilgāku laiku nekā ar ext3. Tas tika darÄ«ts, lai uzlabotu veiktspēju (pēc noklusējuma dati paliek Å”ajā stāvoklÄ« 30 sekundes, to var konfigurēt, izmantojot dirty_expire_centisecs; Å”eit JÅ«s varat atrast vairāk informācijas par to). Tas nozÄ«mē, ka pēc avārijas var neatgriezeniski zaudēt lielu datu apjomu. Å Ä«s problēmas risinājums ir izmantot fsync() lietojumprogrammās, kurām jānodroÅ”ina stabila datu glabāŔana un pēc iespējas vairāk jāaizsargā tās no kļūmju sekām. Funkcija fsync() darbojas daudz efektÄ«vāk ar ext4 nekā ar ext3. Å Ä«s pieejas trÅ«kums ir tāds, ka tās izmantoÅ”ana, tāpat kā iepriekÅ”, palēnina dažas darbÄ«bas, piemēram, programmu instalÄ“Å”anu. Skatiet sÄ«kāku informāciju par to Å”eit Šø Å”eit.

TreŔā problēma saistÄ«bā ar fsync(), radās 2018. gadā. Tad PostgreSQL projekta ietvaros tika noskaidrots, ka ja funkcija fsync() rodas kļūda, tas atzÄ«mē "netÄ«rās" lapas kā "tÄ«ras". Rezultātā Ŕādi zvani fsync() nedari neko ar tādām lapām. Å Ä« iemesla dēļ modificētās lapas tiek saglabātas atmiņā un nekad netiek ierakstÄ«tas diskā. Tā ir Ä«sta katastrofa, jo aplikācija domās, ka daži dati ir ierakstÄ«ti diskā, bet patiesÄ«bā tā nebÅ«s. Tādas neveiksmes fsync() ir reti, lietojumprogramma Ŕādās situācijās gandrÄ«z neko nevar palÄ«dzēt novērst problēmu. MÅ«sdienās, kad tas notiek, PostgreSQL un citas lietojumprogrammas avarē. Å eit, rakstā "Vai lietojumprogrammas var atgÅ«ties no fsync kļūmēm?", Ŕī problēma ir detalizēti izpētÄ«ta. PaÅ”laik labākais risinājums Å”ai problēmai ir izmantot tieÅ”o I/O ar karogu O_SYNC vai ar karogu O_DSYNC. Izmantojot Å”o pieeju, sistēma ziņos par kļūdām, kas var rasties, veicot noteiktas datu rakstÄ«Å”anas darbÄ«bas, taču Ŕī pieeja prasa, lai lietojumprogramma pati pārvaldÄ«tu buferus. Lasiet par to vairāk Å”eit Šø Å”eit.

Failu atvērÅ”ana, izmantojot karogus O_SYNC un O_DSYNC

AtgriezÄ«simies pie diskusijas par Linux mehānismiem, kas nodroÅ”ina pastāvÄ«gu datu glabāŔanu. Proti, runa ir par karoga lietoÅ”anu O_SYNC vai karogs O_DSYNC atverot failus, izmantojot sistēmas zvanu atvērt (). Izmantojot Å”o pieeju, katra datu rakstÄ«Å”anas darbÄ«ba tiek veikta it kā pēc katras komandas write() sistēmai tiek dotas attiecÄ«gi komandas fsync() Šø fdatasync(). Uz POSIX specifikācijas to sauc par "Synchronized I/O File Integrity Completion" un "Data Integrity Completion". Å Ä«s pieejas galvenā priekÅ”rocÄ«ba ir tā, ka, lai nodroÅ”inātu datu integritāti, ir jāizpilda tikai viens sistēmas izsaukums, nevis divi (piemēram, - write() Šø fdatasync()). Å Ä«s pieejas galvenais trÅ«kums ir tas, ka visas rakstÄ«Å”anas darbÄ«bas, izmantojot atbilstoÅ”o faila deskriptoru, tiks sinhronizētas, kas var ierobežot iespēju strukturēt lietojumprogrammas kodu.

Izmantojot tieŔo I/O ar karogu O_DIRECT

Sistēmas zvans open() atbalsta karogu O_DIRECT, kas ir paredzēts, lai apietu operētājsistēmas keÅ”atmiņu, lai veiktu I/O darbÄ«bas, tieÅ”i mijiedarbojoties ar disku. Tas daudzos gadÄ«jumos nozÄ«mē, ka programmas izdotās rakstÄ«Å”anas komandas tiks tieÅ”i tulkotas komandās, kuru mērÄ·is ir strādāt ar disku. Bet kopumā Å”is mehānisms neaizstāj funkcijas fsync() vai fdatasync(). Fakts ir tāds, ka pats disks var kavÄ“Å”anās vai keÅ”atmiņa atbilstoÅ”as ā€‹ā€‹komandas datu rakstÄ«Å”anai. Un, vēl ļaunāk, dažos Ä«paÅ”os gadÄ«jumos I / O darbÄ«bas, kas veiktas, izmantojot karogu O_DIRECT, pārraide tradicionālajās buferizētajās operācijās. VienkārŔākais veids, kā atrisināt Å”o problēmu, ir izmantot karogu, lai atvērtu failus O_DSYNC, kas nozÄ«mēs, ka katrai rakstÄ«Å”anas darbÄ«bai sekos izsaukums fdatasync().

IzrādÄ«jās, ka XFS failu sistēma nesen bija pievienojusi "ātro ceļu". O_DIRECT|O_DSYNC- datu ieraksti. Ja bloks tiek pārrakstÄ«ts, izmantojot O_DIRECT|O_DSYNC, tad XFS, tā vietā, lai izskalotu keÅ”atmiņu, izpildÄ«s FUA rakstÄ«Å”anas komandu, ja ierÄ«ce to atbalsta. Es to pārbaudÄ«ju, izmantojot utilÄ«tu blktrace Linux 5.4/Ubuntu 20.04 sistēmā. Å ai pieejai vajadzētu bÅ«t efektÄ«vākai, jo tā ieraksta diskā minimālo datu apjomu un izmanto vienu darbÄ«bu, nevis divas (rakstÄ«Å”ana un keÅ”atmiņas iztÄ«rÄ«Å”ana). Atradu saiti uz plāksteris 2018 kodols, kas ievieÅ” Å”o mehānismu. Ir dažas diskusijas par Ŕīs optimizācijas piemēroÅ”anu citām failu sistēmām, taču, cik man zināms, XFS ir vienÄ«gā failu sistēma, kas lÄ«dz Å”im to atbalsta.

sync_file_range() funkcija

Linux ir sistēmas izsaukums sync_file_range(), kas ļauj diskā izskalot tikai daļu faila, nevis visu failu. Å is zvans uzsāk asinhrono skaloÅ”anu un negaida, lÄ«dz tas tiks pabeigts. Bet atsaucē uz sync_file_range() Ŕī komanda esot "ļoti bÄ«stama". Nav ieteicams to lietot. ÄŖpaŔības un briesmas sync_file_range() ļoti labi aprakstÄ«ts Å”is materiāls. Jo Ä«paÅ”i Ŕķiet, ka Å”is izsaukums izmanto RocksDB, lai kontrolētu, kad kodols diskā izskalo "netÄ«ros" datus. Bet tajā paŔā laikā, lai nodroÅ”inātu stabilu datu glabāŔanu, tas arÄ« tiek izmantots fdatasync(). Uz kods RocksDB ir daži interesanti komentāri par Å”o tēmu. Piemēram, tas izskatās kā zvans sync_file_range() Izmantojot ZFS, tas neizskalo datus diskā. Pieredze man rāda, ka reti izmantotajā kodā var bÅ«t kļūdas. Tāpēc es ieteiktu neizmantot Å”o sistēmas zvanu, ja vien tas nav absolÅ«ti nepiecieÅ”ams.

Sistēmas zvani, lai palÄ«dzētu nodroÅ”ināt datu noturÄ«bu

Esmu nonācis pie secinājuma, ka pastāv trÄ«s pieejas, ko var izmantot, lai veiktu pastāvÄ«gas I/O darbÄ«bas. Viņiem visiem ir nepiecieÅ”ams funkciju izsaukums fsync() direktorijai, kurā fails tika izveidots. Å Ä«s ir Ŕādas pieejas:

  1. Funkcijas izsaukums fdatasync() vai fsync() pēc funkcijas write() (labāk lietot fdatasync()).
  2. Darbs ar faila deskriptoru, kas atvērts ar karogu O_DSYNC vai O_SYNC (labāk - ar karogu O_DSYNC).
  3. Komandu lietoÅ”ana pwritev2() ar karogu RWF_DSYNC vai RWF_SYNC (vēlams ar karogu RWF_DSYNC).

Veiktspējas piezīmes

Es rÅ«pÄ«gi neizmērÄ«ju dažādu pārbaudÄ«to mehānismu veiktspēju. AtŔķirÄ«bas, ko pamanÄ«ju viņu darba ātrumā, ir ļoti mazas. Tas nozÄ«mē, ka es varu kļūdÄ«ties un ka citos apstākļos viena un tā pati lieta var uzrādÄ«t atŔķirÄ«gus rezultātus. Vispirms es runāŔu par to, kas vairāk ietekmē veiktspēju, un pēc tam par to, kas mazāk ietekmē veiktspēju.

  1. Faila datu pārrakstÄ«Å”ana ir ātrāka nekā datu pievienoÅ”ana failam (veiktspējas ieguvums var bÅ«t 2ā€“100%). Lai failam pievienotu datus, ir jāveic papildu izmaiņas faila metadatos pat pēc sistēmas izsaukuma fallocate(), taču Ŕī efekta apjoms var atŔķirties. Es iesaku, lai nodroÅ”inātu vislabāko sniegumu, piezvanÄ«t fallocate() lai iepriekÅ” pieŔķirtu nepiecieÅ”amo vietu. Tad Ŕī vieta ir skaidri jāaizpilda ar nullēm un jāizsauc fsync(). Tādējādi attiecÄ«gie bloki failu sistēmā tiks atzÄ«mēti kā "pieŔķirti", nevis "nepieŔķirti". Tas nodroÅ”ina nelielu (apmēram 2%) veiktspējas uzlabojumu. Turklāt dažiem diskiem var bÅ«t lēnāka pirmā bloka piekļuves darbÄ«ba nekā citiem. Tas nozÄ«mē, ka vietas aizpildÄ«Å”ana ar nullēm var novest pie ievērojama (apmēram 100%) veiktspējas uzlabojuma. Jo Ä«paÅ”i tas var notikt ar diskiem. AWS EBS (Å”ie ir neoficiāli dati, es nevarēju tos apstiprināt). Tas pats attiecas uz uzglabāŔanu. GCP pastāvÄ«gais disks (un tā jau ir oficiāla informācija, ko apstiprina testi). To ir darÄ«juÅ”i arÄ« citi eksperti novērojumssaistÄ«ti ar dažādiem diskiem.
  2. Jo mazāk sistēmas zvanu, jo augstāka veiktspēja (pieaugums var bÅ«t aptuveni 5%). Tas izskatās pēc zvana open() ar karogu O_DSYNC vai zvaniet pwritev2() ar karogu RWF_SYNC ātrāk nekā zvans fdatasync(). Man ir aizdomas, ka Å”eit ir runa par to, ka, izmantojot Å”o pieeju, nozÄ«me ir tam, ka ir jāveic mazāk sistēmas izsaukumu, lai atrisinātu vienu un to paÅ”u uzdevumu (viens zvans divu vietā). Bet veiktspējas atŔķirÄ«ba ir ļoti maza, tāpēc varat to viegli ignorēt un lietotnē izmantot kaut ko tādu, kas nesarežģī tās loÄ£iku.

Ja jÅ«s interesē ilgtspējÄ«gas datu uzglabāŔanas tēma, Å”eit ir daži noderÄ«gi materiāli:

Vai esat kādreiz pazaudējis datus, kas, jÅ«suprāt, ir droÅ”i glabāti diskā?

Izturīgas datu glabāŔanas un Linux failu API

Izturīgas datu glabāŔanas un Linux failu API

Avots: www.habr.com