Bardoshli ma'lumotlarni saqlash va Linux fayl API'lari

Bulutli tizimlarda ma'lumotlarni saqlash barqarorligini o'rganar ekanman, men asosiy narsalarni tushunganimga ishonch hosil qilish uchun o'zimni sinab ko'rishga qaror qildim. I NVMe spetsifikatsiyasini o'qish bilan boshlandi barqaror ma'lumotlarni saqlash bilan bog'liq qanday kafolatlar (ya'ni, tizim ishlamay qolgandan keyin ma'lumotlar mavjud bo'lishini kafolatlash) bizga NMVe disklarini berishini tushunish uchun. Men quyidagi asosiy xulosalar qildim: ma'lumotlarni yozish buyrug'i berilgan paytdan boshlab uni saqlash vositasiga yozilgunga qadar ma'lumotlar buzilgan deb hisoblanishi kerak. Biroq, ko'pgina dasturlar ma'lumotlarni yozib olish uchun tizim qo'ng'iroqlaridan mamnuniyat bilan foydalanadilar.

Ushbu postda men Linux fayl API-lari tomonidan taqdim etilgan doimiy saqlash mexanizmlarini o'rganaman. Bu erda hamma narsa oddiy bo'lishi kerakdek tuyuladi: dastur buyruqni chaqiradi write(), va bu buyruq bajarilgandan so'ng, ma'lumotlar diskda xavfsiz tarzda saqlanadi. Lekin write() faqat dastur ma'lumotlarini RAMda joylashgan yadro keshiga ko'chiradi. Tizimni diskka ma'lumotlarni yozishga majbur qilish uchun siz ba'zi qo'shimcha mexanizmlardan foydalanishingiz kerak.

Bardoshli ma'lumotlarni saqlash va Linux fayl API'lari

Umuman olganda, ushbu material meni qiziqtirgan mavzuda o'rgangan narsalarimga oid eslatmalar to'plamidir. Agar biz eng muhimi haqida qisqacha gapiradigan bo'lsak, barqaror ma'lumotlarni saqlashni tashkil qilish uchun buyruqni ishlatish kerak bo'ladi. fdatasync() yoki bayroq bilan fayllarni oching O_DSYNC. Agar siz koddan diskka o'tayotganda ma'lumotlar bilan nima sodir bo'lishi haqida ko'proq bilmoqchi bo'lsangiz, ko'rib chiqing bu maqola.

write() funksiyasidan foydalanish xususiyatlari

Tizim chaqiruvi write() standartda belgilangan IEEE POSIX fayl deskriptoriga ma'lumotlarni yozishga urinish sifatida. Muvaffaqiyatli tugagandan so'ng write() Ma'lumotlarni o'qish operatsiyalari ilgari yozilgan baytlarni qaytarishi kerak, hatto ma'lumotlarga boshqa jarayonlar yoki oqimlardan kirish bo'lsa ham (ko'raylik POSIX standartining tegishli bo'limi). u, mavzularning oddiy fayl operatsiyalari bilan oʻzaro taʼsiri boʻlimida eslatma mavjud boʻlib, unda aytilishicha, agar ikkita ipning har biri ushbu funksiyalarni chaqirsa, u holda har bir qoʻngʻiroq boshqa qoʻngʻiroqning barcha belgilangan oqibatlarini koʻrishi yoki umuman koʻrmasligi kerak. oqibatlari. Bu barcha fayl kiritish-chiqarish operatsiyalari ular ishlayotgan resursda qulfni ushlab turishi kerak degan xulosaga olib keladi.

Bu operatsiya degani write() atommi? Texnik nuqtai nazardan, ha. Ma'lumotlarni o'qish operatsiyalari yozilganlarning hammasini yoki hech narsasini qaytarishi kerak write(). Ammo operatsiya write(), standartga ko'ra, yozilishi so'ralgan hamma narsani yozib olish bilan yakunlanishi shart emas. Unga ma'lumotlarning faqat bir qismini yozishga ruxsat beriladi. Misol uchun, bizda bitta fayl identifikatori tomonidan tasvirlangan faylga har biri 1024 bayt qo'shadigan ikkita mavzu bo'lishi mumkin. Standart nuqtai nazardan, har bir yozish operatsiyasi faylga faqat bitta bayt qo'shishi mumkin bo'lgan natija maqbul bo'ladi. Bu operatsiyalar atomik bo'lib qoladi, lekin ular tugallangandan so'ng ular faylga yozgan ma'lumotlar aralashib ketadi. shu yerda Stack Overflow-da ushbu mavzu bo'yicha juda qiziqarli muhokama.

