Байнгын өгөгдөл хадгалах болон файлын API-ууд Linux

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

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

Байнгын өгөгдөл хадгалах болон файлын API-ууд Linux

Ерөнхийдөө энэ материал бол миний сонирхсон сэдвээр миний сурсан зүйлтэй холбоотой тэмдэглэлийн багц юм. Хэрэв бид хамгийн чухал зүйлийн талаар товч ярих юм бол тогтвортой өгөгдөл хадгалах ажлыг зохион байгуулахын тулд та тушаалыг ашиглах хэрэгтэй болж байна. 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 тугуудыг ашиглан файлуудыг нээж байна

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

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

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

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

Байнгын өгөгдөл хадгалах болон файлын API-ууд Linux

Байнгын өгөгдөл хадгалах болон файлын API-ууд Linux

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

DDoS хамгаалалт, VPS VDS сервер бүхий сайтуудад найдвартай хостинг худалдаж аваарай 🔥 DDoS хамгаалалттай, VPS VDS сервертэй найдвартай вэбсайт хостинг худалдаж аваарай | ProHoster