Тұрақты деректерді сақтау және Linux файл API интерфейстері

Бұлтты жүйелерде деректерді сақтаудың тұрақтылығын зерттеу барысында мен негізгі нәрселерді түсінгеніме көз жеткізу үшін өзімді сынап көруді шештім. I NVMe спецификациясын оқудан басталды тұрақты деректерді сақтауға қатысты қандай кепілдіктерді түсіну үшін (яғни, жүйе ақаулығынан кейін деректер қолжетімді болатынына кепілдік береді) бізге NMVe дискілерін береді. Мен келесі негізгі қорытындыларды жасадым: мәліметтерді жазу командасы берілген сәттен бастап оны сақтаушыға жазылғанға дейін бүлінген деп есептелуі керек. Дегенмен, көптеген бағдарламалар деректерді жазу үшін жүйелік қоңырауларды пайдаланады.

Бұл жазбада мен Linux файл API интерфейстері қамтамасыз ететін тұрақты сақтау механизмдерін зерттеймін. Мұнда бәрі қарапайым болуы керек сияқты: бағдарлама команданы шақырады write(), және осы пәрмен аяқталғаннан кейін деректер дискіге қауіпсіз сақталады. Бірақ write() қолданба деректерін тек жедел жадта орналасқан ядролық кэшке көшіреді. Жүйені дискіге деректерді жазуға мәжбүрлеу үшін кейбір қосымша механизмдерді пайдалану қажет.

Тұрақты деректерді сақтау және Linux файл API интерфейстері

Тұтастай алғанда, бұл материал мені қызықтыратын тақырып бойынша білгендеріме қатысты жазбалар жинағы. Ең бастысы туралы қысқаша айтатын болсақ, тұрақты деректерді сақтауды ұйымдастыру үшін пәрменді пайдалану керек. fdatasync() немесе жалаушасы бар файлдарды ашыңыз O_DSYNC. Егер кодтан дискіге өту жолында деректермен не болатыны туралы көбірек білгіңіз келсе, мынаны қараңыз бұл мақала.

write() функциясын қолдану ерекшеліктері

Жүйелік қоңырау write() стандартта анықталған IEEE POSIX файл дескрипторына деректерді жазу әрекеті ретінде. Сәтті аяқталғаннан кейін write() Деректерді оқу әрекеттері деректерге басқа процестерден немесе ағындардан қол жеткізілсе де, дәл бұрын жазылған байттарды қайтаруы керек (қараңыз POSIX стандартының тиісті бөлімі). Бұл, ағындардың қалыпты файл әрекеттерімен өзара әрекеттесуі туралы бөлімде, егер екі ағынның әрқайсысы осы функцияларды шақырса, онда әрбір шақыру басқа шақырудың барлық тағайындалған салдарын немесе ешқайсысын көруі керек екендігі туралы ескертпе бар. салдары. Бұл барлық файлдық енгізу/шығару операциялары олар жұмыс істейтін ресурста құлыпты ұстауы керек деген қорытындыға әкеледі.

Бұл операция дегенді білдіреді write() ол атомдық па? Техникалық тұрғыдан алғанда, иә. Деректерді оқу әрекеттері жазылғанның барлығын немесе ештеңені қайтаруы керек write(). Бірақ операция write(), стандартқа сәйкес, жазу сұралғанның бәрін жазумен аяқталуы міндетті емес. Оған деректердің бір бөлігін ғана жазуға рұқсат етіледі. Мысалы, бізде бірдей файл дескрипторымен сипатталған файлға әрқайсысы 1024 байт қосатын екі ағын болуы мүмкін. Стандарт тұрғысынан әрбір жазу операциясы файлға тек бір байтты қоса алатын кезде қолайлы нәтиже болады. Бұл операциялар атомдық болып қалады, бірақ олар аяқталғаннан кейін олар файлға жазған деректер араласады. осында Stack Overflow бойынша осы тақырып бойынша өте қызықты талқылау.

fsync() және fdatasync() функциялары

