대용량 인덱스를 효율적으로 재구축하는 방법을 안내합니다.

소요 시간: 약 30-60분 (데이터 크기에 따라 수 시간 소요 가능)

이 가이드의 범위

다루는 내용: _reindex API, Snapshot/Restore, Logstash 비교, 대용량 처리 전략, 성능 최적화

다루지 않는 내용: 단순 매핑 변경은 매핑 마이그레이션을, 클러스터 확장은 클러스터 확장을 참조하세요.

TL;DR
  • _reindex API: 가장 간단, 같은 클러스터 내 재구축에 적합
  • Snapshot/Restore: 클러스터 간 이동, 대용량 데이터에 적합
  • Logstash: 변환이 복잡하거나 외부 소스와 연동할 때 적합
  • 성능 최적화: refresh_interval: -1, replica 0, sliced scroll 활용

시작하기 전에#

다음 조건을 확인하세요:

항목요구사항확인 방법
Elasticsearch 버전7.x 이상curl -X GET "localhost:9200"
클러스터 상태greencurl -X GET "localhost:9200/_cluster/health"
디스크 공간인덱스 크기의 2배 이상 여유curl -X GET "localhost:9200/_cat/allocation?v"
인덱스 권한읽기 + 쓰기 + 관리아래 명령어로 테스트
# 인덱스 크기 확인
curl -X GET "localhost:9200/_cat/indices/products?v&h=index,docs.count,store.size"

# 디스크 여유 공간 확인
curl -X GET "localhost:9200/_cat/allocation?v&h=node,disk.avail,disk.total,disk.percent"
주의
인덱스 재구축 중에는 디스크 I/O와 CPU 사용량이 크게 증가합니다. 운영 시간을 피해 진행하거나, 리소스 제한을 설정하세요.

증상#

다음과 같은 상황에서 인덱스 재구축이 필요합니다:

  • 샤드 수를 변경해야 할 때 (생성 후 변경 불가)
  • 매핑을 대규모로 변경해야 할 때
  • 오래된 데이터를 정리하거나 구조를 개편할 때
  • 인덱스 성능이 지속적으로 저하될 때
# 샤드 수 확인 (변경 불가능한 설정)
curl -X GET "localhost:9200/products/_settings?pretty&filter_path=**.number_of_shards"

# 응답: "number_of_shards": "1" ← 변경하려면 재구축 필요

방법 선택 가이드#

flowchart TD
    A["인덱스 재구축 필요"] --> B{같은 클러스터 내?}
    B -->|Yes| C{데이터 변환<br>필요?}
    B -->|No| D{데이터 크기?}
    C -->|간단한 변환| E["_reindex API<br>+ Script"]
    C -->|복잡한 변환| F["Logstash<br>Pipeline"]
    C -->|변환 없음| G["_reindex API"]
    D -->|100GB 이하| H["_reindex API<br>+ Remote"]
    D -->|100GB 이상| I["Snapshot /<br>Restore"]

방법별 비교#

항목_reindex APISnapshot/RestoreLogstash
난이도쉬움보통보통
속도빠름매우 빠름보통
데이터 변환Script (간단)불가매우 유연
클러스터 간remote 옵션가능가능
리소스 사용높음낮음보통
적합한 크기~수백GB제한 없음~수백GB

방법 1: _reindex API#

1.1 새 인덱스 준비#

# 변경된 설정으로 새 인덱스 생성
curl -X PUT "localhost:9200/products-v2" -H 'Content-Type: application/json' -d'
{
  "settings": {
    "number_of_shards": 5,
    "number_of_replicas": 0,
    "refresh_interval": "-1"
  },
  "mappings": {
    "properties": {
      "name": { "type": "text", "analyzer": "standard" },
      "price": { "type": "double" },
      "category": { "type": "keyword" },
      "created_at": { "type": "date" }
    }
  }
}'

1.2 기본 Reindex#

