이 글의 목차

클러스터가 Yellow 상태로 바뀌었을 때

새벽 2시, PagerDuty 알림이 울렸다.

⚠️ [WARNING] Elasticsearch Cluster Status: YELLOW
Unassigned shards: 5
Cluster: production-search

검색 서비스는 동작하고 있었지만, 한 노드가 다운된 상태였다. 이 상황에서 추가로 노드가 하나 더 죽으면? 데이터 유실 가능성이 있었다.

Elasticsearch 클러스터를 운영하려면 클러스터, 노드, 샤드의 관계를 정확히 이해해야 한다. 이 글에서는 Elasticsearch의 분산 아키텍처를 운영 관점에서 깊이 있게 다룬다.

클러스터(Cluster)란?

클러스터는 하나 이상의 노드(서버)가 모여 형성하는 논리적 그룹이다. 같은 클러스터 이름을 가진 노드들이 자동으로 연결된다.

클러스터 구성 예시

elasticsearch.yml
cluster.name: production-search
node.name: node-1

클러스터 상태(Cluster Health)

Terminal window
curl -X GET "localhost:9200/_cluster/health?pretty"
{
"cluster_name": "production-search",
"status": "green",
"number_of_nodes": 3,
"number_of_data_nodes": 3,
"active_primary_shards": 10,
"active_shards": 20,
"unassigned_shards": 0
}

상태별 의미

상태의미조치
🟢 Green모든 Primary/Replica 샤드 정상정상 상태
🟡 YellowPrimary 정상, 일부 Replica 미할당노드 확인, Replica 할당
🔴 Red일부 Primary 샤드 미할당긴급 대응 필요, 데이터 유실 가능

Yellow 상태의 흔한 원인:

  • 노드 1개로 운영 (Replica 할당 불가)
  • 노드 장애로 Replica 재할당 대기 중
  • 디스크 용량 부족 (워터마크 초과)

노드(Node)란?

노드는 클러스터를 구성하는 단일 서버 인스턴스다. 각 노드는 역할에 따라 다른 작업을 수행한다.

노드 역할(Node Roles)

# elasticsearch.yml - 노드 역할 설정
node.roles: [ master, data, ingest ]
역할설명리소스 요구사항
master클러스터 상태 관리, 샤드 할당 결정낮은 CPU/메모리, 안정성 중요
data데이터 저장, 검색/집계 쿼리 실행높은 디스크/메모리/CPU
data_content일반 콘텐츠 데이터 (Hot 데이터)높은 성능 SSD
data_hot최신 데이터, 빈번한 쿼리고성능 SSD
data_warm덜 빈번한 쿼리, 오래된 데이터대용량 HDD
data_cold거의 접근 안 함, 아카이브저비용 스토리지
ingest인덱싱 전 파이프라인 처리CPU 중심
ml머신러닝 작업높은 CPU/메모리
transform데이터 변환 작업CPU/메모리
remote_cluster_client크로스 클러스터 검색네트워크
coordinating요청 라우팅 (역할 없음)CPU/네트워크

Master Node 심층 분석

Master 노드는 클러스터의 두뇌 역할을 한다:

Master Node 역할:
├── 클러스터 상태(Cluster State) 관리
│ ├── 인덱스 메타데이터
│ ├── 노드 정보
│ └── 샤드 할당 정보
├── 인덱스 생성/삭제
├── 노드 추가/제거 처리
└── 샤드 할당 결정

Master 선출 과정:

1. 클러스터 시작 시 모든 master-eligible 노드가 투표
2. 과반수(quorum)를 얻은 노드가 Master로 선출
3. Master 장애 시 나머지 노드들이 재선출

Split-Brain 방지:

# 최소 과반수 설정 (3노드 클러스터)
discovery.zen.minimum_master_nodes: 2
# Elasticsearch 7.x 이상에서는 자동 계산
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]

Data Node 심층 분석

Data 노드는 실제 데이터를 저장하고 쿼리를 처리한다:

Data Node 역할:
├── 샤드(Shard) 저장
│ ├── Primary Shards
│ └── Replica Shards
├── CRUD 작업 수행
├── 검색 쿼리 실행
└── 집계(Aggregation) 처리

Hot-Warm-Cold 아키텍처:

# Hot 노드 (신규 데이터, 빈번한 쿼리)
node.roles: [ data_hot ]
node.attr.data: hot
# Warm 노드 (오래된 데이터, 가끔 쿼리)
node.roles: [ data_warm ]
node.attr.data: warm
# Cold 노드 (아카이브, 드문 접근)
node.roles: [ data_cold ]
node.attr.data: cold

Coordinating Node

모든 노드는 기본적으로 Coordinating 역할을 수행한다. 전용 Coordinating 노드를 두면 대규모 쿼리 처리에 유리하다:

# 전용 Coordinating 노드 (역할 없음 = Coordinating 전용)
node.roles: [ ]

Coordinating 노드 동작:

1. 클라이언트 요청 수신
2. 관련 샤드가 있는 Data 노드로 요청 분배
3. 각 노드의 응답 수집 및 병합
4. 최종 결과 클라이언트에 반환