Деректерді дискіге тазалаудың ең оңай жолы - функцияны шақыру fsync(). Бұл функция операциялық жүйеден барлық өзгертілген блоктарды кэштен дискіге тасымалдауды сұрайды. Бұған барлық файл метадеректері (қол жеткізу уақыты, файлды өзгерту уақыты және т.б.) кіреді. Менің ойымша, бұл метадеректер сирек қажет, сондықтан оның сіз үшін маңызды емес екенін білсеңіз, функцияны пайдалана аласыз. fdatasync(). The Көмектесіңдер туралы fdatasync() Бұл функцияның жұмыс істеуі кезінде дискіде «мәліметтерді оқудың келесі әрекеттерін дұрыс орындау үшін қажет» метадеректердің осындай көлемі сақталады деп айтылады. Бұл көптеген қолданбаларды қызықтырады.

Бұл жерде туындауы мүмкін бір мәселе, бұл механизмдер файлдың мүмкін болатын сәтсіздіктен кейін анықталатынына кепілдік бермейді. Атап айтқанда, жаңа файлды жасау кезінде сізге қоңырау шалу керек fsync() оны қамтитын каталог үшін. Әйтпесе, сәтсіздіктен кейін бұл файл жоқ болып шығуы мүмкін. Мұның себебі UNIX-те қатты сілтемелерді пайдаланудың арқасында файл бірнеше каталогтарда болуы мүмкін. Сондықтан, қоңырау шалғанда fsync() файлдың дискіге қандай каталог деректерін тазарту керектігін білудің ешқандай жолы жоқ (осында Бұл туралы толығырақ оқи аласыз). ext4 файлдық жүйесі қабілетті сияқты автоматты түрде қолданыңыз fsync() сәйкес файлдары бар каталогтарға, бірақ бұл басқа файлдық жүйелерде болмауы мүмкін.

Бұл механизм әртүрлі файлдық жүйелерде басқаша орындалуы мүмкін. Мен пайдаландым blktrace ext4 және XFS файлдық жүйелерінде қандай диск операциялары қолданылатыны туралы білу. Екеуі де файл мазмұны үшін де, файлдық жүйе журналы үшін де дискіге тұрақты жазу пәрмендерін шығарады, кэшті тазартады және журналға жазуды FUA (бірлікті мәжбүрлеу, дискіге деректерді тікелей жазу, кэшті айналып өту) орындау арқылы шығарады. Олар мұны транзакцияның орын алғанын растау үшін жасайтын шығар. FUA қолдамайтын дискілерде бұл кэшті екі рет тазалауды тудырады. Менің эксперименттерім мұны көрсетті fdatasync() сәл жылдамырақ fsync(). Утилита blktrace екенін көрсетеді fdatasync() әдетте дискіге аз деректерді жазады (ext4 fsync() 20 КБ жазады және fdatasync() - 16 КБ). Сондай-ақ, мен XFS ext4-тен сәл жылдамырақ екенін білдім. Ал мұнда көмегімен blktrace соны анықтай алды fdatasync() дискіге деректерді азырақ түсіреді (XFS жүйесінде 4 КБ).

fsync() пайдалану кезінде туындайтын түсініксіз жағдайлар

Мен үш түсініксіз жағдайды ойлай аламын fsync()мен оны тәжірибеде кездестірдім.

Мұндай алғашқы жағдай 2008 жылы болған. Содан кейін файлдардың көп саны дискіге жазылған болса, Firefox 3 интерфейсі қатып қалады. Мәселе интерфейсті іске асыруда оның күйі туралы ақпаратты сақтау үшін SQLite дерекқорын пайдалануында болды. Интерфейстегі әрбір өзгерістен кейін функция шақырылды fsync(), бұл тұрақты деректерді сақтаудың жақсы кепілдіктерін берді. Бұл функция ext3 файлдық жүйесінде қолданылған fsync() Жүйедегі барлық «лас» беттерді тек тиісті файлға қатысты емес, дискіге тастады. Бұл Firefox-та түймені басу магниттік дискіге жазылатын мегабайттық деректерді іске қосуы мүмкін екенін білдіреді, бұл бірнеше секундқа созылуы мүмкін. Менің түсінуімше мәселенің шешімі ол материал дерекқормен жұмысты асинхронды фондық тапсырмаларға көшіру болды. Бұл Firefox бұрын шынымен қажет болғаннан гөрі қатаң сақтау талаптарын енгізгенін білдіреді және ext3 файлдық жүйесінің мүмкіндіктері бұл мәселені тек ушықтырды.

