1. 기존 시스템
기존 프로젝트는 학과에서 제공한 온프레미스 서버 환경에서 운영되었다.
이 환경은 개발 자유도가 높은 편은 아니었고, 몇 가지 구조적인 제약이 명확했다.
우선 서비스는 단일 포트만 개방 가능했기 때문에, 프론트엔드와 백엔드를 포함한 모든 최종 접근 지점은 하나의 포트로만 제공해야 했다. 또한 데이터베이스는 학과에서 제공하는 고정 MariaDB를 사용해야 했으며, 해당 DB는 localhost에서만 접근 가능하도록 제한되어 있어 외부 서버나 컨테이너에서 직접 접근하는 것이 불가능했다.
네트워크 측면에서도 외부 접근 및 포트 추가, 라우팅 설정이 제한적이었고, 이로 인해 일반적인 인프라 구성이나 서비스 분리를 적용하기 어려웠다. 특히 컨테이너 기반 배포를 적용할 경우, DB가 localhost-only로 고정된 특성 때문에 네트워크 경로가 복잡해지며 안정적인 운영을 기대하기 힘들었다.
AI 연동 방식 역시 제약이 있었다. AI 모듈을 별도의 HTTP/gRPC 서버로 띄우는 방식은 허용되지 않았고, Java 애플리케이션 내부에서 Python을 child process로 실행하는 방식만 사용할 수 있었다.
이러한 제약들로 인해, 온프레미스 환경에서는 단일 JAR 실행 구조와 동기 API 기반 처리가 사실상 유일하게 현실적인 선택지였다.
이 방식으로 4주 단기 프로젝트 배포 환경 구성을 마무리했다.

2. 아쉬운 점
기존 시스템은 당시 기준으로 큰 장애 없이 동작하고 있었고, 소규모 사용자 환경에서는 문제를 체감하기 어려웠다. 다만 이는 구조가 안전해서라기보다 사용자 수가 적어 실패 빈도가 낮았기 때문이라고 판단했다.
구조적으로 가장 큰 리스크는 AI 작업이 API 요청 흐름 안에서 동기 실행되는 점이었다.
형식적으로 비동기 응답(요청에 빠르게 응답 반환)을 적용할 수는 있었지만... 이는 응답만 빨라지게 된다.
근본적인 문제는 AI 실행이 지연되거나 실패하면 그 영향이 그대로 API 응답 지연·타임아웃·오류로 이어져, API 안정성이 AI 작업의 성공 여부에 직접 종속되는 구조인 것이다. 😨
또한 Java 애플리케이션 내부에서 Python을 child process로 실행하는 방식은 구현은 단순하지만, AI 실행의 생명주기와 자원 관리 책임이 API 프로세스에 묶인다. 그 결과 실패 격리가 어렵고, 실행 상태를 외부에서 독립적으로 추적·관리하기도 힘들었다.
이 구조에서는 서버 재기동이나 프로세스 종료가 발생하는 순간 실행 중이던 작업이 상태 추적 없이 유실될 수 있다. 작업이 어디까지 진행됐는지, 재시도가 필요한지 판단할 근거도 남지 않는다. 실행 상태를 외부 시스템에서 독립적으로 관리하지 못하기 때문이다.
3. 개선안 후보 비교
배포 클라우드 서비스로는 AWS를 선택했다.
개선안 A: API + Worker 분리 (내부 큐/로컬 의존)
“AI 실행 책임을 API에서 떼어낸다”는 1차 목표는 달성한다. API가 요청을 받으면 작업을 큐(예: 인메모리 큐, 로컬 Redis, 로컬 파일/DB 등)에 넣고, 같은 서버에서 돌아가는 Worker가 받아 처리한다. 구조가 단순하고 구현 난이도도 낮아서, 짧은 기간에 안정화를 노릴 때 매력적인 선택지다.
문제는 ‘단일 서버’와 ‘로컬 의존성’이 합쳐졌을 때다. 서버가 재기동되거나 프로세스가 죽는 순간, 작업 전달 경로와 실행 상태가 함께 흔들린다. 큐가 메모리 기반이면 작업이 사라지고, Redis를 띄워도 같은 서버에 있으면 서버 장애에 같이 죽는다. “작업이 어디까지 진행됐는지”를 외부에서 신뢰할 수 있는 기준으로 남기기 어렵고, 결국 복구는 사람 판단에 가까워진다.
개선안 B: 메시지 큐 + 외부 상태(DB) 기반 비동기 아키텍처 (선택)
SQS 같은 메시지 큐가 들어가고, job 상태는 RDS에 고정한다. 결과물은 S3로 뺀다. 이렇게 되면 서버가 죽어도 작업 전달은 SQS에, 작업 상태는 RDS에 남는다.
순서는 다음과 같다.
1. API는 job을 만들고 DB에 PENDING을 기록한 뒤 큐에 jobId를 넣는다.
2. Worker는 큐에서 메시지를 받아 DB에서 PENDING → RUNNING을 조건부 업데이트로 바꾸고 실행한다.
3. 실행이 끝나면 결과물을 S3에 저장하고, DB를 SUCCEEDED/FAILED로 확정한다.
이 과정에서 중요한 점은 “멱등성”이다. 같은 메시지가 중복 전달되거나 Worker가 재시작되더라도, PENDING이 아닌 job은 RUNNING으로 못 바꾸므로 중복 실행을 스스로 차단한다.
4. AWS 구성요소
- EC2-API (온디맨드)
- 일반 백엔드 API 컨테이너
- EC2-WORKER (Spot)
- Worker 컨테이너
- 역할: SQS poll → AI 실행 → S3 업로드 → DB 상태 업데이트
- RDS (Private subnet)
- jobs 상태의 단일 진실
- SQS
- 작업 전달
- S3
- 결과물 저장
- ECR
- 이미지 저장소
- CloudWatch Logs
- 최소 로그
꽤 힘든 여정이 될 수도 있을 것 같다...
작업 과정을 글로 남기려한다!