샤드(Shard)란?

인덱스는 하나 이상의 샤드로 분할된다. 샤드는 Lucene 인덱스 그 자체다.

Primary Shard와 Replica Shard

Index: products (3 Primary, 1 Replica)
┌─────────────────────────────────────────────────────────┐
│ Cluster │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Node 1 │ │ Node 2 │ │ Node 3 │ │
│ │ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────┐ │ │
│ │ │ P0 │ │ │ │ P1 │ │ │ │ P2 │ │ │
│ │ └───────┘ │ │ └───────┘ │ │ └───────┘ │ │
│ │ ┌───────┐ │ │ ┌───────┐ │ │ ┌───────┐ │ │
│ │ │ R1 │ │ │ │ R2 │ │ │ │ R0 │ │ │
│ │ └───────┘ │ │ └───────┘ │ │ └───────┘ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
P0, P1, P2 = Primary Shards
R0, R1, R2 = Replica Shards (각 Primary의 복제본)
구분Primary ShardReplica Shard
역할원본 데이터, 쓰기 처리복제본, 읽기 분산
개수인덱스 생성 시 결정 (변경 불가)동적 변경 가능
장애 대응장애 시 Replica가 승격Primary 복제
쿼리 처리읽기/쓰기 모두읽기만

샤드 수 결정하기

인덱스 생성 시 샤드 수 지정:

Terminal window
curl -X PUT "localhost:9200/products" -H "Content-Type: application/json" -d'
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}'

샤드 수 결정 가이드라인:

고려 사항권장
샤드 당 크기10GB ~ 50GB
노드 당 샤드 수힙 1GB당 20개 이하
총 샤드 수1000개 이하 권장

나쁜 예 vs 좋은 예:

Terminal window
# ❌ 나쁜 예: 1GB 데이터에 100개 샤드 (오버샤딩)
{
"settings": {
"number_of_shards": 100,
"number_of_replicas": 1
}
}
# ✅ 좋은 예: 데이터 크기에 맞는 샤드 수
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}

오버샤딩의 문제:

  • Master 노드 부하 증가 (클러스터 상태 관리)
  • 메모리 사용량 증가 (샤드당 오버헤드)
  • 검색 성능 저하 (너무 많은 샤드 조회)

샤드 할당 전략

특정 노드에 샤드 할당:

Terminal window
curl -X PUT "localhost:9200/logs-2024" -H "Content-Type: application/json" -d'
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"index.routing.allocation.require.data": "hot"
}
}'

샤드 재배치 확인:

Terminal window
curl -X GET "localhost:9200/_cat/shards/products?v"
index shard prirep state docs store ip node
products 0 p STARTED 1000 5mb 10.0.0.1 node-1
products 0 r STARTED 1000 5mb 10.0.0.3 node-3
products 1 p STARTED 1200 6mb 10.0.0.2 node-2
products 1 r STARTED 1200 6mb 10.0.0.1 node-1
products 2 p STARTED 800 4mb 10.0.0.3 node-3
products 2 r STARTED 800 4mb 10.0.0.2 node-2

문서 라우팅

문서가 어떤 샤드에 저장될지는 라우팅 공식으로 결정된다:

shard_num = hash(_routing) % num_primary_shards

기본 라우팅 (문서 ID 사용):

Terminal window
# 문서 ID "123"을 기반으로 샤드 결정
curl -X PUT "localhost:9200/products/_doc/123" -H "Content-Type: application/json" -d'
{
"name": "블루투스 이어폰"
}'

커스텀 라우팅 (특정 필드 기준):

Terminal window
# user_id 기준으로 라우팅 (같은 사용자의 문서는 같은 샤드에)
curl -X PUT "localhost:9200/orders/_doc/1?routing=user_123" -H "Content-Type: application/json" -d'
{
"user_id": "user_123",
"product": "블루투스 이어폰",
"amount": 50000
}'

커스텀 라우팅의 장점:

  • 관련 문서가 같은 샤드에 위치
  • 특정 쿼리 시 단일 샤드만 조회 (성능 향상)

커스텀 라우팅의 주의점:

  • Hot Spot 발생 가능 (특정 샤드에 데이터 집중)
  • 검색 시 라우팅 값 필요

장애 복구 시나리오

시나리오 1: Data 노드 1개 장애

Before (Green):
Node 1: [P0] [R1]
Node 2: [P1] [R2]
Node 3: [P2] [R0]
Node 2 Down (Yellow):
Node 1: [P0] [R1] [P1*] ← R1이 P1로 승격
Node 3: [P2] [R0]
* 5개 샤드 중 R1, R2 미할당

자동 복구 과정:

  1. Master가 Node 2 장애 감지
  2. Node 2의 Replica(R1)를 Primary로 승격
  3. 새로운 Replica 생성 시도
  4. 노드 부족으로 일부 Replica 미할당 → Yellow

시나리오 2: Master 노드 장애