curl -X POST "localhost:9200/_reindex?wait_for_completion=false" -H 'Content-Type: application/json' -d'
{
  "source": {
    "index": "products-v1",
    "size": 5000
  },
  "dest": {
    "index": "products-v2"
  }
}'

# 응답: {"task": "node-1:54321"}

1.3 Sliced Scroll로 병렬 처리#

대용량 인덱스에서는 sliced scroll을 사용하여 병렬로 Reindex합니다:

# 자동 슬라이싱 (샤드 수에 맞춰 자동 분할)
curl -X POST "localhost:9200/_reindex?wait_for_completion=false" -H 'Content-Type: application/json' -d'
{
  "source": {
    "index": "products-v1",
    "size": 5000,
    "slice": {
      "id": 0,
      "max": 5
    }
  },
  "dest": {
    "index": "products-v2"
  }
}'

# 또는 자동 슬라이싱 (ES 7.x+)
curl -X POST "localhost:9200/_reindex?slices=auto&wait_for_completion=false" -H 'Content-Type: application/json' -d'
{
  "source": { "index": "products-v1" },
  "dest": { "index": "products-v2" }
}'

1.4 진행 상황 모니터링#

# Task 상태 확인
curl -X GET "localhost:9200/_tasks/node-1:54321?pretty"

# 전체 Reindex 작업 목록
curl -X GET "localhost:9200/_tasks?actions=*reindex&detailed&pretty"

# 작업 취소 (필요한 경우)
curl -X POST "localhost:9200/_tasks/node-1:54321/_cancel"

방법 2: Snapshot/Restore#

대용량 데이터나 클러스터 간 이동 시 유용합니다.

2.1 Repository 등록#

# 공유 파일 시스템 Repository
curl -X PUT "localhost:9200/_snapshot/my_backup" -H 'Content-Type: application/json' -d'
{
  "type": "fs",
  "settings": {
    "location": "/mnt/backups/elasticsearch"
  }
}'

2.2 Snapshot 생성#

# 특정 인덱스만 스냅샷
curl -X PUT "localhost:9200/_snapshot/my_backup/products_snapshot?wait_for_completion=true" -H 'Content-Type: application/json' -d'
{
  "indices": "products-v1",
  "ignore_unavailable": true,
  "include_global_state": false
}'

2.3 다른 이름으로 Restore#

# 이름을 변경하여 복원
curl -X POST "localhost:9200/_snapshot/my_backup/products_snapshot/_restore" -H 'Content-Type: application/json' -d'
{
  "indices": "products-v1",
  "rename_pattern": "products-v1",
  "rename_replacement": "products-v2",
  "index_settings": {
    "index.number_of_replicas": 0
  }
}'
참고
Snapshot/Restore는 매핑이나 설정을 변경할 수 없습니다. 구조 변경이 필요하면 Restore 후 _reindex를 추가로 수행하세요.

방법 3: Logstash#

복잡한 데이터 변환이 필요할 때 사용합니다.

3.1 Logstash Pipeline 설정#

# logstash-reindex.conf
input {
  elasticsearch {
    hosts => ["localhost:9200"]
    index => "products-v1"
    query => '{ "query": { "match_all": {} } }'
    size => 5000
    scroll => "5m"
    docinfo => true
  }
}

filter {
  # 복잡한 변환 로직
  mutate {
    convert => { "price" => "float" }
    rename => { "old_field" => "new_field" }
    remove_field => ["unwanted_field"]
  }
}

output {
  elasticsearch {
    hosts => ["localhost:9200"]
    index => "products-v2"
    document_id => "%{[@metadata][_id]}"
    action => "index"
  }
}

3.2 실행#

bin/logstash -f logstash-reindex.conf

성능 최적화#

대상 인덱스 설정#

Reindex 전에 대상 인덱스의 설정을 최적화합니다:

# 1. Refresh 비활성화
curl -X PUT "localhost:9200/products-v2/_settings" -H 'Content-Type: application/json' -d'
{ "refresh_interval": "-1" }'

