본문으로 건너뛰기
김신건의 로그

[Search] ES Indexing: bulk, refresh, ingest pipeline

· 수정 · 📖 약 1분 · 379자/단어 #elasticsearch #indexing #bulk #ingest
ES indexing, bulk API, _bulk, ingest pipeline, refresh, force_merge, _reindex, update by query

정의

Indexing = JSON 문서를 역색인에 등록 하는 행위. Lucene segment 빌드 + refresh + flush 의 흐름.

단일 인덱싱

PUT /products/_doc/sku-1
{ "name": "Keyboard", "price": 100 }
동작결과
ID 명시upsert (있으면 update)
ID 자동POST /products/_doc
Create-onlyPUT /products/_create/sku-1 (있으면 409)

Bulk API (실전)

POST /_bulk
{ "index": { "_index": "products", "_id": "sku-1" } }
{ "name": "Keyboard", "price": 100 }
{ "index": { "_index": "products", "_id": "sku-2" } }
{ "name": "Mouse", "price": 50 }
{ "delete": { "_index": "products", "_id": "sku-old" } }
{ "update": { "_index": "products", "_id": "sku-1" } }
{ "doc": { "price": 95 } }

IMPORTANT

단일 인덱싱 보다 100배 빠름. 5-15 MB / batch, 1000-10000 docs 권장.

Bulk 흐름

sequenceDiagram
    autonumber
    Client->>Coord[Coordinator Node]: _bulk
    Coord->>Coord: 각 doc 의 shard 계산
    par 각 shard 병렬
        Coord->>P1[Primary Shard 1]: 부분
        Coord->>P2[Primary Shard 2]: 부분
    end
    P1->>R1[Replica 1]: 복제
    P2->>R2[Replica 2]: 복제
    P1-->>Coord: ack
    P2-->>Coord: ack
    Coord-->>Client: 결과 (각 item 의 status)

대량 인덱싱 튜닝

PUT /products/_settings
{
  "index.refresh_interval": "-1",      # refresh 끄기
  "index.number_of_replicas": 0,       # replica 끄기
  "index.translog.durability": "async" # WAL 비동기
}

# 인덱싱...

PUT /products/_settings
{
  "index.refresh_interval": "1s",
  "index.number_of_replicas": 1
}
POST /products/_refresh
POST /products/_forcemerge?max_num_segments=1

수 배 빠름. 단 진행 중 검색 결과 stale + 실패 시 손실 위험 있어 별도 인덱스 에 작업 후 alias swap.

Index 생성 + Mapping

PUT /products
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "analysis": { ... }
  },
  "mappings": {
    "properties": {
      "name":    { "type": "text", "analyzer": "korean" },
      "price":   { "type": "double" },
      "tags":    { "type": "keyword" },
      "created": { "type": "date" },
      "vector":  { "type": "dense_vector", "dims": 384 }
    }
  }
}

자세한 mapping 은 elasticsearch-mapping.

Ingest Pipeline (Logstash 대안)

PUT /_ingest/pipeline/products-pipeline
{
  "processors": [
    { "lowercase": { "field": "tags" } },
    { "set": { "field": "ingested_at", "value": "{{_ingest.timestamp}}" } },
    { "remove": { "field": "raw" } },
    { "grok": { "field": "log", "patterns": ["%{IP:client} %{WORD:method}"] } }
  ]
}

POST /_bulk?pipeline=products-pipeline
...

문서가 ES 에 들어오기 전 변환. Logstash 같은 별도 프로세스 없이 ES 내장. 대부분의 경량 변환 은 ingest pipeline 으로.

Update vs Reindex

동작의미
Update문서 재인덱싱 (Lucene 의 immutable 때문). 옛 문서 삭제 + 새 문서 등록
Update by Query쿼리 매칭 모두 update
Reindex다른 인덱스로 copy + 변환
POST /_update_by_query?conflicts=proceed
{
  "query": { "term": { "in_stock": true } },
  "script": { "source": "ctx._source.price *= 1.1" }
}

POST /_reindex
{
  "source": { "index": "products-old" },
  "dest":   { "index": "products-new" },
  "script": { "source": "ctx._source.version = 2" }
}

Alias + Zero-Downtime Reindex

sequenceDiagram
    Note over App: alias = products
    App->>products-v1: search
    Admin->>products-v2: 새 mapping 생성
    Admin->>products-v2: reindex from v1
    Admin->>Alias: atomic switch (v1 → v2)
    App->>products-v2: search (zero-downtime)
    Admin->>products-v1: delete
POST /_aliases
{
  "actions": [
    { "remove": { "index": "products-v1", "alias": "products" } },
    { "add":    { "index": "products-v2", "alias": "products" } }
  ]
}

IMPORTANT

mapping 변경대부분 불가능 (immutable). 새 인덱스 + reindex + alias swap 이 정통. 자세한 무중단 패턴은 Zero Downtime Deployment.

흔한 함정

WARNING

  1. Bulk size 너무 큼 = EsRejectedExecutionException (스레드 큐 초과). 5-15MB 권장.
  2. refresh=true 매번 = 검색 즉시 가능하지만 write throughput 폭락.
  3. 수동 force_merge 자주 = I/O 폭증. 대량 인덱싱 후 1회 만.
  4. mapping 의 dynamic 폭증 = 수만 개 field → 메모리 폭발. dynamic: strict 또는 명시적 mapping.

관련 위키

이 글의 용어 (6개)
[DevOps] 무중단 배포: Blue-Green, Canary, Rolling, Expand-Contractdevops
정의 무중단 배포 (Zero-Downtime Deployment) 는 서비스 가동을 멈추지 않고 새 버전을 배포하는 일련의 전략. 핵심 도전 4가지: 1. 트래픽 전환: 어떻게 …
[Distributed] Kafka: 분산 로그, partition, consumer groupdistributed-systems
정의 Apache Kafka = 분산 commit log. 고처리량 (수백만 msg/s), 영속, 수평 확장. event-driven 아키텍처 의 de facto. 핵심 개념: …
[Pattern] Outbox Pattern: DB + 메시지의 원자성distributed-systems
정의 Outbox Pattern = DB 변경 + 메시지 발행 의 원자성 보장. 이중 쓰기 (dual write) 문제 의 표준 해결. 문제: Dual Write | 시나리오 |…
[Search] ElasticSearch 기본 원리: Inverted Index, Segment, Lucenesearch
정의 ES 의 모든 동작 은 Lucene 의 inverted index 위에 얹혀있다. 문서 단위 저장 + term 단위 검색 의 분리. Inverted Index (역색인) 위…
[Search] ES 한글 인덱싱: nori, mecab, 형태소 분석search
정의 한국어 는 교착어 + 띄어쓰기 모호 + 형태소 변화 때문에 공백 단순 분리 로는 검색 불가. 형태소 분석 (morphological analysis) 필수. 한글이 어려운 …
[Search] ES Mapping: field type, dynamic, multi-fieldsearch
정의 Mapping = ES 의 schema. 각 필드의 type + analyzer + indexing 옵션. 대부분 immutable (재인덱싱 필요). Field Type …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기