DB Study

4장. 인덱스와 샤드 — 데이터는 어디에 놓이는가

Elasticsearch 운영 사고의 최다 원인은 쿼리가 아니라 샤드 설계 실수입니다. 라우팅, 사이징, 장애 대응까지 — 후회 없는 샤드 설계의 전부를 다룹니다.

중급 ⏱ 약 28분 🎬 애니메이션 3개 선수 지식: 2장 클러스터, 3장 Lucene
🧭
이 장에서 배우는 것

① 인덱스=논리, 샤드=물리 — primary/replica가 노드에 분배되는 규칙 ② 문서가 샤드를 찾아가는 라우팅 공식 ③ 노드 장애 시 replica 승격과 클러스터 색상(GREEN/YELLOW/RED) ④ 샤드 사이징 가이드와 oversharding의 파국

4.1 인덱스는 논리, 샤드는 물리

이 구분이 모든 것의 출발점입니다. 인덱스(index)는 사용자가 보는 논리적 이름(products, logs-2026.07.11)이고, 실제 데이터는 인덱스를 쪼갠 샤드(shard)에 저장됩니다. 각 샤드는 3장에서 본 하나의 완전한 Lucene 인덱스이며, 클러스터의 어느 노드에든 배치될 수 있습니다.

# 인덱스 생성 — 샤드 수는 이 순간 결정된다
PUT /logs-2026.07.11
{
  "settings": {
    "number_of_shards": 3,     # primary 수 — 생성 후 변경 불가!
    "number_of_replicas": 1    # replica 수 — 언제든 동적 변경 가능
  },
  "mappings": {
    "properties": {
      "@timestamp": { "type": "date" },
      "message":    { "type": "text" },
      "level":      { "type": "keyword" }
    }
  }
}

3 primary + replica 1이면 총 6개의 샤드가 생깁니다. 이들이 노드에 어떻게 놓이고, 문서가 어떻게 자기 샤드를 찾아가는지 봅시다.

📄 Index "logs-2026.07.11" (논리) shards=3, replicas=1 Node A Node B Node C P0 P1 P2 R2 R0 R1 규칙: primary와 그 replica는 절대 같은 노드에 놓이지 않는다 (P0↔R0 분리) ✉️ 문서 _id="abc123" hash("abc123") % 3 = 1 → P1으로 색인 P1 → R0(Node B)? 아니, R1(Node C)로 동기 복제 ✅ 색인 응답 시점 = primary와 모든 in-sync replica에 도달 완료
⚠️
primary 샤드 수는 되돌릴 수 없다

라우팅 공식이 % num_primary_shards에 묶여 있어서, primary 수를 바꾸면 기존 문서의 위치 계산이 전부 틀어집니다. 그래서 생성 후 변경 불가이며, 바꾸려면 reindex 또는 split/shrink(4.6)가 필요합니다. 반면 replica 수는 라우팅과 무관하므로 언제든 동적 변경 가능합니다.

4.2 Primary vs Replica — 두 개의 얼굴

PrimaryReplica
색인(쓰기)모든 색인은 primary 먼저primary로부터 동기 복제 수신
검색(읽기)가능가능 — 부하 분산 대상
장애 시죽으면 replica가 승격primary 장애 시 승격 후보
수 변경생성 후 변경 불가동적 변경 가능

replica는 고가용성읽기 처리량이라는 두 역할을 동시에 수행합니다. replica를 늘리면 검색 QPS는 선형으로 늘지만, 모든 사본에 복제해야 하므로 색인 처리량은 떨어집니다. 이 트레이드오프를 활용한 대표 패턴이 있습니다.

# 대량 초기 적재 패턴: replica 0으로 색인 → 끝나면 복원
PUT /logs-init/_settings
{ "index": { "number_of_replicas": 0 } }

# ... bulk 색인 (복제 비용 없이 최대 처리량) ...

PUT /logs-init/_settings
{ "index": { "number_of_replicas": 1 } }   # 완료된 세그먼트를 통째로 복사

4.3 라우팅 공식 — 문서의 주소 계산

정확한 공식은 split을 대비해 한 단계가 더 있습니다.

# 공식 문서에 명시된 라우팅 공식
routing_factor = num_routing_shards / num_primary_shards
shard_num      = (hash(_routing) % num_routing_shards) / routing_factor

# _routing 기본값 = 문서의 _id
# num_routing_shards는 미래의 split을 위한 여유분 (예: 4 primary에 128)
# → 4 → 8 → 16 ... 128까지 split해도 hash 결과가 유지됨

같은 사용자의 문서를 한 샤드에 모으고 싶다면 ?routing=user_42처럼 custom routing을 쓸 수 있습니다. 검색이 전체 샤드 broadcast 대신 1개 샤드만 조회해 빨라지지만, 특정 키에 데이터가 몰리는 skew(쏠림) 위험을 항상 평가해야 합니다.

4.4 노드 장애 — replica 승격의 순간

replica가 있는 이유를 가장 극적으로 보여주는 시나리오입니다. 노드 하나가 갑자기 사라지면 무슨 일이 벌어질까요?

