AI 호출이 잘못될 때: 서킷 브레이커(Circuit Breakers), 재시도(Retries), 그리고 스마트한 실패 대응
요약
AI API 호출 시 발생하는 연쇄 실패를 방지하기 위한 서킷 브레이커(Circuit Breaker) 패턴의 중요성을 다룹니다. 단순한 재시도 로직이 오히려 시스템 부하를 가중시킬 수 있음을 경고하며, 빠른 실패와 API 보호를 위한 설계 전략을 제시합니다.
핵심 포인트
- 무분별한 재시도는 과부하 상태인 API의 장애를 악화시킴
- 서킷 브레이커는 실패율 임계값 초과 시 호출을 즉시 차단함
- 빠른 실패(Fast failure)를 통해 시스템 자원 낭비를 방지함
- 캐시나 하위 모델을 활용한 우아한 저하(Graceful degradation) 구현 가능
지난 화요일, 저는 제 AI 파이프라인이 슬로 모션으로 붕괴되는 과정을 지켜보며 시간을 보냈습니다.
시작은 LLM API에서 발생한 일시적인 503 오류였습니다. 제 재시도(retry) 로직이 작동했습니다 — 이건 괜찮은 거 아닌가요? 하지만 LLM은 이미 부하로 인해 어려움을 겪고 있었고, 저의 재시도는 상황을 악화시킬 뿐이었습니다. 그 사이, 다운스트림 서비스(downstream services)들은 제 파이프라인을 기다리다가 타임아웃(timeout)이 발생하기 시작했습니다. 제가 프로세스를 종료했을 때쯤에는, 세 개의 서로 다른 서비스가 연쇄적으로 실패(cascaded failure)한 상태였습니다.
교훈: 서킷 브레이커(circuit breakers) 없는 재시도는 그저 증폭된 피해일 뿐입니다.
AI 파이프라인 실패의 해부
단계별로 일어난 일은 다음과 같습니다:
- LLM API가 503(서비스를 일시적으로 사용할 수 없음)을 반환함
- 2초 후 제 재시도 로직이 실행됨
- API는 여전히 다운 상태였고, 또 다른 503을 반환함
- 재시도 #2, #3, #4가 차례로 실행되었으며 — 각 재시도는 이미 과부하된 시스템에 부하를 더함
- 제 서비스의 커넥션 풀(connection pool)이 가득 참
- 다른 엔드포인트(endpoints)들이 타임아웃되기 시작함
- 모니터링 시스템이 제 서비스를 비정상(unhealthy)으로 표시함
- Kubernetes가 포드(pod)를 재시작함 — 이 과정에서 처리 중이던 요청(in-flight requests)들이 손실됨
503 오류는 불꽃이었습니다. 하지만 불을 지핀 것은 바로 저의 재시도 로직이었습니다.
서킷 브레이커(circuit breaker)가 제공하는 이점
서킷 브레이커는 여러분의 코드와 API 사이에 위치합니다. 이는 실패를 추적하며, 실패율이 임계값(threshold)을 초과하면 회로를 "개방(opens)"합니다. 즉, 이후의 모든 호출은 API에 아예 접근하지 않고 즉시 실패하게 됩니다.
이를 통해 세 가지 결정적인 이점을 얻을 수 있습니다:
빠른 실패 (Fast failure): 회로가 개방되어 있을 때, 요청은 30초 동안 타임아웃을 기다리는 대신 밀리초(milliseconds) 단위로 실패합니다. 사용자들은 시스템이 멈춰 있는 대신 즉각적인 에러 응답을 받게 됩니다.
API 보호 (API protection): API가 이미 어려움을 겪고 있을 때 재시도를 중단함으로써, 여러분의 클라이언트가 문제의 일부가 되는 것을 방지합니다. 이는 요청을 큐(queue)에 쌓는 LLM API의 경우 특히 중요합니다. 여러분의 재시도는 말 그대로 모든 사람에게 장애 상황을 더 악화시키고 있는 것입니다.
우아한 저하 (Graceful degradation): 서킷이 열리면(circuit opens), 더 단순한 모델, 캐시된 응답 또는 사용자 친화적인 오류 메시지로 폴백(fall back)할 수 있습니다. 시스템은 고장나지 않고, 성능만 저하됩니다.
제가 사용하는 구현 방식
여러 접근 방식을 시도한 끝에 정착한 패턴입니다:
import time
import random
from enum import Enum
...
사용법은 간단합니다:
breaker = CircuitBreaker(
failure_threshold=3, # 3회 실패 후 차단(Trip)
recovery_timeout=60, # 테스트 전 60초 대기
...
상황을 더 악화시키지 않는 재시도 전략
서킷 브레이커는
- SLA(Service Level Agreement)가 있는 내부 서비스 호출 (Internal service calls with SLAs): 호출의 양측을 모두 제어할 수 있다면, 브레이커(Breaker) 뒤로 문제를 숨기는 것보다 근본 원인을 해결하는 것이 더 좋습니다.
- Fire-and-forget 방식의 메트릭 (Fire-and-forget metrics): 호출 실패가 단순히 "이 메트릭을 건너뜀"을 의미한다면, 브레이커는 불필요한 복잡성만 더할 뿐입니다.
- 자체 복원력이 내장된 시스템 (Systems with built-in resilience): 일부 관리형 AI 서비스(예: ai.interwestinfo.com의 서비스)는 자체적으로 로드 밸런싱(Load balancing)과 큐잉(Queuing)을 처리합니다. 이러한 경우 클라이언트 측 브레이커는 중복될 수 있습니다.
결론 (The bottom line)
AI 파이프라인(Pipelines)은 분산 시스템(Distributed systems)입니다. 이들은 실패합니다. 문제는 실패할 것인가가 아니라, 어떻게 실패할 것인가입니다.
서킷 브레이커(Circuit breakers)는 실패를 방지하는 것이 아니라, 실패의 연쇄 반응(Failure cascades)을 방지합니다. 그리고 LLM API가 불안정할 수 있는 세상에서, 이 차이는 매우 중요합니다.
AI API 실패를 처리하는 여러분의 방식은 무엇인가요? 다른 분들은 어떤 패턴이 유용하다고 느끼셨는지 궁금합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기