You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: add DLQ support for RabbitMQ messages exceeding max retries
- Add DLQ queue creation in rabbitmq-utils.ts for messages that fail after 10 retries
- Add dlqPublisher in consumer to move failed messages to DLQ with headers:
- x-original-queue: source queue name
- x-final-status-code or x-final-error: last failure reason
- x-final-retry-count: number of retry attempts
- Fix critical bug in getRetryCount(): was summing x-death counts from ALL queues
(both main + retry queue), effectively counting each retry twice. Now only
counts entries with reason="rejected" (actual consumer rejections)
- Add comprehensive e2e test verifying full 10-retry cycle and DLQ behavior
- Add RABBITMQ.md architecture documentation with mermaid flowchart
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
RQ -.->|"dead-letter after TTL<br/>(via default exchange)"| SQ1
53
+
C1 -->|"retry >= 10"| DLQ
54
+
```
55
+
56
+
## Message Flow
57
+
58
+
### Happy Path
59
+
1. Producer publishes message to `{prefix}.main-exchange`
60
+
2. Exchange fans out message to all bound service queues
61
+
3. Consumer reads message from `{prefix}.queue.{service}`
62
+
4. Consumer acknowledges (ack) → message removed
63
+
64
+
### Retry Path (dead-letter)
65
+
Used for: 5xx errors, 429 (rate-limit), 409 (lock conflict)
66
+
67
+
1. Consumer returns `DROP` (nack with requeue=false)
68
+
2. Message dead-letters to `{prefix}.retry-exchange.{service}`
69
+
3. Retry exchange routes to `{prefix}.retry-queue.{service}`
70
+
4. Message sits in retry queue for 5 seconds (TTL)
71
+
5. After TTL expires, message dead-letters directly to `{prefix}.queue.{service}` (via default exchange)
72
+
6. Message is re-delivered only to the failed service (not fanned out to all services)
73
+
7.**Max 10 retries** - after 10 failed attempts, message is moved to `{prefix}.dlq.{service}` for manual retry (logged as `RABBITMQ_MESSAGE_MAX_RETRIES_EXCEEDED`)
74
+
75
+
### Delayed Message Path (local sleep)
76
+
Used for: 425 (too early - `processAfterDelayMs` not yet reached)
77
+
78
+
1. Consumer sleeps locally (randomDelay)
79
+
2. Consumer returns `REQUEUE` (nack with requeue=true)
80
+
3. Message returns to the same queue immediately for retry
81
+
4. This avoids multiple DLX cycles when delay exceeds 5s TTL
0 commit comments