Jepsen: TigerBeetle 0.16.11
(jepsen.io)- TigerBeetle เป็นฐานข้อมูล OLTP ที่ออกแบบมาเฉพาะสำหรับ ระบบบัญชีแบบเดบิต-เครดิตคู่ โดยพัฒนาขึ้นโดยมุ่งเน้น ความปลอดภัยและความเร็วในการประมวลผล
- รองรับโปรโตคอลฉันทามติ Viewstamped Replication และเกณฑ์ strong serializability พร้อมโครงสร้างที่เหมาะกับงาน คอนเทนชันสูงและปริมาณงานสูง
- มีการออกแบบและขั้นตอนทดสอบที่ให้ความสำคัญอย่างมากกับ ความทนทานต่อความขัดข้อง และการกู้คืนจากปัญหา โดยมุ่งให้ทำงานได้โดยไม่สูญเสียข้อมูลในสถานการณ์ขัดข้องหลากหลายรูปแบบ
- การทดสอบของ Jepsen พบ บั๊กและปัญหาด้านประสิทธิภาพหลายประการ ในด้านการอัปเกรด การทดสอบ แบบจำลองการดำเนินการ และความทนทานของคลัสเตอร์ต่อความขัดข้อง ซึ่งช่วยให้สามารถปรับปรุงแนวทางรับมือได้
- เวอร์ชันล่าสุดมีการปรับปรุงและแก้ไขบั๊กหลายด้าน เช่น ประสิทธิภาพการทำ replication แบบ Ring, การจัดการข้อผิดพลาดของไคลเอนต์, ความถูกต้องของ logging และ query
แนะนำ TigerBeetle
- TigerBeetle เป็น ฐานข้อมูลสำหรับการประมวลผลธุรกรรมออนไลน์ (OLTP) ที่ออกแบบมาเฉพาะสำหรับ ระบบบัญชีแบบเดบิต-เครดิตคู่ (Double-entry bookkeeping)
- รับประกัน strong serializability บนพื้นฐานโปรโตคอลฉันทามติ Viewstamped Replication(VR) และจัดเก็บเฉพาะข้อมูลบัญชีและการโอนระหว่างบัญชี
- เหมาะกับสภาพแวดล้อมที่ มีปริมาณธุรกรรมสูงและมีการแข่งขันด้าน concurrency รุนแรง เช่น ระบบสวิตช์ภายในธนาคาร นายหน้า การออกตั๋ว และการวัดพลังงานไฟฟ้า
- ใช้สถาปัตยกรรมที่ให้โหนดเดี่ยว (Core) ดูแลการเขียนทั้งหมด จึง มุ่งเน้นการขยายแบบ Scale-up มากกว่า Scale-out
- ตั้งเป้าเพิ่ม throughput ของโหนดเดี่ยวให้สูงสุดผ่าน การประมวลผลแบบ batch, IO แบบขนาน, fixed schema และการเพิ่มประสิทธิภาพที่เป็นมิตรกับฮาร์ดแวร์
ความสามารถในการกู้คืนจากความขัดข้องและการทนต่อความผิดพลาด
- TigerBeetle มีแบบจำลองและขั้นตอนกู้คืนอย่างชัดเจนสำหรับ ความผิดพลาดของหน่วยความจำ โปรเซส นาฬิกา สตอเรจ และเครือข่าย
- ความทนทานของข้อมูลรับประกันว่า ตราบใดที่ยังเหลือ replica เพียงหนึ่งชุด ข้อมูลจะไม่สูญหาย
- หากบันทึกของ replica ทั้งหมดเสียหาย ระบบจะหยุดอย่างปลอดภัย
- สมมติให้เกิดความขัดข้องได้หลากหลาย เช่น ปัญหาฮาร์ดแวร์/ซอฟต์แวร์ของระบบ ความคลาดเคลื่อนของนาฬิกา ความเสียหายของดิสก์ ความหน่วงของเครือข่าย การสูญหาย และข้อมูลซ้ำ
- ใช้ Viewstamped Replication, เทคนิค Protocol-Aware Recovery, checksum ของบล็อกข้อมูล และการจัดเก็บหลาย replica
- ใช้ runtime verification (assertion) เพื่อลดผลกระทบเมื่อเกิดข้อผิดพลาดหรือบั๊ก
วิธีการอัปเกรด
- ไบนารีมีทั้ง โค้ดของเวอร์ชันปัจจุบันและหลายเวอร์ชันก่อนหน้า รวมอยู่ด้วย
- การอัปเกรดทำได้ง่ายเพียงแค่เปลี่ยนไบนารี
- ทุกโหนดในคลัสเตอร์จะเปลี่ยนเวอร์ชันแบบ rolling โดยอัตโนมัติ ลดการแทรกแซงจากผู้ใช้ให้น้อยที่สุด
- ป้องกันไม่ให้ operation ที่ commit ในเวอร์ชันหนึ่งถูก commit ซ้ำในอีกเวอร์ชันหนึ่ง จึงช่วย ป้องกันความไม่สอดคล้องของสถานะ
แบบจำลองเวลา
- ใช้นาฬิกาเชิงตรรกะจาก VR view และหมายเลข operation ควบคู่กับ นาฬิกากายภาพแบบไฮบริด (physical time)
- ผู้นำจะรวบรวมเวลา POSIX ของ replica ทั้งหมดและซิงก์กันภายในขอบเขตความคลาดเคลื่อนที่กำหนด
- หากการซิงก์นาฬิกาล้มเหลวนานเกิน 60 วินาที ระบบจะปฏิเสธการให้บริการ
- timestamp ใช้หน่วยเป็น "นาโนวินาทีนับจาก Unix epoch" แต่ คลาดจากเวลา POSIX จริง 27 วินาที
- เมื่อมี leap second หรือการปรับเวลาย้อนกลับ นาฬิกาภายในอาจช้าลง
แบบจำลองข้อมูล
- รองรับข้อมูลเพียงสองประเภทคือ account และ transfer
- แต่ละฟิลด์มีขนาดคงที่ ยึดหลัก immutable และออกแบบบนพื้นฐาน unsigned int
- account นิยามด้วย id แบบ 128 บิตที่ผู้ใช้กำหนดเอง, ledger, flags, เวลาสร้าง และ custom fields
- transfer มี debit/credit account id, code, amount, custom fields ฯลฯ
- transfer รองรับทั้งการทำทันที (ขั้นตอนเดียว) และการประมวลผลแบบ 2 ขั้นตอน (การจองและการยืนยันดำเนินการ)
- การโอนแบบ pending สามารถยกเลิกหรือหมดอายุได้
- สามารถใช้ transfer แบบพิเศษเพื่อปิดหรือเปิดบัญชีใหม่ได้
แบบจำลองการดำเนินการและทรานแซกชัน
- ไคลเอนต์ทำงานเป็นหน่วย คำขอเดี่ยว (batch) สำหรับอัปเดตหรืออ่านสถานะข้อมูล
- แต่ละเหตุการณ์ในคำขอจะถูกประมวลผลตามลำดับและเป็น ทรานแซกชันแบบอะตอมมิก (atomic)
- ไม่รองรับการรันซ้ำ ทรานแซกชันหลายคำขอ หรือ interactive query
- ให้ทั้ง Strong Serializability และ strong session consistency
- รองรับผลลัพธ์สำเร็จ/ล้มเหลวของแต่ละ operation, การคืนรหัสข้อผิดพลาด และการประมวลผลแบบซับซ้อนผ่านฟีเจอร์ chain (subtransaction)
การออกแบบการทดสอบของ Jepsen
- ใช้ไลบรารี Jepsen เพื่อทำ property-based testing และ fault injection
- ทดลองกับคลัสเตอร์ 3–6 โหนดในสภาพแวดล้อมหลากหลาย เช่น LXC และ EC2
- เนื่องจากข้อจำกัดของแบบจำลองข้อมูลทำให้ตรวจสอบความสอดคล้องแบบ list/set เดิมได้ยาก จึงใช้ ลำดับ operation ทั้งหมด (total order) เพื่อตรวจสอบความสอดคล้องของสถานะและเวลา
- ตรวจจับข้อผิดพลาดด้วยวิธีที่เสริมกัน เช่น การตรวจความสอดคล้องตาม timestamp, model checking และ simulation
การตรวจสอบแบบจำลองและการสร้าง operation
- ตรวจสอบความถูกต้องของการทำงาน TigerBeetle อย่างละเอียดด้วย แบบจำลอง state machine แบบ single-thread ขนาดกว่า 1600 บรรทัด
- รองรับ การอนุมานและ rollback สำหรับเงื่อนไขข้อผิดพลาดหลากหลายแบบ เช่น id ซ้ำ, timestamp ไม่ต่อเนื่อง, ข้อจำกัดยอดคงเหลือ และ linked chain
- เพื่อเพิ่มประสิทธิภาพในการตรวจสอบ มีการใช้ วิธีที่หลากหลาย เช่น การสร้าง operation/id, การอัปเดตสถานะ และการผสม query แบบอิงความน่าจะเป็น
การฉีดความขัดข้อง
- ครอบคลุม สถานการณ์ความขัดข้องพื้นฐาน เช่น โปรเซสล่ม (SIGKILL), หยุดชั่วคราว (SIGSTOP), network partition และการเปลี่ยนนาฬิกา
- มีการฉีดความขัดข้องของสตอเรจอย่างละเอียด เช่น การอัปเกรดเวอร์ชัน การจำลองไฟล์เสียหาย และความเสียหายบางส่วนเฉพาะบาง replica
- ใช้หลายสถานการณ์ เช่น ความเสียหายของดิสก์แบบ zigzag (helical) เพื่อตรวจสอบการลดความเป็นไปได้ของข้อมูลสูญหาย
ตัวอย่างบั๊กสำคัญและรายละเอียดการปรับปรุง
ปัญหาในการจัดการ request timeout (#206)
- ตามการออกแบบของ TigerBeetle คำขอจากไคลเอนต์จะไม่มีวัน timeout; จะ retry ไม่สิ้นสุดจนกว่าจะได้รับคำตอบจากคลัสเตอร์
- แต่ในทางปฏิบัติ ไคลเอนต์อย่าง Java อาจเกิด timeout exception ระหว่างการทำงานแบบ async และแอปพลิเคชันก็มักต้องกำหนด timeout ภายนอกเอง
- การออกแบบนี้ทำให้ แยกความต่างระหว่างความล้มเหลวแบบชัดเจนกับข้อผิดพลาดที่ยังไม่แน่ชัดได้ยาก เพราะมันซ่อนทั้งปัญหาเครือข่ายและข้อผิดพลาดที่แน่นอนไว้อย่างกำกวม
- Jepsen แนะนำให้เพิ่มรูปแบบการคืนค่าตามประเภทความล้มเหลว (แน่ชัด/ไม่แน่ชัด) และ ตัวเลือกการ retry
JVM crash จากข้อผิดพลาดของไคลเอนต์ (#2435)
- การห่อด้วย thread/async เพื่อหลบเลี่ยง timeout ทำให้เกิดปัญหา JVM segfault
- เกิดจากการอ้างอิงฟิลด์ที่ยัง initialize ไม่ถูกต้องใน Java client และถูกแก้ไขใน 0.16.12
ไคลเอนต์ล่มเมื่อ session หมดอายุ (#2484)
- เกิด การบังคับปิดไคลเอนต์ จาก session จำนวนมากเกินไป
- ตั้งแต่ 0.16.13 ได้ปรับปรุงให้คืนค่าเป็นข้อผิดพลาดแทน
latency พุ่งสูงเมื่อโหนดเดี่ยวขัดข้อง (#2739)
- จุดอ่อนของ replication แบบ ring ทำให้ เมื่อบางโหนดล้มเหลว เวลาตอบสนองโดยรวมเพิ่มขึ้นอย่างรุนแรง
- สาเหตุคือโดยพื้นฐานแล้ว primary จะส่งข้อความไปยังโหนดถัดไปทีละขั้น ทำให้เมื่อบางโหนดล้มเหลวต้องรอเพราะไม่ได้รับ ack
- หลัง 0.16.30 มีการนำ reverse replication และ dynamic ring topology มาใช้ ทำให้ ลดความหน่วงการตอบสนองระหว่างความขัดข้องได้มาก
บั๊กใน Header API ของ Java client (#2495)
- มีการใช้ singleton object กับ response batch ว่าง ทำให้เกิด ปัญหาการแชร์ header และ timestamp ผิดพลาด
- ไม่กระทบต่อความถูกต้องของข้อมูล แต่ทำให้ผลลัพธ์ของ Header API ปนเปื้อน และถูกแก้ใน 0.16.14
ผลลัพธ์ query หายบางส่วน (#2544)
- มีรายงานบั๊กในเวอร์ชัน 0.16.13 ว่าผลลัพธ์ของ
query_accounts,query_transfersฯลฯ หายไปบางส่วน โดยผลตอบสนองถูกจำกัดไว้เพียง prefix ที่ถูกต้อง
บทสรุป
- TigerBeetle เหมาะอย่างยิ่งกับ สภาพแวดล้อมด้านการเงินและบัญชีที่ต้องการความปลอดภัยสูงและการทนต่อความขัดข้อง
- ชุดทดสอบของ Jepsen เผยให้เห็นประเด็นด้าน ความสามารถในการกู้คืน ความสอดคล้อง แบบจำลองการดำเนินการ และประสิทธิภาพ ที่หลากหลาย
- ด้วยความร่วมมืออย่างต่อเนื่อง จึงเกิดการปรับปรุงที่เป็นรูปธรรมในด้าน การกู้คืนจากความขัดข้อง การจัดการข้อผิดพลาดของไคลเอนต์ การทำ replication และระบบอัตโนมัติของการอัปเกรด
- ในเวอร์ชันล่าสุด ระบบมีความน่าเชื่อถือสูงขึ้นอย่างมาก ทั้งด้านการรับมือความขัดข้อง การรับประกันการเชื่อมต่อและการตอบสนอง และความสอดคล้องของ operation
(เนื้อหาบางส่วนนี้อ้างอิงจากโอเพนซอร์สหลายแหล่ง เช่น Github, เอกสารทางการของ TigerBeetle และรายงานการทดสอบ Jepsen)
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
โพสต์ Fuzzer Blind Spots (Meet Jepsen!) ก็เป็นข้อมูลอ้างอิงที่ควรอ่านด้วย โดยมีลิงก์แนะนำ https://tigerbeetle.com/blog/2025-06-06-fuzzer-blind-spots-meet-jepsen/
ขอแชร์ประสบการณ์ที่มักจะใช้รายงาน Jepsen เป็นด่านสุดท้ายในการตรวจสอบคำกล่าวอ้างเรื่องความน่าเชื่อถือและการสเกลของ TigerBeetle เสมอ รายงานครั้งนี้พบปัญหาหลายอย่าง แต่ก็ดูชอบแนวทางวิศวกรรมที่แก้ไขได้รวดเร็วและยังเสริม test suite ภายในเพื่อไม่ให้บั๊กลักษณะเดียวกันเกิดซ้ำ หากยังรักษาท่าทีแบบนี้ไว้ได้ ก็คาดหวังว่าอีก 10 ปีข้างหน้าในวงการฐานข้อมูลเฉพาะทางการเงิน TigerBeetle อาจขึ้นไปถึงสถานะค่าเริ่มต้นแบบ “ก็ใช้ Postgres ไปสิ” ได้เลย และขอยืนยันว่าได้เรียนรู้อะไรมากมายจากงานยอดเยี่ยมของ aphyr
รู้สึกดีใจที่ TigerBeetle ผ่านการตรวจสอบของ aphyr และทำได้ตามที่สัญญาไว้ ทำให้ยังพอมีความหวังว่าการทำสิ่งที่ถูกต้องจะนำไปสู่ผลลัพธ์ที่ถูกต้องได้ ในงานจริงข้อมูลที่อยู่นอกเหนือจาก Account หรือ Transfer มักจะถูกเก็บไว้ในระบบภายนอกและฐานข้อมูลอีกชุดหนึ่ง จึงอยากถามว่าปัญหาเรื่อง consistency และการกู้คืนระหว่างระบบภายนอกที่เชื่อถือได้น้อยกว่าเหล่านี้กับ TigerBeetle ในทางปฏิบัติจัดการกันอย่างไร
หากเคยอ่านโพสต์เรื่อง fuzzer blind spot ของ Jepsen มาก่อน ก็จะยิ่งรู้สึกว่ารายงาน TigerBeetle ครั้งนี้น่าสนใจมากขึ้น กรณี segfault ฝั่ง JNI อาจไม่ถูกป้องกันแม้จะใช้ภาษา memory-safe อย่าง Rust แต่แนวทาง Zig/TigerStyle ของ TigerBeetle ก็ดูเป็นหลักฐานที่ดีในเรื่อง memory safety
ชอบไหวพริบในการตั้งชื่อหัวข้อ "Panic! At the Disk 0" มาก ขอปรบมือเบา ๆ แบบนักกอล์ฟให้เลย
ประทับใจกับรายงาน Jepsen แบบละเอียดฉบับนี้มาก ทั้งที่ยังไม่ถึง v1.0 ก็ยังคาดหวังสูงอยู่แล้ว และอยากชมเพิ่มเติมที่ผู้ก่อตั้งเข้ามาแชร์ insight ในเธรดอย่างกระตือรือร้น
ในการทดสอบ distributed systems ประเด็นที่น่าสนใจคือการที่ระบบต้องรายงานลำดับ/เวลาเหตุการณ์ที่เกิดขึ้นภายในระบบเอง เพื่อนำไปเทียบกับโมเดลภายนอกอย่างแม่นยำ ซึ่งดูเป็นเงื่อนไขจำเป็นสำหรับการตรวจสอบที่ถูกต้อง และก็ให้ความรู้สึกว่า “จริงจนแทบไม่ต้องพูด”
หลังจากดูรายงาน Jepsen บล็อกที่เกี่ยวข้อง และโค้ดเชื่อมต่อกับ Antithesis แล้ว เลยอยากถามในเชิงเรียนรู้เรื่องขอบเขตและประสิทธิผลของการทดสอบ เข้าใจว่า TigerBeetle มีการทดสอบแบบครอบคลุมด้วย Antithesis อยู่แล้ว จึงสงสัยว่าบั๊กที่ Jepsen พบ หลุดจาก Antithesis ไปได้อย่างไร ความต่างระหว่างการทดสอบของ Antithesis กับ Jepsen คืออะไร และท้ายที่สุดขอบเขตการทดสอบภายในต่างกันอย่างไรแบบเจาะจง
สนใจ TigerBeetle อยู่ และรู้สึกแปลกใจที่ในเอกสารไคลเอนต์ไม่มี C หรือ Zig client ทั้งที่ตัวระบบเขียนด้วย Zig เอง เลยอยากถามว่าไคลเอนต์นี้ยังไม่มีจริง ๆ หรือกำลังพัฒนาอยู่
สงสัยว่า TigerBeetle ถูกใช้งานแล้วในธนาคารใหญ่หรือบริษัทหลักทรัพย์หรือยัง