Consumer Rebalance Storm: แค่ทำงานช้า 5 นาที ทำไม Kafka ถึงเฉดหัวทิ้งทั้งแก๊ง?
⏱️ ใช้เวลาอ่านประมาณ: 4 นาที 30 วินาที
คุณเขียน Kafka Consumer ไว้อ่าน Message เพื่อสร้าง Report และส่งอีเมล ทุกอย่างดูราบรื่นดีจนกระทั่งมี Message หนึ่งแนบไฟล์ไซซ์ยักษ์เข้ามา
Consumer ของคุณก้มหน้าก้มตาประมวลผลไฟล์นั้นอย่างขยันขันแข็ง กินเวลาไป 6 นาที... ทันใดนั้น Kafka Broker ตะโกนลั่น: "ไอ้หมอนี่ตายแล้ว! ยึด Partition คืนเดี๋ยวนี้!"
แล้วความวิบัติระดับลูกโซ่ที่เรียกว่า Rebalance Storm ก็เริ่มขึ้น ทั้งระบบหยุดอ่านข้อมูล ข้อความค้างเติ่งนับล้านตัวในพริบตาเดียว!
⏱️ เส้นตายของหัวหน้าห้อง: max.poll.interval.ms
Kafka มีกลไกคอยตรวจว่า Consumer แต่ละตัวใน Group ยังมีชีวิตอยู่ไหม
โดยอาศัยตัวแปรสำคัญตัวหนึ่งชื่อว่า
max.poll.interval.ms (ค่าเริ่มต้นมักจะอยู่ที่ 5 นาที)
กฎเหล็กของ Kafka มีอยู่ว่า:
"หลังจากดึงข้อความรอบล่าสุดไป แกต้องกลับมาเรียกคำสั่ง
poll() รอบถัดไปภายในเวลาที่กำหนด ถ้าเกินเวลา แม้หัวใจ
(Heartbeat Thread) ยังเต้นอยู่ จะถือว่าโปรแกรมแกค้าง (Stuck)
และโดนเตะออกจากกลุ่มทันที!"
พอตัวที่ 1 ทำงานเกินเวลาไป 6 นาที Broker ก็เตะมันทิ้ง แล้วสั่งให้ Consumer ที่เหลือในกลุ่มทำการ Rebalance (แบ่ง Partition กันใหม่)
🌪️ พายุหมุนมหาประลัย (The Poison Pill Loop)
ความพินาศที่แท้จริงไม่ได้หยุดแค่นั้น เพราะ Message ยักษ์เจ้ากรรมตัวเดิม ยังไม่ได้ถูก Commit Offset!
- Broker สั่ง Rebalance ส่ง Partition นั้นไปให้ Consumer ตัวที่ 2 ทำต่อ
- ตัวที่ 2 หยิบ Message พิษตัวเดิมขึ้นมาประมวลผล... แน่นอนว่ามันก็ใช้เวลา 6 นาทีเหมือนกัน!
- ตัวที่ 2 โดนเตะทิ้งตามไปอีกตัว Broker สั่ง Rebalance รอบใหม่
- Message พิษถูกส่งต่อไปให้ตัวที่ 3, ตัวที่ 4 ทยอยตายตามกันไปเป็นโดมิโน
- ระหว่างที่มีการ Rebalance เกิดขึ้นเรื่อยๆ Consumer ทุกตัวจะหยุดอ่านข้อความชั่วคราว (Stop the world)
ผลคือ ทั้งระบบค้างสนิท Message สะสมเป็นภูเขาไฟรอวันระเบิด เพียงเพราะ Message ตัวเดียวที่ใช้เวลาทำนานเกินไป
🛡️ ท่าแก้เผ็ด: แยกงานหยิบ กับงานทำ ออกจากกัน
วิธีแก้ไม่ใช่แค่การเพิ่มเวลา
max.poll.interval.ms ให้ยาวเป็นชั่วโมง
เพราะนั่นจะทำให้ระบบตรวจจับความตายจริงช้าลง แต่คือการ
"อย่าทำงานหนักใน Consumer Thread"
1. จ้างคนงานแยก (Worker Thread Pool / Coroutine):
ให้ Consumer Thread มีหน้าที่แค่ poll() เอาข้อความมาไวๆ
แล้วโยนต่อให้ Worker Pool หรือ Coroutine ไปประมวลผลหนักๆ ข้างหลัง
เพื่อให้ Consumer Thread วิ่งกลับไปกดรับงานรอบถัดไปได้ทันเวลาเสมอ
2. คุมจำนวนข้อความต่อรอบด้วย max.poll.records:
properties[ConsumerConfig.MAX_POLL_RECORDS_CONFIG] = 20
💡 5-Min Takeaway
"ในระบบ Distributed System การทำงานช้าเกินไป มีค่าเท่ากับตายแล้ว" สัญญาณชีพของ Kafka ไม่ได้ดูแค่พัลส์ของเครื่อง แต่ดูว่าคุณยังวนกลับมารับงานตามเวลาไหม อย่าเอา Message ที่ต้องประมวลผลนานมาขวางลูปหลักเด็ดขาด แยกงานยาวไปทำข้างหลัง เพื่อให้หน้าบ้านยังตอบสนองคลัสเตอร์ได้ตลอดเวลา