fsync() va fdatasync() funksiyalari

Diskka ma'lumotlarni o'chirishning eng oson yo'li funksiyani chaqirishdir fsync(). Ushbu funktsiya operatsion tizimdan barcha o'zgartirilgan bloklarni keshdan diskka o'tkazishni so'raydi. Bunga barcha fayl metama'lumotlari (kirish vaqti, faylni o'zgartirish vaqti va boshqalar) kiradi. Menimcha, bu metadata kamdan-kam hollarda kerak, shuning uchun agar bu siz uchun muhim emasligini bilsangiz, funksiyadan foydalanishingiz mumkin. fdatasync(). The Yordam haqida fdatasync() Aytishlaricha, ushbu funktsiyaning ishlashi davomida "quyidagi ma'lumotlarni o'qish operatsiyalarini to'g'ri bajarish uchun zarur bo'lgan" metama'lumotlarning shunday miqdori diskda saqlanadi. Va bu ko'pchilik ilovalar haqida qayg'uradi.

Bu erda paydo bo'lishi mumkin bo'lgan muammolardan biri shundaki, bu mexanizmlar faylni mumkin bo'lgan nosozlikdan keyin topishga kafolat bermaydi. Xususan, yangi fayl yaratishda siz qo'ng'iroq qilishingiz kerak fsync() uni o'z ichiga olgan katalog uchun. Aks holda, muvaffaqiyatsizlikdan so'ng, bu fayl mavjud emasligi aniqlanishi mumkin. Buning sababi shundaki, UNIX da qattiq havolalardan foydalanish tufayli fayl bir nechta kataloglarda mavjud bo'lishi mumkin. Shuning uchun, qo'ng'iroq qilganda fsync() Fayl qaysi katalog ma'lumotlarini diskka o'tkazish kerakligini bilishning hech qanday usuli yo'q (shu yerda Bu haqda ko'proq o'qishingiz mumkin). ext4 fayl tizimi bunga qodir ekan avtomatik ravishda foydalanish fsync() tegishli fayllarni o'z ichiga olgan kataloglarga, lekin boshqa fayl tizimlarida bunday bo'lmasligi mumkin.

Ushbu mexanizm turli fayl tizimlarida turlicha amalga oshirilishi mumkin. foydalandim blktrace ext4 va XFS fayl tizimlarida qanday disk operatsiyalari ishlatilishi haqida ma'lumot olish uchun. Ikkalasi ham fayl mazmuni, ham fayl tizimi jurnali uchun diskka muntazam yozish buyruqlarini chiqaradi, keshni tozalaydi va jurnalga yozishni FUA (Force Unit Access, to'g'ridan-to'g'ri diskka ma'lumotlarni yozish, keshni chetlab o'tish) amalga oshirish orqali chiqadi. Ular, ehtimol, tranzaksiya amalga oshirilganligini tasdiqlash uchun buni qilishadi. FUA-ni qo'llab-quvvatlamaydigan drayvlarda bu ikkita keshni tozalashga olib keladi. Mening tajribalarim shuni ko'rsatdi fdatasync() biroz tezroq fsync(). Qulaylik blktrace shuni bildiradi fdatasync() Odatda diskka kamroq ma'lumot yozadi (ext4 fsync() 20 KiB yozadi va fdatasync() - 16 Kb). Bundan tashqari, men XFS ext4 dan biroz tezroq ekanligini bilib oldim. Va bu erda yordam bilan blktrace buni aniqlashga muvaffaq bo'ldi fdatasync() diskka kamroq ma'lumotlarni o'chiradi (XFSda 4 KiB).

