Бат бөх өгөгдөл хадгалах ба Linux файлын API

Би үүлэн систем дэх өгөгдөл хадгалах тогтвортой байдлын талаар судалж үзээд үндсэн зүйлийг ойлгож байгаа эсэхийг шалгахын тулд өөрийгөө туршихаар шийдсэн. I NVMe техникийн үзүүлэлтийг уншиж эхэлсэн Өгөгдлийн тогтвортой байдлын талаар ямар баталгаа (өөрөөр хэлбэл системийн эвдрэлийн дараа өгөгдөл бэлэн болно гэсэн баталгаа) бидэнд NMVe дискийг өгөхийг ойлгохын тулд. Би дараах үндсэн дүгнэлтийг хийсэн: та өгөгдөл бичих команд өгөхөөс эхлээд тэдгээрийг хадгалах хэрэгсэлд бичих хүртэл гэмтсэн өгөгдлийг анхаарч үзэх хэрэгтэй. Гэсэн хэдий ч ихэнх програмуудад системийн дуудлагыг өгөгдөл бичихэд аюулгүй ашигладаг.

Энэ нийтлэлд би Линукс файлын API-уудын өгсөн тогтвортой байдлын механизмуудыг судлах болно. Энд бүх зүйл энгийн байх ёстой юм шиг санагдаж байна: програм тушаалыг дууддаг write(), мөн энэ тушаалын ажиллагаа дууссаны дараа өгөгдөл дискэн дээр найдвартай хадгалагдах болно. Гэхдээ write() зөвхөн RAM-д байрлах цөмийн кэш рүү програмын өгөгдлийг хуулна. Системийг диск рүү өгөгдөл бичихийг албадахын тулд зарим нэмэлт механизмуудыг ашиглах шаардлагатай.

Бат бөх өгөгдөл хадгалах ба 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 секундын турш энэ төлөвт үлдэнэ, та үүнийг ашиглан тохируулж болно. бохир_хугацаа_центисек; энд Та энэ талаар дэлгэрэнгүй мэдээлэл авах боломжтой). Энэ нь сүйрлийн дараа их хэмжээний өгөгдөл нөхөж баршгүй алдагдах боломжтой гэсэн үг юм. Энэ асуудлыг шийдэх арга бол ашиглах явдал юм fsync() Тогтвортой өгөгдөл хадгалах, бүтэлгүйтлийн үр дагавраас аль болох хамгаалах шаардлагатай програмуудад. Чиг үүрэг fsync() ext4-тай харьцуулахад ext3-тэй илүү үр дүнтэй ажилладаг. Энэ аргын сул тал нь түүнийг ашиглах нь өмнөх шигээ програм суулгах гэх мэт зарим үйлдлүүдийг удаашруулдаг. Энэ талаар дэлгэрэнгүй харна уу энд и энд.

Гурав дахь асуудал fsync(), 2018 онд үүссэн. Дараа нь PostgreSQL төслийн хүрээнд хэрэв функц байгаа нь тогтоогдсон fsync() алдаатай тулгарвал "бохир" хуудсыг "цэвэр" гэж тэмдэглэнэ. Үүний үр дүнд дараах дуудлага ирдэг fsync() ийм хуудсуудтай юу ч бүү хий. Үүнээс болж өөрчилсөн хуудсууд нь санах ойд хадгалагддаг бөгөөд хэзээ ч диск рүү бичигддэггүй. Энэ бол жинхэнэ гамшиг, учир нь програм нь зарим өгөгдлийг дискэнд бичсэн гэж бодох боловч үнэндээ тийм биш юм. Ийм бүтэлгүйтэл fsync() ховор тохиолддог тул ийм нөхцөлд хэрэглэх нь асуудалтай тэмцэхэд бараг юу ч хийж чадахгүй. Эдгээр өдрүүдэд PostgreSQL болон бусад програмууд гацаж байна. энд, "Програмууд fsync алдаанаас сэргэж чадах уу?" гэсэн нийтлэлд энэ асуудлыг нарийвчлан судалсан болно. Одоогоор энэ асуудлыг шийдэх хамгийн сайн шийдэл бол тугтай шууд оролт/гаралтыг ашиглах явдал юм O_SYNC эсвэл тугтай O_DSYNC. Энэ аргын тусламжтайгаар систем нь тодорхой өгөгдөл бичих үйлдлийг гүйцэтгэх үед гарч болох алдаануудыг мэдээлэх боловч энэ арга нь програмаас буферийг өөрөө удирдахыг шаарддаг. Энэ талаар дэлгэрэнгүй уншина уу энд и энд.

