3 min read

Part 8. OpenSearch/Elasticsearch 배치 전략: Scroll, Search After, PIT, Bulk, Rollover

대량 조회와 대량 색인을 함께 고려한 OpenSearch 배치 설계 기준과 운영 트레이드오프를 실무 관점에서 정리한다.

Series: Spring Boot 배치 전략 완전 정복

12편 구성. 현재 8편을 보고 있습니다.

썸네일 - 모니터링 대시보드
썸네일 - 모니터링 대시보드

출처: 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 비교표

항목ScrollSearch 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 단일 컬럼이라 동시각 문서 누락이 발생했다.

개선:

  1. Bulk payload를 10MB 전후로 조정하고 동시성 8 -> 3으로 제한.
  2. 배치 동안 refresh_interval=30s, 종료 후 수동 refresh 1회 수행.
  3. 정렬 키를 (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 락 비교를 아키텍처 관점으로 정리한다.

참고 링크

시리즈 네비게이션

댓글