Before:
Node 1 (Master): [P0]
Node 2: [P1]
Node 3: [P2]
Node 1 Down:
Node 2 (New Master): [P1] ← 새로운 Master 선출
Node 3: [P2]
* P0 미할당 → Red

복구 절차:

  1. 나머지 Master-eligible 노드가 재선출
  2. 새 Master가 클러스터 상태 복구
  3. P0이 있던 노드의 데이터 복구 필요

시나리오 3: 네트워크 파티션 (Split-Brain)

정상 상태:
[Master] ─── [Node 2] ─── [Node 3]
네트워크 분리:
[Master] │ [Node 2] ─── [Node 3]
│ └── 새로운 Master 선출 시도?

Split-Brain 방지 메커니즘:

  • 최소 과반수(quorum) 없이는 Master 선출 불가
  • 3노드 클러스터: 최소 2노드 필요
  • 5노드 클러스터: 최소 3노드 필요

프로덕션 클러스터 구성 예시

소규모 (개발/테스트)

# 단일 노드 (개발용)
node.roles: [ master, data ]
discovery.type: single-node

중규모 (일반 프로덕션)

3-Node Cluster:
┌─────────────────────────────────────────────┐
│ Node 1 Node 2 Node 3 │
│ [M, D] [M, D] [M, D] │
│ │
│ M = Master-eligible │
│ D = Data │
└─────────────────────────────────────────────┘
# 모든 노드에 동일 설정
node.roles: [ master, data ]
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
discovery.seed_hosts: ["10.0.0.1", "10.0.0.2", "10.0.0.3"]

대규모 (엔터프라이즈)

10+ Node Cluster:
┌───────────────────────────────────────────────────────┐
│ Dedicated Masters (3) │
│ [M1] [M2] [M3] │
│ │
│ Coordinating Nodes (2) ← 로드밸런서 연결 │
│ [C1] [C2] │
│ │
│ Data Hot Nodes (3) ← 최신 데이터 │
│ [D-H1] [D-H2] [D-H3] │
│ │
│ Data Warm Nodes (2) ← 오래된 데이터 │
│ [D-W1] [D-W2] │
│ │
│ Ingest Nodes (2) ← 파이프라인 처리 │
│ [I1] [I2] │
└───────────────────────────────────────────────────────┘

클러스터 모니터링 필수 API

클러스터 상태 확인

Terminal window
# 전체 상태
curl -X GET "localhost:9200/_cluster/health?pretty"
# 상세 상태
curl -X GET "localhost:9200/_cluster/health?level=shards&pretty"

노드 정보 확인

Terminal window
# 노드 목록
curl -X GET "localhost:9200/_cat/nodes?v"
# 노드 상세 정보
curl -X GET "localhost:9200/_nodes/stats?pretty"

샤드 할당 확인

Terminal window
# 샤드 목록
curl -X GET "localhost:9200/_cat/shards?v&s=index"
# 미할당 샤드 원인
curl -X GET "localhost:9200/_cluster/allocation/explain?pretty"

디스크 사용량 확인

Terminal window
curl -X GET "localhost:9200/_cat/allocation?v"
shards disk.indices disk.used disk.avail disk.total disk.percent host node
10 50gb 60gb 40gb 100gb 60 10.0.0.1 node-1
10 45gb 55gb 45gb 100gb 55 10.0.0.2 node-2
10 48gb 58gb 42gb 100gb 58 10.0.0.3 node-3

OpenSearch 차이점

OpenSearch는 Elasticsearch 7.10 포크로 대부분의 아키텍처가 동일하다.

주요 차이점

구분ElasticsearchOpenSearch
노드 역할 설정node.roles동일 (1.x부터 지원)
클러스터 설정cluster.initial_master_nodes동일
보안 기본값8.x부터 기본 활성화기본 활성화 (Security Plugin)

OpenSearch 클러스터 설정 예시

opensearch.yml
cluster.name: production-search
node.name: os-node-1
node.roles: [ cluster_manager, data ] # master → cluster_manager
discovery.seed_hosts: ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
cluster.initial_cluster_manager_nodes: ["os-node-1", "os-node-2", "os-node-3"]

용어 변경:

  • master → cluster_manager
  • master_eligible → cluster_manager_eligible

마무리

이 글에서 다룬 핵심 내용:

  1. 클러스터: 노드의 논리적 그룹, Green/Yellow/Red 상태
  2. 노드 역할: Master, Data, Coordinating, Ingest 등
  3. 샤드: Primary/Replica, 데이터 분산의 기본 단위
  4. 문서 라우팅: 해시 기반 샤드 결정
  5. 장애 복구: 자동 Failover, Split-Brain 방지

다음 글에서는 Docker와 Kubernetes에서 Elasticsearch를 설치하고 클러스터를 구성하는 방법을 실습한다. 로컬 개발 환경부터 프로덕션 배포까지 단계별로 알아본다.

참고 자료

NEXT IN SERIES

Elasticsearch CRUD: 문서 색인과 Bulk API 최적화

다음 편 읽기 →
← 전체 글로 돌아가기