Екінші мәселе 2009 жылы орын алды. Содан кейін, жүйенің бұзылуынан кейін, жаңа ext4 файлдық жүйесінің пайдаланушылары көптеген жаңадан жасалған файлдардың ұзындығы нөлге тең болатын фактіге тап болды, бірақ бұл ескі ext3 файлдық жүйесінде болған жоқ. Алдыңғы абзацта мен ext3 дискіге тым көп деректерді қалай тазартқаны туралы айттым, бұл жағдайды айтарлықтай баяулатады. fsync(). Жағдайды жақсарту үшін ext4 бағдарламасында тек белгілі бір файлға қатысты лас беттер дискіге тазартылады. Ал басқа файлдардағы деректер жадта ext3 файлына қарағанда әлдеқайда ұзақ уақыт сақталады. Бұл өнімділікті жақсарту үшін жасалды (әдепкі бойынша деректер 30 секунд бойы осы күйде қалады, оны пайдаланып конфигурациялауға болады. dirty_expire_centisecs; осында Бұл туралы қосымша материалдарды таба аласыз). Бұл қателіктен кейін деректердің үлкен көлемі қайтарылмайтын түрде жоғалуы мүмкін дегенді білдіреді. Бұл мәселені шешу - пайдалану fsync() деректердің тұрақты сақталуын қамтамасыз етуді және оларды сәтсіздіктердің салдарынан мүмкіндігінше қорғауды қажет ететін қолданбаларда. Функция fsync() ext4 пайдаланғанда ext3 пайдаланғанға қарағанда әлдеқайда тиімді жұмыс істейді. Бұл тәсілдің кемшілігі, оны пайдалану бұрынғыдай кейбір операциялардың орындалуын, мысалы, бағдарламаларды орнатуды баяулатады. Бұл туралы мәліметтерді қараңыз осында и осында.

қатысты үшінші мәселе fsync(), 2018 жылы шыққан. Содан кейін, PostgreSQL жобасының аясында, егер функция болатыны анықталды fsync() қатеге тап болса, ол «лас» беттерді «таза» деп белгілейді. Нәтижесінде келесі қоңыраулар fsync() Олар мұндай беттермен ештеңе жасамайды. Осыған байланысты өзгертілген беттер жадта сақталады және ешқашан дискіге жазылмайды. Бұл нағыз апат, өйткені қолданба кейбір деректер дискіге жазылған деп ойлайды, бірақ іс жүзінде олай болмайды. Мұндай сәтсіздіктер fsync() сирек кездеседі, мұндай жағдайларда қолдану проблемамен күресу үшін ештеңе істей алмайды. Бұл күндері PostgreSQL және басқа қолданбалар бұзылады. Бұл, «Қолданбалар fsync қателерінен қалпына келтіре ала ма?» материалында бұл мәселе егжей-тегжейлі қарастырылған. Қазіргі уақытта бұл мәселенің ең жақсы шешімі жалаушамен тікелей енгізу/шығару қызметін пайдалану болып табылады O_SYNC немесе тумен O_DSYNC. Бұл тәсілдің көмегімен жүйе арнайы жазу әрекеттері кезінде орын алуы мүмкін қателер туралы хабарлайды, бірақ бұл тәсіл қолданбаның буферлердің өзін басқаруын талап етеді. Бұл туралы толығырақ оқыңыз осында и осында.

O_SYNC және O_DSYNC жалаушалары арқылы файлдарды ашу

Тұрақты деректерді сақтауды қамтамасыз ететін Linux механизмдерін талқылауға оралайық. Атап айтқанда, біз туды пайдалану туралы айтып отырмыз O_SYNC немесе жалауша O_DSYNC жүйелік қоңырау арқылы файлдарды ашқанда ашық(). Бұл тәсілмен әрбір деректерді жазу операциясы әрбір командадан кейін орындалғандай орындалады write() жүйеге сәйкес командалар беріледі fsync() и fdatasync(). The POSIX спецификациялары бұл «Синхрондалған енгізу-шығару файлының тұтастығын аяқтау» және «Деректердің тұтастығын аяқтау» деп аталады. Бұл тәсілдің басты артықшылығы деректердің тұтастығын қамтамасыз ету үшін екі емес, бір ғана жүйелік қоңырау шалу қажет (мысалы, - write() и fdatasync()). Бұл тәсілдің негізгі кемшілігі сәйкес файл дескрипторын қолданатын барлық жазулар синхрондалады, бұл қолданбалы кодты құрылымдау мүмкіндігін шектеуі мүмкін.

