Skip to content

AI 이미지 생성 응답 큐 DLQ 및 재시도 메커니즘 추가#213

Merged
yooooonshine merged 8 commits intodevelopfrom
modic_backend_212
Nov 3, 2025
Merged

AI 이미지 생성 응답 큐 DLQ 및 재시도 메커니즘 추가#213
yooooonshine merged 8 commits intodevelopfrom
modic_backend_212

Conversation

@goalSetter09
Copy link
Collaborator

@goalSetter09 goalSetter09 commented Oct 12, 2025

요약

AI 이미지 생성 응답 큐(AI_IMAGE_CREATED_QUEUE)에서 예외 발생 시 무한 루프 문제를 해결하기 위해 DLQ 및 재시도 메커니즘을 추가했습니다.

변경사항

1. RabbitMQ DLQ 설정 추가

  • AI_IMAGE_CREATED_QUEUE에 Dead Letter Exchange 설정 추가
  • 재시도 큐(AI_IMAGE_CREATED_RETRY_QUEUE) 구성 (TTL 60초)
  • 최종 실패 큐(AI_IMAGE_CREATED_DLQ) 구성

2. DLQ 리스너 구현

  • AiImageCreatedDlqListener: 최종 실패 메시지 처리
    • 메시지 상태를 RESPONSE_FAILED로 변경
    • SSE 연결이 있으면 클라이언트에 실패 알림 전송

3. 무한 재시도 방지

  • SimpleRabbitListenerContainerFactory 설정 추가
  • setDefaultRequeueRejected(false)로 예외 발생 시 메시지 재큐잉 방지

4. 도메인 모델 확장

  • AiImageStatus enum에 RESPONSE_FAILED 상태 추가
  • ChatMessageResponsecreateErrorResponse() 메서드 추가

5. 단위 테스트

  • AiImageCreatedDlqListenerTest: 4개 테스트 케이스 작성

데이터베이스 변경사항

반드시 배포 전에 실행 필요:

ALTER TABLE ai_chat_messages 
MODIFY COLUMN status ENUM(
    'REQUEST',
    'REQUEST_PENDING', 
    'REQUEST_FAILED',
    'RESPONSE',
    'RESPONSE_FAILED'
) NOT NULL;

메시지 플로우

정상 처리

AI_IMAGE_CREATED_QUEUE → AiImageCreatedListener → DB 저장 → SSE 응답

실패 처리 (재시도)

AI_IMAGE_CREATED_QUEUE (예외 발생)
    ↓
AI_IMAGE_CREATED_DLX
    ↓
AI_IMAGE_CREATED_RETRY_QUEUE (60초 대기)
    ↓
AI_IMAGE_CREATED_QUEUE (재시도)

최종 실패 처리

AI_IMAGE_CREATED_QUEUE (재시도 실패)
    ↓
AI_IMAGE_CREATED_DLX
    ↓
AI_IMAGE_CREATED_DLQ
    ↓
AiImageCreatedDlqListener (상태 업데이트 및 알림)

테스트 계획

  • 단위 테스트 통과 확인
  • 전체 테스트 통과 확인
  • DB 스키마 변경 적용
  • RabbitMQ 기존 큐 삭제 후 재생성
  • 정상 메시지 처리 확인
  • 예외 발생 시 DLQ 이동 확인
  • 재시도 메커니즘 동작 확인
  • DLQ 리스너 처리 확인

주의사항

  1. 배포 전 필수 작업

    • DB 스키마 변경 적용 (위 SQL 실행)
    • RabbitMQ에서 기존 ai.image.created.queue 삭제
  2. 모니터링 포인트

    • DLQ 메시지 수 모니터링
    • 재시도 큐 메시지 체류 시간 확인
    • 최종 실패 로그 확인

Closes #212

Summary by CodeRabbit

  • 신기능
    • AI 이미지 생성 실패가 최종 확정될 경우, 사용자가 오류 알림과 함께 RESPONSE_FAILED 상태를 확인할 수 있습니다.
  • 개선
    • AI 이미지 생성 이벤트에 재시도/지연 처리와 DLQ 연계를 추가하여 처리 안정성과 복원력을 강화했습니다.
    • 메시지 리스너 동작을 조정해 무한 재큐를 방지하고 실패 시 올바르게 사후 처리되도록 했습니다.
  • 테스트
    • 최종 실패 처리 흐름(SSE 알림, 상태 업데이트, 예외 처리 등)에 대한 단위 테스트를 추가했습니다.
  • 기타
    • 포트에 따라 SSL을 선택적으로 활성화하여 연결 보안을 유연하게 구성했습니다.