fsync() dan foydalanishda yuzaga keladigan noaniq vaziyatlar

Men uchta noaniq vaziyat haqida o'ylashim mumkin fsync()Men amalda duch kelganman.

Birinchi bunday holat 2008 yilda sodir bo'lgan. Keyin Firefox 3 interfeysi diskka ko'p sonli fayllar yozilgan bo'lsa, muzlatib qo'ydi. Muammo shundaki, interfeysni amalga oshirishda uning holati haqidagi ma'lumotlarni saqlash uchun SQLite ma'lumotlar bazasidan foydalanilgan. Interfeysda sodir bo'lgan har bir o'zgarishdan so'ng, funktsiya chaqirildi fsync(), bu barqaror ma'lumotlarni saqlashning yaxshi kafolatlarini berdi. Keyinchalik ishlatiladigan ext3 fayl tizimida funksiya fsync() tizimdagi barcha "iflos" sahifalarni diskka tashladi, faqat tegishli fayl bilan bog'liq bo'lgan sahifalarni emas. Bu shuni anglatadiki, Firefox-da tugmani bosish magnit diskga yoziladigan megabayt ma'lumotlarni ishga tushirishi mumkin, bu ko'p soniyalarni oladi. Muammoning yechimi, men tushunganimdek u material ma'lumotlar bazasi bilan ishlashni asinxron fon vazifalariga o'tkazish edi. Bu shuni anglatadiki, Firefox ilgari zarur bo'lgandan ko'ra qattiqroq saqlash talablarini amalga oshirgan va ext3 fayl tizimining xususiyatlari bu muammoni yanada kuchaytirgan.

