큐와 비동기 API · 비동기 API 설계(202 Accepted·폴링·웹훅) · 이론
202 Accepted 가 시작하는 계약
한 줄 요약
202 는 "했다"가 아니라 "받았다"이다. 그러므로 202 를 주는 API 는 반드시 "어디서 결과를 확인하는가"를 함께 알려 줘야 한다.
왜 이게 필요했나
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 을 주면 클라이언트가 "없었던 것"과 "지났던 것"을 구별할 수 있습니다.
다음 실습에서 할 것
작업 제출 API 를 만들어 202 와 Location 을 정확히 돌려주고, 워커가 상태를 전이시키고, Retry-After 로 폴링을 조절하고, 제출을 멱등하게 만들고, 마지막에 완료 웹훅까지 붙입니다.