goalSetter09 and others added 3 commits October 12, 2025 21:23
AI_IMAGE_CREATED_QUEUE에서 예외 발생 시 무한 루프 문제 해결

- AiImageStatus enum에 RESPONSE_FAILED 상태 추가
- ChatMessageResponse에 createErrorResponse() 메서드 추가
- RabbitMqConfig에 AI_IMAGE_CREATED_QUEUE DLQ 설정 추가
  - DLX, DLQ, 재시도 큐 및 바인딩 구성
  - 재시도 TTL 60초 설정
- AiImageCreatedDlqListener 구현
  - 최종 실패 메시지 상태를 RESPONSE_FAILED로 변경
  - SSE 연결 시 클라이언트에 실패 알림 전송
- 단위 테스트 작성 및 검증 완료

Closes #212
포트 5671(AWS AmazonMQ SSL 포트)일 때만 SSL 프로토콜을 활성화하도록 수정하여 로컬 개발 환경과 클라우드 환경 모두 지원

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
리스너에서 예외 발생 시 메시지 requeue를 비활성화하여 DLQ 무한 루프 방지

- SimpleRabbitListenerContainerFactory Bean 추가
- setDefaultRequeueRejected(false) 설정으로 예외 발생 시 메시지를 큐로 반환하지 않음
- DLQ 설정이 있는 큐는 자동으로 DLX로 라우팅되어 정상 처리됨
- 모든 RabbitListener에 전역적으로 적용

Related to #212
@coderabbitai
Copy link

coderabbitai bot commented Oct 12, 2025

Walkthrough

AI 이미지 생성 응답(AI_IMAGE_CREATED) 플로우에 DLQ/재시도 구성을 추가하고, 최종 실패 처리를 위한 DLQ 리스너를 도입했습니다. SSL 적용을 포트 5671일 때만 활성화하도록 조건화하고, 메시지 리스너 팩토리를 추가했습니다. 상태 enum과 응답 DTO에 실패 상태/팩토리 메서드를 보강하고 단위 테스트를 포함했습니다.

Changes

Cohort / File(s) Summary
RabbitMQ 구성(DLQ/Retry/SSL/Listener Factory)
src/main/java/.../common/amqp/config/RabbitMqConfig.java
AI_IMAGE_CREATED용 DLX/DLQ/Retry Exchange/Queue/Binding 추가, AI_IMAGE_CREATED_QUEUE에 DLQ 라우팅 설정, Retry Queue TTL과 DLX로 원본 복귀 구성. SSL은 포트 5671에서만 활성화. Jackson 컨버터와 requeue-off인 SimpleRabbitListenerContainerFactory 추가.
AI 이미지 상태 확장
src/main/java/.../aiServer/enums/AiImageStatus.java
상태값 RESPONSE_FAILED 추가. RESPONSE 뒤 콤마 추가로 구문 수정.
DLQ 최종 실패 리스너(신규)
src/main/java/.../aiServer/listener/AiImageCreatedDlqListener.java
AI_IMAGE_CREATED_DLQ 구독. 요청 미존재 시 로그 처리. 존재 시 상태를 RESPONSE_FAILED로 업데이트/저장 후 SSE로 실패 알림 시도(예외 캐치). 트랜잭션 처리.
DLQ 리스너 단위 테스트(신규)
src/test/java/.../aiServer/listener/AiImageCreatedDlqListenerTest.java
성공 경로, 요청 없음, SSE 실패, 이미지 미생성 케이스 등 검증. 리포지토리/ SSE 서비스 목킹 및 상호작용 검증.
에러 응답 팩토리 추가
src/main/java/.../web/ai/aiChat/dto/response/ChatMessageResponse.java
createErrorResponse(AiChatMessageEntity, String) 추가. RESPONSE_FAILED 상태와 에러 메시지 기반 응답 생성.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Producer as AI Service
    participant EX as AI_IMAGE_CREATED_EXCHANGE
    participant Q as AI_IMAGE_CREATED_QUEUE
    participant L as AiImageCreatedListener
    participant DLX as AI_IMAGE_CREATED_DLX
    participant DLQ as AI_IMAGE_CREATED_DLQ
    participant RX as AI_IMAGE_CREATED_RETRY_EXCHANGE
    participant RQ as AI_IMAGE_CREATED_RETRY_QUEUE (TTL)

    rect rgba(230,240,255,0.5)
    note right of Producer: 이미지 생성 응답 전송
    Producer->>EX: publish(message, routingKey)
    EX->>Q: route
    Q->>L: deliver
    alt 처리 실패(requeue off)
        L--xQ: reject
        Q->>DLX: dead-letter
        DLX->>DLQ: route
    else 처리 성공
        L-->>Q: ack
    end
    end

    rect rgba(240,255,230,0.5)
    note over DLX,RX: 재시도 경로
    DLX-->>RX: route to retry exchange
    RX-->>RQ: enqueue with TTL
    RQ-->>EX: TTL 만료 시 DLX로 원복 설정 통해 원본 EX/Key로 재주입
    end
