Acks กับ Durability
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”ค่า acks เป็นตัวตัดสินว่า ต้องมี replica กี่ตัวยืนยันก่อน Kafka จะถือว่า write นั้น durable — เป็นการแลก latency กับการรับประกันว่า record จะรอดแม้ broker ล่ม
สามระดับของ acks
หัวข้อที่มีชื่อว่า “สามระดับของ acks”ทุก 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 guaranteeacks=0
# Leader only: fast, but unreplicated writes can be lost on leader failureacks=1
# All in-sync replicas must confirm: strongest durabilityacks=allใน KafkaJS acks ไม่ใช่ property ระดับ producer แต่เป็น option ต่อการ send แต่ละครั้ง ที่คุณส่งให้ send() โดยใช้สามค่าเดิม:
// -1 = all (ค่า default), 1 = leader เท่านั้น, 0 = fire-and-forgetawait producer.send({ topic: 'orders', acks: -1, messages })acks=all ต้องมี min.insync.replicas ถึงจะมีความหมาย
หัวข้อที่มีชื่อว่า “acks=all ต้องมี min.insync.replicas ถึงจะมีความหมาย”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"]
Tradeoff แบบตรงไปตรงมา
หัวข้อที่มีชื่อว่า “Tradeoff แบบตรงไปตรงมา”durability ไม่มีของฟรี ทุกระดับ acks ที่แข็งขึ้นเพิ่ม network round trip และรอเครื่องมากขึ้น latency จึงสูงขึ้นตามความแข็งของการรับประกัน เลือกตามต้นทุนของการเสีย record: metrics และ log มักอยู่กับ acks=1 ได้ แต่ order, payment และอะไรที่สร้างใหม่ไม่ได้ควรใช้ acks=all คู่กับ min.insync.replicas=2