한줄 요약:
“언젠가는 오류가 난다.”
그래서 장애를 ‘예방’보다 ‘복구’ 중심으로 설계하자.
👉 자동 재시도·대체경로·휴면 복구 루프를 구현해 완전 무중단 시스템으로!
1. 목표
- 리트라이 + 지수 백오프(Exponential Backoff) 로 자동 재시도
- 대체 경로(Fallback): 예비 API나 저장소로 전환
- 휴면 후 복구(Retry Loop): 일정 시간 후 자동 재시작
- 에러 유형별 대응(4xx/5xx/Timeout/Network)
- 알림 & 상태 리포트 자동 발송
2. 장애 복구 기본 구조
[실패 감지]
├─ 1차 리트라이(3회, 5초 간격)
├─ 2차 백오프(10·20·40초)
├─ 3차 대체 API 전환
└─ 4차 휴면 모드(5분 후 재시작)
3. 공통 유틸 — resilience.py (복붙)
import time, random, logging
from functools import wraps
logger = logging.getLogger("resilience")
logger.setLevel(logging.INFO)
if not logger.handlers:
h = logging.StreamHandler()
h.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
logger.addHandler(h)
# ───────────────────────────────
# 1️⃣ 지수 백오프 리트라이
# ───────────────────────────────
def retry_with_backoff(max_retries=3, base_delay=3, jitter=0.3, exceptions=(Exception,)):
"""
실패 시 지수 백오프 + 랜덤 지연
예: 3초 → 6초 → 12초 (+랜덤 0~0.3초)
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_retries + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
wait = base_delay * (2 ** (attempt - 1))
wait += random.uniform(0, jitter)
logger.warning(f"⚠️ {func.__name__} 실패({attempt}/{max_retries}): {e} → {wait:.1f}s 후 재시도")
time.sleep(wait)
raise
return wrapper
return decorator
# ───────────────────────────────
# 2️⃣ 대체 경로(Fallback)
# ───────────────────────────────
def fallback(primary, secondary):
"""기본 함수 실패 시 대체 함수 실행"""
try:
return primary()
except Exception as e:
logger.warning(f"⚠️ 주 경로 실패, 대체 경로로 전환: {e}")
return secondary()
# ───────────────────────────────
# 3️⃣ 휴면 복구 루프 (일정 주기로 재실행)
# ───────────────────────────────
def recovery_loop(job, sleep_min=5):
"""무한 루프형 자동 복구"""
while True:
try:
job()
logger.info("✅ 정상 완료, 다음 스케줄까지 대기")
break
except Exception as e:
logger.error(f"💥 오류 발생: {e} → {sleep_min}분 후 재시도")
time.sleep(sleep_min * 60)
4. 실제 적용 예시
예시 ① — LLM 호출 안정화
from resilience import retry_with_backoff
from openai import OpenAI
client = OpenAI()
@retry_with_backoff(max_retries=4, base_delay=2)
def call_llm(prompt):
resp = client.responses.create(
model="o4-mini",
input=prompt,
)
return resp.output_text
try:
text = call_llm("오늘의 주요 뉴스 요약")
print("✅ 결과:", text[:200])
except Exception as e:
print("❌ 완전 실패:", e)
⚙️
retry_with_backoff덕분에 일시적 네트워크 문제나 429 Rate limit에도 자동 재시도
예시 ② — 워드프레스 API 대체 경로
from resilience import fallback
import requests, os
def post_main():
return requests.post(f"{os.getenv('WP_URL')}/wp-json/wp/v2/posts", timeout=5)
def post_backup():
return requests.post("https://backup-blog.example.com/wp-json/wp/v2/posts", timeout=5)
response = fallback(post_main, post_backup)
print("응답 코드:", response.status_code)
⚡ 워드프레스 서버 장애 시 자동으로 백업 블로그로 전송
예시 ③ — 전체 파이프라인 자동 복구 루프
from resilience import recovery_loop
import subprocess
def pipeline():
subprocess.check_call(["python", "orchestrator.py"])
# 5분 후 재시작
recovery_loop(pipeline, sleep_min=5)
💡 크론/스케줄러에 등록해두면, 오류 발생 후 자동 휴면 복구 가능
5. 장애 유형별 대응전략
| 장애유형 | 탐지기준 | 조치 |
|---|---|---|
| 네트워크 타임아웃 | 응답 없음 > 15초 | 백오프 재시도 |
| API 429 | Rate limit 응답 | 지수 백오프 후 재호출 |
| 5xx 서버오류 | 응답 코드 500~599 | 대체 경로(Fallback) 전환 |
| 데이터 포맷 오류 | JSONDecodeError 등 | 예외 포착 → 로그 → 스킵 |
| 워드프레스 인증실패 | 401/403 | 재인증 or 키 회전 실행 |
| LLM 오류(모델/토큰 초과) | APIError / InvalidRequest | 입력 자르기 + 재시도 |
6. 백오프 알고리즘 요약
| 단계 | 지연 공식 | 설명 |
|---|---|---|
| 1 | base_delay * 2^(n-1) | 1, 2, 4, 8, 16초 등 기하급수 증가 |
| 2 | + 랜덤 jitter | 과도한 동시 재시도 방지 |
| 3 | 최대 지연 제한 | 예: 60초 이상은 고정 |
| 4 | 재시도 로그 남기기 | 시각, 횟수, 예외 타입 |
☑️ “짧게 자주보다, 길게 간격 두고” 접근이 서버 보호에 효과적
7. 보강 팁 — “자동화의 실패도 자동으로 보고하라”
from common_ops import notify_webhook
from resilience import recovery_loop
def safe_pipeline():
try:
subprocess.check_call(["python", "orchestrator.py"])
except Exception as e:
notify_webhook(f"🔥 파이프라인 실패: {e}")
raise
recovery_loop(safe_pipeline, sleep_min=10)
실패 시 슬랙 알림 + 10분 후 자동 재시작.
완전 무인 운영이 가능해진다.
8. 체크리스트
retry_with_backoff()적용 완료- 예외 유형별 처리 분리 (429, 5xx, Timeout 등)
fallback()대체 경로 등록recovery_loop()휴면 복구 루프 활성화- 알림(슬랙/메일) 연동
- 실패/재시도 로그 구조화
9. 요약 한 줄
“오류가 나면 알아서 쉬고, 다시 일어나게 하라.”
백오프·대체·복구 루프만 있어도 시스템은 절대 멈추지 않는다 ✅
이전 강좌 👈 [운영#2] 보안·비밀관리(.env/키 회전/권한/로그 마스킹)
다음 강좌 👉 [운영#4] 성능 튜닝 & 확장 (멀티스레드·비동기·큐 기반 처리)