Skip to main content

Producer & Consumer

Partitioning Key — ใครไปอยู่ partition ไหน​

Producer ส่ง message พร้อม key (optional) — ถ้ามี key, Kafka จะ hash key นั้นแล้วคำนวณว่า ต้องไปลง partition ไหน (key เดียวกัน เสมอ ไปลง partition เดียวกัน) ถ้าไม่มี key, Kafka จะ กระจาย (round-robin หรือ sticky partitioning) ไปทุก partition แบบสุ่ม

producer.send("orders", key="user-123", value={...})
// key "user-123" hash แล้วได้ partition 1 เสมอ ไม่ว่าจะส่งกี่ครั้งก็ตาม
🧠 จำง่ายๆ

"Key = ที่อยู่บ้าน, Partition = ตู้ไปรษณีย์ประจำซอย" — จดหมายที่มีที่อยู่เดียวกันจะถูกส่งไปตู้ ไปรษณีย์ซอยเดียวกันเสมอ (เรียงลำดับตามเวลาที่ส่งในตู้นั้น) จดหมายไม่มีที่อยู่ (ไม่มี key) จะถูก สุ่มแจกไปตู้ไหนก็ได้

🎯 Beyond: จุดที่ชาวบ้านไม่ค่อยรู้

ตั้งแต่ Kafka 2.4+ พฤติกรรม "ไม่มี key" เปลี่ยนจาก round-robin (สลับ partition ทุก message) เป็น sticky partitioning (ยิง message หลายตัวไปที่ partition เดียวกันก่อนจนกว่า batch จะเต็มหรือครบเวลา แล้วค่อยสลับ partition ใหม่) เหตุผลคือ round-robin แบบเดิมทำให้แต่ละ batch มี message แค่ 1 ตัวต่อ partition (batch เล็กมาก) ส่งผลให้ throughput แย่ ส่วน sticky partitioning รวม message หลายตัวเข้า batch เดียวกันก่อนส่ง ทำให้ network request น้อยลงและ compression มีประสิทธิภาพมากขึ้น (compress batch ใหญ่ได้ดีกว่า batch เล็ก) — เป็นการเปลี่ยน default ที่ส่งผล performance อย่างมีนัยสำคัญที่คนใช้ Kafka มานานหลายปีอาจไม่เคยสังเกต

Delivery Semantics​

Semanticความหมายใช้เมื่อ
At-most-onceส่ง/ประมวลผลได้สูงสุด 1 ครั้ง อาจหายได้ถ้า failยอมรับข้อมูลหายได้ (metric, log ที่ไม่ critical)
At-least-onceรับประกันได้ประมวลผลอย่างน้อย 1 ครั้ง อาจซ้ำได้ส่วนใหญ่ของระบบจริง (ต้อง idempotent consumer)
Exactly-onceรับประกันประมวลผลเป๊ะ 1 ครั้งไม่ซ้ำไม่หายการเงิน, การนับสต็อก (ต้องใช้ idempotent producer + transaction)
🧠 จำง่ายๆ

"At-most = ยิงแล้วไม่สนผล, At-least = ยิงซ้ำจนมั่นใจว่าโดน (อาจโดนซ้ำ), Exactly = ยิงนัดเดียว รับประกันโดนแค่ครั้งเดียว" — ไล่ตามความซับซ้อนและ cost: at-most ถูกสุดเร็วสุดแต่เสี่ยงข้อมูล หาย, exactly-once แพงสุดซับซ้อนสุดแต่ปลอดภัยสุด

🎯 Beyond: จุดที่ชาวบ้านไม่ค่อยรู้

