WAS 처리량 1.16 → 1,949 TPS, 동기 대기 30초 → 비동기 즉시 응답(4.9ms)
AI 이미지 합성 동기 블로킹 → RabbitMQ 비동기 전환
Problem
ML 이미지 합성(~30초) 작업이 Tomcat 스레드를 직접 점유하여, 동시 사용자 10~20명 수준에서도 전체 서비스가 마비되었습니다.Flask ML 서버에 동기 호출을 보내고 응답을 기다리는 구조여서, 한 요청이 30초씩 스레드를 점유하면 일기 작성·건강 기록 등 AI와 무관한 API까지 스레드를 할당받지 못해 전부 무응답 상태에 빠졌습니다.k6 부하 테스트 결과 최대 처리량이 1.16 TPS에 불과했습니다.
Approach
@Async로 먼저 시도한 뒤, 부하 테스트에서 한계를 확인하고 RabbitMQ로 전환했습니다.
@Async로 톰캣 스레드를 즉시 반환하도록 전환하자 처리량은 개선됐으나, 부하가 몰리자 JVM 내부 큐가 포화되어 메모리 고갈이 발생했습니다. 서버 재배포 시 메모리 큐의 미처리 작업이 유실되는 구조적 한계도 확인했습니다.이에 RabbitMQ를 도입하여 메시지 영속성을 확보하고, AI 합성 처리를 WAS와 물리적으로 분리했습니다. WAS는 큐에 메시지를 발행하고 즉시 응답을 반환하며, Python Worker가 메시지를 소비해 ML 처리를 수행합니다. Worker 완료 시 Webhook으로 Spring Boot에 콜백하여 결과를 저장하고, 클라이언트는 폴링으로 완료 여부를 확인하는 구조로 설계했습니다.Worker 장애 시에는 DLQ로 메시지를 보존하고, 메시지 재전달에 대비한 멱등성 가드를 각 단계에 적용했습니다.Result
WAS 처리량 1.16 → 1,949 TPS, 동기 대기 30초 → 비동기 즉시 응답(4.9ms)으로 개선했습니다.500 VU 부하에서 p95 318ms, 총 175,463건을 오류 없이 처리했습니다. Flask 서버를 의도적으로 중단시킨 상태에서도 코어 API는 정상 응답하는 장애 격리를 확인했고, 미처리 메시지는 큐에 보존되어 복구 후 자동으로 재처리되었습니다.
@Async 한계: 부하 테스트에서 로컬 큐 포화로 메모리 고갈 실측 / WAS 내부 메모리 큐 의존으로 서버 종료·재배포 시 작업 유실
장애 격리: AI 연산을 별도 Worker로 분리하여, Flask 서버가 다운되더라도 로그인·일기 작성 등 주요 기능은 정상 동작
DLQ 및 3중 멱등성 가드: Worker 장애 시 메시지 유실 방지(DLQ), 메시지 재전달로 인한 중복 처리 방지를 Worker → Webhook → DB 각 단계에서 적용
RabbitMQ 비동기 처리 흐름 — WAS와 AI 처리 분리
sequenceDiagram
participant C as Client
participant W as WAS (Spring Boot)
participant Q as RabbitMQ
participant P as Python Worker
C->>+W: POST /api/images/analyze
W->>Q: 작업 메시지 발행
W-->>-C: 202 Accepted (4.9ms)
Q->>+P: 메시지 소비 (prefetch=1)
Note over P: MediaPipe 특징 추출<br/>AI 이미지 생성 (~30s)
P->>P: basic_ack()
P->>-W: POST /api/images/webhook (완료 콜백)
W->>C: 폴링으로 완료 알림