ฉันซึ่งค้นคว้าเกี่ยวกับความเสถียรของการจัดเก็บข้อมูลในระบบคลาวด์ ตัดสินใจที่จะทดสอบตัวเองเพื่อให้แน่ใจว่าฉันเข้าใจสิ่งพื้นฐาน ฉัน
ในบทความนี้ ฉันจะสำรวจกลไกการคงอยู่ซึ่งจัดทำโดย API ไฟล์ของ Linux ดูเหมือนว่าทุกอย่างควรจะง่ายที่นี่: โปรแกรมเรียกคำสั่ง write()
และหลังจากการทำงานของคำสั่งนี้เสร็จสิ้น ข้อมูลจะถูกจัดเก็บไว้ในดิสก์อย่างปลอดภัย แต่ write()
คัดลอกข้อมูลแอปพลิเคชันไปยังแคชเคอร์เนลที่อยู่ใน RAM เท่านั้น เพื่อบังคับให้ระบบเขียนข้อมูลลงดิสก์ ต้องใช้กลไกเพิ่มเติมบางอย่าง
โดยทั่วไปแล้ว เนื้อหานี้เป็นชุดบันทึกที่เกี่ยวข้องกับสิ่งที่ฉันได้เรียนรู้ในหัวข้อที่ฉันสนใจ หากเราพูดสั้นๆ เกี่ยวกับสิ่งที่สำคัญที่สุด ปรากฎว่าในการจัดระเบียบการจัดเก็บข้อมูลอย่างยั่งยืน คุณต้องใช้คำสั่ง fdatasync()
หรือเปิดไฟล์ที่มีการตั้งค่าสถานะ O_DSYNC
. หากคุณสนใจที่จะเรียนรู้เพิ่มเติมเกี่ยวกับสิ่งที่เกิดขึ้นกับข้อมูลระหว่างทางจากโค้ดไปยังดิสก์ ลองดูที่
คุณสมบัติของการใช้ฟังก์ชั่นเขียน ()
ระบบเรียก write()
ที่กำหนดไว้ในมาตรฐาน write()
การดำเนินการอ่านข้อมูลจะต้องส่งคืนไบต์ที่เขียนไว้ก่อนหน้านี้ทุกประการ แม้ว่าข้อมูลจะถูกเข้าถึงจากกระบวนการหรือเธรดอื่น (
นี่หมายความว่าการดำเนินการ write()
คือปรมาณู? จากมุมมองทางเทคนิค ใช่ การดำเนินการอ่านข้อมูลต้องส่งคืนสิ่งที่เขียนทั้งหมดหรือไม่มีเลย write()
. แต่การดำเนินการ write()
ตามมาตรฐานไม่ต้องจบลงโดยจดทุกอย่างที่เธอถูกขอให้จด อนุญาตให้เขียนข้อมูลเพียงบางส่วนเท่านั้น ตัวอย่างเช่น เราอาจมีสองสตรีม แต่ละสตรีมเพิ่ม 1024 ไบต์ต่อท้ายไฟล์ที่อธิบายโดยตัวอธิบายไฟล์เดียวกัน จากมุมมองของมาตรฐาน ผลลัพธ์จะยอมรับได้เมื่อการดำเนินการเขียนแต่ละครั้งสามารถต่อท้ายไฟล์ได้เพียงหนึ่งไบต์เท่านั้น การดำเนินการเหล่านี้จะยังคงเป็นปรมาณู แต่หลังจากเสร็จสิ้น ข้อมูลที่เขียนลงในไฟล์จะสับสน
ฟังก์ชัน fsync() และ fdatasync()
วิธีที่ง่ายที่สุดในการล้างข้อมูลลงดิสก์คือการเรียกใช้ฟังก์ชัน fdatasync()
. ใน fdatasync()
มันบอกว่าในระหว่างการทำงานของฟังก์ชั่นนี้ ข้อมูลเมตาจำนวนดังกล่าวจะถูกบันทึกไว้ในดิสก์ ซึ่ง "จำเป็นสำหรับการดำเนินการอ่านข้อมูลต่อไปนี้อย่างถูกต้อง" และนี่คือสิ่งที่แอปพลิเคชันส่วนใหญ่สนใจ
ปัญหาหนึ่งที่อาจเกิดขึ้นที่นี่คือกลไกเหล่านี้ไม่รับประกันว่าจะพบไฟล์ได้หลังจากเกิดความล้มเหลว โดยเฉพาะอย่างยิ่งเมื่อมีการสร้างไฟล์ใหม่ควรเรียก fsync()
สำหรับไดเร็กทอรีที่มี มิฉะนั้นหลังจากเกิดความผิดพลาด อาจกลายเป็นว่าไม่มีไฟล์นี้อยู่ เหตุผลก็คือภายใต้ UNIX เนื่องจากการใช้ฮาร์ดลิงก์ ไฟล์สามารถอยู่ในหลายไดเร็กทอรีได้ ดังนั้นเมื่อโทร fsync()
ไม่มีทางที่ไฟล์จะรู้ว่าควรล้างข้อมูลไดเร็กทอรีใดไปยังดิสก์ (fsync()
ไปยังไดเร็กทอรีที่มีไฟล์ที่เกี่ยวข้อง แต่อาจไม่ใช่กรณีนี้กับระบบไฟล์อื่น
กลไกนี้สามารถนำไปใช้แตกต่างกันในระบบไฟล์ที่แตกต่างกัน ฉันใช้ fdatasync()
เร็วขึ้นเล็กน้อย fsync()
. คุณประโยชน์ blktrace
แสดงว่า fdatasync()
มักจะเขียนข้อมูลลงดิสก์น้อยลง (ใน ext4 fsync()
เขียน 20 KiB และ fdatasync()
- 16 กิโลไบต์). นอกจากนี้ ฉันยังพบว่า XFS นั้นเร็วกว่า ext4 เล็กน้อย และที่นี่ด้วยความช่วยเหลือ blktrace
สามารถค้นพบว่า fdatasync()
ล้างข้อมูลลงดิสก์น้อยลง (4 KiB ใน XFS)
สถานการณ์ที่ไม่ชัดเจนเมื่อใช้ fsync()
ฉันสามารถคิดถึงสถานการณ์ที่คลุมเครือสามประการเกี่ยวกับ fsync()
ที่ข้าพเจ้าได้ประสบมาในทางปฏิบัติ
เหตุการณ์ดังกล่าวเกิดขึ้นครั้งแรกในปี 2008 ในขณะนั้น อินเทอร์เฟซของ Firefox 3 จะ "หยุดทำงาน" หากมีไฟล์จำนวนมากถูกเขียนลงดิสก์ ปัญหาคือการใช้อินเทอร์เฟซใช้ฐานข้อมูล SQLite เพื่อเก็บข้อมูลเกี่ยวกับสถานะของมัน หลังจากการเปลี่ยนแปลงแต่ละครั้งที่เกิดขึ้นในอินเทอร์เฟซ ฟังก์ชันจะถูกเรียกใช้ fsync()
ซึ่งให้การรับประกันที่ดีในการจัดเก็บข้อมูลที่มีเสถียรภาพ ในระบบไฟล์ ext3 ที่ใช้แล้ว ฟังก์ชัน fsync()
ล้างข้อมูลเพจ "สกปรก" ทั้งหมดในระบบลงดิสก์ ไม่ใช่เฉพาะเพจที่เกี่ยวข้องกับไฟล์ที่เกี่ยวข้อง ซึ่งหมายความว่าการคลิกปุ่มใน Firefox อาจทำให้ข้อมูลหลายเมกะไบต์ถูกเขียนลงในจานแม่เหล็ก ซึ่งอาจใช้เวลาหลายวินาที วิธีแก้ปัญหาเท่าที่ฉันเข้าใจจาก
ปัญหาที่สองเกิดขึ้นในปี 2009 จากนั้น หลังจากระบบหยุดทำงาน ผู้ใช้ระบบไฟล์ ext4 ใหม่พบว่าไฟล์ที่สร้างขึ้นใหม่จำนวนมากมีความยาวเป็นศูนย์ แต่สิ่งนี้ไม่ได้เกิดขึ้นกับระบบไฟล์ ext3 รุ่นเก่า ในย่อหน้าที่แล้ว ฉันได้พูดถึงวิธีที่ ext3 ถ่ายโอนข้อมูลมากเกินไปบนดิสก์ ซึ่งทำให้สิ่งต่างๆ ช้าลงอย่างมาก fsync()
. เพื่อปรับปรุงสถานการณ์ ext4 จะล้างเฉพาะหน้าที่ "สกปรก" ที่เกี่ยวข้องกับไฟล์เฉพาะ และข้อมูลของไฟล์อื่นยังคงอยู่ในหน่วยความจำเป็นเวลานานกว่า ext3 สิ่งนี้ทำเพื่อปรับปรุงประสิทธิภาพ (โดยค่าเริ่มต้น ข้อมูลจะอยู่ในสถานะนี้เป็นเวลา 30 วินาที คุณสามารถกำหนดค่าได้โดยใช้ fsync()
ในแอปพลิเคชันที่ต้องการให้การจัดเก็บข้อมูลที่เสถียรและปกป้องข้อมูลเหล่านั้นจากผลที่ตามมาจากความล้มเหลวให้ได้มากที่สุด การทำงาน fsync()
ทำงานได้อย่างมีประสิทธิภาพด้วย ext4 มากกว่า ext3 ข้อเสียของแนวทางนี้คือการใช้งานเหมือนเมื่อก่อนทำให้การดำเนินการบางอย่างช้าลง เช่น การติดตั้งโปรแกรม ดูรายละเอียดเรื่องนี้
ปัญหาที่สามเกี่ยวกับ fsync()
ถือกำเนิดขึ้นในปี 2018 จากนั้นภายในกรอบของโครงการ PostgreSQL พบว่าถ้าฟังก์ชัน fsync()
พบข้อผิดพลาด จะทำเครื่องหมายหน้า "สกปรก" เป็น "สะอาด" เป็นผลให้การโทรต่อไปนี้ fsync()
ไม่ทำอะไรกับหน้าดังกล่าว ด้วยเหตุนี้ เพจที่แก้ไขจะถูกจัดเก็บไว้ในหน่วยความจำและไม่เคยเขียนลงดิสก์ นี่เป็นหายนะที่แท้จริงเนื่องจากแอปพลิเคชันจะคิดว่าข้อมูลบางส่วนถูกเขียนลงดิสก์ แต่ในความเป็นจริงแล้วจะไม่เป็นเช่นนั้น ความล้มเหลวดังกล่าว fsync()
หายาก แอปพลิเคชันในสถานการณ์ดังกล่าวแทบจะทำอะไรไม่ได้เลยเพื่อต่อสู้กับปัญหา เมื่อสิ่งนี้เกิดขึ้น ทุกวันนี้ PostgreSQL และแอปพลิเคชันอื่นๆ หยุดทำงาน O_SYNC
หรือมีธง O_DSYNC
. ด้วยวิธีการนี้ ระบบจะรายงานข้อผิดพลาดที่อาจเกิดขึ้นเมื่อดำเนินการเขียนข้อมูลเฉพาะ แต่วิธีการนี้ต้องการให้แอปพลิเคชันจัดการบัฟเฟอร์เอง อ่านเพิ่มเติมเกี่ยวกับเรื่องนี้
การเปิดไฟล์โดยใช้แฟล็ก O_SYNC และ O_DSYNC
กลับไปที่การสนทนาเกี่ยวกับกลไกของ Linux ที่ให้การจัดเก็บข้อมูลแบบถาวร กล่าวคือเรากำลังพูดถึงการใช้ธง O_SYNC
หรือธง O_DSYNC
เมื่อเปิดไฟล์โดยใช้การเรียกระบบ write()
ระบบจะได้รับคำสั่งตามลำดับ fsync()
и fdatasync()
. ใน write()
и fdatasync()
). ข้อเสียเปรียบหลักของแนวทางนี้คือการดำเนินการเขียนทั้งหมดโดยใช้ตัวอธิบายไฟล์ที่เกี่ยวข้องจะถูกซิงโครไนซ์ ซึ่งอาจจำกัดความสามารถในการจัดโครงสร้างรหัสแอปพลิเคชัน
การใช้ Direct I/O กับแฟล็ก O_DIRECT
ระบบเรียก open()
รองรับธง O_DIRECT
ซึ่งออกแบบมาเพื่อข้ามแคชของระบบปฏิบัติการ ดำเนินการ I / O โต้ตอบโดยตรงกับดิสก์ ในหลายกรณี หมายความว่าคำสั่งเขียนที่ออกโดยโปรแกรมจะถูกแปลโดยตรงเป็นคำสั่งที่มุ่งทำงานกับดิสก์ แต่โดยทั่วไปแล้ว กลไกนี้ใช้แทนฟังก์ชันต่างๆ ไม่ได้ 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 วิธีการนี้ควรมีประสิทธิภาพมากกว่า เนื่องจากเขียนจำนวนข้อมูลขั้นต่ำลงในดิสก์และใช้การดำเนินการเพียงครั้งเดียว ไม่ใช่สองรายการ (เขียนและล้างแคช) ฉันพบลิงค์ไปยัง
ฟังก์ชัน sync_file_range()
Linux มีการเรียกระบบ sync_file_range()
คำสั่งนี้กล่าวกันว่า "อันตรายมาก" ไม่แนะนำให้ใช้ คุณสมบัติและอันตราย sync_file_range()
อธิบายได้ดีมากใน fdatasync()
. ใน sync_file_range()
เมื่อใช้ ZFS จะไม่ล้างข้อมูลลงดิสก์ ประสบการณ์บอกฉันว่าโค้ดที่ไม่ค่อยได้ใช้อาจมีข้อบกพร่อง ดังนั้น ฉันไม่แนะนำให้ใช้การเรียกระบบนี้ เว้นแต่จะมีความจำเป็นจริงๆ
การเรียกระบบเพื่อช่วยให้มั่นใจถึงการคงอยู่ของข้อมูล
ฉันได้ข้อสรุปว่ามีสามวิธีที่สามารถใช้เพื่อดำเนินการ I/O แบบถาวร พวกเขาทั้งหมดต้องการการเรียกใช้ฟังก์ชัน fsync()
สำหรับไดเร็กทอรีที่สร้างไฟล์ นี่คือแนวทาง:
- การเรียกใช้ฟังก์ชัน
fdatasync()
หรือfsync()
หลังจากฟังก์ชั่นwrite()
(ใช้ดีกว่า.fdatasync()
). - การทำงานกับตัวอธิบายไฟล์ที่เปิดด้วยแฟล็ก
O_DSYNC
หรือO_SYNC
(ดีกว่า - ด้วยธงO_DSYNC
). - การใช้คำสั่ง
pwritev2()
ด้วยธงRWF_DSYNC
หรือRWF_SYNC
(โดยเฉพาะอย่างยิ่งกับธงRWF_DSYNC
).
หมายเหตุประสิทธิภาพ
ฉันไม่ได้วัดประสิทธิภาพของกลไกต่างๆ ที่ฉันตรวจสอบอย่างระมัดระวัง ความแตกต่างที่ฉันสังเกตเห็นในความเร็วของการทำงานนั้นน้อยมาก ซึ่งหมายความว่าฉันอาจคิดผิดได้ และในเงื่อนไขอื่นๆ สิ่งเดียวกันอาจแสดงผลลัพธ์ที่แตกต่างกัน อันดับแรก ฉันจะพูดถึงสิ่งที่ส่งผลต่อประสิทธิภาพการทำงานมากกว่า จากนั้นเกี่ยวกับสิ่งที่ส่งผลต่อประสิทธิภาพการทำงานน้อยลง
- การเขียนทับข้อมูลไฟล์นั้นเร็วกว่าการผนวกข้อมูลเข้ากับไฟล์ (ประสิทธิภาพที่เพิ่มขึ้นอาจอยู่ที่ 2-100%) การแนบข้อมูลกับไฟล์จำเป็นต้องเปลี่ยนแปลงข้อมูลเมตาของไฟล์เพิ่มเติม แม้หลังจากการเรียกระบบ
fallocate()
แต่ขนาดของผลกระทบนี้อาจแตกต่างกันไป ฉันแนะนำให้โทรเพื่อประสิทธิภาพที่ดีที่สุดfallocate()
เพื่อจัดสรรพื้นที่ที่ต้องการไว้ล่วงหน้า จากนั้นช่องว่างนี้จะต้องเต็มไปด้วยศูนย์อย่างชัดเจนและถูกเรียกfsync()
. ซึ่งจะทำให้บล็อกที่เกี่ยวข้องในระบบไฟล์ถูกทำเครื่องหมายเป็น "จัดสรร" แทน "ไม่ได้จัดสรร" สิ่งนี้ช่วยปรับปรุงประสิทธิภาพเล็กน้อย (ประมาณ 2%) นอกจากนี้ ดิสก์บางตัวอาจมีการดำเนินการเข้าถึงบล็อกแรกช้ากว่าดิสก์อื่นๆ ซึ่งหมายความว่าการเติมช่องว่างด้วยศูนย์สามารถนำไปสู่การปรับปรุงประสิทธิภาพที่สำคัญ (ประมาณ 100%) โดยเฉพาะอย่างยิ่งสิ่งนี้สามารถเกิดขึ้นได้กับดิสก์AWS EBS (นี่เป็นข้อมูลที่ไม่เป็นทางการ ฉันไม่สามารถยืนยันได้) เช่นเดียวกับการจัดเก็บดิสก์ถาวร GCP (และนี่เป็นข้อมูลอย่างเป็นทางการที่ได้รับการยืนยันจากการทดสอบ) ผู้เชี่ยวชาญคนอื่น ๆ ได้ทำเช่นเดียวกันการสังเกต ที่เกี่ยวข้องกับดิสก์ต่างๆ - ยิ่งการเรียกระบบน้อยลง ประสิทธิภาพก็จะยิ่งสูงขึ้น (สามารถรับได้ประมาณ 5%) ดูเหมือนว่าการโทร
open()
ด้วยธงO_DSYNC
หรือโทรpwritev2()
ด้วยธงRWF_SYNC
โทรเร็วขึ้นfdatasync()
. ฉันสงสัยว่าประเด็นคือด้วยวิธีนี้ ข้อเท็จจริงที่ต้องมีการเรียกระบบน้อยลงเพื่อแก้ไขงานเดียวกัน (การโทรหนึ่งครั้งแทนที่จะเป็นสองครั้ง) มีบทบาท แต่ความแตกต่างของประสิทธิภาพนั้นน้อยมาก ดังนั้นคุณจึงสามารถเพิกเฉยและใช้บางอย่างในแอปพลิเคชันที่ไม่ทำให้เกิดความซับซ้อนของตรรกะ
หากคุณสนใจหัวข้อการจัดเก็บข้อมูลอย่างยั่งยืน ต่อไปนี้เป็นเนื้อหาที่มีประโยชน์:
วิธีการเข้าถึง I/O — ภาพรวมของพื้นฐานของกลไกอินพุต / เอาท์พุตทำให้มั่นใจได้ว่าข้อมูลจะไปถึงดิสก์ - เรื่องราวเกี่ยวกับสิ่งที่เกิดขึ้นกับข้อมูลระหว่างทางจากแอปพลิเคชันไปยังดิสก์เมื่อใดที่คุณควร fsync ไดเร็กทอรีที่มี - คำตอบสำหรับคำถามที่ว่าจะสมัครเมื่อใดfsync()
สำหรับไดเร็กทอรี สรุปก็คือ คุณต้องทำสิ่งนี้เมื่อสร้างไฟล์ใหม่ และเหตุผลสำหรับคำแนะนำนี้คือใน Linux สามารถมีการอ้างอิงถึงไฟล์เดียวกันได้มากมายSQL Server บน Linux: FUA Internals - ต่อไปนี้คือคำอธิบายวิธีการใช้ที่จัดเก็บข้อมูลถาวรใน SQL Server บนแพลตฟอร์ม Linux มีการเปรียบเทียบที่น่าสนใจระหว่างการเรียกระบบ Windows และ Linux ที่นี่ ฉันเกือบจะแน่ใจว่าต้องขอบคุณเนื้อหานี้ที่ฉันได้เรียนรู้เกี่ยวกับการเพิ่มประสิทธิภาพ FUA ของ XFS
คุณเคยสูญเสียข้อมูลที่คุณคิดว่าจัดเก็บอย่างปลอดภัยบนดิสก์หรือไม่?
ที่มา: will.com