● GREEN — 모든 샤드 정상 ● YELLOW — replica 미할당 ● GREEN 복귀 ✨ Node A Node B Node C P0 R1 P1 R0 R1' 💥 Node B 다운 — P1과 R0 상실! R1→P1 마스터가 replica를 primary로 승격! P1 R0' 살아있는 primary들이 새 replica를 다른 노드에 재복제 ✅ 데이터 유실 0 — 이것이 replica가 존재하는 이유
💡
클러스터 색상 읽는 법

GREEN = 모든 primary·replica 할당됨. YELLOW = primary는 다 있지만 일부 replica 미할당(읽기·쓰기는 정상, 여유가 사라진 상태). RED = 일부 primary가 미할당 — 해당 데이터에 접근 불가, 즉시 대응 필요.

4.5 샤드 사이징 — 10~50GB의 과학

공식 문서의 가이드는 명확합니다: "샤드 크기는 10GB~50GB, 샤드당 문서 2억 개 이하". 그리고 노드 관점에서는 "JVM heap 1GB당 샤드 20개 이하". 이 범위를 벗어나면 양쪽 어디로든 사고가 납니다.

지표위험 임계의미
노드당 샤드 수heap GB × 20 초과메타데이터만으로 heap 압박
클러스터 총 샤드50,000 초과cluster state 비대화
평균 샤드 크기1GB 미만oversharding — 오버헤드가 데이터보다 큼
평균 샤드 크기100GB 초과undersharding — recovery·검색 느려짐
cluster state publish수 초 이상변경마다 클러스터 일시정지

샤드가 너무 많으면 어떤 파국이 오는지, 2장의 cluster state 지식과 연결해서 봅시다.

😀 적정: 600 샤드 / 3노드 샤드당 30GB · state ~30MB · publish <100ms 🤯 60,000 샤드 / 3노드 샤드당 수십 MB — 오버헤드가 데이터를 압도 cluster state ~3GB로 비대 → 변경마다 전 노드 publish 30초+ 인덱스 하나 만들 때마다 클러스터 전체가 30초씩 멈칫 GC pause 빈발 → 마스터 타임아웃 → 재선출 반복 → GREEN 회복 불가 💀 해법: shrink·reindex로 샤드 통합 + rollover를 크기 기준(50GB)으로

시계열 데이터라면 매일 새 인덱스 대신 rollover가 정석입니다. 크기 조건으로 전환하면 샤드가 늘 적정 크기를 유지합니다.

# ILM 정책 — 샤드가 50GB에 도달하면 새 인덱스로 자동 전환
PUT /_ilm/policy/logs-policy
{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": {
            "max_primary_shard_size": "50gb",   # 공식 권장 패턴
            "max_age": "1d"
          }
        }
      }
    }
  }
}

4.6 사후 도구함 — Shrink · Split · Clone · 그리고 watermark

이미 만들어진 인덱스의 샤드 수를 조정해야 한다면:

# shrink 예: 8 shards → 1 shard
PUT /my-index/_settings
{
  "settings": {
    "index.routing.allocation.require._name": "node-1",  # 한 노드로 모으기
    "index.blocks.write": true                             # read-only
  }
}
POST /my-index/_shrink/my-index-shrunk
{ "settings": { "index.number_of_shards": 1 } }
🚨
디스크 watermark — "색인이 갑자기 안 돼요"의 정체

디스크 사용률 85%(low)부터 새 샤드 할당 거부, 90%(high)면 샤드를 다른 노드로 이동, 95%(flood_stage)에 도달하면 인덱스가 자동으로 read-only가 됩니다. 디스크를 확보한 뒤 PUT /_all/_settings {"index.blocks.read_only_allow_delete": null}로 해제해야 합니다.

✍️ 이해도 체크

primary 샤드 수는 인덱스 생성 후 변경할 수 없습니다. 근본적인 이유는?
✅ 라우팅 공식이 primary 샤드 수에 묶여 있어서입니다. 샤드 수를 바꾸면 "이 _id는 어느 샤드에 있는가"의 답이 달라져 기존 문서를 찾을 수 없게 됩니다. 그래서 변경하려면 reindex나 split/shrink 같은 재배치 작업이 필요합니다. replica 수는 라우팅과 무관해 동적 변경이 가능합니다.
클러스터가 YELLOW 상태입니다. 올바른 해석은?
✅ YELLOW = replica 미할당. 읽기·쓰기는 정상 동작하지만, 지금 노드가 하나 더 죽으면 데이터 유실(RED)로 이어질 수 있는 "여유 없음" 상태입니다. primary가 미할당인 RED는 즉시 대응 대상입니다. 노드 수가 replica 배치 제약(같은 노드 금지)을 만족 못 해도 YELLOW가 됩니다 — 단일 노드에 replicas=1 인덱스가 대표적입니다.
📚
원문으로 더 깊이

이 장의 원문 문서: chapters/ch04_index_and_shards.md — 샤드 수 산정 공식, allocation awareness(랙/AZ 분산), oversharding 진단 체크리스트가 담겨 있습니다.