---
title: "Queue와 Idempotency"
slug: "llm-backend-05-queue-idempotency"
canonicalUrl: "https://moonshotnotes.com/posts/llm-backend-05-queue-idempotency/"
sourceUrl: "https://moonshotnotes.com/posts/llm-backend-05-queue-idempotency/"
markdownUrl: "https://moonshotnotes.com/agent/posts/llm-backend-05-queue-idempotency.md"
language: "ko"
category: "AI Backend"
updatedAt: "2026-05-12"
agentTokenEstimate: 1381
---

# Queue와 Idempotency

문서 색인, embedding 생성, 대량 요약처럼 오래 걸리는 AI 작업을 큐로 분리하고 idempotency key, retry, DLQ로 안정화하는 방법을 정리합니다.

## Agent metadata

- Source: https://moonshotnotes.com/posts/llm-backend-05-queue-idempotency/
- Markdown: https://moonshotnotes.com/agent/posts/llm-backend-05-queue-idempotency.md
- Language: ko
- Category: AI Backend
- Tags: LLM, Backend, Queue, Idempotency, Reliability
- Updated: 2026-05-12
- Estimated tokens: 1381

## 오래 걸리는 AI 작업을 안전하게 처리하기

이 글에서는 오래 걸리는 AI 작업을 큐와 멱등성으로 안전하게 처리하는 방법을 정리합니다.

LLM 서비스에는 요청 즉시 끝나지 않는 작업이 많습니다. PDF를 파싱하고, 문서를 chunk로 나누고, embedding을 만들고, vector DB에 저장하고, 대량 문서를 요약하는 작업은 수 초에서 수 분까지 걸릴 수 있습니다.

이런 작업을 API 요청 안에서 동기 처리하면 사용자는 오래 기다리고, 서버는 쉽게 포화되고, 실패했을 때 복구도 어렵습니다. 그래서 큐가 필요합니다.

하지만 큐를 쓰는 순간 또 다른 문제가 생깁니다. 메시지는 중복될 수 있고, 재시도될 수 있고, 처리 중 worker가 죽을 수 있습니다. 그래서 idempotency가 필요합니다.

분석 기준일: 2026-05-12
실습 기준 환경: FastAPI, PostgreSQL, Redis Queue 또는 SQS/Kafka 개념
주요 참고자료: AWS Builders Library, Amazon SQS Docs, Kafka Docs

## 핵심 요약

- 오래 걸리는 AI 작업은 API 요청과 분리해야 한다.
- 큐는 장애와 부하를 흡수하지만 중복 처리 가능성을 만든다.
- idempotency key는 같은 작업이 여러 번 실행되어도 결과가 깨지지 않게 한다.
- 작업 상태 테이블은 비동기 처리의 관측성을 높인다.
- retry와 DLQ는 실패를 숨기는 장치가 아니라 분석 가능한 상태로 남기는 장치다.

## 1. 왜 AI 작업은 오래 걸리는가

AI 백엔드에서 오래 걸리는 작업은 다음과 같습니다.

| 작업 | 오래 걸리는 이유 |
|---|---|
| PDF 파싱 | 파일 크기, OCR, 표/이미지 처리 |
| Chunking | 문서 구조 분석 필요 |
| Embedding 생성 | 외부 모델 호출 또는 batch 처리 |
| Vector 저장 | 대량 insert, index update |
| 대량 요약 | 모델 호출 횟수 증가 |
| Eval 실행 | 테스트 데이터셋 반복 호출 |

이 작업들은 사용자 요청과 같은 lifecycle에 묶어두기 어렵습니다.

## 2. 동기 처리의 문제

동기 처리는 처음에는 간단합니다.

```text
# 예시입니다.
POST /documents/{id}/index
→ PDF 파싱
→ Chunking
→ Embedding
→ Vector 저장
→ 응답
```

하지만 문제가 생깁니다.

```text
# 예시입니다.
[ ] 요청 시간이 길어진다.
[ ] reverse proxy timeout에 걸릴 수 있다.
[ ] 사용자는 진행 상태를 알 수 없다.
[ ] 실패 시 어디까지 처리됐는지 알기 어렵다.
[ ] 같은 요청이 다시 들어오면 중복 색인이 발생할 수 있다.
```

그래서 비동기 처리로 바꿔야 합니다.

## 3. Queue 기반 처리 흐름

```text
# 예시입니다.
Client
→ POST /documents/{id}/index
→ API server creates job
→ enqueue message
→ return job_id
→ Worker consumes message
→ process indexing
→ update job status
→ Client polls GET /jobs/{job_id}
```

이 구조의 장점은 API 서버가 긴 작업을 직접 붙잡지 않는다는 점입니다. 사용자는 `job_id`를 받고 상태를 조회합니다.

## 4. Idempotency Key 설계

같은 문서를 같은 버전으로 색인하는 작업은 한 번만 실행되어야 합니다.

```text
# 예시입니다.
idempotency_key = document_id + document_version + pipeline_version
```

예시:

```text
# 예시입니다.
doc_123:v7:rag-pipeline-v2
```

같은 key로 요청이 다시 들어오면 새 job을 만들지 않고 기존 job을 반환합니다.

```python
# 예시 코드입니다.
async def create_index_job(document_id: str, document_version: int):
    key = f"{document_id}:v{document_version}:rag-pipeline-v2"

    existing = await job_repo.find_by_idempotency_key(key)
    if existing:
        return existing

    job = await job_repo.create(
        type="DOCUMENT_INDEX",
        idempotency_key=key,
        status="PENDING",
    )
    await queue.publish({"job_id": job.id})
    return job
```

