3 min read
Part 8. OpenSearch/Elasticsearch 배치 전략: Scroll, Search After, PIT, Bulk, Rollover
대량 조회와 대량 색인을 함께 고려한 OpenSearch 배치 설계 기준과 운영 트레이드오프를 실무 관점에서 정리한다.
Series: Spring Boot 배치 전략 완전 정복
총 12편 구성. 현재 8편을 보고 있습니다.
- 01Part 1. 배치의 본질과 분류: 스케줄, 이벤트, 수동, 대량, Near-real-time
- 02Part 2. @Scheduled 실전 운영: 단순함의 대가와 멀티 인스턴스 함정
- 03Part 3. Quartz 클러스터 아키텍처: JobStore, Misfire, 대규모 스케줄 관리
- 04Part 4. Spring Batch 핵심: Chunk, 트랜잭션 경계, Restart 가능한 Job 설계
- 05Part 5. Spring Batch 확장: Partition과 Multi-threaded Step의 트레이드오프
- 06Part 6. 수동 배치 전략: REST 트리거, Admin UI, 파라미터 재처리, 롤백
- 07Part 7. DB 대량 조회 전략: OFFSET/LIMIT 한계와 Keyset, ID Range, Covering Index
- 08Part 8. OpenSearch/Elasticsearch 배치 전략: Scroll, Search After, PIT, Bulk, RolloverCURRENT
- 09Part 9. 분산 환경 배치: Leader Election, Kubernetes CronJob, 락 전략 비교
- 10Part 10. 성능 최적화: 배치 사이즈, 커밋 간격, JVM 메모리, Backpressure
- 11Part 11. 장애 대응 아키텍처: Partial Failure, Poison Data, DLQ, 재시도, 멱등성
- 12Part 12. 통합 레퍼런스 아키텍처와 최종 선택 가이드

출처: Pexels - Multi monitor workspace
버전 기준
- Java 21
- Spring Boot 3.3.x
- Spring Batch 5.2.x
- Quartz 2.3.x
- PostgreSQL 15
- OpenSearch 2.x
1) 문제 제기
검색 배치는 DB 배치와 실패 패턴이 다르다. DB는 트랜잭션이 기준이지만, OpenSearch는 세그먼트/리프레시/샤드 상태가 성능과 정합성을 좌우한다. 특히 "전체 재색인"에서 다음 문제가 자주 발생한다.
- Scroll 컨텍스트 누수로 클러스터 메모리 압박
- Search After 정렬 키 설계 미흡으로 누락/중복
- Bulk 크기 과대 설정으로 429(Too Many Requests) 연쇄 발생
- Refresh 정책 오남용으로 색인 처리량 급락
2) 핵심 개념 정리
조회 방식 비교
| 방식 | 장점 | 단점 | 권장 상황 |
|---|---|---|---|
| Scroll API | 대량 순차 조회에 익숙한 모델 | 컨텍스트 유지 비용 큼, 장시간 점유 | 단기 일괄 추출 |
| Search After | 상태 유지 비용 낮음, 확장성 우수 | 정렬 키 설계 필요 | 지속적 대량 처리 |
| PIT + Search After | 일관된 스냅샷 + 확장성 | PIT 수명 관리 필요 | 운영 배치 기본 권장 |
Scroll vs SearchAfter 비교표
| 항목 | Scroll | Search After |
|---|---|---|
| 서버 상태 유지 | 높음(Scroll context) | 낮음(클라이언트 포인터) |
| 장시간 배치 안정성 | 컨텍스트 만료 리스크 | 상대적으로 안정 |
| 정렬 요구사항 | 상대적으로 단순 | 안정적 정렬 키 필수 |
| 리소스 영향 | 메모리/파일 핸들 점유 | 비교적 경량 |
| 현대 운영 권장 | 제한적 | 기본 선택(특히 PIT 조합) |
색인 전략 핵심
- Bulk는 "문서 수"보다 "payload bytes" 기준으로 조절(예: 5~15MB).
- 대량 색인 중
refresh_interval을 늘리거나 일시 비활성화. - 인덱스 롤오버(
max_age,max_docs,max_size)로 세그먼트/샤드 크기 제어.
파이프라인 다이어그램
Mermaid diagram rendering...

출처: Pexels - Black server racks
3) 코드 예시
예시 A: PIT + Search After 조회 DSL
POST /products/_pit?keep_alive=2m
POST /_search
{
"size": 1000,
"pit": {
"id": "${pit_id}",
"keep_alive": "2m"
},
"sort": [
{ "updated_at": "asc" },
{ "_shard_doc": "asc" }
],
"search_after": ["2026-03-03T00:00:00Z", 120341],
"query": {
"range": {
"updated_at": {
"gte": "2026-03-01T00:00:00Z"
}
}
}
}
예시 B: Bulk API NDJSON
POST /products_v3/_bulk
{ "index": { "_id": "1001" } }
{ "product_id": 1001, "name": "Keyboard", "price": 49000, "updated_at": "2026-03-03T01:02:03Z" }
{ "index": { "_id": "1002" } }
{ "product_id": 1002, "name": "Mouse", "price": 29000, "updated_at": "2026-03-03T01:02:04Z" }
예시 C: 소스 DB 증분 조회 SQL
SELECT id, name, price, updated_at
FROM products
WHERE updated_at > :last_synced_at
ORDER BY updated_at, id
LIMIT 1000;
예시 D: 롤오버 정책
PUT _ilm/policy/products-rollover
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "40gb",
"max_age": "7d",
"max_docs": 50000000
}
}
}
}
}
}
4) 실제 장애/운영 시나리오
상황: 야간 재색인에서 Bulk size를 50MB로 설정했고 refresh=true를 유지한 채 8개 워커가 동시에 색인했다. 10분 후 429가 폭증하고 인덱싱 지연이 누적되어 배치 종료 시간이 6배 증가했다.
원인:
- Bulk payload가 샤드 write buffer를 초과했다.
- refresh 강제 수행으로 세그먼트 병합 비용이 급증했다.
- Search After 정렬 키가
updated_at단일 컬럼이라 동시각 문서 누락이 발생했다.
개선:
- Bulk payload를 10MB 전후로 조정하고 동시성 8 -> 3으로 제한.
- 배치 동안
refresh_interval=30s, 종료 후 수동 refresh 1회 수행. - 정렬 키를
(updated_at, _id)또는(updated_at, _shard_doc)로 보완.
5) 설계 체크리스트
- 대량 조회 시 Scroll보다 PIT + Search After를 기본값으로 고려했는가?
- Search After 정렬 키가 유일성과 안정성을 보장하는가?
- Bulk 크기를 bytes 기준으로 제어하는가?
- 429/rejection 발생 시 백오프와 동시성 축소 전략이 있는가?
- refresh 정책을 색인 모드에 맞게 조정하는가?
- 인덱스 롤오버와 샤드 크기 목표치를 운영 지표로 관리하는가?
6) 요약
OpenSearch 배치는 단순 API 호출이 아니라 클러스터 자원 제어 문제다. 조회는 PIT + Search After, 색인은 작은 Bulk + 제어된 refresh + 롤오버 정책이 실무 기본값이다.
7) 다음 편 예고
다음 편에서는 분산 환경 배치 전략을 다룬다. 멀티 인스턴스 중복 실행 방지, 리더 선출, Kubernetes CronJob vs 앱 내부 배치, DB/Redis/Zookeeper 락 비교를 아키텍처 관점으로 정리한다.
참고 링크
- Spring Batch Reference
- Quartz Scheduler Documentation
- PostgreSQL Transaction Isolation
- 블로그: Idempotency Key API 설계