O_SYNC болон O_DSYNC тугуудыг ашиглан файлуудыг нээж байна

Тогтвортой өгөгдөл хадгалах боломжийг олгодог Линукс механизмуудын тухай хэлэлцүүлэгт буцаж орцгооё. Тодруулбал, туг ашиглах тухай ярьж байна O_SYNC эсвэл туг O_DSYNC системийн дуудлага ашиглан файлуудыг нээх үед нээлттэй(). Энэ аргын тусламжтайгаар өгөгдөл бичих үйлдэл бүрийг команд бүрийн дараа хийсэн мэт гүйцэтгэдэг write() системд тус тус тушаал өгсөн fsync() и fdatasync(). The POSIX техникийн үзүүлэлтүүд Үүнийг "Synchronized I/O File Integrity Completion" болон "Data Integrity Completion" гэж нэрлэдэг. Энэ аргын гол давуу тал нь өгөгдлийн бүрэн бүтэн байдлыг хангахын тулд зөвхөн нэг системийн дуудлагыг гүйцэтгэх шаардлагатай болохоос хоёр биш (жишээ нь - write() и fdatasync()). Энэ аргын гол сул тал нь харгалзах файлын тодорхойлогчийг ашиглан бичих бүх үйлдлүүд синхрончлогдох бөгөөд энэ нь програмын кодыг зохион байгуулах боломжийг хязгаарлаж болзошгүй юм.

O_DIRECT тугтай шууд I/O ашиглах

Системийн дуудлага open() тугийг дэмждэг O_DIRECT, үйлдлийн системийн кэшийг тойрч гарах, оролт / гаралтын үйлдлийг гүйцэтгэх, дисктэй шууд харьцах зориулалттай. Энэ нь ихэнх тохиолдолд програмаас гаргасан бичих командуудыг дисктэй ажиллахад чиглэсэн командууд руу шууд хөрвүүлнэ гэсэн үг юм. Гэхдээ ерөнхийдөө энэ механизм нь функцийг орлуулах зүйл биш юм fsync() буюу fdatasync(). Үнэн хэрэгтээ диск өөрөө боломжтой юм саатал эсвэл кэш өгөгдөл бичихэд тохиромжтой командууд. Үүнээс ч дор, зарим онцгой тохиолдолд туг ашиглах үед I / O үйлдлүүд хийгддэг 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() функц

Линукс системийн дуудлагатай 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(). Энд байгаа гол зүйл бол энэ хандлагыг ашигласнаар нэг ажлыг шийдвэрлэхийн тулд цөөн тооны системийн дуудлага хийх шаардлагатай (хоёр биш нэг дуудлага) үүрэг гүйцэтгэдэг гэж би бодож байна. Гэхдээ гүйцэтгэлийн ялгаа нь маш бага тул та үүнийг үл тоомсорлож, програмын логикийн хүндрэлд хүргэхгүй зүйлийг ашиглаж болно.

Тогтвортой өгөгдөл хадгалах сэдвийг сонирхож байгаа бол дараах хэрэгтэй материалууд энд байна.

  • I/O хандалтын аргууд - оролт / гаралтын механизмын үндсүүдийн тойм.
  • Өгөгдөл дискэнд хүрэхийг баталгаажуулах - програмаас диск рүү явах замд өгөгдөлд юу тохиолдох тухай түүх.
  • Та агуулсан лавлахыг хэзээ fсинк хийх ёстой вэ? - хэзээ өргөдөл гаргах вэ гэсэн асуултын хариулт fsync() сангуудын хувьд. Товчхондоо, та шинэ файл үүсгэхдээ үүнийг хийх хэрэгтэй болж байгаа бөгөөд энэ зөвлөмжийн шалтгаан нь Линукс дээр нэг файлд олон лавлагаа байж болно.
  • Линукс дээрх SQL сервер: FUA дотоод - Линукс платформ дээрх SQL Server-д өгөгдөл хадгалах ажиллагааг хэрхэн хэрэгжүүлдэг тухай тайлбарыг энд оруулав. Windows болон Линукс системийн дуудлагуудын хооронд сонирхолтой харьцуулалт энд байна. Энэ материалын ачаар би XFS-ийн FUA оновчлолын талаар олж мэдсэн гэдэгт би бараг итгэлтэй байна.

Та хэзээ нэгэн цагт дискэн дээр найдвартай хадгалагдсан гэж бодож байсан мэдээллээ алдаж байсан уу?

Бат бөх өгөгдөл хадгалах ба Linux файлын API

Бат бөх өгөгдөл хадгалах ба Linux файлын API

Эх сурвалж: www.habr.com