Design Scenarios — ออกแบบระบบ Event-Driven จริง
หน้านี้คือเวอร์ชัน "live coding/system design" ของหมวด Kafka — โจทย์ออกแบบระบบที่มักถูกถามใน สัมภาษณ์ตำแหน่ง backend/platform engineer เน้น การพูดถึง trade-off มากกว่าคำตอบที่ถูกต้อง ตายตัว เพราะโจทย์ระบบจริงแทบไม่มี "คำตอบเดียวที่ถูก"
"ทุกคำถามออกแบบระบบ Kafka วนอยู่ 4 แกนเสมอ": Ordering (ต้องเรียงลำดับแค่ไหน) × Delivery semantics (ยอมข้อมูลซ้ำ/หายได้แค่ไหน) × Throughput (ต้องเร็วแค่ไหน) × Consistency กับระบบภายนอก (ต้อง sync กับ DB/API อื่นแค่ไหน) — ตอบโจทย์ไหนก็ให้วนเช็ค 4 แกนนี้เสมอ
Scenario 1: Order Processing Pipeline
โจทย์: ออกแบบระบบประมวลผล order e-commerce — order ถูกสร้าง → ตรวจสต็อก → เรียกเก็บเงิน → ส่ง notification ให้ลูกค้า ต้องไม่ double-charge และไม่ ship ของซ้ำ
แนวทางออกแบบ:
Topic: orders (key = orderID)
→ Consumer Group: inventory-service (ตรวจ/ตัดสต็อก)
→ Topic: orders-validated (key = orderID)
→ Consumer Group: payment-service (เรียกเก็บเงิน, ใช้ idempotency key = orderID)
→ Topic: orders-paid (key = orderID)
→ Consumer Group: notification-service (ส่ง email/SMS)
ประเด็นที่ต้องพูดถึง:
- ใช้
orderIDเป็น key ทุก topic เพื่อการันตี order ของ order เดียวกันเรียงลำดับเสมอ (ไม่ต้อง global order ทั้งระบบ แค่ order เดียวกันต้องเรียงถูก) payment-serviceต้อง idempotent: เช็คว่าorderIDนี้เรียกเก็บเงินไปแล้วหรือยังก่อน เรียก payment gateway ซ้ำ (เก็บ state แยกไว้ เช่น Redis/DB) เพราะ at-least-once delivery อาจทำให้ consumer ได้รับ message ซ้ำ- แยก topic เป็นขั้นๆ (choreography-based saga) แทนที่จะให้ service เดียวทำทุกอย่าง เพื่อให้ แต่ละ service scale/deploy อิสระจากกันได้
Pattern นี้คือ choreography-based saga (แต่ละ service ฟัง event แล้วทำงานของตัวเอง แล้ว emit event ต่อ ไม่มีตัวกลางคอยสั่ง) ซึ่งต่างจาก orchestration-based saga (มี central coordinator สั่งแต่ละ step ตามลำดับ) — ข้อเสียที่คนมักมองข้ามของ choreography คือ debug ยากขึ้นเรื่อยๆ เมื่อจำนวน step เพิ่มขึ้น เพราะไม่มีที่เดียวที่เห็น "flow ทั้งหมดของ order หนึ่งใบ" ต้องไล่ดู log กระจายหลาย service ทีมที่ step เยอะเกิน 4-5 step มักเปลี่ยนไปใช้ orchestration pattern แทน (มี saga orchestrator service ที่เห็น state ทั้งหมดและเรียก service อื่นตามลำดับชัดเจน) เพื่อแลก debuggability กับความซับซ้อนของการมี central coordinator เพิ่มขึ้นมา — เป็นคำถาม follow-up ที่พบบ่อยหลังเสนอ choreography pattern ว่า "scale ไป step เยอะๆ จะเป็นยังไง"
Scenario 2: Real-time Analytics Dashboard
โจทย์: ต้องแสดง metric แบบ real-time (เช่น "ยอดขายรวมของวันนี้ อัปเดตทุกวินาที") จาก stream ของ order event ที่เข้ามาต่อเนื่อง
แนวทางออกแบบ: ใช้ Kafka Streams หรือ ksqlDB ทำ stateful aggregation บน stream
orders topic → Kafka Streams app
.groupBy(date)
.aggregate(sum of amount)
→ เก็บผลใน KTable (local state store, backed by compacted changelog topic)
→ expose ผ่าน interactive query API หรือ sink ไป topic ใหม่ให้ dashboard subscribe
ประเด็นที่ต้องพูดถึง: ทำไมไม่ query database ตรงๆ ทุกครั้งที่ dashboard refresh (เปลือง DB มาก ถ้า traffic สูง) แทนที่จะทำ pre-aggregation แบบ stream processing ที่คำนวณผลล่วงหน้า ไว้ตลอดเวลาแล้ว dashboard แค่ query ผลลัพธ์ที่คำนวณเสร็จแล้ว (ถูกกว่ามาก)
KTable state store ของ Kafka Streams ไม่ได้อยู่แค่ใน memory — มันถูก backed ด้วย compacted changelog topic โดยอัตโนมัติ (ตามที่กล่าวถึงใน Topic Design) หมายความว่าถ้า Kafka Streams instance ล่มแล้วรันใหม่ (หรือ scale ไป instance อื่น) มันสามารถ rebuild state ทั้งหมดกลับมาได้จาก changelog topic โดยไม่ต้อง replay ข้อมูลจาก source topic ทั้งหมดใหม่ตั้งแต่ต้น (ซึ่งอาจมีข้อมูลหลายปี) — กลไกนี้ทำให้ stateful stream processing ที่ Kafka Streams ทำ fault-tolerant โดยไม่ต้องพึ่ง external database เก็บ state เลย เป็น จุดขายสำคัญที่แยก Kafka Streams ออกจาก stream processing framework อื่นที่มักต้องพึ่ง external state store (เช่น Flink ที่มักใช้ RocksDB + external checkpoint storage)
Scenario 3: Multi-Region Event Replication
โจทย์: บริษัทมี data center 2 ภูมิภาค (เช่น US, EU) ต้องการให้ event ที่เกิดขึ้นที่ region หนึ่ง ถูกส่งไปประมวลผลที่อีก region ด้วย (สำหรับ disaster recovery หรือ compliance ที่ต้องเก็บ ข้อมูลไว้หลาย region)
แนวทางออกแบบ: ใช้ MirrorMaker 2 (หรือ Confluent's Cluster Linking) ทำ cross-cluster replication จาก cluster ภูมิภาคหนึ่งไปอีกภูมิภาคหนึ่ง
ประเด็นที่ต้องพูดถึง: replication ข้าม region มี latency สูงกว่าภายใน cluster เดียวกัน มาก (ข้าม network ระหว่างทวีป) ต้องยอมรับว่าข้อมูลที่ replicate ไปจะ eventual consistent เสมอ ไม่ใช่ synchronous (ต่างจาก replication ภายใน cluster ที่ใช้ ISR แบบ near-synchronous)
MirrorMaker 2 มีจุดที่คนมักไม่รู้เรื่อง offset translation: offset ของ topic เดียวกันใน
cluster ต้นทางกับปลายทาง ไม่ใช่เลขเดียวกัน (เพราะปลายทางอาจมี message อื่นปนเข้ามา หรือ
compaction ทำงานต่างเวลากัน) ทำให้ consumer ที่ fail-over จาก cluster หนึ่งไปอีก cluster หนึ่ง
ใช้ offset เดิมที่เคยอ่านค้างไว้ตรงๆ ไม่ได้ MirrorMaker 2 แก้ปัญหานี้ด้วยการเก็บ
offset mapping ไว้ใน topic พิเศษ (__consumer_offsets ที่ translate แล้ว) ให้ consumer
กลุ่มที่ fail-over ไปอ่าน cluster ใหม่สามารถ resume จากตำแหน่งที่ถูกต้องเทียบเท่ากัน
ได้ (ไม่ใช่เริ่มอ่านจาก offset 0 ใหม่ หรือข้ามข้อมูลไปแบบผิดตำแหน่ง) — รายละเอียดนี้คือสิ่งที่
แยกทีมที่เคย "ลองทำ multi-region Kafka แล้วเจอปัญหา" ออกจากทีมที่เข้าใจ MirrorMaker 2 จริงๆ
ตั้งแต่ตอนออกแบบ
Every Kafka system-design question revolves around four axes: ordering strictness, delivery semantics, throughput, and consistency with external systems. An order-processing pipeline typically uses choreography-based saga (topic-per-stage, keyed by orderID) with idempotent payment handling — but choreography gets hard to debug past ~4-5 steps, at which point orchestration-based saga (a central coordinator) often wins on debuggability despite added complexity. Real-time analytics favor pre-aggregation via Kafka Streams/ksqlDB over querying a database directly; Kafka Streams' KTable state is fault-tolerant because it's backed by a compacted changelog topic, letting state rebuild without replaying the entire source history — a key differentiator from frameworks needing external state stores. Multi-region replication via MirrorMaker 2 is inherently eventually consistent, and offset values differ between source and target clusters — MirrorMaker 2's offset translation mechanism is what lets failed-over consumers resume from the correct equivalent position instead of from scratch.