"Exactly-once" ของ Kafka ที่แท้จริงคือ exactly-once ภายใน Kafka ecosystem เท่านั้น (producer → topic → consumer ที่ใช้ Kafka transaction API ทั้งหมด) ไม่ได้การันตี exactly-once กับ side effect ภายนอก เช่น ถ้า consumer ประมวลผล message แล้วเรียก external API หรือเขียนลง database ที่ไม่ใช่ Kafka, การรับประกัน exactly-once จะขาดตอนทันทีที่ข้าม boundary ออกจาก Kafka เพราะไม่มีทางทำ atomic commit ระหว่าง Kafka transaction กับระบบภายนอก พร้อมกันได้ วิธีแก้ที่ใช้จริงคือทำให้ consumer idempotent (ประมวลผลซ้ำกี่ครั้งผลลัพธ์ เหมือนเดิม เช่น ใช้ unique key กันการเขียนซ้ำที่ database) แทนที่จะหวังพึ่ง "exactly-once" ของ Kafka อย่างเดียวสำหรับระบบที่มี side effect ภายนอก

Consumer Group — การกระจายงานแบบ auto-scale​

Consumer หลายตัวที่อยู่ consumer group เดียวกัน (ตั้งค่า group.id เหมือนกัน) จะแบ่ง partition กันอ่าน โดยอัตโนมัติ (แต่ละ partition ถูกอ่านโดย consumer แค่ตัวเดียวใน group เดียวกัน ณ เวลาหนึ่ง) เพิ่ม consumer เข้า group = กระจายงานได้มากขึ้น (จนถึงขีดจำกัดจำนวน partition)

Topic "orders" มี 4 partition, Consumer Group "order-processor" มี 2 consumer:
Consumer A: partition 0, partition 1
Consumer B: partition 2, partition 3

เพิ่ม Consumer C เข้า group → rebalance:
Consumer A: partition 0, partition 1
Consumer B: partition 2
Consumer C: partition 3
🧠 จำง่ายๆ

"Consumer group = ทีมพนักงานแบ่งโต๊ะลูกค้ากันดูแล" — โต๊ะ (partition) หนึ่งโต๊ะมีพนักงาน (consumer) ดูแลแค่คนเดียวในทีมเดียวกัน เพิ่มพนักงานเข้าทีม = แบ่งโต๊ะกันดูแลใหม่ (rebalance) แต่ถ้าพนักงานเยอะกว่าโต๊ะ บางคนจะไม่มีโต๊ะให้ดูแลเลย (idle)

🎯 Beyond: จุดที่ชาวบ้านไม่ค่อยรู้

Consumer group หลาย group ที่อ่าน topic เดียวกัน ไม่แย่ง partition กันเอง (แต่ละ group เห็นข้อมูลครบทุก partition เป็นอิสระจากกัน) — นี่คือกลไกที่ทำให้ Kafka ทำหน้าที่เป็นทั้ง queue (ภายใน group เดียวกัน = แบ่งงานกัน ไม่ประมวลผลซ้ำ) และ pub/sub (ข้าม group = ทุก group ได้ข้อมูลครบ เหมือน broadcast) พร้อมกันได้ในระบบเดียว ซึ่งต่างจาก traditional message queue ที่ต้องเลือกแบบใดแบบหนึ่ง (หรือต้องสร้าง queue แยกสำหรับแต่ละ consumer ที่ต้องการ ข้อมูลครบ) — ความสามารถนี้มาจาก design ที่ consumer group แต่ละกลุ่มเก็บ offset ของตัวเองแยก กันใน __consumer_offsets โดยสมบูรณ์ ไม่ยุ่งเกี่ยวกัน

English Summary

A message's optional key determines its partition via hashing — same key always lands on the same partition, preserving per-key order. Since Kafka 2.4, keyless messages use sticky partitioning (batching into one partition before rotating) instead of pure round-robin, trading even distribution for much better batching/compression throughput. Delivery semantics range from at-most-once to exactly-once; Kafka's "exactly-once" only holds within the Kafka transaction boundary — any external side effect (DB write, API call) still needs an idempotent consumer design. Consumer groups auto-distribute partitions across member consumers (one partition, one consumer per group at a time) and rebalance on membership change; critically, separate consumer groups reading the same topic don't compete — this lets Kafka act as both a queue (within a group) and pub/sub (across groups) simultaneously.