오래 걸리는 작업을 API에서 바로 처리하면 안 되는 이유

보고서 생성, 대량 메일, 이미지 변환, 외부 API 수집을 일반 API 요청 안에서 끝내려 하면 처음에는 단순해 보인다. 하지만 실행 시간이 길어질수록 브라우저, 프록시, 서버리스 함수 중 하나가 먼저 연결을 끊을 가능성이 커진다. 사용자가 다시 누르면 같은 작업이 중복 실행되기도 한다.

오래 걸리는 작업의 핵심은 더 긴 타임아웃이 아니라 요청과 실행을 분리하는 것이다.

요청과 작업을 분리한다

권장 흐름은 다음과 같다.

사용자가 작업을 요청한다.

API는 권한과 입력을 확인하고 작업 레코드를 만든다.

큐에 작업 ID를 넣고 즉시 202 Accepted와 작업 ID를 반환한다.

워커가 큐에서 작업을 가져와 처리한다.

결과와 진행 상태를 DB에 기록한다.

화면은 Polling, SSE 또는 실시간 알림으로 상태를 확인한다.

이 구조에서는 브라우저를 닫아도 작업이 이어지고, 실패한 작업만 다시 시도할 수 있다. API 서버와 워커의 자원도 독립적으로 조정할 수 있다.

큐만 넣으면 끝나지 않는다

중복 실행을 견디게 만든다

네트워크 문제로 완료 응답이 사라지면 같은 메시지가 다시 보일 수 있다. 작업마다 멱등성 키를 두고 이미 완료된 작업인지 확인한다. 메일 발송, 결제, 외부 시스템 등록처럼 부작용이 있는 단계는 특히 중요하다.

재시도 간격을 늘린다

즉시 반복하면 장애 중인 외부 서비스에 더 큰 부하를 준다. 지수 백오프와 임의 지연을 적용하고, 재시도 가능한 오류와 입력 오류를 구분한다.

영원히 실패하는 작업을 격리한다

최대 재시도 횟수를 넘긴 작업은 실패 큐나 별도 상태로 이동한다. 오류 사유, 입력 요약, 마지막 시도 시간을 남기고 담당자가 수정 후 재실행할 수 있게 한다.

작업 가시성 시간을 맞춘다

워커가 메시지를 읽으면 일정 시간 다른 워커에서 보이지 않게 한다. 이 시간이 실제 처리 시간보다 짧으면 같은 작업이 동시에 실행될 수 있고, 너무 길면 워커 장애 후 재시작이 늦어진다.

Supabase Queues는 Postgres 기반의 내구성 있는 메시지 큐로, 가시성 시간 동안 소비자 한 곳에 메시지를 전달하고 명시적으로 삭제하거나 보관할 때까지 메시지를 유지한다. 기존 Supabase 환경에서 작은 작업 큐를 시작할 때 선택지가 될 수 있다. Supabase Queues

워커는 어디에 둘까

작업이 간헐적이고 짧다면 예약 함수나 이벤트 기반 함수로 충분할 수 있다. 계속 큐를 확인해야 하거나 처리 시간이 길고 외부 프로그램을 실행한다면 소형 상시 컨테이너가 단순하다. Render Background Worker처럼 큐를 계속 폴링하는 전용 서비스나 Railway, Fly.io, VPS의 컨테이너를 검토할 수 있다. Render Background Worker

선택 기준은 다음과 같다.

항상 켜져 있어야 하는가?

한 작업의 최대 시간과 메모리는 얼마인가?

동시에 몇 개를 처리해야 하는가?

특정 바이너리나 GPU가 필요한가?

장애 시 다른 인스턴스가 이어받아야 하는가?

화면에 보여줄 상태

접수됨, 대기 중, 처리 중, 완료, 실패, 취소 요청, 취소됨을 구분한다. 결과가 아직 확정되지 않았는데 완료라고 표시하지 않는다. 실패 화면에는 재시도 가능 여부와 사용자가 할 일을 구체적으로 적는다.

취소도 상태 전이다. 워커가 안전한 중단 지점에서 취소 요청을 확인하고, 이미 만들어진 임시 파일이나 부분 결과를 정리해야 한다. 외부 서비스 호출이 이미 끝났다면 되돌릴 수 있는지 별도 보상 작업을 정의한다.

운영할 때 볼 지표

대기 작업 수와 가장 오래된 작업의 나이

작업 종류별 처리 시간

성공률과 재시도 횟수

실패 큐의 증가 속도

워커 CPU, 메모리, 재시작 횟수

사용자 취소 후 실제 중단까지 걸린 시간

오래 걸리는 작업을 API 밖으로 빼는 목적은 단순히 타임아웃을 피하는 데 있지 않다. 실패, 재시도, 취소, 진행률을 업무 상태로 드러내고 복구 가능한 흐름으로 만드는 데 있다.