IT Best Practise/Technical

지수 백오프 (Exponential Backoff): 안정적 재시도 전략

GilliLab IT 2025. 5. 15. 15:27
728x90
반응형

지수 백오프 (Exponential Backoff): 안정적 재시도 전략

정의

요청 실패 시, 다음 재시도를 점점 지연시키며(wait) 재요청하는 전략.
서버 과부하를 피하면서 안정적으로 요청을 재시도할 수 있습니다.

수식 예시

재시도 대기 시간 = base * (2 ** retry_count) + random_jitter
  • base: 기본 지연 시간 (예: 1초)
  • retry_count: 재시도 횟수 (0부터 시작)
  • random_jitter: 무작위 지터(소폭 난수)를 추가해 동시 재시도 방지

예시 (초 단위)

시도 횟수 대기 시간 (기본값 1초 기준)
1 2^1 = 2초 + 지터
2 2^2 = 4초 + 지터
3 2^3 = 8초 + 지터
4 2^4 = 16초 + 지터

사용 범위

  • HTTP 429 (Too Many Requests)
  • HTTP 503 (Service Unavailable)
  • quotaExceeded, rateLimitExceeded, userRateLimitExceeded

Python 예제 코드

import time
import random
import requests

def call_google_api_with_backoff(url, max_retries=5, base_delay=1):
    for retry in range(max_retries):
        try:
            response = requests.get(url)
            if response.status_code == 200:
                return response.json()
            elif response.status_code in [429, 503]:
                raise Exception(f"Retryable error: {response.status_code}")
            else:
                response.raise_for_status()
        except Exception as e:
            if retry < max_retries - 1:
                sleep_time = base_delay * (2 ** retry) + random.uniform(0, 1)
                print(f"[{retry+1}] Error: {e}. Retrying in {sleep_time:.2f} seconds...")
                time.sleep(sleep_time)
            else:
                print(f"[{retry+1}] Failed after {max_retries} retries.")
                raise

고급 전략 (Google 권장 패턴)

전략 설명
지수 백오프 + 지터 모든 요청이 동시에 재시도되는 상황 방지
최대 대기 시간 제한 32초 이상 기다리면 UX에 문제 발생 가능
최대 시도 횟수 제한 무한 재시도 금지 (보통 5~6회)
오류 코드 필터링 HTTP 400 계열은 보통 재시도하면 안 됨

요약

항목 내용
목적 서버 과부하 방지, 안정적 재시도
적용 상황 429, 503, quotaExceeded 등
기본 전략 base * 2^retry_count + jitter
권장 최대 재시도 횟수 5~6회
구현 팁 sleep, random, 예외 catch 사용

라이브러리별 지수 백오프 구현

1. aiohttp (비동기 기반)

특징

  • async/await 기반 비동기 HTTP 클라이언트
  • 백오프 로직을 직접 구현해야 함

예제 코드

import asyncio
import aiohttp
import random

async def fetch_with_backoff(url, max_retries=5, base_delay=1):
    async with aiohttp.ClientSession() as session:
        for retry in range(max_retries):
            try:
                async with session.get(url) as response:
                    if response.status == 200:
                        return await response.json()
                    elif response.status in [429, 503]:
                        raise Exception(f"Retryable error: {response.status}")
                    else:
                        response.raise_for_status()
            except Exception as e:
                if retry < max_retries - 1:
                    delay = base_delay * (2 ** retry) + random.uniform(0, 1)
                    print(f"[{retry+1}] Error: {e}. Retrying in {delay:.2f} seconds...")
                    await asyncio.sleep(delay)
                else:
                    print(f"[{retry+1}] Max retries reached.")
                    raise

# 실행 예시
# asyncio.run(fetch_with_backoff("https://example.com"))

2. httpx (동기 및 비동기 모두 지원)

특징

  • requests와 호환되는 구조
  • 비동기 버전도 있음
  • Retry 기능은 내장되어 있지 않음 → 수동 구현 필요

동기 예제

import httpx
import time
import random

def get_with_backoff(url, max_retries=5, base_delay=1):
    for retry in range(max_retries):
        try:
            with httpx.Client(timeout=10.0) as client:
                response = client.get(url)
                if response.status_code == 200:
                    return response.json()
                elif response.status_code in [429, 503]:
                    raise httpx.HTTPStatusError("Retryable", request=None, response=response)
                else:
                    response.raise_for_status()
        except Exception as e:
            if retry < max_retries - 1:
                delay = base_delay * (2 ** retry) + random.uniform(0, 1)
                print(f"[{retry+1}] Error: {e}. Retrying in {delay:.2f}s...")
                time.sleep(delay)
            else:
                print("Exceeded max retries.")
                raise

3. google-api-python-client (Google 공식 클라이언트)

특징

  • 일부 Google API는 내부적으로 지수 백오프 자동 적용
  • 하지만, 명시적 재시도 처리를 해야 안전함
  • googleapiclient.errors.HttpError 예외 발생

예제

import time
import random
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from google.oauth2 import service_account

SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly']
SERVICE_ACCOUNT_FILE = 'key.json'

creds = service_account.Credentials.from_service_account_file(
    SERVICE_ACCOUNT_FILE, scopes=SCOPES)

service = build('drive', 'v3', credentials=creds)

def call_drive_api_with_backoff(max_retries=5, base_delay=1):
    for retry in range(max_retries):
        try:
            results = service.files().list(pageSize=10).execute()
            return results.get('files', [])
        except HttpError as error:
            if error.resp.status in [429, 500, 503] and retry < max_retries - 1:
                delay = base_delay * (2 ** retry) + random.uniform(0, 1)
                print(f"[{retry+1}] Retryable error {error.resp.status}. Retrying in {delay:.2f}s...")
                time.sleep(delay)
            else:
                print(f"[{retry+1}] Failed: {error}")
                raise

범용 라이브러리: tenacity 라이브러리 (모든 라이브러리에 적용 가능한 범용 백오프 도구)

설치

pip install tenacity

예제 사용 (Google API 예시)

from tenacity import retry, wait_exponential, stop_after_attempt
from googleapiclient.errors import HttpError

@retry(wait=wait_exponential(multiplier=1, min=2, max=30),
       stop=stop_after_attempt(5),
       retry_error_callback=lambda retry_state: print("All retries failed."))
def robust_google_call():
    try:
        return service.files().list(pageSize=10).execute()
    except HttpError as e:
        if e.resp.status in [429, 500, 503]:
            raise
        else:
            raise

요약 비교표

라이브러리 지원 방식 지수 백오프 구현 방식 비고
aiohttp 비동기 수동 구현 async/await 필수
httpx 동기/비동기 수동 구현 requests 호환
google-api-client 동기 일부 자동 + 수동 권장 HttpError 기반 예외
tenacity 범용 리트라이 도구 데코레이터 자동 지원 모든 HTTP 클라이언트에 사용 가능

Keywords

지수 백오프, 안정적 재시도, HTTP 429, HTTP 503, quotaExceeded, rateLimitExceeded, random jitter, exponential backoff, 최대 재시도 횟수, 비동기 HTTP

728x90
반응형