운영 가능한 API 설계
메뉴
Moonshot Notes orbit notebook mark
Moonshot NotesAI 도구와 개발 워크플로우 기록하는 공간

AI Backend

운영 가능한 API 설계

LLM 백엔드에서 운영 가능한 API를 만들기 위해 성공 응답보다 실패 응답, trace ID, idempotency, rate limit, health check를 먼저 설계하는 방법을 정리합니다.

운영 가능한 API 설계 hero image
Markdown약 1525 tokens

엔드포인트보다 실패 응답과 추적 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에는 필요합니다.

APIIdempotency 필요 여부이유
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. 참고자료와 불확실성

참고자료

불확실성

  • 실제 error code 체계는 조직의 API governance 기준에 맞춰야 합니다.
  • trace ID header는 사용하는 observability stack에 따라 세부 구현이 달라질 수 있습니다.

댓글

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

TOP