Loading
sequenceDiagram
    autonumber
    participant DLQ as AI_IMAGE_CREATED_DLQ
    participant DQL as AiImageCreatedDlqListener
    participant Repo as AiChatMessageRepository
    participant SSE as AiResponseSseService
    DLQ->>DQL: AiImageResponseMessageDto
    DQL->>Repo: findByRequestId(requestId)
    alt 엔티티 없음
        DQL-->>DLQ: log and return
    else 엔티티 있음
        DQL->>Repo: update status = RESPONSE_FAILED, save
        opt SSE 연결 시도
            DQL->>SSE: send error response
            SSE-->>DQL: ok or throws
            note over DQL: 예외는 로그 후 삼킴
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

작은 큐 위에 떨리는 귀, hop hop!
실패도 담아 두는 DLQ의 컵 ☕
TTL 틱톡, 다시 한 번 도약해 보자—
그래도 끝이면, 내가 알릴게요요 📣
RESPONSE_FAILED라 쓰고도, 내 맘은 계속 뛰죠 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning RabbitMqConfig에 DLQ 및 재시도 설정 이외에 SSL 포트 조건부 초기화 로직이 추가되었는데 이는 이슈 #212의 범위를 벗어난 변경으로 보입니다. SSL 초기화 관련 코드는 별도의 PR로 분리하거나 변경 의도를 문서화해 주시기 바랍니다.
Description Check ⚠️ Warning PR 설명에는 상세한 변경사항이 포함되어 있으나 저장소의 템플릿에서 요구하는 ## 개요## 작업사항 섹션이 누락되고 대신 ## 요약## 변경사항 등 다른 제목을 사용하고 있어 템플릿 형식과 일치하지 않습니다. 설명 섹션을 저장소에 지정된 ## 개요## 작업사항 형식으로 수정하여 템플릿과 일치하도록 변경해 주시기 바랍니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed PR 제목은 AI 이미지 생성 응답 큐에 DLQ와 재시도 메커니즘을 추가하는 주요 변경 내용을 간결하고 명확하게 요약하고 있어 적절합니다.
Linked Issues Check ✅ Passed 연결된 이슈 #212의 요구사항인 RabbitMqConfig의 DLQ 및 재시도 설정 추가, AiImageCreatedDlqListener 구현, AiImageStatus enum 및 ChatMessageResponse 메서드 확장, 단위 테스트 작성 등 모든 코딩 관련 목표가 충족되었습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch modic_backend_212

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

goalSetter09 and others added 4 commits October 12, 2025 22:47
AI_IMAGE_CREATED_QUEUE에서 예외 발생 시 무한 루프 문제 해결

- AiImageStatus enum에 RESPONSE_FAILED 상태 추가
- ChatMessageResponse에 createErrorResponse() 메서드 추가
- RabbitMqConfig에 AI_IMAGE_CREATED_QUEUE DLQ 설정 추가
  - DLX, DLQ, 재시도 큐 및 바인딩 구성
  - 재시도 TTL 60초 설정
