การตรวจสอบ TigerBeetle 0.16.11 โดย Jepsen
(jepsen.io)- ฐานข้อมูล OLTP สำหรับบัญชีคู่ TigerBeetle ชูจุดเด่นด้านความปลอดภัยและความเร็ว โดย Jepsen ได้ตรวจสอบตระกูลเวอร์ชัน 0.16.11~0.16.30 บนคลัสเตอร์ Debian 3~6 เครื่องพร้อมการฉีดความขัดข้อง
- การทดสอบผสาน ลำดับ timestamp แบบชัดเจนเข้ากับโมเดล state machine แบบเธรดเดียวอิงเอกสาร เพื่อตรวจสอบทั้ง Strong Serializability และความหมายเชิง semantics ของบัญชี การโอน และ query
- บั๊กด้านความปลอดภัยหลักคือผลลัพธ์ query แบบหลาย filter หายไป และข้อผิดพลาด timestamp ใน header ของ Java client โดยใน 0.16.26 ขึ้นไป พบผลลัพธ์ที่สอดคล้องกับคำกล่าวอ้างเรื่อง Strong Serializability แม้ภายใต้ชุดความขัดข้องหลายแบบ
- ในแง่ availability พบปัญหา client retry ไม่สิ้นสุด, process crash เมื่อมี session eviction, latency พุ่งสูงเมื่อ node เดียวล้มเหลว, server panic ระหว่าง disk bit flip และการอัปเกรด, และไม่มีเส้นทางกู้คืนเมื่อ disk ของ node เดียวสูญหาย
- TigerBeetle 0.16.43 ได้นำประเด็นที่รายงานส่วนใหญ่ไปแก้ไขแล้ว โดยรวมถึงการลด latency เมื่อ node เดียวล้มเหลวและ
tigerbeetle recoverและผู้ดูแลระบบจำเป็นต้อง ตรวจสอบ release notes เมื่ออัปเกรดเป็น 0.16.43 และเมื่อย้ายไปใช้ 0.16.26 ขึ้นไป
การออกแบบและขอบเขตการทดสอบของ TigerBeetle
- TigerBeetle เป็นฐานข้อมูล OLTP สำหรับบัญชีคู่ ซึ่งจัดเก็บเฉพาะบัญชี (accounts) และการโอน (transfers) แทนที่จะเก็บ row, object, graph หรือ blob แบบทั่วไป
- ให้คำมั่นว่าจะมอบความสอดคล้องแบบ Strong Serializable โดยอิงจาก Viewstamped Replication (VR) และออกแบบมาให้เหมาะกับโมเดลอย่างธุรกรรมการเงิน สินค้าคงคลัง การจำหน่ายตั๋ว และการวัดค่าสาธารณูปโภค
- เพื่อรองรับ workload ที่มี contention สูงและ throughput สูง จึงส่งการเขียนทั้งหมดผ่าน core เดียวของ primary VR node และมุ่งเน้น scale-up มากกว่า scale-out
- เพื่อประสิทธิภาพ ใช้การประมวลผลแบบ batch, การทำ I/O แบบขนาน, schema คงที่ และโครงสร้างข้อมูลขนาดคงที่ที่จัดแนวกับ cache
- โมเดลความขัดข้องจัดการกับหน่วยความจำ process นาฬิกา storage และ network อย่างชัดเจน
- process อาจหยุดทำงานหรือ crash ได้
- นาฬิกาอาจกระโดดไปข้างหน้าหรือย้อนหลังได้
- disk อาจไม่เพียงล้มเหลวทั้งหมด แต่ยังเกิดความเสียหายจากการเขียนบางส่วนและข้อมูลปนเปื้อนได้ด้วย
- network อาจทำให้เกิด delay, drop, duplicate, ส่งผิดที่ และทำให้ message เสียหายได้
- TigerBeetle ใช้ deterministic simulation testing และการทดสอบ VOPR จะจำลองทั้งคลัสเตอร์ รวมถึง clock, disk และ network interface
โมเดลข้อมูลและ semantics ของคำขอ
- โมเดลข้อมูลประกอบด้วย record สองชนิดคือ accounts และ transfers
- บัญชีมีฟิลด์อย่าง
idแบบ 128 บิตที่ผู้ใช้กำหนด,ledger,flags,timestamp,code,user_data_32,user_data_64,user_data_128เป็นต้น - การโอนเป็น record แบบ immutable ที่มี
debit_account_id,credit_account_id,amount,ledger,flags, ฟิลด์ที่ผู้ใช้กำหนด และอื่น ๆ
- บัญชีมีฟิลด์อย่าง
- การโอนสามารถ post ได้ทันทีในขั้นตอนเดียว และยังรองรับ การโอนแบบ 2 ขั้นตอน ที่แบ่งเป็น pending กับ post/void ได้ด้วย
- pending transfer จะจอง capacity ของบัญชี debit และ credit
- หลังจากนั้นสามารถ post หรือ void ได้ไม่เกิน pending amount
- ควบคุมการหมดอายุอัตโนมัติด้วยฟิลด์
timeout
- บัญชีเป็น immutable ยกเว้น flag
closedและฟิลด์ยอดคงเหลือสี่รายการ ส่วนการโอนเป็น immutable เสมอ- หากต้องการแก้ไขหรือย้อนกลับการโอน ต้องสร้างการโอนชดเชยใหม่
- คำขอแทน logical operation ชนิดเดียว และโดยทั่วไปมี batch สูงสุด 8190 events
create_accounts,create_transfersเป็นคำขอเขียนlookup_accounts,lookup_transfers,query_accounts,query_transfers,get_account_transfers,get_account_balancesเป็นคำขออ่าน
- คำขอแต่ละรายการเป็น transaction หนึ่งรายการจากมุมมองของฐานข้อมูล แต่บาง event ภายในคำขอที่ commit แล้วอาจล้มเหลวเชิงตรรกะและคืน error code ได้
- หากต้องการ atomicity แบบมีเงื่อนไขระหว่าง event ให้ใช้ chain เพื่อให้ event ใน chain เดียวกันทั้งหมดสำเร็จหรือทั้งหมดล้มเหลว
วิธีการทดสอบของ Jepsen
- test suite ของ Jepsen ใช้ Jepsen testing library เพื่อผสาน property-based testing กับการฉีดความขัดข้อง
- เป้าหมายการทดสอบคือ TigerBeetle ตั้งแต่ 0.16.11 ถึง 0.16.30 รวมถึง build สำหรับพัฒนาหลายรายการด้วย
- คลัสเตอร์ประกอบด้วย node Debian 3~6 เครื่อง
- รันทั้งบน LXC container และ EC2 VM
- client ทางการของ TigerBeetle เป็น smart client ที่เชื่อมต่อกับทุก node จึงอาจซ่อนข้อผิดพลาดด้าน concurrency ได้
- Jepsen ทดสอบพฤติกรรม smart-client ปกติด้วย
- พร้อมกันนั้นยังใช้วิธีจำกัด client แต่ละตัวไว้กับ node เดียวด้วย
- verifier ทำงานสองขั้นตอน
- อ่าน timestamp การรันของคำขอที่สำเร็จ และสำหรับการเขียนที่ล้มเหลวหรือ timeout จะอนุมาน timestamp จากผลกระทบที่สังเกตเห็นภายหลัง
- รันโมเดล state machine ของ TigerBeetle ที่อิงเอกสารตามลำดับ timestamp ที่อนุมานได้ เพื่อตรวจสอบผลลัพธ์และ error code
- โมเดล state machine เขียนด้วย Clojure มากกว่า 1,600 บรรทัด และรวมถึง map ของบัญชีและการโอน, index, transient error, สถิติภายใน, การเดินของเวลา เป็นต้น
- รองรับ ID ซ้ำ, timestamp ที่ไม่เป็น monotonic, ข้อจำกัดยอดคงเหลือ, flag ที่เข้ากันไม่ได้, speculative execution และ rollback ของ chain เป็นต้น
- ใช้ไลบรารี persistent data structure ประสิทธิภาพสูง Bifurcan
การฉีดความขัดข้องและการทดสอบไฟล์เสียหาย
- Jepsen ฉีด process
SIGKILL,SIGSTOP, network partition หลายรูปแบบ, การเปลี่ยนนาฬิกาตั้งแต่มิลลิวินาทีไปจนถึงหลายร้อยวินาที และการเปลี่ยนนาฬิกาไป-กลับอย่างรวดเร็ว - ระหว่างการทดสอบยังดำเนินการ อัปเกรด node ไปยังหลายเวอร์ชันด้วย
- สร้าง storage fault หลากหลายรูปแบบด้วย nemesis สำหรับความเสียหายของไฟล์ตัวใหม่
- จำลองความเสียหายแบบการรบกวนจากรังสีคอสมิกด้วยการ flip bit แบบสุ่ม
- จำลอง misdirected write โดยแทนที่ chunk ของไฟล์ด้วย chunk อื่น
- จำลอง lost write โดยกู้คืน snapshot ของ chunk ไฟล์ในภายหลัง
- node ของ TigerBeetle มีไฟล์ข้อมูลเดี่ยว และไฟล์แบ่งเป็น zone ตาม offset ที่คาดการณ์ได้
- ทำการทดสอบที่ทำให้เสียหายเฉพาะ zone บางประเภท เช่น WAL header และสำเนาซ้ำของ superblock zone
- รวมถึงการทดสอบที่ทำให้หลาย zone หรือทั้งไฟล์เสียหายด้วย
- disk fault แบบ “helical” คือการทำให้ไฟล์ของทุก node เสียหาย แต่ทำให้ chunk ที่เสียหายในแต่ละ node แตกต่างกัน
- จุดประสงค์คือหลีกเลี่ยงสถานการณ์ที่ record เดียวเสียหายจนกู้คืนไม่ได้ใน replica ทุกตัว เพราะโดยทั่วไปการจัดวางไฟล์ replica ล่าสุดของ TigerBeetle จะเหมือนกันแบบ bit-for-bit
- head ของ WAL เป็นข้อยกเว้น เนื่องจากตำแหน่งอาจแตกต่างกันในแต่ละ node
ปัญหาด้านความปลอดภัยที่พบ
- ใน 0.16.13 พบปัญหาบ่อยครั้งที่การตอบกลับของ
query_accounts,query_transfers,get_account_transfersขาดผลลัพธ์บางส่วนหรือทั้งหมด- ผลลัพธ์ที่หายไปจะอยู่ช่วงท้ายของการตอบกลับเสมอ และการตอบกลับเป็น prefix ของผลลัพธ์ที่ถูกต้อง
- ไม่พบในคิวรีที่ใช้ฟิลเตอร์เดี่ยว แต่เกิดขึ้นกับการผสมฟิลเตอร์หลายตัว เช่น
ledgerและcode - สาเหตุคือบั๊ก bounds check ใน zig-zag merge join ระหว่างหลายอินเด็กซ์
- ติดตามใน #2544 และแก้ไขแล้วใน 0.16.17
- API header ของ Java client ที่เพิ่มเข้ามาเพื่อรองรับการทดสอบ Jepsen ใน 0.16.13 ส่งคืน timestamp การรันที่ผิดพลาดหรือซ้ำกัน
- สาเหตุคืออ็อบเจกต์ตอบกลับแบบ mutable singleton
Batch.EMPTYของ Java client - เมื่อการตอบกลับสำเร็จถูกแทนด้วย batch ว่าง การตอบกลับหลายรายการจึงเขียนทับ header ของอ็อบเจกต์เดียวกัน
- แก้ไขด้วย #2495 และรวมอยู่ใน 0.16.14
- ไม่กระทบต่อความสอดคล้องของข้อมูลจริง กระทบเฉพาะ timestamp ของคำขอใน Java client header API เท่านั้น
- สาเหตุคืออ็อบเจกต์ตอบกลับแบบ mutable singleton
- ผลลัพธ์ที่สังเกตได้ใน 0.16.26 ขึ้นไปสอดคล้องกับข้ออ้าง Strong Serializability ของ TigerBeetle
- คุณสมบัตินี้ยังคงรักษาไว้ได้แม้ในกรณีที่มีการผสมกันของ process pause, crash, network partition, clock error, disk corruption และ upgrade
ปัญหาเกี่ยวกับ client และการประมวลผลคำขอ
- เอกสารของ TigerBeetle อธิบายว่าคำขอจะไม่ timeout และ client จะ retry ต่อไปจนกว่าจะได้รับการตอบกลับ
- เมธอดแบบ async ของ Java คืนค่า
CompletableFutureและสามารถใช้ timeout API เช่น.get(timeout, timeUnit)หรือ.orTimeout(...)ได้ Taskของ .NET client ก็มีWait()ที่อิง timeout ให้ใช้เช่นกัน
- เมธอดแบบ async ของ Java คืนค่า
- การ retry แบบไม่สิ้นสุดอาจซ่อนทั้ง definite error และ indefinite error ได้
- เช่น หากการเชื่อมต่อ TCP ล้มเหลวด้วย
ECONNREFUSEDคำขอเดิมนั้นจะไม่สามารถถูกรันได้ ซึ่งเป็น definite failure - แต่ถ้า client ไม่แจ้งเรื่องนี้แก่ผู้เรียกและทำเพียง retry ภายในต่อไป จากมุมมองของผู้เรียกก็จะกลายเป็น indefinite failure เช่น timeout หรือการหยุดกลางคัน
- เช่น หากการเชื่อมต่อ TCP ล้มเหลวด้วย
- ประเด็นนี้กำลังหารือใน #206 และตามรายงานยัง unresolved
- Jepsen แนะนำให้แสดง definite error และ indefinite error เป็นสิ่งระดับ first-class และส่งคืนให้ผู้เรียก
- สามารถคง automatic retry ไว้ได้ แต่ควรกำหนดค่าได้ และแนะนำให้มีตัวเลือกสำหรับเวลาสูงสุดในการเริ่มการเชื่อมต่อและการรอการตอบกลับ
- Java client 0.16.11 มีปัญหาที่ JVM ทั้งหมด segfault เมื่อ interrupt เธรดที่เรียกแบบ synchronous เพื่อจัดการ timeout หรือเมื่อ close client หลังจากเรียกแบบ asynchronous
- สาเหตุคือ unset field ใน request data structure
- หาก client ถูกปิดระหว่างการสร้างและการส่ง request จะ dereference ที่อยู่ค่าเริ่มต้นของ Zig อย่าง
0xaaa... - แก้ไขด้วย #2435 และรวมอยู่ใน 0.16.12
- official client เคยทำให้ทั้ง process crash เมื่อ server แจ้ง session eviction
- โดยค่าเริ่มต้น TigerBeetle จำกัด concurrent session ไว้ที่ 64 รายการ
- eviction ยังเกิดขึ้นได้เมื่อใช้ client เวอร์ชันใหม่กว่า server
- หลัง #2484 ตั้งแต่ 0.16.13 เป็นต้นมา เมื่อเกิด eviction จะส่งคืน error ให้ผู้เรียกแทนการทำให้ process crash
latency พุ่งสูงเมื่อโหนดเดียวล้มเหลว
- พบซ้ำหลายครั้งว่า client latency เพิ่มขึ้นถึง 3~5 หลัก เมื่อมีความล้มเหลวของโหนดเดียว
- ในคลัสเตอร์ 5 โหนด เมื่อฆ่าโหนดเดียว minimum latency เพิ่มจากต่ำกว่า 1ms เป็น 10 วินาที
- ในการทดสอบคลัสเตอร์ 3 โหนดที่ฆ่าโหนดหนึ่ง latency ที่เคยอยู่ 1~50ms เพิ่มเป็นประมาณ 100 วินาทีต่อคำขอ และคงอยู่นานเกือบ 1000 วินาทีจนกว่าจะรีสตาร์ตโหนด
- สาเหตุเกี่ยวข้องกับวิธีเผยแพร่ prepare ของ TigerBeetle
- VR แบบดั้งเดิมจะให้ primary ส่ง prepare ไปยัง secondary ทั้งหมดและรับ ack โดยตรง
- TigerBeetle จัดวางโหนดเป็น ring และเมื่อ primary ส่ง prepare ไปยัง secondary ถัดไป secondary แต่ละตัวจะส่งต่อไปยังโหนดถัดไป
- วิธีนี้ลดความต้องการ bandwidth ของโหนดหนึ่ง ๆ แต่ถ้า replica หนึ่งใน
fตัวถัดไปของ ring ล้มเหลว commit อาจถูกบล็อกได้
- ประเด็นนี้ติดตามใน #2739
- 0.16.30 บรรเทาปัญหาด้วยการส่งข้อความ prepare ครึ่งหนึ่งไปในทิศทางตรงข้ามของ ring
- prepare บางส่วนสามารถอ้อมโหนดที่ล้มเหลวได้
- ในการทดสอบของ Jepsen latency ระดับ 100 วินาทีลดลงมาอยู่ระดับ 1~30 วินาที
- 0.16.43 มีการปรับปรุงประสิทธิภาพเพิ่มเติม
- โหนด replicate ทั้งสองทิศทางของ ring
- ring topology เปลี่ยนแบบไดนามิก และคลัสเตอร์ปรับลำดับโหนดตามสภาพเครือข่ายและความล้มเหลว
disk corruption และ server crash
- ใน 0.16.20 มีบางกรณีที่ความเสียหายแบบบิตเดียวใน superblock, WAL และ grid zone ทำให้เกิด startup crash
- log แสดง
panic: reached unreachable codeแล้วจบการทำงาน - สาเหตุคือบั๊กในการตรวจสอบ sector padding
- log แสดง
- checksum ของ TigerBeetle ครอบคลุมข้อมูลของ chunk แต่ไม่รวม padding
- หากบิต 0 ใน padding เปลี่ยนเป็น 1 checksum จะยังผ่าน
- จากนั้น assertion ที่ตรวจว่าค่า padding ยังคงเป็น 0 จะล้มเหลว ทำให้ server crash
- ความเสียหายของ padding ไม่กระทบ safety และสามารถทำให้กลับเป็น 0 หรือกู้คืนจาก replica อื่นได้
- เดิม VOPR ไม่พบบั๊กนี้เพราะทำให้ทั้ง sector เสียหาย
- ความเสียหายของ sector ทำให้ checksum ล้มเหลวและเข้าสู่เส้นทาง repair จึงไปไม่ถึง padding assertion
- TigerBeetle เพิ่ม single-byte error เข้าไปใน VOPR ใน #2681
- ตั้งแต่ 0.16.26 เป็นต้นมา sector ที่ padding เสียหายจะถูก repair แทนการ crash
- การ bitflip ของ copy number ใน superblock ก็สามารถทำให้เกิด panic แบบเดียวกันได้
- superblock ทั้งสี่ copy มีหมายเลข
copyแบบ 2 ไบต์ที่ต่างกัน และ checksum จะข้ามหมายเลขนี้ - เมื่อ copy number ที่เสียหายบนดิสก์ถูกอ่านเข้าหน่วยความจำ แล้วมีการ write จะทำให้ assertion ช่วงค่า 0~3 ล้มเหลว
- แก้ไขใน 0.16.26 ด้วยการ reset copy number
- superblock ทั้งสี่ copy มีหมายเลข
ปัญหาเกี่ยวกับการอัปเกรด
- พบ crash แบบ
panic: checkpoint divergedซ้ำ ๆ เมื่ออัปเกรดจาก 0.16.25 หรือต่ำกว่าไปเป็น 0.16.26 หรือสูงกว่า- สาเหตุคือการเปลี่ยนแปลงโครงสร้าง
CheckpointStateใน 0.16.26 - เวอร์ชันใหม่รวมชุดของ released blocks ไว้ด้วย แต่ในกระบวนการส่งต่อสถานะเพื่อความเข้ากันได้กับเวอร์ชันก่อนหน้า ข้อมูลนี้อาจว่างได้
- หลังจากนั้น หากโหนดรีสตาร์ตเป็น 0.16.26 อาจอยู่ในสภาพที่สูญเสีย released blocks ที่ replica อื่นรู้จัก
- assertion ตรวจพบ divergence และทำให้ crash เพื่อไม่ให้ไคลเอนต์สังเกตเห็นข้อมูลที่ไม่สอดคล้องกัน
- สาเหตุคือการเปลี่ยนแปลงโครงสร้าง
- ปัญหานี้ถูกบันทึกไว้ใน changelog ผ่าน #2745
- TigerBeetle ไม่ได้ออก 0.16.26 ที่แพตช์แล้ว
- ผู้ดูแลระบบต้องหยุด client และรอให้ replica catch up ก่อนอัปเกรดเป็น 0.16.26 หรือสูงกว่า
- เมื่อทำหลาย upgrade จาก 0.16.16 ไปเป็น 0.16.28 ต่อเนื่องกันภายในประมาณ 20 วินาที หรือเมื่อโหนดถูก pause/crash ระหว่าง upgrade จะเกิด
release_transitionassertion failure- โหนดที่กำลังทำงานจะเปิด binary ใหม่ด้วย
memfdและแทนที่ด้วยexec()แต่ระหว่างนั้น binary บนดิสก์อาจถูกแทนที่ด้วยเวอร์ชันที่ใหม่กว่า - โค้ด assert ว่า version header บนดิสก์ต้องเหมือนกับเวอร์ชันที่กำลังรันอยู่ จึงล้มเหลว
- #2758 เปลี่ยน assertion เป็น warning ใน 0.16.29
- โหนดที่กำลังทำงานจะเปิด binary ใหม่ด้วย
- เมื่ออัปเกรดจาก 0.16.26 เป็น 0.16.27 เกิด
panic: switch on corrupt valueเนื่องจาก deprecated message type- ในคำสั่ง
switchของโหนดใหม่ไม่มี case สำหรับ message type เก่า จึง crash - #2763 แก้ไขใน 0.16.29 โดยเพิ่ม deprecated message type กลับเข้าไปใน case และให้เพิกเฉย
- ในคำสั่ง
การกู้คืนเมื่อดิสก์ของโหนดเดี่ยวสูญหาย
- TigerBeetle ทนต่อความเสียหายของไฟล์ได้ดี แต่ไฟล์ข้อมูลทั้งหมดของโหนดอาจหายไปหรือเสียหายจนกู้คืนไม่ได้จากเหตุการณ์อย่างดิสก์ล้มเหลว ไฟไหม้ EBS volume error หรือความผิดพลาดของผู้ดูแลระบบ
- ในเอกสาร ณ เวลาที่รายงาน ยังไม่มีวิธีเปลี่ยนโหนดที่ล้มเหลว และมีขั้นตอน recovery ที่ไม่ได้ระบุในเอกสาร โดยรัน
tigerbeetle formatเพื่อเริ่มต้นใหม่ด้วยไฟล์ข้อมูลว่าง แล้วคาดหวังให้มีการ repair - Jepsen พบว่าการ reformat ใช้งานได้เป็นส่วนใหญ่ แต่อาจไม่ปลอดภัย
- หากใน 2 จาก 3 โหนดมี committed operation
opอยู่ และ reformat หนึ่งในนั้น majority 2/3 ที่ไม่เคยสังเกตเห็นopอาจทำ view change และทำให้ operation สูญหายได้ - ในการทดสอบจริง มี run หนึ่งที่ acknowledged transfer 5 รายการสูญหาย
- ยังมีกรณีที่โหนดซึ่งถูก format ด้วย binary ที่ใหม่กว่าระหว่าง upgrade เกิด startup crash ก่อนที่ cluster version transition จะเสร็จสมบูรณ์
- หากใน 2 จาก 3 โหนดมี committed operation
- ปัญหานี้ถูกติดตามใน #2767
- ต่อมา TigerBeetle 0.16.43 ได้รวมคำสั่ง
tigerbeetle recoverสำหรับกู้คืนโหนดที่ประสบ catastrophic data loss
ข้อสรุปและคำแนะนำของ Jepsen
- ปัญหาด้านความปลอดภัยที่พบมีสองรายการ
- ผลลัพธ์ query แบบหลาย filter ขาดหายไปในเวอร์ชันก่อน 0.16.17
- timestamp ที่ผิดพลาดและซ้ำกันใน Java client debugging API สำหรับการทดสอบของ Jepsen
- ปัญหา crash มีทั้งหมด 7 รายการ
- Java client 2 รายการ: uninitialized memory access, process crash ระหว่าง eviction
- เซิร์ฟเวอร์ 5 รายการ: panic ที่เกี่ยวข้องกับความเสียหายของดิสก์ 2 รายการ, panic ที่เกี่ยวข้องกับการอัปเกรด 3 รายการ
- #2745 ถูกบันทึกในเอกสารแล้ว และ crash ที่เหลือได้รับการแก้ไขภายใน 0.16.29
- 0.16.43 แก้ไขประเด็นในรายงานทั้งหมด ยกเว้นหนึ่งรายการ
- รายการที่ยัง unresolved คือปัญหาที่ client request จะ retry ต่อไปตามการออกแบบ
- คำแนะนำสำหรับผู้ใช้ชัดเจน
- อัปเกรดเป็น 0.16.43
- ตรวจสอบ release note เมื่อย้ายไปเป็น 0.16.26 หรือเวอร์ชันหลังจากนั้น
- จำลองความล้มเหลวของโหนดเดี่ยวในสภาพแวดล้อมทดสอบ และวัดว่าแอปพลิเคชันตอบสนองต่อ latency ที่เพิ่มขึ้นอย่างไร
- สถาปัตยกรรมของ TigerBeetle ดูมีความ sound และสังเกตได้ว่าการผสาน VR, flexible quorum และ protocol-aware recovery ไม่ได้ทำลาย invariant หลักของ Strong Serializability
- อย่างไรก็ตาม การตรวจสอบของ Jepsen เป็นแนวทางเชิงทดลอง ดังนั้นจึงพิสูจน์ได้ว่ามีบั๊กอยู่ แต่พิสูจน์ว่าไม่มีบั๊กไม่ได้
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
บทความที่น่าอ่านประกอบ: Fuzzer Blind Spots (Meet Jepsen!) – https://tigerbeetle.com/blog/2025-06-06-fuzzer-blind-spots-m...
รายงานนี้น่าประทับใจจริง ๆ ทุกครั้งที่เห็น TigerBeetle อ้างเรื่อง ความน่าเชื่อถือและความสามารถในการขยายระบบ ผมมักคิดว่า “โอเค รอดูรายงาน Jepsen กันก่อน”
ในรายงานมีหลายประเด็นออกมาและอาจทำให้กังวลได้ แต่สิ่งที่เป็นบวกคือพวกเขาไม่ได้จบแค่แก้ไขปัญหา แต่ยังขยายชุดทดสอบภายในเพื่อจับบั๊กที่คล้ายกันในอนาคตด้วย ด้วยแนวทางวิศวกรรมแบบนี้ อีก 10 ปีข้างหน้า TigerBeetle อาจกลายเป็นฐานข้อมูลมาตรฐานใน niche ของแอปพลิเคชันการเงิน ระดับที่ว่า “ใช้ Postgres ก็พอ” ได้เลย
งานของ aphyr ก็ยอดเยี่ยม และอ่านรายงานแล้วรู้สึกว่าได้เรียนรู้อะไรมากมาย
นอกเหนือจากนั้น ถ้าไม่นับบั๊กความถูกต้องเล็ก ๆ ในฟีเจอร์ทดสอบภายในที่ใส่ไว้เฉพาะใน Java client เพื่อช่วยการ audit ของ Jepsen แล้ว บั๊กด้านความถูกต้องที่ Jepsen พบมีเพียงตัวเดียว และไม่ได้กระทบต่อ durability บทความที่เกี่ยวข้องอยู่ที่นี่: https://tigerbeetle.com/blog/2025-06-06-fuzzer-blind-spots-m...
ถ้าพูดอย่างเป็นธรรม TigerBeetle ถูกออกแบบและทดสอบให้ทนต่อความล้มเหลวได้มากกว่า Postgres เพราะมีโมเดลความล้มเหลวของ storage ที่ชัดเจน และใช้ประโยชน์จากงานวิจัยที่ยังไม่มีตอน Postgres ออกมาในปี 1996 โมเดลความล้มเหลวของ TB ยังได้รับการตรวจสอบเพิ่มเติมด้วย deterministic simulation testing และใช้เทคนิคอย่างการจัดสรรหน่วยความจำแบบ static ตาม Power of Ten Rules สำหรับ Safety-Critical Code ของ NASA ด้วย ในเอกสารวิชาการมี scenario ที่รู้กันว่า Postgres อาจทำข้อมูลหายได้ แต่ TigerBeetle สามารถตรวจจับและกู้คืนได้
ถ้าอยากดูเพิ่มเติม ให้ดูส่วน helical fault injection ในรายงานของ Kyle ได้ การทำงานส่วนใหญ่ของ Raft และ Paxos ไม่ได้ถูกออกแบบมาให้ทนต่อสิ่งนี้ และยังมีงานนำเสนอ QCon London ด้วย: https://m.youtube.com/watch?v=_jfOk4L7CiY
ดีใจจริง ๆ ที่ผลการตรวจสอบของ aphyr แสดงให้เห็นว่า TigerBeetle ทำได้สอดคล้องกับสิ่งที่ตนเองอ้างไว้ การได้เห็นว่าเมื่อเลือกแนวทางที่ถูกต้องก็ได้ผลลัพธ์ที่ถูกต้องนั้นเป็นเรื่องดี
ผมสงสัยว่าในการใช้งานจริง TigerBeetle จะถูกใช้กันอย่างไร รอบ ๆ การติดตั้ง TigerBeetle น่าจะมีระบบภายนอกและฐานข้อมูลอื่น ๆ จำนวนมากสำหรับทุกอย่างที่ไม่ใช่ Account หรือ Transfer เลยอยากรู้ว่า pattern ทั่วไปที่ระบบซึ่งเชื่อถือได้น้อยกว่าเหล่านั้นใช้เพื่อทำงานให้สอดคล้องกับ TigerBeetle คืออะไร โดยเฉพาะเมื่อเกิดปัญหาความสอดคล้องระหว่างสองฝั่ง จะกู้คืนกันอย่างไร
ข้อมูลผู้ใช้ (ชื่อ ที่อยู่ รหัสผ่าน ฯลฯ) และข้อมูลสินค้า (คำอธิบาย ราคา ฯลฯ) จะใส่ไว้ใน OLGP ในฐานะ “ตู้เอกสาร”
ส่วนธุรกรรมทั้งหมดในวัน Black Friday ที่ผู้ใช้ย้ายสินค้าจากบัญชีสต็อกไปยังบัญชีตะกร้าสินค้า แล้วต่อไปยังบัญชีชำระเงินและจัดส่ง จะใส่ไว้ใน OLTP ในฐานะ “ตู้นิรภัย” TigerBeetle อนุญาตให้เก็บตัวระบุข้อมูลผู้ใช้ได้สูงสุด 3 ค่า ต่อ account หรือ transfer ทำให้สามารถเชื่อม event ระหว่าง entity เข้ากับฐานข้อมูล OLGP ที่อธิบาย entity เหล่านั้นได้
สถาปัตยกรรมนี้ [1] ให้ การแยกความรับผิดชอบ ที่ชัดเจน ทำให้สามารถ scale และจัดการ workload ที่ต่างกันได้อย่างอิสระ ถ้าเป็นธนาคาร การเก็บเงินสดทั้งหมดไว้ในตู้เอกสารที่มีประวัติลูกค้าคงไม่สมเหตุสมผลเท่ากับเก็บเงินสดไว้ในตู้นิรภัย เพราะเงินสดมีคุณลักษณะด้าน performance, compliance และ retention ที่ต่างออกไป
เหตุผลที่ pattern นี้เหมาะสมคือ ความถี่ที่ผู้ใช้เปลี่ยนชื่อหรืออีเมล (OLGP) ต่ำกว่าความถี่ที่ทำธุรกรรม (OLTP) มาก
เพื่อรักษาความสอดคล้อง ใน write path ให้ปฏิบัติต่อ TigerBeetle ในฐานะ OLTP data plane และเป็น “แหล่งข้อมูลจริง” เมื่อมีธุรกรรม “ย้ายไปยังตะกร้าสินค้า” หรือ “ชำระเงิน” เข้ามา ให้เขียน data dependency ที่จำเป็นลง OLGP ก่อน และถ้ามี blob data ที่เกี่ยวข้องก็เขียนไปยังที่อย่าง S3 ด้วย จากนั้นจึงเขียนลง TigerBeetle เป็นขั้นสุดท้ายเพื่อ commit ธุรกรรม ใน read path ให้ query แหล่งข้อมูลจริงก่อนเพื่อรักษา strict serializability
[1] https://docs.tigerbeetle.com/coding/system-architecture/
เป็น รายงาน Jepsen ที่น่าสนใจเป็นพิเศษเมื่ออ่านหลังจากบทความเรื่อง blind spots ของ fuzzer ใน TigerBeetle
segfault ฝั่ง JNI ดูเหมือนว่าจะป้องกันไม่ได้แม้ใช้ Rust หรือภาษาอื่นที่ memory-safe การที่แทบไม่มีบั๊กด้าน memory safety เป็นหลักฐานว่าแนวทางการเขียนโปรแกรม Zig ของ TigerBeetle หรือถ้าจำไม่ผิดคือ TigerStyle ทำหน้าที่ตามที่ตั้งใจไว้ได้ค่อนข้างดี
แต่ก็ใช่ ถ้าไม่มี TigerStyle คงโดน nasal demons เล่นงานไปแล้ว
ชอบรายงานที่ละเอียดอย่างยอดเยี่ยมมาก การที่ Jepsen ทดสอบและลงชื่อรับรองให้ ถือเป็น การรับประกัน ครั้งใหญ่สำหรับ TigerBeetle ทั้งที่ยังไม่ถึง v1.0 ด้วยซ้ำ เลยตั้งตารอหมุดหมายใหม่ ๆ ต่อไป
ขอปรบมือเป็นพิเศษให้เหล่าผู้ก่อตั้งที่แบ่งปันมุมมองดี ๆ ในเธรดนี้ด้วย
กำลังจะมีเรื่องใหม่มาแชร์ในการบรรยาย SD25 ที่ Amsterdam เร็ว ๆ นี้ด้วย เลยตั้งตารอมาก
แอบชอบชื่อหัวข้อ “Panic! At the Disk 0” นิด ๆ
มองย้อนกลับไปแล้วก็ชัดเจน แต่ก็น่าสนใจที่ระบบกระจายตัวที่ถูกทดสอบต้องรายงาน เวลาและลำดับ ที่เหตุการณ์เกิดขึ้นจริง เพื่อให้ตรวจสอบกับโมเดลภายนอกของระบบได้อย่างถูกต้อง แทนที่จะใช้เวลา wall-clock
เป็นเมตาแพตเทิร์นที่น่าสนใจว่า เมื่อทำสิ่งที่ยากกว่าได้ ระบบกลับเรียบง่ายขึ้น
อีกตัวอย่างหนึ่งคือ เมื่อสมมติว่าดิสก์อาจล้มเหลวและต้องมีโปรโตคอลกู้คืน เราจึงได้การซิงก์สถานะของ replica ที่ตามหลังมาแทบจะ “ฟรี ๆ” เพราะเป็นปัญหาเดียวกันเป๊ะกับกรณีที่ดิสก์ทั้งลูกเสียหาย
น่าเสียดายที่ลิงก์ไปยังเปเปอร์ “Viewstamped Replication” ในบทความเสีย https://pmg.csail.mit.edu/papers/vr-revisited.pdf ถูกปฏิเสธการเชื่อมต่อ
น่าจะต้องใช้สคีมเป็น http ไม่ใช่ https แบบนี้ http://pmg.csail.mit.edu/papers/vr-revisited.pdf
ตอนนี้ก็มีอะไรให้อ่านคืนวันศุกร์แล้ว
เปเปอร์ VSR 2012 เป็นหนึ่งในเปเปอร์ที่ชอบที่สุด และ “Protocol-Aware Recovery for Consensus-Based Storage” ก็ทรงพลังมากจริง ๆ
ขอให้อ่านให้สนุก
เป็นคำถามที่ถามเพราะอยากเรียนรู้ล้วน ๆ และหวังว่าจะไม่ถูกเข้าใจผิด ผมกำลังเรียนรู้ระบบกระจายตัว และหลงใหลใน การทดสอบจำลองแบบกำหนดผลได้
หลังจากดูรายงาน Jepsen เกี่ยวกับ TigerBeetle, บล็อกโพสต์ที่เกี่ยวข้อง และโค้ดการผสาน Antithesis ใน GitHub workflow แบบคร่าว ๆ แล้ว เลยอยากเข้าใจขอบเขตการทดสอบให้ดีขึ้น
คำถามหลักคือ ชุดทดสอบ Jepsen พบ bug เหล่านี้ แล้วการผสาน Antithesis ก็น่าจะพบได้ด้วยหรือไม่
คำถามนี้มาจากสมมติฐานบางอย่างซึ่งอาจผิดก็ได้ ผมคิดว่า TigerBeetle ถูกทดสอบอย่างครอบคลุมอยู่แล้วด้วยชุดทดสอบภายในและผลิตภัณฑ์ Antithesis และเข้าใจว่าชุดทดสอบ Antithesis แข็งแกร่งกว่า Jepsen จึงแปลกใจที่ Jepsen พบปัญหาที่ Antithesis ไม่พบ
เลยอยากรู้ว่าผมเข้าใจผิดตรงไหนหรือเปล่า เช่น 1) ชุดทดสอบ Antithesis ไม่สามารถตรวจจับ bug ประเภทเฉพาะนี้ได้หรือไม่, 2) ส่วนนี้ของระบบยังไม่ถูกครอบคลุมด้วยการทดสอบ Antithesis หรือไม่, 3) ผมกำลังเปรียบเทียบคนละเรื่องกันเพราะเข้าใจจุดแข็งและเป้าหมายที่ต่างกันของชุดทดสอบ Jepsen กับ Antithesis ผิดหรือไม่
generator สำหรับทดสอบของ Jepsen เองก็แทบจะแน่นอนว่ามี blind spot เช่นกัน นี่จึงเป็นเหตุผลว่าทำไมการออกแบบ generator หลายแบบที่ต่างกันจึงมีประโยชน์
Antithesis จัดการปัญหาข้อ 1 เป็นหลัก โดยให้ สภาพแวดล้อมจำลองแบบกำหนดผลได้ ด้วย virtual machine ส่วน Jepsen จัดการปัญหาเดียวกันด้วยการใช้เครื่องจริง แต่ฉีด failure ในระดับระบบปฏิบัติการ และ VOPR ของ TigerBeetle เองถูกออกแบบมาพร้อมกับฐานข้อมูลเพื่อให้รันทั้งคลัสเตอร์ในเธรดเดียวได้ ทั้งสามแนวทางนี้เสริมกันและมีจุดที่แต่ละแบบถนัดต่างกัน
ส่วนที่ชี้ขาดใน bug นี้คือข้อ 2 และ 3 คือการเขียนตัวตรวจสอบ workload และ auditor ที่สามารถกระตุ้น bug ได้จริง ในกรณีนี้ โค้ด Clojure เฉพาะสำหรับ TigerBeetle จำนวน 1,600 บรรทัดที่ aphyr เขียนเป็นตัวกระตุ้นและตรวจพบ bug และหลังจากนั้นฝั่ง TigerBeetle ก็แพตช์การทดสอบที่เทียบเท่ากันให้กระตุ้นมันได้เช่นกัน ที่จริงสิ่งที่มี bug ตรงนี้ไม่ใช่ฐานข้อมูลเท่าไร แต่เป็น VOPR การมี bug ในฐานข้อมูลเป็นเรื่องปกติอยู่แล้ว และหลีกเลี่ยง bug ด้วยความตั้งใจอย่างเดียวไม่ได้ ดังนั้นจึงต้องมีกลยุทธ์ทดสอบที่สามารถกระตุ้น bug ส่วนใหญ่ได้ และ bug ที่หลุดรอดออกไปชี้ถึงข้อบกพร่องของ workload generator
ใช้ Antithesis ด้วย แต่ใช้เป็นชั้นที่สองของการทดสอบจำลองแบบกำหนดผลได้
ดูสาเหตุที่ bug ใน query engine หลุดรอดไปได้ที่นี่: https://tigerbeetle.com/blog/2025-06-06-fuzzer-blind-spots-m...
สงสัยว่าธนาคารขนาดใหญ่หรือตลาดหลักทรัพย์ใช้ TigerBeetle กันหรือไม่
ในระดับองค์กร TigerBeetle ถูกใช้งานจริงในโปรดักชันโดยลูกค้าที่ประมวลผลทรานแซกชันมากกว่า 100 ล้านรายการต่อเดือนอยู่แล้ว และเมื่อไม่นานมานี้ได้เซ็นสัญญาฉบับแรกกับยูนิคอร์นฟินเทคในยุโรปมูลค่า 2 พันล้านดอลลาร์ รวมถึงในสหรัฐฯ ก็มีอีกหลายดีลที่ใกล้จะปิดได้เช่นกัน เนื่องจากกระแสทั่วโลกกำลังย้ายไปสู่การประมวลผลทรานแซกชันแบบเรียลไทม์ [2] จึงมีบริษัทจำนวนไม่น้อยที่สนใจย้ายมาใช้ TigerBeetle เพื่อให้ได้ประสิทธิภาพที่สูงขึ้น
ถ้าตอบคำถามนี้ ผู้ก่อตั้งบางคนของ Clear Street ซึ่งเป็นโบรกเกอร์รายค่อนข้างใหญ่ใน Wall Street ได้ลงทุนใน TigerBeetle [3]
[1] https://mojaloop.io/how-mojaloop-enables-rndps-2-0-ekash/
[2] https://tigerbeetle.com/blog/2024-07-23-rediscovering-transa...
[3] https://tigerbeetle.com/company