Ikkinchi muammo 2009 yilda yuz berdi. Keyin, tizimning ishdan chiqishidan so'ng, yangi ext4 fayl tizimining foydalanuvchilari ko'plab yangi yaratilgan fayllar nol uzunlikka ega ekanligi bilan duch kelishdi, ammo bu eski ext3 fayl tizimida sodir bo'lmadi. Oldingi xatboshida men ext3 diskka juda ko'p ma'lumotni qanday yuvganligi haqida gapirgan edim, bu esa ishlarni juda sekinlashtirdi. fsync(). Vaziyatni yaxshilash uchun ext4-da faqat ma'lum bir faylga tegishli bo'lgan iflos sahifalar diskka tozalanadi. Va boshqa fayllardan olingan ma'lumotlar ext3-ga qaraganda ancha uzoq vaqt xotirada qoladi. Bu samaradorlikni oshirish uchun qilingan (sukut bo'yicha, ma'lumotlar 30 soniya davomida shu holatda qoladi, siz buni yordamida sozlashingiz mumkin. dirty_expire_centisecs; shu yerda Bu haqda qo'shimcha materiallarni topishingiz mumkin). Bu shuni anglatadiki, katta hajmdagi ma'lumotlar muvaffaqiyatsizlikdan keyin qaytarib bo'lmaydigan darajada yo'qolishi mumkin. Ushbu muammoning echimi foydalanishdir fsync() ma'lumotlarning barqaror saqlanishini ta'minlash va ularni nosozliklar oqibatlaridan iloji boricha himoya qilish kerak bo'lgan ilovalarda. Funktsiya fsync() ext4 dan foydalanganda ext3 dan foydalanishga qaraganda ancha samarali ishlaydi. Ushbu yondashuvning kamchiligi shundaki, uni qo'llash, avvalgidek, dasturlarni o'rnatish kabi ba'zi operatsiyalarning bajarilishini sekinlashtiradi. Bu haqda tafsilotlarni ko'ring shu yerda и shu yerda.

Tegishli uchinchi muammo fsync(), 2018 yilda paydo bo'lgan. Keyin, PostgreSQL loyihasi doirasida, agar funktsiya aniqlandi fsync() xatoga duch kelsa, u "iflos" sahifalarni "toza" deb belgilaydi. Natijada, quyidagi qo'ng'iroqlar fsync() Ular bunday sahifalar bilan hech narsa qilmaydi. Shu sababli, o'zgartirilgan sahifalar xotirada saqlanadi va hech qachon diskka yozilmaydi. Bu haqiqiy falokat, chunki dastur ba'zi ma'lumotlar diskka yozilgan deb o'ylaydi, lekin aslida bunday bo'lmaydi. Bunday muvaffaqiyatsizliklar fsync() kamdan-kam uchraydi, bunday vaziyatlarda dastur muammoga qarshi kurashish uchun deyarli hech narsa qila olmaydi. Shu kunlarda, bu sodir bo'lganda, PostgreSQL va boshqa ilovalar ishdan chiqadi. u, "Ilovalar fsync xatoliklaridan tiklana oladimi?" Materialida bu muammo batafsil o'rganilgan. Hozirgi vaqtda ushbu muammoning eng yaxshi yechimi bayroq bilan to'g'ridan-to'g'ri kiritish-chiqarishdan foydalanishdir O_SYNC yoki bayroq bilan O_DSYNC. Ushbu yondashuv yordamida tizim maxsus yozish operatsiyalari paytida yuzaga kelishi mumkin bo'lgan xatolar haqida xabar beradi, ammo bu yondashuv dasturdan buferlarni o'zi boshqarishini talab qiladi. Bu haqda ko'proq o'qing shu yerda и shu yerda.

O_SYNC va O_DSYNC bayroqlari yordamida fayllarni ochish

Keling, barqaror ma'lumotlarni saqlashni ta'minlaydigan Linux mexanizmlari muhokamasiga qaytaylik. Ya'ni, biz bayroqdan foydalanish haqida gapiramiz O_SYNC yoki bayroq O_DSYNC tizim chaqiruvi yordamida fayllarni ochishda ochiq(). Ushbu yondashuv bilan har bir ma'lumot yozish operatsiyasi har bir buyruqdan keyin bajariladi write() tizimga mos ravishda buyruqlar beriladi fsync() и fdatasync(). The POSIX spetsifikatsiyalari bu "Sinxronlashtirilgan kiritish-chiqarish fayli yaxlitligini yakunlash" va "Ma'lumotlar yaxlitligini yakunlash" deb ataladi. Ushbu yondashuvning asosiy afzalligi shundaki, ma'lumotlar yaxlitligini ta'minlash uchun siz ikkita emas, balki faqat bitta tizim qo'ng'irog'ini qilishingiz kerak (masalan, - write() и fdatasync()). Ushbu yondashuvning asosiy kamchiligi shundaki, tegishli fayl deskriptoridan foydalangan holda barcha yozishlar sinxronlashtiriladi, bu esa dastur kodini tuzish imkoniyatini cheklashi mumkin.

O_DIRECT bayrog'i bilan Direct I/U dan foydalanish

Tizim chaqiruvi open() bayroqni qo'llab-quvvatlaydi O_DIRECT, bu disk bilan to'g'ridan-to'g'ri o'zaro ta'sir qilish orqali kiritish-chiqarish operatsiyalarini bajarish uchun operatsion tizim keshini chetlab o'tish uchun mo'ljallangan. Bu, ko'p hollarda, dastur tomonidan berilgan yozish buyruqlari to'g'ridan-to'g'ri disk bilan ishlashga qaratilgan buyruqlarga tarjima qilinishini anglatadi. Ammo, umuman olganda, bu mexanizm funktsiyalarni almashtirmaydi fsync() yoki fdatasync(). Haqiqat shundaki, diskning o'zi ham mumkin kechiktirish yoki keshlash tegishli ma'lumotlarni yozish buyruqlari. Va bundan ham yomoni, ba'zi bir maxsus holatlarda bayroqdan foydalanishda I/U operatsiyalari bajariladi O_DIRECT, efirga uzatish an'anaviy bufer operatsiyalariga. Ushbu muammoni hal qilishning eng oson yo'li fayllarni ochish uchun bayroqdan foydalanishdir O_DSYNC, bu har bir yozish operatsiyasidan keyin qo'ng'iroq bo'lishini anglatadi fdatasync().

Ma'lum bo'lishicha, XFS fayl tizimi yaqinda uchun "tezkor yo'l" qo'shgan O_DIRECT|O_DSYNC-ma'lumotlarni yozib olish. Agar blok yordamida qayta yozilsa O_DIRECT|O_DSYNC, keyin XFS keshni tozalash o'rniga, agar qurilma uni qo'llab-quvvatlasa, FUA yozish buyrug'ini bajaradi. Men buni yordamchi dastur yordamida tasdiqladim blktrace Linux 5.4/Ubuntu 20.04 tizimida. Ushbu yondashuv samaraliroq bo'lishi kerak, chunki foydalanilganda diskka minimal miqdordagi ma'lumotlar yoziladi va ikkitadan emas, balki bitta operatsiyadan foydalaniladi (keshni yozish va tozalash). ga havola topdim yamoq Ushbu mexanizmni amalga oshiradigan 2018 yadrosi. Ushbu optimallashtirishni boshqa fayl tizimlariga qo'llash haqida ba'zi munozaralar mavjud, ammo men bilganimdek, XFS hozirgacha buni qo'llab-quvvatlaydigan yagona fayl tizimidir.

sync_file_range() funktsiyasi

Linuxda tizim chaqiruvi mavjud sync_file_range(), bu butun faylni emas, balki faylning faqat bir qismini diskka tushirish imkonini beradi. Ushbu qo'ng'iroq ma'lumotlarni asinxron tozalashni boshlaydi va uning tugashini kutmaydi. Ammo sertifikatda sync_file_range() jamoa "juda xavfli" ekani aytiladi. Uni ishlatish tavsiya etilmaydi. Xususiyatlari va xavf-xatarlari sync_file_range() ichida juda yaxshi tasvirlangan bu material. Xususan, bu qo'ng'iroq RocksDB-dan yadro iflos ma'lumotlarni diskka qachon tozalashini nazorat qilish uchun ishlatadi. Shu bilan birga, barqaror ma'lumotlarni saqlashni ta'minlash uchun u ham qo'llaniladi fdatasync(). The kod RocksDB ushbu mavzu bo'yicha ba'zi qiziqarli sharhlarga ega. Misol uchun, bu qo'ng'iroq paydo bo'ladi sync_file_range() ZFS dan foydalanganda u ma'lumotlarni diskka o'tkazmaydi. Tajriba shuni ko'rsatadiki, kamdan-kam ishlatiladigan kodda xatolar bo'lishi mumkin. Shuning uchun, agar zarurat bo'lmasa, ushbu tizim chaqiruvidan foydalanishni maslahat beraman.

Ma'lumotlarning barqarorligini ta'minlashga yordam beradigan tizim qo'ng'iroqlari

Men ma'lumotlarning barqarorligini ta'minlaydigan kiritish-chiqarish operatsiyalarini bajarish uchun uchta yondashuvdan foydalanish mumkin degan xulosaga keldim. Ularning barchasi funksiya chaqiruvini talab qiladi fsync() fayl yaratilgan katalog uchun. Bu yondashuvlar:

  1. Funktsiyani chaqirish fdatasync() yoki fsync() funktsiyadan keyin write() (foydalanish yaxshiroq fdatasync()).
  2. Bayroq bilan ochilgan fayl deskriptori bilan ishlash O_DSYNC yoki O_SYNC (yaxshisi - bayroq bilan O_DSYNC).
  3. Buyruqdan foydalanish pwritev2() bayroq bilan RWF_DSYNC yoki RWF_SYNC (afzal bayroq bilan RWF_DSYNC).

Ishlash qaydlari

Men tekshirgan turli mexanizmlarning ishlashini diqqat bilan o'lchamaganman. Ularning ish tezligida men sezgan farqlar juda kichik. Bu shuni anglatadiki, men noto'g'ri bo'lishim mumkin va turli sharoitlarda bir xil narsa turli natijalar berishi mumkin. Birinchidan, men ishlashga ko'proq ta'sir qiladigan narsa haqida gapiraman, keyin esa unumdorlikka nima ta'sir qiladi.

  1. Fayl ma'lumotlarini qayta yozish faylga ma'lumot qo'shishdan ko'ra tezroq (ish samaradorligi 2-100% bo'lishi mumkin). Faylga ma'lumotlarni qo'shish tizim chaqiruvidan keyin ham faylning metama'lumotlariga qo'shimcha o'zgartirishlarni talab qiladi fallocate(), lekin bu ta'sirning kattaligi farq qilishi mumkin. Men eng yaxshi ishlash uchun qo'ng'iroq qilishni tavsiya qilaman fallocate() kerakli joyni oldindan ajratish uchun. Keyin bu bo'shliq aniq nol bilan to'ldirilishi va chaqirilishi kerak fsync(). Bu fayl tizimidagi tegishli bloklarning "ajratilmagan" emas, balki "ajratilgan" deb belgilanishini ta'minlaydi. Bu unumdorlikni kichik (taxminan 2%) oshiradi. Bundan tashqari, ba'zi disklar blokga birinchi kirishni boshqalarga qaraganda sekinroq bo'lishi mumkin. Bu shuni anglatadiki, bo'sh joyni nol bilan to'ldirish ishlashning sezilarli (taxminan 100%) yaxshilanishiga olib kelishi mumkin. Xususan, bu disklar bilan sodir bo'lishi mumkin AWS EBS (bu norasmiy ma'lumotlar, tasdiqlay olmadim). Xuddi shu narsa saqlash uchun ham amal qiladi GCP doimiy disk (va bu allaqachon rasmiy ma'lumot, sinovlar bilan tasdiqlangan). Boshqa mutaxassislar ham xuddi shunday qilishdi kuzatishlar, turli disklar bilan bog'liq.
  2. Tizim qo'ng'iroqlari qanchalik kam bo'lsa, unumdorlik shunchalik yuqori bo'ladi (daromad taxminan 5% bo'lishi mumkin). Qiyinchilikka o'xshaydi open() bayroq bilan O_DSYNC yoki qo'ng'iroq qiling pwritev2() bayroq bilan RWF_SYNC qo'ng'iroqdan tezroq fdatasync(). O'ylaymanki, bu erda gap shundaki, bu yondashuv bir xil muammoni hal qilish uchun kamroq tizim qo'ng'iroqlari bajarilishi kerakligida rol o'ynaydi (ikkita o'rniga bitta qo'ng'iroq). Ammo ishlashdagi farq juda kichik, shuning uchun siz uni butunlay e'tiborsiz qoldirishingiz va dasturda uning mantig'ini murakkablashtirmaydigan biror narsadan foydalanishingiz mumkin.

Agar siz barqaror ma'lumotlarni saqlash mavzusiga qiziqsangiz, bu erda bir nechta foydali materiallar mavjud:

  • I/U kirish usullari — kiritish/chiqarish mexanizmlari asoslariga umumiy nuqtai.
  • Ma'lumotlarning diskka etib borishini ta'minlash — dasturdan diskka o'tish yo'lida ma'lumotlar bilan nima sodir bo'lishi haqida hikoya.
  • O'z ichiga olgan katalogni qachon sinxronlashtirish kerak - qachon foydalanish kerakligi haqidagi savolga javob fsync() kataloglar uchun. Buni qisqacha qilib aytadigan bo'lsak, yangi fayl yaratishda buni qilish kerakligi ma'lum bo'ldi va bu tavsiyaning sababi Linuxda bir xil faylga ko'p havolalar bo'lishi mumkin.
  • Linuxda SQL Server: FUA ichki — bu yerda Linux platformasida SQL Serverda doimiy maʼlumotlarni saqlash qanday amalga oshirilganligi tavsifi. Bu erda Windows va Linux tizimi qo'ng'iroqlari o'rtasida qiziqarli taqqoslashlar mavjud. Ishonchim komilki, ushbu material tufayli men FUA XFSni optimallashtirish haqida bilib oldim.

Diskda xavfsiz saqlangan deb o'ylagan ma'lumotlarni yo'qotdingizmi?

Bardoshli ma'lumotlarni saqlash va Linux fayl API'lari

Bardoshli ma'lumotlarni saqlash va Linux fayl API'lari

Manba: www.habr.com