Celery 태스크 재시도: 유료 API 호출용 백오프와 지터

autoretry_for, retry_backoff, max_retries로 Celery 태스크를 재시도하고, 429에는 retry-after만큼 기다리고, 재시도마다 같은 Idempotency-Key를 보내세요.

읽는 시간 5분Sume
전체 글

Celery 태스크를 재시도하려면 재시도할 예외를 autoretry_for에 나열하거나, 오류를 잡아 raise self.retry(exc=exc, countdown=60)으로 재시도를 일으키세요. retry_backoff=True를 추가하면 이런 자동 재시도의 지연이 1, 2, 4, 8초…로 늘어나며, 지터가 각 지연을 무작위로 바꿉니다. 그리고 max_retries를 설정하세요. 기본값은 3입니다. 태스크가 유료 API를 호출한다면 재시도로 해결되는 오류만 재시도하고, 모든 시도에 같은 Idempotency-Key를 보내세요. 그래야 재시도가 두 번째 실행을 결제하는 대신 원래 실행을 돌려줍니다.

Celery 관련 내용은 Celery의 태스크 가이드에서, Requests 관련 내용은 빠른 시작에서, Sume 관련 내용은 Format 호출하기 (영문)와 오류와 비용 (영문)에서 가져왔습니다. 모두 2026-09-28에 확인했습니다. Sume에는 Celery 패키지가 없으며, 태스크가 일반 HTTPS 호출을 한 번 보냅니다. 영상 작업에 Celery 워커가 애초에 필요한지는 FastAPI 오래 걸리는 작업: 202와 Job ID로 응답하기에서 다룹니다.

Celery 태스크에는 어떤 재시도 옵션이 있나요?

태스크 데코레이터에 설정합니다. retry_backoff, retry_backoff_max, retry_jitter는 autoretry_for로 일어나는 자동 재시도의 지연 방식을 정하고, 수동으로 호출한 self.retry()는 countdown을 넘기지 않으면 default_retry_delay만큼 기다립니다. 어느 쪽이든 Celery는 같은 태스크 ID로 새 메시지를 보냅니다.

Celery의 태스크 가이드 기준, 2026-09-28 확인.
옵션기본값동작
autoretry_for예외 없음나열한 예외 클래스 중 하나가 발생하면 태스크를 재시도
max_retries3포기하기 전까지의 최대 재시도 횟수. None이면 무한히 재시도
retry_backoffFalse: 자동 재시도에 지연 없음True이면 1, 2, 4, 8초…를 기다림. 숫자를 주면 지연 계수
retry_backoff_max600초백오프 지연의 상한
retry_jitterTrue영과 백오프 값 사이에서 무작위 지연을 고름
default_retry_delay3분self.retry()의 지연. 호출마다 countdown으로 덮어씀

유료 API 호출은 어떻게 안전하게 재시도하나요?

키는 시도마다 새로 만드는 uuid4()가 아니라, 만들고 있는 대상인 태스크 인수로 만드세요. Sume 문서는 요청마다 새로 만든 키는 헤더를 장식에 불과하게 만든다고 말합니다. 그다음에는 Sume의 오류 봉투가 판단하게 하세요. retryable 플래그는 같은 요청을 다시 보내 성공할 수 있는지 알려 주고, 429에는 기다릴 초가 담긴 retry-after 헤더가 있으며, 문서는 503은 나중에 같은 키로 재시도하라고 말합니다. Requests는 네트워크 문제가 생기면 ConnectionError를, 서버가 timeout 안에 응답하지 않으면 Timeout을 일으킵니다.

import os, requests
from celery import shared_task

class Transient(Exception): pass
@shared_task(bind=True, max_retries=5, retry_backoff=True,
             autoretry_for=(requests.ConnectionError, requests.Timeout, Transient))
def start_video(self, order_id):
    r = requests.post(
        "https://api.sume.com/v1/formats/acme/product-promo/runs",
        headers={
            "Authorization": f"Bearer {os.environ['SUME_API_KEY']}",
            "Idempotency-Key": f"order-{order_id}-promo-v1",  # same on every retry
        },
        json={"input": {"order_id": order_id},
              "communication": {"webhook_url": "https://example.com/hooks/sume"}},
        timeout=30,
    )
    if r.ok:
        return r.json()["data"]["id"]  # 202: new run, 200: replay
    err = r.json()["error"]
    if r.status_code == 429:
        raise self.retry(countdown=int(r.headers["retry-after"]))
    if err["retryable"] or r.status_code == 503:
        raise Transient(err["code"])  # autoretried with backoff and jitter
    raise RuntimeError(f"{r.status_code} {err['code']}")  # fix the call instead

태스크는 어떤 오류를 재시도하고, 어떤 오류에서 실패해야 하나요?

타임아웃, 끊긴 연결, 그리고 첫 시도가 아직 처리 중일 때의 409 idempotency_key_in_use처럼 Sume가 retryable로 표시한 응답은 재시도하세요. 429는 retry-after만큼 기다린 뒤, 503 studio_agent_upstream_unavailable은 나중에 재시도하세요. 타임아웃만으로는 생성 요청이 도착했는지 알 수 없지만, 키가 있으면 재전송이 안전합니다. Sume는 같은 키와 본문에 원래 영수증과 idempotency_hit: true를 담아 200으로 응답하므로, 두 번 청구되는 것은 없습니다.

나머지는 모두 즉시 실패시키세요. 생성 단계의 4xx는 아무것도 실행되지 않았고 아무것도 청구되지 않았다는 뜻이므로, 코드는 autoretry_for에 없는 RuntimeError를 일으키고 Celery는 이를 재시도하지 않습니다. 재시도로 해결되지 않는 코드는 402 insufficient_credits부터 원인이 사실 입력에 있는 502까지 Axios retry: POST 안전하게 재시도하기에 정리되어 있습니다. max_retries를 다 써도 태스크는 실패합니다. Celery는 현재 예외를 다시 일으키거나, 429 분기처럼 self.retry()에 exc를 넘기지 않았다면 MaxRetriesExceededError를 일으킵니다.

실행 자체가 실패하거나 20분이 걸리면 어떻게 하나요?

failed로 끝난 실행은 다른 경우입니다. 이전 키는 그 영수증에 묶여 있으므로, 같은 키로 재시도하면 실패를 재전송할 뿐입니다. order-1042-promo-v2처럼 새 키로 재시도하세요. 또 워커가 영상을 기다리게 두지 마세요. 롱폼 영상은 만드는 데 15분에서 30분이 걸리며, 워커가 기다리기를 멈춰도 실행과 그 지출은 멈추지 않습니다. 실행 ID를 반환해 저장하고, 실행이 완료되거나 실패하면 Sume가 서명된 format.run.terminal 영수증 하나를 communication.webhook_url로 POST하게 하세요.

키가 있으면 태스크는 멱등적입니다. 이는 Celery 문서가 acks_late를 켜기 전에 갖추라고 하는 조건입니다. acks_late를 켜면 태스크가 시작되기 전이 아니라 반환된 뒤에 메시지를 확인(ack)합니다.

출처

관련 글

연동 카테고리의 다른 글

연동 글 전체 보기

작성자 Sume