큐와 비동기 API · 비동기 API 설계(202 Accepted·폴링·웹훅) · 실습
작업 제출과 상태 폴링 API 만들기
목표
202 Accepted 로 시작하는 비동기 API 의 전체 계약 — 접수, 상태 조회, 폴링 조절, 멱등 제출, 완료 통보 — 를 직접 구현한다.
왜 중요한가
30초 걸리는 작업을 동기 API 로 만들면 세 가지가 동시에 깨집니다. 로드밸런서 유휴 타임아웃에 걸리고, 클라이언트 재시도가 같은 작업을 두 번 돌리고, 워커가 30초씩 묶입니다. 비동기 API 는 이것을 접수와 확인으로 나눕니다. 그런데 202 만 돌려주고 끝내는 API 가 의외로 많습니다. 클라이언트는 결과를 어디서 봐야 할지 모르고, 얼마나 기다려야 하는지 모르고, 재시도해도 되는지 모릅니다. 이 실습은 그 계약의 빠진 조각들을 하나씩 채웁니다 — Location 헤더, 상태 어휘, Retry-After, 멱등성 키, 그리고 웹훅입니다. 이것들을 다 갖추면 그때부터 클라이언트가 안심하고 재시도할 수 있습니다.
단계
1. /root/aj/api.py 를 127.0.0.1:8140 에 띄운다. POST /jobs 는 {"n":5} 를 받아 큐 q:jobs2 에 넣는다.
2. POST /jobs 응답은 202 이고 Location: /jobs/<job_id> 헤더와 본문 {"job_id":"...","status":"queued"} 를 준다.
3. GET /jobs/<job_id> 는 200 과 status 를 준다. 값은 queued, running, succeeded, failed 중 하나이다. 없는 id 는 404 이다.
4. /root/aj/worker.py 는 큐에서 꺼내 상태를 running 으로 바꾸고 처리 후 succeeded 로 바꾼다.
5. 완료된 작업 조회 응답에 result 키가 있어야 한다. queued 상태에서는 result 키가 없어야 한다.
6. queued 또는 running 상태 응답에는 Retry-After 헤더가 정수 초로 붙고, succeeded 응답에는 붙지 않는다.
7. 같은 Idempotency-Key 로 두 번 제출하면 같은 job_id 를 돌려주고 큐에는 1건만 들어간다.
8. /opt/app/hooksink.py 를 127.0.0.1:8141 에 띄운다. 제출 시 callback_url 을 함께 받아 완료 시 그 주소로 POST 한다. /root/aj/hook.log 에 job_id 와 status=succeeded 가 기록된다.
참고
- 202 는 '했다'가 아니라 '받았다'입니다. Location 이 없으면 계약이 미완성입니다.
- 완료된 작업 상태에는 TTL 을 두고, 만료 조회에는 404 대신 410 Gone 을 주면 클라이언트가 구별할 수 있습니다.
- 흔한 실수 1: job id 로 아직 만들어지지 않은 결과 리소스의 ID 를 쓰는 것 — 실패하면 유령 ID 가 남습니다.
- 흔한 실수 2: 완료 응답에도
Retry-After를 붙여 클라이언트가 영원히 폴링하게 만드는 것.
단계 8개
- 작업 제출 API 띄우기
- 202 와 Location 헤더 돌려주기
- 상태 조회 엔드포인트 만들기
- 워커가 상태를 전이시키기
- 완료 후 결과 담기
- 폴링 간격 제안하기
- 제출을 멱등하게 만들기
- 완료 웹훅 보내기