## 5. 작업 상태 테이블 설계

비동기 작업은 상태를 저장해야 합니다.

| 컬럼 | 설명 |
|---|---|
| `job_id` | 작업 ID |
| `job_type` | DOCUMENT_INDEX, EVAL_RUN 등 |
| `idempotency_key` | 중복 방지 key |
| `status` | PENDING, RUNNING, SUCCEEDED, FAILED |
| `attempt_count` | 재시도 횟수 |
| `last_error_code` | 마지막 실패 원인 |
| `created_at` | 생성 시각 |
| `updated_at` | 갱신 시각 |

상태 테이블이 없으면 worker가 실패했을 때 “어디까지 처리됐는지” 알 수 없습니다.

## 6. Retry와 Backoff

재시도는 필요하지만 무한 재시도는 위험합니다. 특히 외부 모델 API 장애 상황에서 모든 worker가 즉시 재시도하면 장애를 키울 수 있습니다.

추천 정책:

```text
# 예시입니다.
1차 실패: 5초 후 재시도
2차 실패: 30초 후 재시도
3차 실패: 2분 후 재시도
4차 실패: DLQ 이동
```

재시도 가능한 오류와 불가능한 오류도 구분해야 합니다.

| 오류 | 재시도 여부 |
|---|---:|
| 외부 API timeout | 가능 |
| rate limit | 가능, backoff 필요 |
| 잘못된 문서 형식 | 불가능 |
| schema validation 실패 | 조건부 가능 |
| 권한 없음 | 불가능 |

## 7. DLQ 설계

DLQ는 실패 메시지를 버리는 곳이 아닙니다. 분석 가능한 상태로 격리하는 곳입니다.

DLQ에 들어가는 메시지에는 다음 정보가 있어야 합니다.

```jsonc
// 예시 JSON 구조입니다.
{
  "job_id": "job_123",
  "idempotency_key": "doc_123:v7:rag-pipeline-v2",
  "attempt_count": 4,
  "last_error_code": "EMBEDDING_RATE_LIMIT",
  "last_error_message": "Rate limit exceeded",
  "trace_id": "0af..."
}
```

DLQ 메시지는 알림, 대시보드, 재처리 도구와 연결되어야 합니다.

## 8. 문서 색인 예제

```text
# 예시입니다.
1. 사용자가 문서 업로드
2. API가 document_version 생성
3. index job 생성
4. queue publish
5. worker가 문서 파싱
6. chunk 생성
7. embedding 생성
8. pgvector에 저장
9. job status를 SUCCEEDED로 변경
```

실패 시:

```text
# 예시입니다.
worker timeout
→ message visibility timeout 만료
→ 다른 worker가 재처리
→ 같은 idempotency key 확인
→ 이미 처리된 chunk는 skip
→ 실패 반복 시 DLQ 이동
```

## 9. 운영 지표

| 지표 | 의미 |
|---|---|
| `queue_depth` | 대기 중인 메시지 수 |
| `job_duration_p95` | 작업 처리 지연 |
| `job_failure_rate` | 실패율 |
| `retry_count` | 재시도 횟수 |
| `dlq_message_count` | 분석 필요한 실패 |
| `duplicate_request_count` | idempotency로 막은 중복 |

## 10. 실무 체크리스트

```text
# 예시입니다.
[ ] 오래 걸리는 작업을 API 요청에서 분리했는가?
[ ] job_id로 상태를 조회할 수 있는가?
[ ] idempotency_key가 작업 단위에 맞게 설계되었는가?
[ ] worker 재시작 후에도 이어서 처리할 수 있는가?
[ ] retry 가능한 오류와 불가능한 오류를 구분했는가?
[ ] backoff와 최대 재시도 횟수가 있는가?
[ ] DLQ 메시지에 trace_id와 error_code가 포함되어 있는가?
[ ] DLQ 재처리 절차가 있는가?
```

## 11. Q&A

### Q1. Redis Queue, SQS, Kafka 중 무엇을 써야 하나요?

초기 학습 프로젝트는 Redis Queue로 충분합니다. 운영 환경에서는 내구성, 재시도, DLQ, 처리량 요구사항에 따라 SQS나 Kafka를 고려합니다.

### Q2. 멱등성은 DB unique constraint만으로 충분한가요?

많은 경우 unique constraint가 좋은 출발점입니다. 하지만 작업 상태, 재시도, 부분 성공을 함께 다루려면 별도 job table이 필요합니다.

### Q3. 메시지가 한 번만 처리된다고 가정해도 되나요?

그렇게 가정하지 않는 편이 안전합니다. 큐 기반 시스템에서는 중복 가능성을 전제로 idempotent하게 설계하는 것이 현실적입니다.

## 12. 참고자료와 불확실성

### 참고자료

- AWS Builders Library — Making retries safe with idempotent APIs: https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/
- Amazon SQS Visibility Timeout: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html
- Amazon SQS Dead-letter Queues: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html
- Apache Kafka Documentation: https://kafka.apache.org/documentation/

### 불확실성

- 큐 선택은 트래픽, 내구성, 팀 운영 경험에 따라 달라집니다.
- 정확히 한 번 처리 semantics는 시스템 전체에서 보장하기 어렵기 때문에, 비즈니스 결과 기준의 멱등성을 우선 설계해야 합니다.

---