- AiImageCreatedDlqListener 구현
  - 최종 실패 메시지 상태를 RESPONSE_FAILED로 변경
  - SSE 연결 시 클라이언트에 실패 알림 전송
- 단위 테스트 작성 및 검증 완료

Closes #212
포트 5671(AWS AmazonMQ SSL 포트)일 때만 SSL 프로토콜을 활성화하도록 수정하여 로컬 개발 환경과 클라우드 환경 모두 지원

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
리스너에서 예외 발생 시 메시지 requeue를 비활성화하여 DLQ 무한 루프 방지

- SimpleRabbitListenerContainerFactory Bean 추가
- setDefaultRequeueRejected(false) 설정으로 예외 발생 시 메시지를 큐로 반환하지 않음
- DLQ 설정이 있는 큐는 자동으로 DLX로 라우팅되어 정상 처리됨
- 모든 RabbitListener에 전역적으로 적용

Related to #212
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between acfff78 and c199bed.

📒 Files selected for processing (5)
  • src/main/java/hanium/modic/backend/common/amqp/config/RabbitMqConfig.java (5 hunks)
  • src/main/java/hanium/modic/backend/domain/ai/aiServer/enums/AiImageStatus.java (1 hunks)
  • src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedDlqListener.java (1 hunks)
  • src/main/java/hanium/modic/backend/web/ai/aiChat/dto/response/ChatMessageResponse.java (1 hunks)
  • src/test/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedDlqListenerTest.java (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: compile-test
src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedDlqListener.java

[error] 12-12: cannot find symbol: import hanium.modic.backend.domain.ai.aiChat.dto.ChatMessageResponse; symbol: class ChatMessageResponse location: package hanium.modic.backend.domain.ai.aiChat.dto

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (6)
src/main/java/hanium/modic/backend/domain/ai/aiChat/dto/ChatMessageResponse.java (1)

39-49: 에러 메시지 노출 정책 점검 및 안전 기본값 오버로드 제안

현재 errorMessage가 그대로 클라이언트로 전달됩니다. 내부 상세/서버 메시지 노출 위험이 있어, 안전한 기본 문구/에러코드(예: USER_SAFE_MESSAGE, ERROR_CODE)로 표준화하는 오버로드 추가를 권장합니다.

예:

+ public static ChatMessageResponse createErrorResponse(AiChatMessageEntity entity) {
+   String safe = "이미지 생성 응답 처리에 실패했습니다. 잠시 후 다시 시도해주세요.";
+   return createErrorResponse(entity, safe);
+ }

또한 createdAt을 엔티티 생성시각으로 유지하는 현재 설계가 의도와 맞는지(에러 이벤트 시각이 필요한지) 확인 부탁드립니다.

src/test/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedDlqListenerTest.java (1)

79-82: SSE 페이로드 내용까지 검증 추가 제안

sendToClient 호출 여부 외에 전달된 ChatMessageResponse의 status, textContent 등을 ArgumentCaptor로 검증하면 회귀 감지에 유용합니다.

예:

ArgumentCaptor<ChatMessageResponse> cap = ArgumentCaptor.forClass(ChatMessageResponse.class);
verify(aiResponseSseService).sendToClient(eq(requestId), cap.capture());
assertThat(cap.getValue().status()).isEqualTo(AiImageStatus.RESPONSE_FAILED);
assertThat(cap.getValue().textContent()).isNotBlank();

Also applies to: 205-208

src/main/java/hanium/modic/backend/common/amqp/config/RabbitMqConfig.java (2)

255-263: SSL 포트 기반 활성화 OK

AmazonMQ(5671) 환경 타깃으로 합리적입니다. 멀티 엔드포인트(SSL/Non-SSL) 지원 필요 시 속성 기반 토글 추가를 고려해도 좋습니다.


267-280: 리스너 컨테이너 설정 적절 — 메시지 재큐 방지로 DLX 흐름 유도

Jackson 컨버터 및 setDefaultRequeueRejected(false) 설정은 목적에 부합합니다. 다만 상기 ‘최종 DLQ 경로’가 없으면 재시도 무한 루프만 지연됩니다. RetryInterceptor(또는 재시도 계수 기반 라우팅) 병행을 권장합니다.

src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedDlqListener.java (2)

58-69: DB 커밋 이후에 SSE 전송하도록 조정 권장

현재 트랜잭션 내에서 SSE를 전송하면 커밋 실패 시 불일치가 발생할 수 있습니다. afterCommit 훅으로 이동을 권장합니다.

예:

import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
  @Override public void afterCommit() {
    aiResponseSseService.sendToClient(messageDto.requestId(),
      ChatMessageResponse.createErrorResponse(requestChatMessage,
        "이미지 생성 응답 처리에 실패했습니다. 잠시 후 다시 시도해주세요."));
  }
});