O_DIRECT жалауымен тікелей енгізу/шығаруды пайдалану

Жүйелік қоңырау open() жалаушаны қолдайды O_DIRECT, ол дискімен тікелей әрекеттесу арқылы енгізу/шығару операцияларын орындау үшін операциялық жүйе кэшін айналып өтуге арналған. Бұл, көп жағдайда, бағдарлама шығарған жазу командалары дискімен жұмыс істеуге бағытталған командаларға тікелей аударылатынын білдіреді. Бірақ, тұтастай алғанда, бұл механизм функцияларды алмастырмайды fsync() немесе fdatasync(). Өйткені, дискінің өзі мүмкін кейінге қалдыру немесе кэш сәйкес деректерді жазу командалары. Сонымен қатар, кейбір ерекше жағдайларда жалаушаны пайдалану кезінде енгізу/шығару операциялары орындалады O_DIRECT, тарату дәстүрлі буферлі операцияларға. Бұл мәселені шешудің ең оңай жолы - файлдарды ашу үшін жалаушаны пайдалану O_DSYNC, бұл әрбір жазу әрекетінен кейін шақыру болатынын білдіреді fdatasync().

XFS файлдық жүйесі жақында «жылдам жолды» қосқаны белгілі болды O_DIRECT|O_DSYNC- мәліметтерді жазу. Егер блок қайта жазылса O_DIRECT|O_DSYNC, содан кейін XFS кэшті тазалаудың орнына, егер құрылғы оны қолдайтын болса, FUA жазу пәрменін орындайды. Мен мұны қызметтік бағдарлама арқылы тексердім blktrace Linux 5.4/Ubuntu 20.04 жүйесінде. Бұл тәсіл тиімдірек болуы керек, өйткені пайдаланған кезде дискіге деректердің ең аз мөлшері жазылады және екі емес (кэшті жазу және тазалау) емес, бір операция қолданылады. сілтемесін таптым патч Бұл механизмді жүзеге асыратын 2018 ядросы. Бұл оңтайландыруды басқа файлдық жүйелерге қолдану туралы кейбір талқылаулар бар, бірақ менің білуімше, XFS осы уақытқа дейін қолдайтын жалғыз файлдық жүйе.

sync_file_range() функциясы

Linux жүйесінде жүйелік қоңырау бар sync_file_range(), ол бүкіл файлды емес, файлдың бір бөлігін ғана дискіге шығаруға мүмкіндік береді. Бұл шақыру асинхронды деректерді тазартуды бастайды және оның аяқталуын күтпейді. Бірақ сертификатта sync_file_range() команда «өте қауіпті» деп айтылады. Оны пайдалану ұсынылмайды. Ерекшеліктер мен қауіптер sync_file_range() өте жақсы сипатталған бұл материал. Атап айтқанда, бұл қоңырау ядроның лас деректерді дискіге тазартуын басқару үшін RocksDB қолданбасын пайдаланатын сияқты. Бірақ сонымен бірге деректерді тұрақты сақтауды қамтамасыз ету үшін ол да қолданылады fdatasync(). The код RocksDB-де осы тақырып бойынша қызықты пікірлер бар. Мысалы, бұл қоңырау sync_file_range() ZFS пайдаланған кезде ол деректерді дискіге жібермейді. Тәжірибе маған сирек қолданылатын кодта қателер болуы мүмкін екенін айтады. Сондықтан, өте қажет болмаса, бұл жүйелік қоңырауды қолданбауға кеңес берер едім.

Деректердің тұрақтылығын қамтамасыз етуге көмектесетін жүйелік қоңыраулар

Мен деректердің тұрақтылығын қамтамасыз ететін енгізу/шығару операцияларын орындау үшін пайдалануға болатын үш тәсіл бар деген қорытындыға келдім. Олардың барлығы функцияны шақыруды қажет етеді fsync() файл жасалған каталог үшін. Бұл тәсілдер:

  1. Функция шақыру fdatasync() немесе fsync() функциядан кейін write() (қолданған дұрыс fdatasync()).
  2. Жалаушамен ашылған файл дескрипторымен жұмыс O_DSYNC немесе O_SYNC (жақсы - жалаумен O_DSYNC).
  3. Пәрменді пайдалану pwritev2() тумен RWF_DSYNC немесе RWF_SYNC (жалауы бар жақсырақ RWF_DSYNC).

