ข้ามไปยังเนื้อหา

Acks กับ Durability

ค่า acks เป็นตัวตัดสินว่า ต้องมี replica กี่ตัวยืนยันก่อน Kafka จะถือว่า write นั้น durable — เป็นการแลก latency กับการรับประกันว่า record จะรอดแม้ broker ล่ม

ทุก partition มี replica ที่เป็น leader หนึ่งตัว และ follower อีกศูนย์ตัวขึ้นไป ค่า acks ควบคุมว่าต้องมีกี่ตัว acknowledge การ write ก่อน producer จะถือว่า send() สำเร็จ สามค่านี้ — 0, 1 และ all — คือชื่อมาตรฐานที่ใช้ทั่วทุก client ของ Kafka และใน official docs

  • acks=0 — fire-and-forget producer ไม่รอ acknowledge ใด ๆ ถือว่าส่งแล้วทันทีที่ record ออกจาก client latency ต่ำสุด แต่ record หายเงียบ ๆ ได้ถ้า leader ล่ม
  • acks=1 — leader เท่านั้น leader เขียน record ลง log ของตัวเองแล้วตอบกลับ เร็ว แต่ถ้า leader crash ก่อน ที่ follower จะ replicate record นั้น record จะหาย
  • acks=all (มีค่าเท่ากับ acks=-1) — ทุก in-sync replica ต้องยืนยัน record ปลอดภัยตราบใดที่ยังมี in-sync replica เหลืออยู่หนึ่งตัว durability สูงสุด latency สูงสุด
# Fire-and-forget: fastest, weakest guarantee
acks=0
# Leader only: fast, but unreplicated writes can be lost on leader failure
acks=1
# All in-sync replicas must confirm: strongest durability
acks=all

ใน KafkaJS acks ไม่ใช่ property ระดับ producer แต่เป็น option ต่อการ send แต่ละครั้ง ที่คุณส่งให้ send() โดยใช้สามค่าเดิม:

// -1 = all (ค่า default), 1 = leader เท่านั้น, 0 = fire-and-forget
await producer.send({ topic: 'orders', acks: -1, messages })

acks=all ลำพังหมายถึง “ทุก replica ที่ in-sync อยู่ตอนนั้น” ถ้าตอนนั้นมีแค่ leader ที่ in-sync คำว่า “all” ก็คือ leader ตัวเดียว — เท่ากับกลับไปเป็น acks=1 ค่าฝั่ง broker min.insync.replicas ปิดช่องนี้ โดยตั้ง จำนวนขั้นต่ำ ของ in-sync replica ที่ต้องมีอยู่ถึงจะรับ write ได้เลย

ด้วย min.insync.replicas=2 บน topic ที่มี replication factor 3 producer ที่ใช้ acks=all ต้องมี replica in-sync อย่างน้อย สอง ตัว ถ้าเหลือแค่ตัวเดียว broker จะ ปฏิเสธ การ write (error NotEnoughReplicas) แทนที่จะรับ record ที่อาจหายได้ การปฏิเสธนี่แหละคือจุดสำคัญ — เรายอมให้ fail ดัง ๆ ดีกว่าเสียข้อมูลเงียบ ๆ

รวมทั้งหมดเข้าด้วยกัน producer ของ KafkaJS ที่ทนทานจะจับคู่ option acks ต่อการ send (หรือ idempotent producer ที่มีผลเท่ากับ acks: -1 โดยอัตโนมัติ — บทถัดไปจะพูดถึงเรื่องนี้) กับ topic ที่ตั้ง min.insync.replicas=2:

const producer = kafka.producer({ idempotent: true }) // implies acks: -1 internally
await producer.send({
topic: 'orders', // a topic configured with min.insync.replicas=2 (broker/topic setting)
messages: [{ key: 'order-42', value: JSON.stringify({ amount: 19.99 }) }],
})
flowchart LR
  p["Producer send()"] --> a0["acks=0: no wait, lowest latency"]
  p --> a1["acks=1: leader writes and replies"]
  p --> aa["acks=all: all in-sync replicas confirm"]
  aa --> misr["'min.insync.replicas=2' must be met or write is rejected"]
  misr --> safe["Durable: survives a broker failure"]
Durability climbs from acks=0 to acks=all with min.insync.replicas

durability ไม่มีของฟรี ทุกระดับ acks ที่แข็งขึ้นเพิ่ม network round trip และรอเครื่องมากขึ้น latency จึงสูงขึ้นตามความแข็งของการรับประกัน เลือกตามต้นทุนของการเสีย record: metrics และ log มักอยู่กับ acks=1 ได้ แต่ order, payment และอะไรที่สร้างใหม่ไม่ได้ควรใช้ acks=all คู่กับ min.insync.replicas=2

acks=all รับประกันอะไรที่ต่างจาก acks=1
ทำไม acks=all ต้องมี min.insync.replicas ถึงจะมีความหมาย
เมื่อ replication factor 3 และ min.insync.replicas=2 จะเกิดอะไรถ้ามี replica in-sync แค่ตัวเดียวตอนส่งแบบ acks=all
tradeoff หลักเมื่อเพิ่มระดับ acks คืออะไร