(try/catch는 afterCommit 블록 내부로 이동)


39-39: 미사용 파라미터 제거 또는 활용

Message message 파라미터가 사용되지 않습니다. 제거하거나, 상관관계/진단을 위해 headers(예: x-death) 로그에 활용하세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between acfff78 and a1e7a64.

📒 Files selected for processing (5)
  • src/main/java/hanium/modic/backend/common/amqp/config/RabbitMqConfig.java (5 hunks)
  • src/main/java/hanium/modic/backend/domain/ai/aiChat/dto/ChatMessageResponse.java (1 hunks)
  • src/main/java/hanium/modic/backend/domain/ai/aiServer/enums/AiImageStatus.java (1 hunks)
  • src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedDlqListener.java (1 hunks)
  • src/test/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedDlqListenerTest.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedDlqListener.java (1)
src/main/java/hanium/modic/backend/common/amqp/config/RabbitMqConfig.java (1)
  • RequiredArgsConstructor (23-298)
🔇 Additional comments (2)
src/test/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedDlqListenerTest.java (1)

26-83: 테스트 커버리지 좋습니다

성공/미존재/SSE 실패/isImageGenerated=false 경로 검증이 명확합니다.

src/main/java/hanium/modic/backend/domain/ai/aiServer/enums/AiImageStatus.java (1)

7-8: RESPONSE_FAILED 추가 검토 필요

  • DB enum 제약조건 및 마이그레이션 스크립트에 RESPONSE_FAILED 포함 여부 확인
  • switch/if 분기 및 직렬화·역직렬화 로직에서 새 상태 누락 여부 점검

