엔드포인트보다 실패 응답과 추적 ID가 먼저다
이 글에서는 LLM 서비스의 API를 운영 가능한 형태로 설계하는 방법을 정리합니다.
API 설계라고 하면 보통 URL과 HTTP method부터 떠올립니다. POST /chat, GET /documents/{id} 같은 엔드포인트를 먼저 정합니다. 하지만 운영 관점에서는 엔드포인트보다 먼저 정해야 하는 것이 있습니다.
실패 응답입니다.
추적 ID입니다.
재시도 가능 여부입니다.
rate limit 정책입니다.
health check와 readiness 기준입니다.
분석 기준일: 2026-05-12
실습 기준 환경: FastAPI, Pydantic, PostgreSQL, OpenTelemetry
주요 참고자료: OpenAPI Specification, FastAPI Error Handling, W3C Trace Context, Google SRE
공식 문서의 사실 확인과 이 글의 운영 해석은 본문에서 구분합니다.
핵심 요약
- API 계약은 성공 응답만이 아니라 실패 응답까지 포함한다.
- 모든 요청에는
request_id또는trace_id가 있어야 한다. - LLM API는 retry, timeout, idempotency 여부를 문서화해야 한다.
- health check는 프로세스 생존 확인과 의존성 준비 상태를 분리해야 한다.
- API 문서는 프론트엔드용 스펙이 아니라 운영자와 장애 대응자를 위한 문서이기도 하다.
1. 운영 가능한 API란 무엇인가
운영 가능한 API는 정상 상황에서만 잘 동작하는 API가 아닙니다. 장애가 났을 때 원인을 추적할 수 있고, 호출자가 재시도 여부를 판단할 수 있으며, 운영자가 지표로 상태를 확인할 수 있는 API입니다.
| 항목 | 단순 API | 운영 가능한 API |
|---|---|---|
| 성공 응답 | 데이터 반환 | schema와 version 포함 |
| 실패 응답 | 문자열 또는 임의 JSON | 표준 error envelope |
| 추적성 | 로그 검색 | request_id, trace_id |
| 재시도 | 호출자 판단 | retryable 명시 |
| 제한 정책 | 없음 | rate limit header |
| 상태 확인 | /health 하나 | liveness/readiness 분리 |
LLM 서비스에서는 이 차이가 더 중요합니다. 모델 호출 실패, schema validation 실패, rate limit, 긴 지연이 자주 발생하기 때문입니다.
2. 성공 응답보다 실패 응답이 먼저다
실무에서 장애 대응을 어렵게 만드는 API는 대부분 실패 응답이 제각각입니다.
// 예시 JSON 구조입니다.{ "message": "failed"}
이 정도 응답으로는 원인을 알 수 없습니다. 어떤 요청인지, 재시도 가능한지, 사용자가 잘못 보낸 것인지, 서버 내부 문제인지 구분할 수 없습니다.
운영 가능한 실패 응답은 최소한 아래 정보를 포함해야 합니다.
// 예시 JSON 구조입니다.{ "error": { "code": "LLM_SCHEMA_VALIDATION_FAILED", "message": "Model response did not match the expected schema.", "retryable": true, "details": { "schema_version": "answer.v1" } }, "request_id": "req_01HX...", "trace_id": "0af7651916cd43dd8448eb211c80319c"}
3. Error Response Envelope 설계
추천하는 error envelope 구조는 다음과 같습니다.
| 필드 | 설명 |
|---|---|
error.code | 시스템 내부에서 검색 가능한 고유 코드 |
error.message | 사용자 또는 개발자가 이해할 수 있는 설명 |
error.retryable | 클라이언트가 재시도해도 되는지 여부 |
error.details | 디버깅용 추가 정보. 민감정보 제외 |
request_id | 단일 요청 식별자 |
trace_id | 분산 추적 식별자 |
주의할 점은 details에 프롬프트 원문, 개인정보, API key, 내부 문서 전문을 넣지 않는 것입니다.
보안 주의
LLM 요청/응답은 민감한 문서를 포함할 수 있습니다. 실패 응답과 로그에 prompt, document chunk, user input 원문을 그대로 넣지 않도록 마스킹 정책을 먼저 정해야 합니다.
4. Trace ID와 Request ID 설계
request_id는 서비스 내부의 단일 요청 식별자입니다. trace_id는 여러 서비스와 외부 호출을 관통하는 분산 추적 식별자입니다.
# 예시입니다.Client→ API Server span→ Retrieval span→ LLM Provider span→ Validation span→ DB Save span
LLM 서비스에서는 한 요청이 여러 하위 작업으로 나뉩니다. 따라서 trace를 연결해야 “어디서 느려졌는지” 볼 수 있습니다.
응답 header 예시는 다음과 같습니다.
# 예시입니다.X-Request-Id: req_01HXABCtraceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
5. Idempotency Key가 필요한 API
모든 API에 idempotency key가 필요한 것은 아닙니다. 하지만 상태를 바꾸는 API에는 필요합니다.
| API | Idempotency 필요 여부 | 이유 |
|---|---|---|
GET /answers/{id} | 낮음 | 읽기 요청 |
POST /questions | 중간 | 같은 질문 중복 생성 방지 가능 |
POST /documents/{id}/index | 높음 | 중복 색인 방지 |
POST /payments | 매우 높음 | 중복 결제 방지 |
문서 색인 API는 대표적인 예입니다. 같은 문서가 여러 번 색인되면 vector row가 중복되고 검색 품질이 떨어질 수 있습니다.
# 예시입니다.POST /documents/doc_123/indexIdempotency-Key: doc_123:index:v4
서버는 이 key를 작업 테이블에 저장하고, 같은 key가 다시 들어오면 기존 작업 상태를 반환합니다.
6. Rate Limit 응답 설계
LLM API는 비용과 provider 제한이 함께 걸립니다. 따라서 rate limit은 단순 보안 장치가 아니라 비용 통제 장치입니다.
# 예시입니다.HTTP/1.1 429 Too Many RequestsX-RateLimit-Limit: 100X-RateLimit-Remaining: 0X-RateLimit-Reset: <unix_timestamp>Retry-After: 30
429 응답에도 request_id와 error code를 포함해야 합니다.
// 예시 JSON 구조입니다.{ "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Too many requests. Retry after 30 seconds.", "retryable": true }, "request_id": "req_01HX..."}
7. Health Check와 Readiness
/health 하나만 두면 충분하지 않습니다.
| 엔드포인트 | 목적 | 확인 대상 |
|---|---|---|
/livez | 프로세스 생존 | 애플리케이션 프로세스 |
/readyz | 트래픽 수신 가능 | DB, Redis, Queue, config |
/metrics | 지표 수집 | Prometheus 등 |
LLM provider 상태까지 readiness에 넣을지는 신중해야 합니다. 외부 모델 API가 일시적으로 느리다고 전체 서비스를 트래픽에서 제외하면 오히려 장애가 커질 수 있습니다. 보통은 provider 상태를 별도 metric으로 보고 fallback 정책을 둡니다.
8. OpenAPI 문서화 기준
API 문서에는 성공 응답뿐 아니라 실패 응답도 포함해야 합니다.
# 예시입니다.responses: '200': description: Answer created '400': description: Invalid request '429': description: Rate limit exceeded '500': description: Internal server error
문서화할 항목은 다음과 같습니다.
# 예시입니다.[ ] request schema[ ] response schema[ ] error schema[ ] authentication[ ] rate limit[ ] retry policy[ ] idempotency key[ ] timeout expectation[ ] trace/request ID header
9. 실무 체크리스트
# 예시입니다.[ ] 모든 API 응답에 request_id가 있는가?[ ] error.code가 검색 가능한 고유 문자열인가?[ ] retryable 필드가 있는가?[ ] 429 응답에 Retry-After가 있는가?[ ] 상태 변경 API에 idempotency key를 지원하는가?[ ] liveness와 readiness를 분리했는가?[ ] OpenAPI 문서에 실패 응답이 포함되어 있는가?[ ] 로그에 민감정보가 남지 않는가?
10. Q&A
Q1. request_id와 trace_id는 둘 다 필요한가요?
작은 서비스에서는 request_id만으로 시작해도 됩니다. 하지만 서비스가 여러 컴포넌트로 나뉘면 trace_id가 필요합니다. request_id는 단일 요청 추적에, trace_id는 여러 span 연결에 유리합니다.
Q2. 모든 API에 idempotency key를 넣어야 하나요?
아닙니다. 상태를 변경하고 재시도 가능성이 있는 API부터 적용하면 됩니다. 문서 색인, 결제, 외부 이벤트 처리 같은 API가 우선순위입니다.
Q3. LLM provider 장애는 readiness 실패로 봐야 하나요?
대부분의 경우 별도 provider health metric으로 보는 편이 낫습니다. provider 장애 때문에 전체 API 서버를 내리는 것보다 fallback, provider routing, degraded response가 더 안전할 수 있습니다.
11. 참고자료와 불확실성
참고자료
- OpenAPI Specification: https://swagger.io/specification/
- FastAPI Error Handling: https://fastapi.tiangolo.com/tutorial/handling-errors/
- W3C Trace Context: https://www.w3.org/TR/trace-context/
- Google SRE: Monitoring Distributed Systems: https://sre.google/sre-book/monitoring-distributed-systems/
- AWS Builders Library: Idempotent APIs: https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/
불확실성
- 실제 error code 체계는 조직의 API governance 기준에 맞춰야 합니다.
- trace ID header는 사용하는 observability stack에 따라 세부 구현이 달라질 수 있습니다.

댓글
GitHub 계정으로 로그인하면 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions를 통해 운영됩니다.