BullMQ 재시도: 유료 API 작업의 지수 백오프
BullMQ 작업에 attempts와 지수 백오프를 설정하고, UnrecoverableError로 일찍 멈추고, 유료 API 호출마다 작업을 기준으로 키를 만들어 재시도가 원래 실행을 재전송하게 하세요.

BullMQ 작업을 재시도하려면 1보다 큰 attempts와 { type: 'exponential', delay: 1000 } 같은 backoff를 지정해 작업을 추가하세요. 프로세서가 예외를 던지면 BullMQ는 2^(attempts − 1) × delay 밀리초 뒤에 작업을 재시도하며, 선택적으로 지터를 더할 수 있습니다. 백오프가 없으면 즉시 재시도합니다. 유료 API를 호출하는 작업이라면 작업을 기준으로 Idempotency-Key를 만들어 모든 시도가 같은 실행을 재전송하게 하고, 재시도로 해결되지 않는 오류에서는 UnrecoverableError를 던지세요.
BullMQ 관련 내용은 BullMQ의 실패한 작업 재시도 가이드를 비롯해 출처에 나열한 다른 BullMQ 문서에서, Sume 관련 내용은 Format 호출하기 (영문)와 오류와 비용 (영문)에서 가져왔습니다. 모두 2026-09-28에 확인했습니다. Sume에는 BullMQ 패키지가 없으며, 워커가 일반 HTTPS 호출을 한 번 보냅니다. 작업 전체가 아니라 한 프로세스 안에서 요청 하나를 재시도하는 방법은 Axios retry: 멱등성 키로 POST 안전하게 재시도하기를 참고하세요.
attempts와 backoff는 어떻게 설정하나요?
작업을 추가할 때 넘기거나, 큐의 defaultJobOptions에 한 번만 설정하세요. 작업은 프로세서가 예외를 던지면 실패하며, attempts는 첫 시도까지 셉니다. 즉 attempts: 3은 재시도가 최대 2번이라는 뜻입니다.
| 설정 | BullMQ의 동작 |
|---|---|
backoff 없음 | 작업이 실패하는 즉시 지연 없이 재시도 |
{ type: 'fixed', delay } | 재시도할 때마다 먼저 delay 밀리초를 기다림 |
{ type: 'exponential', delay } | 2^(attempts − 1) × delay만큼 기다림. 1000이면 1초, 2초, 4초 간격으로 재시도 |
jitter | 0부터 1까지, 기본값 0. 0.5이면 각 대기 시간이 계산된 지연의 절반에서 전체 사이의 무작위 값 |
워커 설정의 backoffStrategy | 시도별 사용자 지정 지연. -1을 반환하면 재시도 없이 작업을 failed로 옮김 |
throw new UnrecoverableError() | attempts보다 우선하며, 재시도 없이 작업을 failed로 옮김 |
import { Queue } from "bullmq";
const queue = new Queue("videos", { connection });
await queue.add(
"promo",
{ orderId: "1042" },
{
jobId: "order-1042-promo", // a second add is ignored while this id is in the queue
attempts: 5,
backoff: { type: "exponential", delay: 1000, jitter: 0.5 },
},
);재시도되는 작업을 결제 면에서 안전하게 만들려면 어떻게 하나요?
여러분의 레코드에서 가져온 사용자 지정 jobId를 작업에 주세요. BullMQ는 아직 큐에 있는 ID로 다시 추가하면 그 추가를 무시하고, ID는 모든 상태에서 작업을 따라다닙니다. 그래서 그 ID로 만든 키는 모든 시도에서 같고, Sume는 반복 요청에 두 번째 청구 대신 원래 영수증과 idempotency_hit: true를 담은 200으로 응답합니다. 그다음에는 Sume의 오류 봉투가 판단하게 하세요. retryable은 같은 요청을 다시 보내 성공할 수 있는지 알려 줍니다. 문서는 503은 나중에 같은 키로 재시도하라고 하며, 429에는 초 단위의 retry-after가 담겨 있습니다. BullMQ의 수동 요청 한도인 queue.rateLimit()(밀리초 단위)으로 큐 전체가 이 값을 따르게 할 수 있습니다.
import { Worker, RateLimitError, UnrecoverableError } from "bullmq";
new Worker("videos", async (job) => {
const res = await fetch("https://api.sume.com/v1/formats/acme/product-promo/runs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUME_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `${job.id}-v1`, // same on every attempt
},
body: JSON.stringify({
input: { order_id: job.data.orderId },
communication: { webhook_url: "https://example.com/hooks/sume" },
}),
});
const body = await res.json();
if (res.ok) return body.data.id; // 202: new run, 200: replay
if (res.status === 429) {
await queue.rateLimit(Number(res.headers.get("retry-after")) * 1000); // ms
throw new RateLimitError(); // back to waiting, not a failed attempt
}
if (body.error.retryable || res.status === 503) throw new Error(body.error.code);
throw new UnrecoverableError(`${res.status} ${body.error.code}`);
}, { connection, limiter: { max: 1, duration: 500 } });어떤 오류에서 재시도를 멈춰야 하나요?
대부분의 4xx 응답입니다. 생성 단계의 4xx는 아무것도 실행되지 않았고 아무것도 청구되지 않았다는 뜻이므로, 워커는 UnrecoverableError를 던지고 작업은 attempts를 소모하지 않고 즉시 실패합니다. Sume의 모든 오류에는 어떤 경우인지 알려 주는 next_action(fix_input, authenticate, add_funds, retry_later, …)이 담겨 있습니다. 재시도로 해결되지 않는 코드는 402 insufficient_credits부터 원인이 사실 입력에 있는 502까지 Axios retry: POST 안전하게 재시도하기에 정리되어 있습니다.
첫 시도가 아직 처리 중일 때의 409 idempotency_key_in_use처럼 Sume가 retryable로 표시한 응답과 503은 일반 오류로 던져지므로, 작업의 백오프를 따릅니다. 429는 다릅니다. BullMQ는 요청 한도를 실제 오류로 세지 않으므로, RateLimitError로 요청 한도에 걸린 작업은 attempts를 소모하지 않습니다. 그래도 상한을 두려면, BullMQ 문서에서는 job.attemptsStarted를 job.opts.attempts와 비교해 UnrecoverableError를 던집니다.
작업이 성공한 뒤 실행이 실패하면 어떻게 하나요?
그때는 BullMQ 재시도로 해결할 수 없습니다. 이전 키는 실패한 영수증에 묶여 있으므로, 같은 키는 실패를 재전송할 뿐입니다. order-1042-promo-2처럼 새 jobId로 새 작업을 시작해 키도 새로 만드세요. 프로세서가 영상을 기다리게 하지도 마세요. 롱폼 영상은 만드는 데 15분에서 30분이 걸리며, 워커가 기다리기를 멈춰도 실행과 그 지출은 멈추지 않습니다. 실행 ID를 반환하고, 실행이 완료되거나 실패하면 Sume가 서명된 format.run.terminal 영수증 하나를 communication.webhook_url로 POST하게 하세요.
출처
관련 글
연동 카테고리의 다른 글
- Cloud Scheduler로 Cloud Run 작업 예약: 재시도 대비
cron과 시간대를 지정해 Cloud Run 작업에 Cloud Scheduler 트리거를 추가하세요. 실패한 태스크는 기본적으로 3번 재시도되므로 유료 호출은 날짜로 키를 만드세요.
- Continue MCP 서버: mcpServers 폴더에 Sume 추가
.continue/mcpServers의 YAML 블록으로 Continue에 Sume 호스팅 MCP 서버를 추가하세요. type은 streamable-http로 두고, URL과 시크릿에서 읽은 키를 넣습니다.
- Copilot CLI MCP 서버: copilot mcp add로 Sume 추가
copilot mcp add로 GitHub Copilot CLI에 원격 MCP 서버를 추가하세요. Sume 호스팅 MCP, 키 헤더나 OAuth, 55초를 넘는 타임아웃을 씁니다.
- CrewAI MCP 서버: 에이전트에 Sume 도구 연결하기
mcps 필드의 MCPServerHTTP로 CrewAI 에이전트에 Sume 호스팅 MCP 도구를 주세요. Bearer 키 헤더, 도구 필터, 짧은 jobs_wait 슬라이스를 씁니다.
작성자 Sume