OpenAI Batch API 운영법: 50% 비용 절감보다 먼저 설계해야 할 것들
OpenAI Batch API는 비동기 작업 묶음을 보내고 나중에 결과를 받는 방식입니다. 공식 문서에서 강조하는 장점은 명확합니다. 동기 API 대비 50% 낮은 비용, 별도 rate limit 풀, 24시간 내 완료입니다. 하지만 실무에서 Batch API를 제대로 쓰려면 “싸다”보다 먼저 봐야 할 것이 있습니다. 작업을 다시 실행해도 안전한가, 결과를 원래 데이터와 정확히 매칭할 수 있는가, 실패한 줄만 재처리할 수 있는가입니다.
Batch API는 실시간 챗봇용이 아닙니다. 사용자가 버튼을 누르고 바로 답을 기다리는 화면에 붙이면 안 됩니다. 대신 평가 실행, 대량 분류, 임베딩 생성, 콘텐츠 검수, 데이터셋 라벨링처럼 즉시 응답이 필요 없는 작업에 맞습니다.
Batch API가 맞는 작업과 아닌 작업
먼저 작업을 나눠야 합니다. Batch에 잘 맞는 작업은 입력이 많고, 각 요청이 독립적이며, 결과를 몇 분에서 몇 시간 뒤 받아도 되는 작업입니다.
잘 맞는 예시는 다음입니다.
- 10만 개 고객 문의를 의도별로 분류
- 기존 문서 저장소 전체 임베딩 생성
- RAG 답변 평가를 밤에 일괄 실행
- 상품 설명 5,000개 금칙어·과장 표현 검수
- 블로그 초안 1,000개 메타 설명 생성
- 영상 생성 요청을 야간 큐로 처리
반대로 잘 맞지 않는 작업도 있습니다.
- 사용자가 채팅창에서 기다리는 답변
- 결제 승인, 주문 취소처럼 즉시 확정이 필요한 요청
- 이전 요청 결과가 다음 요청 입력이 되는 체인 작업
- 실패 시 사람이 즉시 개입해야 하는 운영 작업
기준은 간단합니다. “24시간 뒤 결과가 와도 비즈니스적으로 괜찮은가?” 이 질문에 아니오라면 Batch가 아닙니다.
JSONL 설계에서 custom_id가 가장 중요하다
Batch 입력은 JSONL 파일입니다. 각 줄에는 method, url, body, custom_id가 들어갑니다. 여기서 가장 중요한 필드는 custom_id입니다. 결과가 돌아왔을 때 어떤 원본 레코드의 결과인지 맞추는 키이기 때문입니다.
나쁜 custom_id는 request-1, request-2처럼 순서에 의존하는 값입니다. 중간에 입력 파일을 다시 만들거나 일부 줄을 제거하면 추적이 어려워집니다. 좋은 custom_id는 도메인 ID와 작업 버전을 포함합니다.
예를 들어 다음처럼 설계할 수 있습니다.
- ticket:84291:intent:v3
- doc:pricing-page:embedding:2026-07-27
- article:ko-193:meta-description:v2
- eval:rag-smoke:case-00031
이렇게 만들면 결과 파일만 봐도 어떤 작업인지 알 수 있습니다. 재처리도 쉬워집니다. 실패한 custom_id만 뽑아 새 JSONL을 만들면 됩니다.
비용 절감은 입력 정리에서 시작된다
Batch API가 50% 할인된다고 해서 아무 입력이나 넣으면 안 됩니다. 대량 작업은 작은 비효율이 크게 증폭됩니다. 1개 요청에서 300토큰을 낭비하면 10만 개 요청에서는 3,000만 토큰 낭비입니다.
입력 정리 기준은 다음입니다.
- 시스템 프롬프트를 짧고 고정한다.
- 작업별 출력 스키마를 명확히 한다.
- 원문 전체가 필요 없으면 필요한 필드만 보낸다.
- 같은 설명을 각 요청 body에 반복하지 않는다.
- 결과에 필요 없는 긴 reasoning을 요구하지 않는다.
- JSON 출력이면 예시 1개만 넣고 나머지는 스키마로 제한한다.
예를 들어 고객 문의 분류라면 고객의 전체 히스토리를 매번 넣을 필요가 없습니다. 최근 메시지, 상품명, 국가, 플랜, 기존 태그 정도면 충분한 경우가 많습니다. 원문 전체를 넣어야 한다면 그 이유를 먼저 검증해야 합니다.
실패 처리는 파일 단위가 아니라 줄 단위로 설계한다
대량 작업에서 가장 위험한 운영 방식은 “배치가 실패했으니 전체를 다시 돌린다”입니다. 비용도 낭비되고 중복 결과가 생깁니다. 처음부터 줄 단위 재처리를 전제로 설계해야 합니다.
실패 처리 테이블에는 최소한 다음 필드를 저장합니다.
| 필드 | 설명 |
|---|---|
| batch_id | OpenAI Batch 작업 ID |
| custom_id | 원본 레코드와 작업 버전 |
| status | success, failed, retry_pending |
| error_code | 모델 오류, validation 오류, rate 오류 등 |
| input_hash | 입력이 같은지 확인하는 해시 |
| output_hash | 결과 중복 저장 방지용 해시 |
| created_at | 배치 요청 시간 |
| completed_at | 결과 반영 시간 |
특히 input_hash가 중요합니다. 같은 custom_id라도 입력이 바뀌었으면 이전 결과를 재사용하면 안 됩니다. 반대로 input_hash가 같고 이미 성공 결과가 있으면 다시 저장하지 않아야 합니다.
Batch 결과 반영은 별도 단계로 분리하라
많은 팀이 Batch 결과를 받자마자 DB에 바로 반영합니다. 작은 작업에서는 괜찮지만 운영 규모가 커지면 위험합니다. 결과 수집과 반영을 분리하는 편이 안전합니다.
추천 파이프라인은 다음입니다.
- 입력 후보를 DB에서 조회한다.
- custom_id와 input_hash를 생성한다.
- JSONL 파일을 만든다.
- Batch를 생성한다.
- 완료 상태를 폴링하거나 스케줄러로 확인한다.
- 결과 파일을 원시 그대로 저장한다.
- 파싱 검증을 통과한 줄만 staging 테이블에 넣는다.
- 사람이 샘플 검수한다.
- 최종 테이블에 반영한다.
이 구조면 문제가 생겨도 원시 결과로 되돌아갈 수 있습니다. 파서 버그가 있어도 Batch를 다시 돌리지 않고 재파싱하면 됩니다.
운영에서 자주 터지는 실수
Batch API는 단순하지만 실수는 반복됩니다.
첫째, 한 파일에 서로 다른 모델 요청을 섞는 실수입니다. 입력 파일은 같은 엔드포인트와 모델 기준으로 관리하는 편이 안전합니다. 둘째, custom_id 중복입니다. 중복되면 결과 매칭이 꼬입니다. 셋째, 출력 JSON을 너무 믿는 것입니다. 모델 출력은 항상 파싱 실패 가능성이 있으므로 JSON schema 검증과 fallback이 필요합니다. 넷째, 24시간 완료를 실시간 SLA처럼 해석하는 것입니다. Batch는 지연 허용 작업에만 써야 합니다.
바로 적용할 체크리스트
- 작업이 24시간 지연을 허용하는지 먼저 확인한다.
- custom_id에 원본 ID, 작업명, 버전을 포함한다.
- input_hash로 중복 실행과 stale 결과를 막는다.
- 실패한 줄만 재처리할 수 있게 저장 구조를 만든다.
- 결과 원본 파일과 파싱 결과를 분리해서 보관한다.
- DB 반영 전 staging 단계와 샘플 검수를 둔다.
- 출력 JSON은 반드시 schema 검증한다.
- Batch 비용 절감률보다 총 토큰 절감률을 같이 본다.
OpenAI Batch API의 진짜 장점은 할인만이 아닙니다. 동기 요청과 다른 rate limit 풀을 쓰면서 대량 작업을 운영 큐로 분리할 수 있다는 점입니다. 단, 큐가 생기면 재처리, 추적, 검증도 같이 설계해야 합니다. 그 세 가지가 없으면 싼 API가 아니라 싼 사고가 됩니다.