LLM API 호출이 실패해도 서비스가 멈추지 않게 만드는 6가지 설계 원칙
LLM API를 제품에 연결하는 것과 안정적으로 운영하는 것은 전혀 다른 문제입니다.
개발 단계에서는 한 번 호출해서 답변이 돌아오면 기능이 완성된 것처럼 보입니다.
실제 운영 환경에서는 그렇지 않습니다.
외부 AI API에는 언제든 다음과 같은 문제가 발생할 수 있습니다.
- 응답 지연
- 네트워크 오류
- Rate Limit
- 일시적인 서버 오류
- 긴 입력으로 인한 처리 실패
- 특정 모델 사용 불가
- 예상보다 긴 응답 시간
이런 상황을 모두 단순한 try/catch 하나로 처리하면 작은 API 문제가 서비스 전체 장애처럼 보이게 됩니다.
AI 기능을 실제 제품에 넣으려면 모델 품질뿐 아니라 실패를 전제로 한 호출 구조가 필요합니다.
이번 글에서는 LLM API를 사용할 때 적용할 수 있는 6가지 기본 설계 원칙을 정리합니다.
1. 모든 AI 요청에 타임아웃을 설정한다
외부 API 요청을 무제한 기다리는 것은 좋은 기본값이 아닙니다.
AI 모델은 요청의 복잡도와 서버 상태에 따라 응답 시간이 크게 달라질 수 있습니다.
타임아웃이 없으면 다음 문제가 생길 수 있습니다.
- 사용자 요청이 계속 대기
- 서버 연결 점유
- 후속 작업 지연
- 요청 큐 증가
- 장애 전파
따라서 요청 목적에 따라 최대 대기 시간을 정하는 것이 좋습니다.
예를 들어:
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort();
}, 15000);
try {
const response = await fetch(API_URL, {
method: "POST",
signal: controller.signal,
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
return await response.json();
} finally {
clearTimeout(timeout);
}
여기서 중요한 것은 15초라는 숫자 자체가 아닙니다.
사용자 인터랙션인지, 백그라운드 작업인지에 따라 타임아웃 기준은 달라져야 합니다.
실시간 UI라면 짧은 대기 시간이 중요하고, 백그라운드 문서 분석이라면 더 긴 시간을 허용할 수 있습니다.
2. 모든 오류를 재시도하지 않는다
API 호출이 실패했다고 무조건 다시 요청하는 것도 위험합니다.
예를 들어 잘못된 API 키나 유효하지 않은 요청 형식은 몇 번을 다시 보내도 성공하지 않습니다.
오류를 크게 두 종류로 나누는 편이 좋습니다.
재시도할 수 있는 오류
- 일시적인 네트워크 오류
- 429 Rate Limit
- 일부 5xx 서버 오류
- 일시적인 타임아웃
바로 중단해야 하는 오류
- 인증 실패
- 권한 없음
- 잘못된 요청 데이터
- 존재하지 않는 모델
- 입력 제한 초과
재시도 정책은 오류 코드와 상황에 따라 달라져야 합니다.
function shouldRetry(status) {
return status === 429 || status >= 500;
}
물론 실제 구현에서는 네트워크 예외와 공급자별 오류 코드도 함께 처리해야 합니다.
3. 재시도에는 지수 백오프를 사용한다
실패 직후 같은 요청을 즉시 반복하면 문제가 더 커질 수 있습니다.
특히 Rate Limit이나 API 공급자 장애 상황에서는 수많은 클라이언트가 동시에 재시도하면서 서버 부하가 더 증가합니다.
이런 상황을 피하기 위해 많이 사용하는 방식이 Exponential Backoff입니다.
예:
1차 실패 → 1초 대기
2차 실패 → 2초 대기
3차 실패 → 4초 대기
4차 실패 → 8초 대기
간단한 구현은 다음처럼 만들 수 있습니다.
async function retryWithBackoff(fn, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
const delay = 1000 * Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
운영 환경에서는 여기에 작은 랜덤 지연을 추가하는 jitter도 고려할 수 있습니다.
여러 요청이 정확히 같은 시점에 다시 몰리는 것을 줄이기 위해서입니다.
4. 모델 폴백 전략을 준비한다
주 모델을 사용할 수 없다고 전체 서비스를 멈출 필요는 없습니다.
작업 특성에 따라 두 번째 모델이나 단순한 기능으로 전환할 수 있습니다.
예를 들어:
Primary Model
↓ 실패
Secondary Model
↓ 실패
Rule-based fallback
모든 작업에서 폴백이 가능한 것은 아닙니다.
하지만 다음과 같은 기능에서는 현실적으로 적용할 수 있습니다.
- 간단한 텍스트 분류
- 태그 생성
- 짧은 요약
- 제목 생성
- 내부 검색 보조
- 초안 생성
반대로 특정 모델 특성에 의존하는 작업이라면 억지로 다른 모델로 바꾸기보다 사용자에게 처리 지연을 알리는 편이 더 나을 수 있습니다.
폴백의 목적은 어떤 답이든 반환하는 것이 아니라 서비스의 핵심 기능을 유지하는 것입니다.
5. 반복 요청은 캐시할 수 있는지 확인한다
LLM 요청은 일반 API보다 비용과 응답 시간이 더 큰 경우가 많습니다.
동일하거나 거의 동일한 요청이 반복되는 시스템이라면 캐시가 상당한 효과를 낼 수 있습니다.
예를 들어 다음 작업은 캐시 후보가 될 수 있습니다.
- 고정 문서 요약
- 반복 FAQ 답변
- 동일 제품 설명 생성
- 동일 데이터 분류
- 자주 요청되는 분석 결과
단순한 구조는 다음과 같습니다.
Request
↓
Cache 확인
├─ 존재 → 캐시 결과 반환
└─ 없음 → LLM API 호출
↓
결과 저장
다만 사용자 개인정보나 계속 변하는 최신 정보는 캐시 정책을 신중하게 정해야 합니다.
캐시 키 역시 프롬프트 문자열 하나만 사용하기보다 다음 정보를 고려하는 편이 좋습니다.
- 모델
- 프롬프트 버전
- 입력 데이터
- 주요 옵션
프롬프트를 수정했는데 이전 결과가 계속 반환되는, 꽤 인간적인 종류의 혼란을 막을 수 있습니다.
6. 반복 장애에는 Circuit Breaker를 고려한다
API가 이미 장애 상태인데 계속 요청을 보내는 것은 의미가 없습니다.
이때 사용할 수 있는 패턴 중 하나가 Circuit Breaker입니다.
기본 개념은 단순합니다.
정상 상태
↓
연속 실패 증가
↓
임계값 초과
↓
Circuit Open
↓
일정 시간 API 호출 중단
↓
시험 요청
↓
정상 확인 후 복구
일반적으로 세 가지 상태로 설명합니다.
Closed
정상적으로 API 요청을 보냅니다.
Open
오류가 임계값을 넘으면 API 요청을 잠시 차단합니다.
Half-Open
일정 시간이 지난 뒤 일부 요청을 보내 정상 복구 여부를 확인합니다.
이 방식은 장애 중인 외부 서비스를 계속 두드리는 것을 줄이고, 내부 리소스를 보호하는 데 도움이 됩니다.
AI API 호출 흐름을 하나로 정리하면
실제 요청 흐름은 다음처럼 구성할 수 있습니다.
사용자 요청
↓
입력 검증
↓
캐시 확인
↓
Primary LLM
↓
타임아웃 / 오류 확인
↓
재시도 가능 여부 판단
↓
Exponential Backoff
↓
Primary 재시도
↓
계속 실패
↓
Fallback Model
↓
출력 검증
↓
사용자에게 결과 반환
그리고 이 모든 과정에서 로그를 남겨야 합니다.
최소한 다음 항목은 추적할 수 있어야 합니다.
| 항목 | 확인 목적 |
|---|---|
| 요청 시간 | 지연 분석 |
| 모델 | 모델별 장애 확인 |
| 응답 시간 | 성능 측정 |
| HTTP 상태 | 오류 분류 |
| 재시도 횟수 | 비정상 호출 탐지 |
| 폴백 여부 | 기본 모델 안정성 확인 |
| 토큰 사용량 | 비용 분석 |
| 최종 결과 상태 | 실제 성공률 측정 |
단순 성공률보다 중요한 지표
AI API 운영에서는 단순히 요청이 성공했는지만 보면 부족합니다.
다음 지표를 함께 보는 편이 좋습니다.
- 평균 응답 시간
- p95 / p99 latency
- API 오류율
- Rate Limit 발생률
- 재시도율
- 폴백 사용률
- 타임아웃 비율
- 요청당 평균 비용
예를 들어 전체 성공률이 99%여도 p95 응답 시간이 25초라면 사용자 경험은 좋지 않을 수 있습니다.
반대로 폴백 비율이 갑자기 증가한다면 주 모델에 문제가 생겼다는 신호일 수 있습니다.
완벽한 자동 복구가 목표는 아니다
실패 처리 로직을 많이 넣는다고 항상 좋은 시스템이 되는 것도 아닙니다.
복구 로직 자체가 복잡해지면 오히려 디버깅이 어려워집니다.
처음에는 다음 네 가지부터 적용해도 충분합니다.
- 타임아웃
- 오류별 재시도 정책
- 제한된 지수 백오프
- 로그와 모니터링
그다음 실제 장애 패턴을 확인하면서 캐시, 폴백, Circuit Breaker를 추가하는 편이 좋습니다.
AI 서비스를 선택할 때 API 안정성도 확인한다
AI 모델을 선택할 때 답변 품질만 비교하기 쉽습니다.
실제 개발 환경에서는 다음 요소도 중요합니다.
- API 제공 여부
- Rate Limit 정책
- 구조화 출력
- 스트리밍 지원
- 오류 코드 문서
- 사용량 확인 기능
- 모델 버전 정책
- 데이터 처리 정책
AI 관련 서비스를 탐색할 때는 레이더 주소모음안내사이트처럼 카테고리별로 정리된 페이지를 출발점으로 활용할 수 있습니다.
실제 API 사양과 요금, 제한은 반드시 각 서비스의 공식 개발자 문서에서 확인해야 합니다.
마무리
LLM API를 안정적으로 운영하려면 모델이 항상 정상적으로 응답한다는 가정을 버려야 합니다.
외부 API는 느려질 수 있고, 제한될 수 있으며, 잠시 사용할 수 없을 수도 있습니다.
따라서 최소한 다음 구조를 준비하는 것이 좋습니다.
- 모든 호출에 타임아웃 설정
- 오류 유형에 따른 재시도 판단
- 지수 백오프 적용
- 필요한 경우 모델 폴백
- 반복 요청 캐시
- 반복 장애에는 Circuit Breaker 적용
좋은 AI 서비스는 모델이 한 번도 실패하지 않는 서비스가 아닙니다.
AI가 실패해도 나머지 시스템이 정상적으로 동작하는 서비스에 더 가깝습니다.