# 2. Replica 0으로 설정
curl -X PUT "localhost:9200/products-v2/_settings" -H 'Content-Type: application/json' -d'
{ "number_of_replicas": 0 }'

# 3. Translog 설정 조정 (선택)
curl -X PUT "localhost:9200/products-v2/_settings" -H 'Content-Type: application/json' -d'
{
  "index.translog.durability": "async",
  "index.translog.sync_interval": "30s"
}'
주의
translog.durability: async는 데이터 유실 위험이 있습니다. 재구축 중에만 사용하고, 완료 후 반드시 request로 복구하세요.

완료 후 설정 복구#

# Refresh 복구
curl -X PUT "localhost:9200/products-v2/_settings" -H 'Content-Type: application/json' -d'
{ "refresh_interval": "1s" }'

# Replica 복구
curl -X PUT "localhost:9200/products-v2/_settings" -H 'Content-Type: application/json' -d'
{ "number_of_replicas": 1 }'

# Translog 복구
curl -X PUT "localhost:9200/products-v2/_settings" -H 'Content-Type: application/json' -d'
{ "index.translog.durability": "request" }'

# 수동 Refresh
curl -X POST "localhost:9200/products-v2/_refresh"

# Force Merge (선택: 세그먼트 최적화)
curl -X POST "localhost:9200/products-v2/_forcemerge?max_num_segments=1"

체크리스트#

인덱스 재구축 시 확인사항:

  • 디스크 공간이 충분한가? - 인덱스 크기의 2배 이상 여유
  • 클러스터 상태가 green인가? - yellow이면 기존 문제 먼저 해결
  • 재구축 방법을 선택했는가? - _reindex / Snapshot / Logstash
  • 성능 최적화를 적용했는가? - refresh, replica, translog 설정
  • 문서 수가 일치하는가? - 원본과 새 인덱스 비교
  • 설정을 복구했는가? - refresh_interval, replica, translog

성공 확인#

재구축이 성공했는지 다음 방법으로 확인하세요:

  1. 문서 수 비교: 원본과 동일한지 확인

    echo "원본:" && curl -s "localhost:9200/products-v1/_count" | python3 -m json.tool
    echo "새 인덱스:" && curl -s "localhost:9200/products-v2/_count" | python3 -m json.tool
  2. 클러스터 상태 확인: 재구축 후 green 상태인지 확인

    curl -X GET "localhost:9200/_cluster/health?pretty"
  3. 샘플 쿼리 테스트: 주요 쿼리가 정상 동작하는지 확인

    curl -X GET "localhost:9200/products-v2/_search?pretty" -H 'Content-Type: application/json' -d'
    {
      "query": { "match_all": {} },
      "size": 5
    }'
성공 기준
  • 문서 수가 원본과 동일
  • 클러스터 상태 green
  • 주요 쿼리가 정상 동작
  • 설정(refresh, replica, translog)이 복구됨

자주 발생하는 오류#

“circuit_breaking_exception” during reindex#

{
  "error": {
    "type": "circuit_breaking_exception",
    "reason": "Data too large"
  }
}

원인: Reindex 중 메모리 부족

해결: source.size 값을 줄이세요:

"source": { "index": "products-v1", "size": 1000 }

Task가 사라짐#

원인: 노드 재시작으로 Task가 유실됨

해결: .tasks 인덱스에서 완료된 Task를 확인하세요:

curl -X GET "localhost:9200/.tasks/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": { "match": { "task.action": "indices:data/write/reindex" } }
}'

버전 충돌#

{
  "failures": [{
    "cause": { "type": "version_conflict_engine_exception" }
  }]
}

원인: 소스 인덱스에 동시에 쓰기가 발생

해결: conflicts: proceed로 충돌을 무시하거나, 소스 인덱스를 read-only로 설정하세요:

# 충돌 무시
"conflicts": "proceed"

# 또는 소스 인덱스를 read-only로 설정
curl -X PUT "localhost:9200/products-v1/_settings" -H 'Content-Type: application/json' -d'
{ "index.blocks.write": true }'

관련 문서#