Comment on lines 82 to 88
@Bean
public Queue aiImageCreatedQueue() {
return new Queue(AI_IMAGE_CREATED_QUEUE, true);
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", AI_IMAGE_CREATED_DLX);
args.put("x-dead-letter-routing-key", AI_IMAGE_CREATED_RETRY_ROUTING_KEY);
return new Queue(AI_IMAGE_CREATED_QUEUE, true, false, false, args);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

현재 구성은 무한 재시도 루프가 됩니다 — DLQ로 절대 도달하지 않음

  • aiImageCreatedQueue: DLX 라우팅키를 항상 "…retry"로 지정
  • aiImageCreatedRetryQueue: TTL 만료 시 원래 EX/ROUTING_KEY로 반환
  • 결과적으로 실패 → 재시도큐 → 원래큐 → 실패 … 가 반복되고, DLQ 바인딩("…dlq")은 사용되지 않습니다. DLQ 리스너는 메시지를 받지 못합니다.

두 가지 해결책 중 하나를 권장합니다:

  1. 간단한 설정 기반(1회 지연 후 최종 DLQ로 이동)
@@ public Queue aiImageCreatedRetryQueue() {
- args.put("x-dead-letter-exchange", AI_IMAGE_CREATED_EXCHANGE);
- args.put("x-dead-letter-routing-key", AI_IMAGE_CREATED_ROUTING_KEY);
+ // TTL 만료 시 최종 DLQ로 보냄 (지연 재시도는 1회로 제한)
+ args.put("x-dead-letter-exchange", AI_IMAGE_CREATED_DLX);
+ args.put("x-dead-letter-routing-key", AI_IMAGE_CREATED_DLQ_ROUTING_KEY);
}

이렇게 하면: 원래 큐에서 1회 처리 실패 → 재시도 큐로 이동(60초 대기) → DLQ로 최종 이동(무한 루프 차단).

  1. 권장(정교한) 접근: Spring Retry + RepublishMessageRecoverer
  • 리스너 컨테이너 팩토리에 RetryInterceptor를 추가하여 최대 N회(예: 3) 시도 후 DLQ로 republish.
  • 재시도 간 백오프(지수/고정) 설정 가능. DLQ 라우팅키는 “…dlq”.

예:

+import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder;
+import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
+import org.springframework.retry.interceptor.RetryOperationsInterceptor;

@@ public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
-  return factory;
+  factory.setAdviceChain(
+    RetryInterceptorBuilder.stateless()
+      .maxAttempts(3)
+      .backOffOptions(2000, 2.0, 60000) // 2s 시작, 2배, 최대 60s
+      .recoverer(new RepublishMessageRecoverer(rabbitTemplate(connectionFactory),
+        AI_IMAGE_CREATED_DLX, AI_IMAGE_CREATED_DLQ_ROUTING_KEY))
+      .build()
+  );
+  return factory;

참고: Request 경로도 동일한 패턴(무한 루프)입니다. 동일한 방식으로 정리하시길 권장합니다.

무한 루프가 의도인지, 최종 DLQ로 보내고자 하는 최대 재시도 횟수(또는 지연 전략)를 알려주시면 정확한 설정/코드 패치를 드리겠습니다.

Also applies to: 128-136, 147-154, 102-120


다음 스크립트를 실행하여 RabbitMqConfig.java의 DLX/TTL 및 컨테이너 팩토리 재시도 설정을 확인하겠습니다.


🏁 Script executed:

#!/bin/bash
FILE=src/main/java/hanium/modic/backend/common/amqp/config/RabbitMqConfig.java

echo "=== aiImageCreatedQueue 정의 ==="
grep -n "aiImageCreatedQueue" $FILE

echo "=== aiImageCreatedRetryQueue 정의 ==="
grep -n "aiImageCreatedRetryQueue" $FILE

echo "=== x-dead-letter 설정 ==="
grep -n "x-dead-letter" $FILE

echo "=== Listener 컨테이너 팩토리 설정 ==="
grep -R -n "rabbitListenerContainerFactory" src/main/java/hanium/modic/backend/common/amqp/config

echo "=== setDefaultRequeueRejected 사용 여부 ==="
grep -R -n "setDefaultRequeueRejected" src/main/java/hanium/modic/backend/common/amqp/config

Length of output: 1496


무한 재시도 루ープ 방지 및 DLQ 도달 설정 필요

  • 현재 aiImageCreatedQueue → 재시도큐 → 원큐 반복만 수행되어 최종 DLQ(“…dlq”)에는 메시지가 도달하지 않음

  • 아래 두 가지 중 하나로 수정하세요

    1. 재시도큐의 DLX를 DLQ로 변경해 1회 재시도 후 최종 DLQ로 이동
      @@ public Queue aiImageCreatedRetryQueue() {
      - args.put("x-dead-letter-exchange", AI_IMAGE_CREATED_EXCHANGE);
      - args.put("x-dead-letter-routing-key", AI_IMAGE_CREATED_ROUTING_KEY);
      + // 1회 지연 후 최종 DLQ로 이동
      + args.put("x-dead-letter-exchange", AI_IMAGE_CREATED_DLX);
      + args.put("x-dead-letter-routing-key", AI_IMAGE_CREATED_DLQ_ROUTING_KEY);
      }
    2. Spring Retry + RepublishMessageRecoverer 적용
      • rabbitListenerContainerFactoryRetryInterceptorBuilder.stateless().maxAttempts(3)…recoverer(new RepublishMessageRecoverer(rabbitTemplate, AI_IMAGE_CREATED_DLX, AI_IMAGE_CREATED_DLQ_ROUTING_KEY)) 추가하여 N회 실패 시 DLQ로 발행
  • 동일 패턴이 Request 큐에도 적용되어 있으니 함께 수정 필요

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/hanium/modic/backend/common/amqp/config/RabbitMqConfig.java
around lines 82 to 88, the current aiImageCreatedQueue setup causes messages to
cycle between the original queue and the retry queue and never reach the final
DLQ; fix by either (A) changing the retry queue's
dead-letter-exchange/routing-key so that after the retry it is routed to the
final DLQ (i.e. set the retry queue DLX to AI_IMAGE_CREATED_DLX and routing key
to AI_IMAGE_CREATED_DLQ_ROUTING_KEY), or (B) configure Spring Retry on
rabbitListenerContainerFactory using
RetryInterceptorBuilder.stateless().maxAttempts(3)...recoverer(new
RepublishMessageRecoverer(rabbitTemplate, AI_IMAGE_CREATED_DLX,
AI_IMAGE_CREATED_DLQ_ROUTING_KEY)) so that after N failures the message is
republished to the DLQ; apply the same correction for the corresponding Request
queue(s).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedDlqListener.java (2)

53-56: RESPONSE_FAILED로 이미 상태인 경우 불필요 저장 방지

중복 전달/재처리에서도 DB write를 줄이려면 변경 시에만 업데이트/저장을 수행하세요.

-        requestChatMessage.updateStatus(AiImageStatus.RESPONSE_FAILED);
-        aiChatMessageRepository.save(requestChatMessage);
+        if (requestChatMessage.getStatus() != AiImageStatus.RESPONSE_FAILED) {
+            requestChatMessage.updateStatus(AiImageStatus.RESPONSE_FAILED);
+            aiChatMessageRepository.save(requestChatMessage);
+        }

37-40: DLQ 단계 예외 처리/관측성 보강

DLQ 소비는 재시도 종착점입니다. DB 예외가 발생하면 메시지가 소실될 수 있으므로(재큐 없음) 오류 로깅 외에 운영 알림/메트릭을 남기고, 필요 시 DB 작업을 try/catch로 감싸 후속 조치를 취하는 것을 권장합니다. 또한 message 헤더(x-death 등)를 로그에 포함하면 원인 분석에 유용합니다.

예시:

  • DB 업데이트 try/catch로 감싸 상세 로깅/알림(Webhook/Slack/Metric) 추가
  • message.getMessageProperties().getHeaders()에서 x-death/x-exception-message 등을 추출해 로그에 포함

Also applies to: 53-56, 58-69

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1e7a64 and 89fac9d.

📒 Files selected for processing (2)
  • src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedDlqListener.java (1 hunks)
  • src/main/java/hanium/modic/backend/web/ai/aiChat/dto/response/ChatMessageResponse.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

Comment on lines +39 to +49
public static ChatMessageResponse createErrorResponse(AiChatMessageEntity entity, String errorMessage) {
return new ChatMessageResponse(
entity.getId(),
entity.getMessageOrder(),
SenderType.AI,
errorMessage,
entity.getRequestId(),
null,
entity.getCreateAt(),
AiImageStatus.RESPONSE_FAILED
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

오류 응답의 senderType 하드코딩으로 인한 불일치

USER 요청 엔티티의 id/order를 그대로 쓰면서 senderType을 AI로 고정하면, 클라이언트가 senderType 기반 렌더링/정렬 시 혼선이 생깁니다. 상태 변경 알림이라면 엔티티의 senderType을 그대로 사용하세요. 또한 createdAt을 요청 시각 대신 now로 표기하는 것도 고려해 주세요(선택).

필수 수정(불일치 해소):

-            SenderType.AI,
+            entity.getSenderType(),

선택(타임스탬프 의미 명확화):

-            entity.getCreateAt(),
+            LocalDateTime.now(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static ChatMessageResponse createErrorResponse(AiChatMessageEntity entity, String errorMessage) {
return new ChatMessageResponse(
entity.getId(),
entity.getMessageOrder(),
SenderType.AI,
errorMessage,
entity.getRequestId(),
null,
entity.getCreateAt(),
AiImageStatus.RESPONSE_FAILED
);
public static ChatMessageResponse createErrorResponse(AiChatMessageEntity entity, String errorMessage) {
return new ChatMessageResponse(
entity.getId(),
entity.getMessageOrder(),
entity.getSenderType(),
errorMessage,
entity.getRequestId(),
null,
LocalDateTime.now(),
AiImageStatus.RESPONSE_FAILED
);
}

@yooooonshine yooooonshine merged commit b2b3c43 into develop Nov 3, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AI 이미지 생성 응답 큐(AI_IMAGE_CREATED_QUEUE)에 DLQ 설정 추가

2 participants