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

Dead-Letter Exchanges

consumer ดึง message มา พยายาม process แล้ว fail — payload พัง, downstream service ล่ม หรือ record ที่อ้างถึงไม่มีอยู่ แล้วยังไงต่อ?

ถ้าคุณ nack แบบ requeue RabbitMQ จะเอา message กลับไปไว้หน้า queue คุณดึงมาอีก แล้วก็ fail อีก แล้วคุณก็ได้ poison message วนอยู่ในลูปแน่น ๆ เผา CPU และ block ทุกอย่างที่อยู่ข้างหลัง ถ้าคุณแค่ drop ทิ้ง คุณก็ทำข้อมูลหายแบบเงียบ ๆ ทั้งสองอย่างรับไม่ได้

ทางออกคือส่ง message ที่ fail ไปที่อื่นโดยตั้งใจ “ที่อื่น” นั้นคือ dead-letter exchange (DLX)

message จะถูก dead-letter — route ไปยัง DLX ที่ตั้งไว้ของ queue — เมื่อเกิดกรณีใดกรณีหนึ่ง:

  • ถูก reject หรือ nack แบบ requeue=false (consumer บอกว่า “อย่าเอาอันนี้กลับมาให้ฉัน”)
  • message TTL หมดอายุ ขณะนั่งอยู่ใน queue
  • queue ชน limit max-length และ message ถูก drop เพื่อเปิดที่ว่าง (overflow)

คุณผูก DLX เข้ากับ queue ด้วย argument x-dead-letter-exchange (และถ้าต้องการก็ x-dead-letter-routing-key เพื่อเปลี่ยน label ตอนออก)

flowchart LR
  q["work queue
(x-dead-letter-exchange: dlx)"] -->|"nack requeue=false
or TTL expiry
or overflow"| dlx["DLX (exchange)"]
  dlx --> dlq["dead-letter queue"]
  dlq --> ops["ops / inspector
/ retry logic"]
message ที่ fail ไหลไปยัง dead-letter queue
await channel.assertExchange('dlx', 'fanout', { durable: true });
await channel.assertQueue('orders.dead', { durable: true });
await channel.bindQueue('orders.dead', 'dlx', '');
// the work queue dead-letters to the DLX
await channel.assertQueue('orders', {
durable: true,
arguments: { 'x-dead-letter-exchange': 'dlx' },
});
// in the consumer, reject a bad message WITHOUT requeue → it dead-letters
channel.nack(msg, false, false);

ไม่ใช่ทุกอย่างควร retry ตลอดไป message ที่จะ ไม่มีวัน สำเร็จ — schema พัง, อ้างถึงสิ่งที่ไม่มีอยู่แล้ว — คือ poison message pattern คือ: นับว่า message ถูกลองมากี่ครั้ง (counter ใน header หรือ record x-death ที่ RabbitMQ เพิ่มให้ทุกครั้งที่ dead-letter) แล้วหลังจาก N ครั้งก็ route ไปยัง parking (หรือ “dead”) queue ที่คนหรือ alert เข้าไปดูได้ อย่าปล่อยให้ poison message วนลูป

นี่คือทริกฉลาด ๆ ที่ dead-lettering ปลดล็อก บางที failure เป็นแบบ ชั่วคราว — downstream service ล่มแป๊บเดียว — และคุณอยากลองใหม่ แต่ ไม่ใช่ทันที คุณอยากรอ 30 วินาทีแล้วค่อยลอง

รวม message/queue TTL กับ DLX:

  1. เมื่อ fail ก็ publish message ไปยัง retry queue ที่มี TTL 30 วินาที และมี DLX ชี้กลับไปยัง main exchange
  2. message นั่งอยู่ใน retry queue เฉย ๆ 30 วินาที
  3. เมื่อ TTL หมด RabbitMQ จะ dead-letter message นั้น — ซึ่ง route กลับไปยัง main queue เพื่อลองอีกครั้ง

คุณสร้าง delayed retry พร้อม backoff ด้วย queue config ล้วน ๆ — ไม่ต้องมี scheduler ไม่ต้อง sleep ใน consumer (โมดูล Consumer Patterns จะต่อยอดเป็น retry ladder เต็ม ๆ)

ทำไม nack message ที่ fail แบบ requeue=true ถึงอันตราย?
ข้อใดที่ *ไม่* ทำให้ message ถูก dead-letter?
จัดการ poison message ที่จะไม่มีวันสำเร็จอย่างไร?
TTL + DLX สร้าง delayed retry อย่างไร?