LabHub
배우기 러닝패스 코스

キューと非同期API

202 Acceptedが始める契約

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

202 는 "했다"가 아니라 "받았다"이다. 그러므로 202 를 주는 API 는 반드시 "어디서 결과를 확인하는가"를 함께 알려 줘야 한다.

概念マップ: Location・Retry-After・작업 ID・재시도와 서명

왜 이게 필요했나

30초 걸리는 작업을 동기 API 로 만들면 세 가지가 동시에 깨집니다. 로드밸런서의 유휴 타임아웃(대개 60초)에 걸리고, 클라이언트가 재시도하면 같은 작업이 두 번 돌고, 서버 워커가 30초씩 묶입니다.

비동기 API 는 이것을 두 단계로 나눕니다. 접수와 확인입니다. 접수는 빠르게 끝나고 식별자를 돌려줍니다. 확인은 그 식별자로 상태를 물어봅니다.

어떻게 동작하나

HTTP 에는 이미 이 패턴을 위한 어휘가 있습니다.

접수 응답은 202 Accepted 이고, Location 헤더에 상태 조회 URL 을 담습니다. 본문에는 작업 식별자와 현재 상태를 넣습니다. 이 세 가지가 없으면 클라이언트는 무엇을 해야 할지 모릅니다.

상태 조회는 200 으로 현재 상태를 돌려줍니다. 상태 어휘는 단순할수록 좋습니다 — queued, running, succeeded, failed 정도입니다. 아직 진행 중이면 Retry-After 로 다음 폴링 시점을 제안합니다. 이게 없으면 클라이언트들이 각자 1초마다 폴링해서 상태 API 가 새 병목이 됩니다.

완료되면 결과를 어떻게 줄지는 두 갈래입니다. 상태 응답 본문에 결과를 함께 담거나, 303 See Other 로 결과 리소스 URL 을 가리키는 것입니다. 후자가 REST 스럽지만 클라이언트 구현이 늘어납니다. 결과가 작으면 전자로 충분합니다.

제출도 멱등해야 합니다. 클라이언트가 202 를 받기 전에 연결이 끊겨 재시도하면 같은 작업이 두 개 만들어집니다. 제출에 멱등성 키를 요구하고, 같은 키에는 같은 job id 를 돌려주면 됩니다.

웹훅은 폴링의 대안입니다. 완료 시 서버가 클라이언트의 URL 로 POST 합니다. 지연이 없고 폴링 부하도 없지만 대가가 있습니다 — 클라이언트가 공개 엔드포인트를 가져야 하고, 그 엔드포인트가 죽어 있을 수 있으므로 서버가 재시도와 DLQ 를 갖춰야 하고, 위조를 막기 위해 서명이 필요합니다. 실무에서는 웹훅과 폴링을 함께 제공하는 경우가 많습니다.

현장에서 만나는 모습

가장 흔한 설계 실수는 job id 로 진짜 결과 리소스의 ID 를 그대로 쓰는 것입니다. 아직 만들어지지 않은 리소스의 ID 를 미리 준다는 뜻이라, 실패했을 때 그 ID 는 영원히 유령이 됩니다. job id 와 result id 는 분리하는 편이 안전합니다.

두 번째. 작업 상태를 영원히 보관하지 마세요. 완료된 작업 상태에 TTL 을 두고, 만료된 조회에는 404 대신 410 Gone 을 주면 클라이언트가 "없었던 것"과 "지났던 것"을 구별할 수 있습니다.

202 를 돌려준 뒤의 계약

202 Accepted 는 "받았다" 일 뿐 "될 것이다" 가 아닙니다. 그래서 세 가지를 함께 줘야 클라이언트가 코드를 쓸 수 있습니다.

HTTP/1.1 202 Accepted
Location: /api/jobs/7f3a-91cd
Retry-After: 3
Content-Type: application/json

{"job_id": "7f3a-91cd", "status": "queued",
 "poll_url": "/api/jobs/7f3a-91cd", "estimated_sec": 30}

상태 조회는 최소 네 상태를 구분합니다.

{"status": "queued"}                                  → 아직 시작 안 함
{"status": "running", "progress": 0.4}                → 진행 중
{"status": "succeeded", "result_url": "/files/…"}     → 결과가 있다
{"status": "failed", "error": {"code": "…","message": "…"}, "retriable": false}

retriable 이 중요합니다. 클라이언트가 다시 보낼지 사람에게 물을지를 이 값으로 정합니다.

폴링 대신 알림

폴링은 단순하지만 낭비입니다. 세 대안이 있고 각각 값이 다릅니다.

방식 서버 부담 클라이언트 복잡도 방화벽
폴링 요청 수만큼 가장 단순 문제 없다
롱폴링 연결 유지 단순 프록시 타임아웃 주의
SSE 연결 유지 보통 대개 통과
웹훅 가장 적다 수신 엔드포인트 필요 상대가 공개 주소여야

웹훅을 쓰면 재시도와 서명 을 함께 설계해야 합니다. 받는 쪽이 잠깐 죽으면 다시 보내야 하고, 위조를 막으려면 본문에 HMAC 서명을 붙여야 합니다.

멱등 키로 중복 제출 막기

클라이언트가 202 를 받기 전에 타임아웃이 나면 같은 요청을 또 보냅니다. 그러면 작업이 두 개 생깁니다.

POST /api/jobs
Idempotency-Key: 8f14e45f-ea3b-4d29-9a1c-2b3c4d5e6f70

서버는 이 키를 저장하고, 같은 키가 다시 오면 새로 만들지 않고 처음 만든 작업의 202 를 그대로 돌려줍니다. 키는 클라이언트가 만들되 재시도 사이에 바뀌지 않아야 하므로, 요청 내용에서 결정적으로 유도하거나 요청 시작 시점에 한 번 생성해 보관합니다.

키의 보존 기간은 24시간 정도가 흔합니다. 그보다 오래 두면 저장소가 자라고, 짧으면 느린 재시도를 못 잡습니다.

다음 실습에서 할 것

작업 제출 API 를 만들어 202 와 Location 을 정확히 돌려주고, 워커가 상태를 전이시키고, Retry-After 로 폴링을 조절하고, 제출을 멱등하게 만들고, 마지막에 완료 웹훅까지 붙입니다.