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
반응형