Өнімділік жазбалары

Мен зерттеген әртүрлі механизмдердің өнімділігін мұқият өлшеген жоқпын. Олардың жұмыс жылдамдығынан байқаған айырмашылықтарым өте аз. Бұл менің қателесуім мүмкін екенін білдіреді және әртүрлі жағдайларда бірдей нәрсе әртүрлі нәтижелерге әкелуі мүмкін. Біріншіден, өнімділікке не әсер ететіні туралы сөйлесемін, содан кейін өнімділікке не әсер етеді.

  1. Файл деректерін қайта жазу файлға деректерді қосудан жылдамырақ (өнімділік артықшылығы 2-100% болуы мүмкін). Файлға деректерді қосу тіпті жүйелік қоңыраудан кейін де файлдың метадеректеріне қосымша өзгертулерді қажет етеді fallocate(), бірақ бұл әсердің шамасы әртүрлі болуы мүмкін. Ең жақсы өнімділік үшін қоңырау шалуды ұсынамын fallocate() қажетті орынды алдын ала бөлу үшін. Содан кейін бұл кеңістік нөлдермен анық толтырылып, шақырылуы керек fsync(). Бұл файлдық жүйедегі сәйкес блоктардың «бөлінбеген» емес, «бөлінген» деп белгіленуін қамтамасыз етеді. Бұл аз ғана (шамамен 2%) өнімділікті жақсартуға мүмкіндік береді. Сонымен қатар, кейбір дискілердің блокқа бірінші қол жеткізуі басқаларға қарағанда баяу болуы мүмкін. Бұл кеңістікті нөлдермен толтыру өнімділіктің айтарлықтай (шамамен 100%) жақсаруына әкелуі мүмкін дегенді білдіреді. Атап айтқанда, бұл дискілерде болуы мүмкін AWS EBS (бұл бейресми деректер, оны растай алмадым). Сақтауға да қатысты GCP тұрақты дискі (және бұл сынақтармен расталған ресми ақпарат). Басқа сарапшылар да солай жасады бақылау, әртүрлі дискілерге қатысты.
  2. Жүйелік қоңыраулар неғұрлым аз болса, өнімділік соғұрлым жоғары болады (пайда шамамен 5% болуы мүмкін). Сынақ сияқты open() тумен O_DSYNC немесе қоңырау шалыңыз pwritev2() тумен RWF_SYNC қоңырауға қарағанда жылдамырақ fdatasync(). Менің ойымша, бұл әдіс бір мәселені шешу үшін (екі емес, бір қоңырау) азырақ жүйелік қоңырауларды орындау керек екендігі маңызды рөл атқарады. Бірақ өнімділіктегі айырмашылық өте аз, сондықтан сіз оны толығымен елемеуге және қолданбада оның логикасын қиындатпайтын нәрсені пайдалануға болады.

Егер сіз тұрақты деректерді сақтау тақырыбына қызығушылық танытсаңыз, мұнда бірнеше пайдалы материалдар бар:

  • Енгізу/шығару қатынасының әдістері — енгізу/шығару механизмдерінің негіздеріне шолу.
  • Деректер дискіге жетуін қамтамасыз ету — қолданбадан дискіге дейінгі жолда деректермен не болатыны туралы әңгіме.
  • Құрамындағы каталогты қашан синхрондау керек - қашан қолдану керек деген сұраққа жауап fsync() каталогтар үшін. Қысқаша айтқанда, мұны жаңа файлды жасау кезінде жасау керек екендігі белгілі болды және бұл ұсыныстың себебі Linux-та бір файлға көптеген сілтемелер болуы мүмкін.
  • Linux жүйесіндегі SQL сервері: FUA ішкі — мұнда Linux платформасында SQL серверінде тұрақты деректерді сақтаудың қалай жүзеге асырылатынын сипатталған. Мұнда Windows және Linux жүйелік қоңыраулары арасында қызықты салыстырулар бар. Мен XFS FUA оңтайландыруы туралы осы материалдың арқасында білгеніме сенімдімін.

Дискіде қауіпсіз сақталған деп ойлаған деректерді жоғалтып алдыңыз ба?

Тұрақты деректерді сақтау және Linux файл API интерфейстері

Тұрақты деректерді сақтау және Linux файл API интерфейстері

Ақпарат көзі: www.habr.com