Conversation
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
WalkthroughAI 이미지 생성 응답(AI_IMAGE_CREATED) 플로우에 DLQ/재시도 구성을 추가하고, 최종 실패 처리를 위한 DLQ 리스너를 도입했습니다. SSL 적용을 포트 5671일 때만 활성화하도록 조건화하고, 메시지 리스너 팩토리를 추가했습니다. 상태 enum과 응답 DTO에 실패 상태/팩토리 메서드를 보강하고 단위 테스트를 포함했습니다. Changes
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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
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
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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
src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedDlqListener.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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 포트 기반 활성화 OKAmazonMQ(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
📒 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 분기 및 직렬화·역직렬화 로직에서 새 상태 누락 여부 점검
| @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); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
현재 구성은 무한 재시도 루프가 됩니다 — DLQ로 절대 도달하지 않음
- aiImageCreatedQueue: DLX 라우팅키를 항상 "…retry"로 지정
- aiImageCreatedRetryQueue: TTL 만료 시 원래 EX/ROUTING_KEY로 반환
- 결과적으로 실패 → 재시도큐 → 원래큐 → 실패 … 가 반복되고, DLQ 바인딩("…dlq")은 사용되지 않습니다. 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);
+ // 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로 최종 이동(무한 루프 차단).
- 권장(정교한) 접근: 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/configLength of output: 1496
무한 재시도 루ープ 방지 및 DLQ 도달 설정 필요
-
현재 aiImageCreatedQueue → 재시도큐 → 원큐 반복만 수행되어 최종 DLQ(“…dlq”)에는 메시지가 도달하지 않음
-
아래 두 가지 중 하나로 수정하세요
- 재시도큐의 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); } - Spring Retry + RepublishMessageRecoverer 적용
rabbitListenerContainerFactory에RetryInterceptorBuilder.stateless().maxAttempts(3)…recoverer(new RepublishMessageRecoverer(rabbitTemplate, AI_IMAGE_CREATED_DLX, AI_IMAGE_CREATED_DLQ_ROUTING_KEY))추가하여 N회 실패 시 DLQ로 발행
- 재시도큐의 DLX를 DLQ로 변경해 1회 재시도 후 최종 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).
There was a problem hiding this comment.
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
📒 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
| 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 | ||
| ); |
There was a problem hiding this comment.
오류 응답의 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.
| 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 | |
| ); | |
| } |
요약
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로 변경3. 무한 재시도 방지
SimpleRabbitListenerContainerFactory설정 추가setDefaultRequeueRejected(false)로 예외 발생 시 메시지 재큐잉 방지4. 도메인 모델 확장
AiImageStatusenum에RESPONSE_FAILED상태 추가ChatMessageResponse에createErrorResponse()메서드 추가5. 단위 테스트
AiImageCreatedDlqListenerTest: 4개 테스트 케이스 작성데이터베이스 변경사항
반드시 배포 전에 실행 필요:
메시지 플로우
정상 처리
실패 처리 (재시도)
최종 실패 처리
테스트 계획
주의사항
배포 전 필수 작업
ai.image.created.queue삭제모니터링 포인트
Closes #212
Summary by CodeRabbit