End-to-End Delivery Guarantees
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”guarantee แบบ end-to-end แข็งแรงได้เท่ากับจุดที่อ่อนที่สุดของตัวเองเท่านั้น ฝั่ง producer (acks และ idempotence) กับฝั่ง consumer (จังหวะ commit) ต่างก็มีส่วน และคุณต้องคิดถึงทั้งสองฝั่ง พร้อมกัน
guarantee เดียวที่มีสองครึ่ง
หัวข้อที่มีชื่อว่า “guarantee เดียวที่มีสองครึ่ง”delivery semantics ไม่ใช่สวิตช์ตัวเดียว data ไหลจาก producer → log → consumer และแต่ละ hop มี failure mode ของตัวเอง
- Producer → log ด้วย
acks=allและenable.idempotence=truerecord ถูกเขียนแบบทนทานและ retry ไม่สร้าง duplicate ใน partition ถ้าใช้acks=0หรือacks=1broker ล่มจังหวะไม่ดีก็ทำให้ write หายได้ - Log → consumer commit หลัง ประมวลผลได้ at-least-once commit ก่อน ได้ at-most-once ตัว log เองไม่เคยทำ record ที่ commit แล้วหายภายใน retention
รวมสองอย่างเข้าด้วยกันก็ได้พฤติกรรม end-to-end จริง ๆ
| Producer | Consumer commit | End-to-end |
|---|---|---|
acks=all + idempotent | หลังประมวลผล | at-least-once (ไม่หาย อาจซ้ำ) |
acks=1 | หลังประมวลผล | at-least-once แต่ broker ล่มแบบหายากยังทำ write หายได้ |
acks=all + idempotent | ก่อนประมวลผล | at-most-once (ไม่ซ้ำ อาจหาย) |
| transactional producer | commit offset ใน transaction | exactly-once ภายใน Kafka |
flowchart LR prod["Producer: acks=all + idempotence"] -->|durable, no dupes| log["Partition log"] log -->|committed records retained| cons["Consumer"] cons -->|commit after processing| out["at-least-once end-to-end"] out -->|idempotent sink or transactions| eos["effectively exactly-once"]
default ที่ควรออกแบบเผื่อไว้
หัวข้อที่มีชื่อว่า “default ที่ควรออกแบบเผื่อไว้”ระบบส่วนใหญ่รันแบบ at-least-once end-to-end คือ acks=all producer เป็น idempotent commit หลังประมวลผล แล้วทำให้ผลลัพธ์ปลายทาง idempotent เพื่อให้ duplicate ไม่เป็นอันตราย วิธีนี้เรียบง่าย ทนทาน และเร็ว หยิบ exactly-once เต็มรูปแบบ (transactions) มาใช้ก็ต่อเมื่อ pipeline อยู่ภายใน Kafka และ duplicate ยอมรับไม่ได้จริง ๆ เพราะเพิ่มต้นทุนการ coordinate ส่วนที่เหลือของ module นี้จะค่อย ๆ สร้างเส้นทาง exactly-once นั้นขึ้นมา