การสืบสวนล่าสุดเกี่ยวกับการตั้งค่าการซิงโครไนซ์เริ่มต้นของ SQLite ได้เผยให้เห็นความไม่สอดคล้องกันที่น่าประหลาดใจในแต่ละแพลตฟอร์มและการแจกจ่าย ทำให้เกิดการถกเถียงเรื่องค่าเริ่มต้นความปลอดภัยของฐานข้อมูลในชุมชนนักพัฒนาอีกครั้ง
ความขัดแย้งเริ่มต้นขึ้นเมื่อนักพัฒนาค้นพบว่าพฤติกรรมของ SQLite เกี่ยวกับการทำงานของ fsync - ซึ่งช่วยให้แน่ใจว่าข้อมูลถูกเขียนลงดิสก์จริงๆ - แตกต่างกันอย่างมีนัยสำคัญขึ้นอยู่กับว่าฐานข้อมูลถูกคอมไพล์และติดตั้งอย่างไรและที่ไหน ความแปรปรวนนี้มีผลกระทบสำคัญต่อความทนทานของข้อมูล โดยเฉพาะในสถานการณ์ที่อาจเกิดไฟฟ้าดับหรือระบบขัดข้อง
ความแปรปรวนของค่าเริ่มต้นเฉพาะแพลตฟอร์มสร้างความสับสน
การค้นพบที่โดดเด่นที่สุดเกี่ยวข้องกับ macOS ของ Apple ซึ่ง SQLite ที่ระบบจัดให้มีค่าเริ่มต้นเป็น synchronous=NORMAL
(ค่า 1) ในขณะที่การติดตั้งใหม่จากตัวจัดการแพ็กเกจเช่น Homebrew มีค่าเริ่มต้นเป็น synchronous=FULL
(ค่า 2) ความไม่สอดคล้องกันนี้ขยายไปนอกเหนือจากระบบนิเวศของ Apple โดยมีการแจกจ่าย Linux ที่แตกต่างกันและตัวจัดการแพ็กเกจที่ใช้ตัวเลือกของตนเอง
เมื่อ SQLite ทำงานในโหมด WAL (Write-Ahead Logging) ด้วย synchronous=NORMAL
มันจะไม่ทำการ fsync ในแต่ละการคอมมิตธุรกรรม แต่จะซิงค์เฉพาะระหว่างการทำงาน checkpoint ซึ่งเกิดขึ้นไม่บ่อยนัก แม้ว่าวิธีการนี้จะป้องกันการเสียหายของฐานข้อมูล แต่ก็ยังสามารถทำให้ธุรกรรมที่คอมมิตแล้วสูญหายระหว่างไฟฟ้าดับโดยไม่คาดคิด
โหมด WAL: วิธีการบันทึกรายการที่เขียนการเปลี่ยนแปลงลงในไฟล์บันทึกแยกต่างหากก่อนที่จะนำไปใช้กับฐานข้อมูลหลัก ช่วยให้สามารถเข้าถึงพร้อมกันได้ดีขึ้น
ค่าเริ่มต้นที่แตกต่างกันตามแพลตฟอร์ม
- macOS (system SQLite):
synchronous=NORMAL
(1) - macOS (Homebrew):
synchronous=FULL
(2) - การแจกจ่าย Linux: โดยทั่วไปใช้
synchronous=FULL
(2) - ค่าเริ่มต้นจากการคอมไพล์ของ SQLite อย่างเป็นทางการ:
synchronous=FULL
(2) - พฤติกรรมที่กำหนดเองของ Apple: ใช้
F_BARRIERFSYNC
แทนF_FULLFSYNC
การแลกเปลี่ยนระหว่างความปลอดภัยและประสิทธิภาพ
การถกเถียงในชุมชนมุ่งเน้นไปที่ว่าระบบฐานข้อมูลควรให้ความสำคัญกับความปลอดภัยหรือประสิทธิภาพในการกำหนดค่าเริ่มต้น เอกสารอย่างเป็นทางการของ SQLite ระบุว่าค่าเริ่มต้นเวลาคอมไพล์ควรเป็นการซิงโครไนซ์แบบ FULL
แต่ผู้แจกจ่ายต่างๆ แก้ไขการตั้งค่านี้เพื่อเหตุผลด้านประสิทธิภาพ
ค่าเริ่มต้นควรปลอดภัย ปรับแต่งเพื่อประสิทธิภาพ ไม่ใช่ทางกลับกัน
ความรู้สึกนี้สะท้อนถึงความผิดหวังที่กว้างขึ้นในหมู่นักพัฒนาที่คาดหวังให้ระบบฐานข้อมูลให้การรับประกันความทนทานที่แข็งแกร่งตั้งแต่เริ่มต้น ความกังวลกลายเป็นเรื่องเฉียบพลันโดยเฉพาะเมื่อ SQLite ถูกใช้ในสภาพแวดล้อมเซิร์ฟเวอร์หรือแอปพลิเคชันที่สำคัญซึ่งการสูญเสียข้อมูลอาจมีผลที่ตามมาร้ายแรง
fsync: การเรียกระบบที่บังคับให้ระบบปฏิบัติการเขียนข้อมูลที่บัฟเฟอร์ไปยังที่เก็บข้อมูลถาวร เพื่อให้แน่ใจว่ามีความทนทาน
การเปรียบเทียบโหมด Synchronous ของ SQLite
โหมด | ค่า | พฤติกรรม fsync | ความเสี่ยงต่อความทนทาน | ประสิทธิภาพ |
---|---|---|---|---|
OFF | 0 | ไม่มีการเรียก fsync | ความเสี่ยงสูงต่อการเสียหาย | เร็วที่สุด |
NORMAL | 1 | fsync น้อยที่สุด (เฉพาะ WAL checkpoints) | อาจสูญเสียธุรกรรม | เร็ว |
FULL | 2 | fsync ในทุกการ commit | ความทนทานเต็มรูปแบบ | ช้ากว่า |
EXTRA | 3 | มีการ sync ไดเรกทอรีเพิ่มเติม | ความทนทานสูงสุด | ช้าที่สุด |
การใช้งานเฉพาะของ Apple เพิ่มความซับซ้อนอีกชั้นหนึ่ง
การใช้งานของ Apple แนะนำความซับซ้อนเพิ่มเติมนอกเหนือจากการตั้งค่าเริ่มต้น บริษัทได้แก้ไขการสร้าง SQLite ของตนเพื่อใช้ F_BARRIERFSYNC
แทน F_FULLFSYNC
มาตรฐาน แม้เมื่อแอปพลิเคชันขอการซิงโครไนซ์แบบเต็มอย่างชัดเจน การเปลี่ยนแปลงนี้ส่งผลต่อการรับประกันความทนทานบนระบบ macOS และ iOS แม้ว่าจะให้ลักษณะประสิทธิภาพที่ดีกว่าก็ตาม
การค้นพบนี้ได้กระตุ้นให้นักพัฒนาชัดเจนมากขึ้นเกี่ยวกับความต้องการการซิงโครไนซ์ของตนแทนที่จะพึ่งพาค่าเริ่มต้นของแพลตฟอร์ม หลายคนกำลังแนะนำให้แอปพลิเคชันตั้งค่าโหมดการซิงโครไนซ์ที่ต้องการอย่างชัดเจนระหว่างการเริ่มต้นฐานข้อมูล
ผลกระทบต่อแอปพลิเคชันฐานข้อมูล
สถานการณ์นี้เน้นย้ำถึงความท้าทายที่กว้างขึ้นในการออกแบบระบบฐานข้อมูล: การสร้างสมดุลระหว่างประสิทธิภาพกับความปลอดภัยในการกำหนดค่าเริ่มต้น ในขณะที่ความยืดหยุ่นของ SQLite ช่วยให้นักพัฒนาเลือกการตั้งค่าที่เหมาะสมสำหรับกรณีการใช้งานของตน ค่าเริ่มต้นที่ไม่สอดคล้องกันในแต่ละแพลตฟอร์มอาจนำไปสู่พฤติกรรมที่ไม่คาดคิดในสภาพแวดล้อมการผลิต
การค้นพบนี้เป็นเครื่องเตือนใจว่านักพัฒนาควรตรวจสอบและกำหนดค่าการตั้งค่าฐานข้อมูลที่สำคัญต่อแอปพลิเคชันของตนอย่างชัดเจน แทนที่จะสมมติว่ามีพฤติกรรมที่สอดคล้องกันในสภาพแวดล้อมการปรับใช้ที่แตกต่างกัน สำหรับแอปพลิเคชันที่ต้องการการรับประกันความทนทานที่แข็งแกร่ง การตั้งค่า synchronous=FULL
อย่างชัดเจนช่วยให้แน่ใจว่ามีพฤติกรรมที่สอดคล้องกันโดยไม่คำนึงถึงตัวเลือกของแพลตฟอร์มหรือการแจกจ่ายพื้นฐาน
อ้างอิง: SQLite (with WAL) doesn't do fsync
on each commit under default settings