Elasticsearch 아키텍처: 클러스터, 노드, 샤드 이해하기
클러스터가 Yellow 상태로 바뀌었을 때
새벽 2시, PagerDuty 알림이 울렸다.
⚠️ [WARNING] Elasticsearch Cluster Status: YELLOWUnassigned shards: 5Cluster: production-search검색 서비스는 동작하고 있었지만, 한 노드가 다운된 상태였다. 이 상황에서 추가로 노드가 하나 더 죽으면? 데이터 유실 가능성이 있었다.
Elasticsearch 클러스터를 운영하려면 클러스터, 노드, 샤드의 관계를 정확히 이해해야 한다. 이 글에서는 Elasticsearch의 분산 아키텍처를 운영 관점에서 깊이 있게 다룬다.
클러스터(Cluster)란?
클러스터는 하나 이상의 노드(서버)가 모여 형성하는 논리적 그룹이다. 같은 클러스터 이름을 가진 노드들이 자동으로 연결된다.
클러스터 구성 예시
cluster.name: production-searchnode.name: node-1클러스터 상태(Cluster Health)
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 샤드 정상 | 정상 상태 |
| 🟡 Yellow | Primary 정상, 일부 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: coldCoordinating 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 ShardsR0, R1, R2 = Replica Shards (각 Primary의 복제본)| 구분 | Primary Shard | Replica Shard |
|---|---|---|
| 역할 | 원본 데이터, 쓰기 처리 | 복제본, 읽기 분산 |
| 개수 | 인덱스 생성 시 결정 (변경 불가) | 동적 변경 가능 |
| 장애 대응 | 장애 시 Replica가 승격 | Primary 복제 |
| 쿼리 처리 | 읽기/쓰기 모두 | 읽기만 |
샤드 수 결정하기
인덱스 생성 시 샤드 수 지정:
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 좋은 예:
# ❌ 나쁜 예: 1GB 데이터에 100개 샤드 (오버샤딩){ "settings": { "number_of_shards": 100, "number_of_replicas": 1 }}
# ✅ 좋은 예: 데이터 크기에 맞는 샤드 수{ "settings": { "number_of_shards": 1, "number_of_replicas": 1 }}오버샤딩의 문제:
- Master 노드 부하 증가 (클러스터 상태 관리)
- 메모리 사용량 증가 (샤드당 오버헤드)
- 검색 성능 저하 (너무 많은 샤드 조회)
샤드 할당 전략
특정 노드에 샤드 할당:
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" }}'샤드 재배치 확인:
curl -X GET "localhost:9200/_cat/shards/products?v"index shard prirep state docs store ip nodeproducts 0 p STARTED 1000 5mb 10.0.0.1 node-1products 0 r STARTED 1000 5mb 10.0.0.3 node-3products 1 p STARTED 1200 6mb 10.0.0.2 node-2products 1 r STARTED 1200 6mb 10.0.0.1 node-1products 2 p STARTED 800 4mb 10.0.0.3 node-3products 2 r STARTED 800 4mb 10.0.0.2 node-2문서 라우팅
문서가 어떤 샤드에 저장될지는 라우팅 공식으로 결정된다:
shard_num = hash(_routing) % num_primary_shards기본 라우팅 (문서 ID 사용):
# 문서 ID "123"을 기반으로 샤드 결정curl -X PUT "localhost:9200/products/_doc/123" -H "Content-Type: application/json" -d'{ "name": "블루투스 이어폰"}'커스텀 라우팅 (특정 필드 기준):
# 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 미할당자동 복구 과정:
- Master가 Node 2 장애 감지
- Node 2의 Replica(R1)를 Primary로 승격
- 새로운 Replica 생성 시도
- 노드 부족으로 일부 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복구 절차:
- 나머지 Master-eligible 노드가 재선출
- 새 Master가 클러스터 상태 복구
- 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
클러스터 상태 확인
# 전체 상태curl -X GET "localhost:9200/_cluster/health?pretty"
# 상세 상태curl -X GET "localhost:9200/_cluster/health?level=shards&pretty"노드 정보 확인
# 노드 목록curl -X GET "localhost:9200/_cat/nodes?v"
# 노드 상세 정보curl -X GET "localhost:9200/_nodes/stats?pretty"샤드 할당 확인
# 샤드 목록curl -X GET "localhost:9200/_cat/shards?v&s=index"
# 미할당 샤드 원인curl -X GET "localhost:9200/_cluster/allocation/explain?pretty"디스크 사용량 확인
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-3OpenSearch 차이점
OpenSearch는 Elasticsearch 7.10 포크로 대부분의 아키텍처가 동일하다.
주요 차이점
| 구분 | Elasticsearch | OpenSearch |
|---|---|---|
| 노드 역할 설정 | node.roles | 동일 (1.x부터 지원) |
| 클러스터 설정 | cluster.initial_master_nodes | 동일 |
| 보안 기본값 | 8.x부터 기본 활성화 | 기본 활성화 (Security Plugin) |
OpenSearch 클러스터 설정 예시
cluster.name: production-searchnode.name: os-node-1node.roles: [ cluster_manager, data ] # master → cluster_managerdiscovery.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_managermaster_eligible→cluster_manager_eligible
마무리
이 글에서 다룬 핵심 내용:
- 클러스터: 노드의 논리적 그룹, Green/Yellow/Red 상태
- 노드 역할: Master, Data, Coordinating, Ingest 등
- 샤드: Primary/Replica, 데이터 분산의 기본 단위
- 문서 라우팅: 해시 기반 샤드 결정
- 장애 복구: 자동 Failover, Split-Brain 방지
다음 글에서는 Docker와 Kubernetes에서 Elasticsearch를 설치하고 클러스터를 구성하는 방법을 실습한다. 로컬 개발 환경부터 프로덕션 배포까지 단계별로 알아본다.