feat: AI 이미지 생성 DLQ 및 재시도 메커니즘 구현#132
Conversation
AI 이미지 생성 요청의 실패 처리 및 재시도 메커니즘을 위한 DLX, DLQ, Retry 관련 상수들을 RabbitMqConfig에 추가 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
- DLX, DLQ, 재시도 Exchange 및 Queue Bean 추가 - aiImageRequestQueue에 DLX 설정 적용 - TTL 60초 재시도 대기 큐 구성 - 실패한 메시지의 자동 재시도 및 최종 처리 인프라 완성 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
- 실패한 AI 이미지 생성 요청 처리용 리스너 클래스 추가 - 최대 재시도 횟수 3회, 재시도 헤더 관리 설정 - AiRequestRepository, RabbitTemplate 의존성 주입 - 후속 메서드 구현을 위한 기본 프레임워크 완성 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
- 최종 실패한 AI 이미지 생성 요청을 위한 핸들러 추가 - AI_IMAGE_REQUEST_DLQ 큐 리스너 구현 - 요청 상태를 FAILED로 업데이트하는 트랜잭션 처리 - 요청이 존재하지 않는 경우 오류 로깅 추가 - 단순화된 DLQ 처리 로직으로 안정성 향상 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
WalkthroughAI 이미지 요청 큐에 대한 RabbitMQ dead-letter(사망 편지) 및 재시도 메커니즘이 추가되었습니다. 큐, 익스체인지, 라우팅 키 관련 상수와 빈이 신설 및 확장되었고, DLQ 메시지를 처리하여 요청 엔티티 상태를 갱신하는 새로운 리스너 클래스가 도입되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Producer
participant AIImageRequestQueue
participant Consumer
participant DLX (Dead-letter Exchange)
participant RetryExchange
participant RetryQueue
participant DLQ
participant DlqListener
participant DB
Producer->>AIImageRequestQueue: 메시지 전송
AIImageRequestQueue->>Consumer: 메시지 전달
Consumer-->>AIImageRequestQueue: 처리 실패 (예외 발생)
AIImageRequestQueue->>DLX: 메시지 dead-lettering
DLX->>RetryExchange: dead-letter routing
RetryExchange->>RetryQueue: 메시지 저장 (TTL 60초)
RetryQueue->>AIImageRequestQueue: TTL 만료 후 재전송
AIImageRequestQueue->>Consumer: 재시도
alt 재시도 실패 반복 후 최대 횟수 초과
AIImageRequestQueue->>DLX: 메시지 dead-lettering
DLX->>DLQ: 최종 실패 메시지 전달
DLQ->>DlqListener: 메시지 수신
DlqListener->>DB: 요청 상태 FAILED로 변경
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~15–20 minutes Possibly related issues
Poem
Note 🔌 MCP (Model Context Protocol) integration is now available in Early Access!Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Pull Request Overview
This PR implements a Dead Letter Queue (DLQ) and retry mechanism for AI image generation requests to improve system reliability. The implementation includes automatic retry logic for failed messages and final failure handling through a dedicated DLQ listener.
- Adds RabbitMQ DLQ and retry infrastructure with proper exchange and queue configurations
- Implements DlqListener to handle final failed messages and update AI request status to FAILED
- Configures 60-second retry delay mechanism with TTL-based message reprocessing
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| RabbitMqConfig.java | Adds DLQ/retry exchanges, queues, and bindings with TTL configuration |
| DlqListener.java | Creates listener to process final failed messages and update request status |
| // DLX에서 재시도 Exchange로의 바인딩 | ||
| @Bean | ||
| public Binding aiImageRequestDlxToRetryBinding(TopicExchange aiImageRequestRetryExchange, TopicExchange aiImageRequestDlx) { | ||
| return BindingBuilder.bind(aiImageRequestRetryExchange) |
There was a problem hiding this comment.
The binding direction is incorrect. This method attempts to bind a TopicExchange to another TopicExchange, but the bind() method expects a Queue as the first parameter. This should bind the retry exchange to the DLX, not the other way around.
| return BindingBuilder.bind(aiImageRequestRetryExchange) | |
| public Binding aiImageRequestDlxToRetryBinding(Queue aiImageRequestRetryQueue, TopicExchange aiImageRequestDlx) { | |
| return BindingBuilder.bind(aiImageRequestRetryQueue) |
| return new Queue(AI_IMAGE_REQUEST_QUEUE, true); | ||
| Map<String, Object> args = new HashMap<>(); | ||
| args.put("x-dead-letter-exchange", AI_IMAGE_REQUEST_DLX); | ||
| args.put("x-dead-letter-routing-key", AI_IMAGE_REQUEST_RETRY_ROUTING_KEY); // 기본적으로 재시도로 라우팅 |
There was a problem hiding this comment.
The dead letter routing key configuration may cause infinite retry loops. When a message fails, it goes to DLX with retry routing key, but there's no mechanism to limit retry attempts or eventually route to the final DLQ. Consider adding retry count tracking or max retry limit.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/main/java/hanium/modic/backend/common/amqp/config/RabbitMqConfig.java (1)
108-114: TTL 하드코딩 대신 설정값으로 분리 권장
x-message-ttl을 60 000 ms로 직접 넣어 두었습니다.
운영 환경에서 재시도 지연 시간이 바뀔 때마다 코드를 다시 배포해야 하므로,
application.yml(RabbitMqProperties 등)로 이동해 동적으로 주입하는 편이 유지보수에 유리합니다.src/main/java/hanium/modic/backend/domain/ai/listener/DlqListener.java (1)
32-40: 엔티티 미존재 시 idempotent 처리가 필요합니다DB에 해당
requestId가 없을 때 단순 로그만 남기고 종료하고 있습니다.
동일 메시지가 중복 수신될 경우에도 안전하게 무시되도록if(aiRequestOpt.isEmpty()) { // 이미 DLQ 처리된 건인지 확인 후 ACK return; }처럼 idempotent 로직을 명시하거나,
운영 상 문제라면 별도 알림/모니터링으로 연결해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/main/java/hanium/modic/backend/common/amqp/config/RabbitMqConfig.java(3 hunks)src/main/java/hanium/modic/backend/domain/ai/listener/DlqListener.java(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (1)
src/main/java/hanium/modic/backend/common/amqp/config/RabbitMqConfig.java (1)
125-130: Exchange-to-Exchange 바인딩 호환성 확인 필요
BindingBuilder.bind(aiImageRequestRetryExchange).to(aiImageRequestDlx)…형태의 E2E 바인딩은
Spring AMQP 2.1+ / RabbitMQ 3.6+ 에서만 지원됩니다.
운영 브로커·라이브러리 버전이 해당 요구 사항을 충족하는지 확인해 주세요.
| Map<String, Object> args = new HashMap<>(); | ||
| args.put("x-dead-letter-exchange", AI_IMAGE_REQUEST_DLX); | ||
| args.put("x-dead-letter-routing-key", AI_IMAGE_REQUEST_RETRY_ROUTING_KEY); // 기본적으로 재시도로 라우팅 | ||
| return new Queue(AI_IMAGE_REQUEST_QUEUE, true, false, false, args); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
재시도 루프가 무한 반복될 수 있습니다 – 최종 DLQ 라우팅 로직을 명확히 해주세요.
현재 x-dead-letter-exchange 를 DLX로, x-dead-letter-routing-key 를 재시도용 라우팅 키로만 지정해 두어 최초 실패 시에는 무조건 재시도 큐로만 흐르게 됩니다.
메시지가 재시도 큐에서 다시 만료-실패를 반복하면 동일 경로로 계속 순환하여 실제 DLQ(ai.image.request.dlq)에 도달하지 못할 가능성이 있습니다.
x-death헤더를 검사해 재시도 횟수를 초과하면 DLQ 라우팅 키로 재게시하거나,- 별도의 정책(예: DLX-policy)으로
max-length,max-delivery-count등을 설정해 DLQ로 강제 전환
과 같은 명시적 종료 지점을 추가해 주세요.
무한 재시도는 메시지 폭주 및 비용 증가로 이어질 수 있습니다.
🤖 Prompt for AI Agents
In src/main/java/hanium/modic/backend/common/amqp/config/RabbitMqConfig.java
around lines 45 to 48, the current dead-letter exchange and routing key setup
causes infinite retry loops because messages always route back to the retry
queue. To fix this, implement logic to check the 'x-death' header for retry
count and once a maximum retry threshold is exceeded, route the message to the
final dead-letter queue using its routing key. Alternatively, configure queue
policies like max-length or max-delivery-count to automatically move messages to
the DLQ after retries. This ensures messages do not endlessly cycle and properly
reach the DLQ after retry limits.
| @Transactional | ||
| @RabbitListener(queues = AI_IMAGE_REQUEST_DLQ) | ||
| public void handleFinalFailedMessage(AiImageRequestMessageDto messageDto, Message message) { | ||
| log.error("[최종 실패] AI 이미지 생성 요청 최종 실패: requestId={}", messageDto.requestId()); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
메시지 소비 실패 시 재큐잉/ACK 전략을 명시하세요
@Transactional 내부에서 DB 예외가 발생하면 런타임 예외가 throw 되어 컨테이너가 NACK + requeue false 로 처리할 수 있습니다.
현재 리스너는 수동 ACK 설정이 없으므로, 예외 발생 시 메시지가 즉시 DLX 로 이동해 재시도 없이 종료될 수 있으니
ackMode=MANUAL+ 성공 시 수동 ACK, 실패 시channel.basicNack(..., requeue=true)- 또는
SimpleRabbitListenerContainerFactory의defaultRequeueRejected=true
등 원하는 재시도·폐기 정책을 명확히 지정해 주세요.
🤖 Prompt for AI Agents
In src/main/java/hanium/modic/backend/domain/ai/listener/DlqListener.java around
lines 26 to 29, the message listener lacks explicit acknowledgment and requeue
strategy, which can cause messages to be lost or prematurely dead-lettered on
exceptions. To fix this, configure the listener with manual acknowledgment mode
by setting ackMode=MANUAL, then in the method, acknowledge the message manually
on success and on failure call channel.basicNack with requeue=true to enable
retries. Alternatively, set defaultRequeueRejected=true in the
SimpleRabbitListenerContainerFactory configuration to control requeue behavior
globally.
Summary
resolved #131
주요 변경사항
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit