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

Flow Control & Alarms

queue จะแข็งแรงได้ก็ต่อเมื่อ consumer ตาม producer ทัน ในระยะยาว เมื่อ publisher วิ่งเร็วกว่า consumer อย่างต่อเนื่อง backlog จะโต, memory จะเต็ม และต้องมีอะไรสักอย่างยอมแตก RabbitMQ มีกลไกหลายอย่าง — ตั้งแต่นุ่มนวลไปถึงเด็ดขาด — เพื่อกัน broker ที่ overload ไม่ให้ crash

คุณเจอ alarm ไปแล้วในบทที่แล้ว นี่คือว่า รู้สึก ยังไง เมื่อ memory high-watermark หรือ limit ของ disk ว่างถูกข้าม RabbitMQ จะยก alarm และ block ทุก publishing connection publisher จะถูกหยุด — การเรียก publish จะไม่คืบหน้า — ขณะที่ consumer ยังไหล queue ออกต่อ

flowchart TB
  fast["Publisher
(เร็วเกินไป)"] --> q["Queue เต็ม
memory สูงขึ้น"]
  q --> alarm["ข้าม memory high-watermark
→ ALARM"]
  alarm --> block["publishing connection
ถูก BLOCK"]
  cons["Consumer ยังไหล
ออกต่อ"] --> q
  block --> recover["memory ลด →
alarm เคลียร์ →
publisher ทำงานต่อ"]
alarm ดัน backpressure กลับไปที่ publisher

นี่คือ backpressure โดยตั้งใจ: แทนที่จะรับ message จนตัวเองตาย broker ดันแรงกดกลับไปที่ producer producer ที่ประพฤติดีจะสังเกตว่าถูก block (client มีสัญญาณ “connection blocked”) แล้วชะลอตัว ส่วนตัวที่ประพฤติไม่ดีก็แค่กอง publish ที่ถูก block ไว้ ไม่ว่าทางไหน broker ก็รอด

ก่อนจะถึง alarm ระดับ global ด้วยซ้ำ RabbitMQ ใช้ internal flow control ต่อ connection: ถ้า publisher ส่งเร็วกว่าที่ broker จะ route และ persist ได้ broker จะ throttle connection นั้นโดยเฉพาะเพื่อไม่ให้ publisher ถล่ม pipeline ภายใน ใน management UI connection แบบนี้จะแสดงสถานะ flow — เป็นสัญญาณปกติที่บอกว่า publisher นี้กำลังถูกจังหวะ ไม่ใช่ error

classic queue รุ่นเก่าพยายามเก็บ message ไว้ใน memory เป็นหลักเพื่อความเร็ว queue ที่ลึกมาก (message เป็นล้าน) จึงกิน RAM เยอะและอาจ trip memory alarm RabbitMQ เคยมี lazy queue mode แบบ opt-in (x-queue-mode: lazy) ให้ดัน message ไปเก็บบน disk แทน — แต่ตอนนี้ setting นั้นถูกเพิกเฉยแล้ว ตั้งแต่ RabbitMQ 3.12 classic queue version 2 (CQv2) เป็น storage engine default และมันทำงานแบบ lazy เดิมโดยอัตโนมัติอยู่แล้ว: backlog ที่ลึกส่วนใหญ่อยู่บน disk เหลือแค่ buffer เล็ก ๆ ใน memory สำหรับ message ที่กำลังจะถูก deliver classic queue สมัยใหม่จึงรับมือ backlog ขนาดใหญ่ได้ดีโดยไม่ต้อง opt-in อะไร ส่วน quorum queue เก็บบน disk เสมอและรับมือ backlog ลึก ๆ ได้ดีเช่นกัน

alarm และ flow control เป็นตาข่ายกันตก ไม่ใช่โหมดการทำงานปกติ ถ้าทำงานสม่ำเสมอ แปลว่าออกแบบผิด วิธีทำให้เกิดยาก:

  • scale consumer ให้ ack rate เท่ากับ publish rate ในระยะยาว
  • bound queue ด้วย max-length หรือ TTL เพื่อไม่ให้ producer ที่หลุดควบคุมโต queue ได้ไม่จำกัด (drop หรือ dead-letter ส่วนที่ล้นแทน)
  • classic queue v2 จัดการเรื่องนี้ให้อยู่แล้ว — ตั้งแต่ RabbitMQ 3.12 ไม่มี lazy mode ให้ opt-in อีกต่อไป CQv2 เป็น default และเก็บ backlog ลึก ๆ ไว้บน disk โดยอัตโนมัติ ใช้ quorum queue เมื่อต้องการ replication ด้วย
  • จัดการ “connection blocked” ใน producer — ถอยแทนที่จะกระหน่ำ
  • alert เมื่อ queue depth ไต่ขึ้น เพื่อ scale consumer ก่อน memory alarm จะทำงาน
RabbitMQ ทำอะไรกับ publisher เมื่อ memory หรือ disk alarm ทำงาน?
connection แสดงสถานะ "flow" ใน management UI หมายความว่าอะไร?
อะไรที่ทำให้ backlog ลึก ๆ ของ classic queue สมัยใหม่ไม่กิน RAM ของ broker?
วิธีที่ดีที่สุดในการทำให้ alarm และ flow control เกิดยากคืออะไร?