---
title: "운영 가능한 API 설계"
slug: "llm-backend-03-production-api-design"
canonicalUrl: "https://moonshotnotes.com/posts/llm-backend-03-production-api-design/"
sourceUrl: "https://moonshotnotes.com/posts/llm-backend-03-production-api-design/"
markdownUrl: "https://moonshotnotes.com/agent/posts/llm-backend-03-production-api-design.md"
language: "ko"
category: "AI Backend"
updatedAt: "2026-05-12"
agentTokenEstimate: 1525
---

# 운영 가능한 API 설계

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

## Agent metadata

- Source: https://moonshotnotes.com/posts/llm-backend-03-production-api-design/
- Markdown: https://moonshotnotes.com/agent/posts/llm-backend-03-production-api-design.md
- Language: ko
- Category: AI Backend
- Tags: LLM, Backend, API Design, Trace ID, OpenAPI
- Updated: 2026-05-12
- Estimated tokens: 1525

## 엔드포인트보다 실패 응답과 추적 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는 대부분 실패 응답이 제각각입니다.

```jsonc
// 예시 JSON 구조입니다.
{
  "message": "failed"
}
```

이 정도 응답으로는 원인을 알 수 없습니다. 어떤 요청인지, 재시도 가능한지, 사용자가 잘못 보낸 것인지, 서버 내부 문제인지 구분할 수 없습니다.

운영 가능한 실패 응답은 최소한 아래 정보를 포함해야 합니다.

```jsonc
// 예시 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`는 여러 서비스와 외부 호출을 관통하는 분산 추적 식별자입니다.

```text
# 예시입니다.
Client
→ API Server span
→ Retrieval span
→ LLM Provider span
→ Validation span
→ DB Save span
```

LLM 서비스에서는 한 요청이 여러 하위 작업으로 나뉩니다. 따라서 trace를 연결해야 “어디서 느려졌는지” 볼 수 있습니다.

응답 header 예시는 다음과 같습니다.

```http
# 예시입니다.
X-Request-Id: req_01HXABC
traceparent: 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가 중복되고 검색 품질이 떨어질 수 있습니다.

```http
# 예시입니다.
POST /documents/doc_123/index
Idempotency-Key: doc_123:index:v4
```

서버는 이 key를 작업 테이블에 저장하고, 같은 key가 다시 들어오면 기존 작업 상태를 반환합니다.

## 6. Rate Limit 응답 설계

LLM API는 비용과 provider 제한이 함께 걸립니다. 따라서 rate limit은 단순 보안 장치가 아니라 비용 통제 장치입니다.

```http
# 예시입니다.
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: <unix_timestamp>
Retry-After: 30
```

429 응답에도 request_id와 error code를 포함해야 합니다.

```jsonc
// 예시 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 문서에는 성공 응답뿐 아니라 실패 응답도 포함해야 합니다.

```yaml
# 예시입니다.
responses:
  '200':
    description: Answer created
  '400':
    description: Invalid request
  '429':
    description: Rate limit exceeded
  '500':
    description: Internal server error
```

문서화할 항목은 다음과 같습니다.

```text
# 예시입니다.
[ ] request schema
[ ] response schema
[ ] error schema
[ ] authentication
[ ] rate limit
[ ] retry policy
[ ] idempotency key
[ ] timeout expectation
[ ] trace/request ID header
```

## 9. 실무 체크리스트

```text
# 예시입니다.
[ ] 모든 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에 따라 세부 구현이 달라질 수 있습니다.

---
