Elasticsearch 샤딩 아키텍처
인덱스를 샤드로 쪼개고 레플리카로 복제하는 두 결정이 Elasticsearch의 확장성과 가용성을 어떻게 만드는지 정리합니다.
Contents
인덱스를 샤드로 쪼개고 그 샤드를 레플리카로 복제하는 두 결정이 Elasticsearch의 확장성과 가용성을 함께 만듭니다.
인덱스를 샤드로 나누는 이유
Elasticsearch에서 인덱스(Index)는 관계형 데이터베이스의 테이블에 대응하는 문서들의 논리적 집합입니다. 데이터가 테라바이트(TB)나 페타바이트(PB) 규모로 늘면 단일 서버의 스토리지와 입출력(I/O) 대역폭을 넘어섭니다. 그래서 하나의 인덱스를 여러 독립 조각인 샤드(Shard)로 나눠 클러스터 전체에 분산합니다.
샤드는 데이터를 분산하는 기본 단위이면서, 그 자체로 완결된 Apache Lucene 인덱스 인스턴스입니다. 물리적으로 이 Lucene 인덱스는 다시 여러 세그먼트(Segment)로 구성됩니다. 문서가 색인될 때마다 새 세그먼트가 디스크에 쓰이고, 백그라운드 병합(Merge)이 작은 세그먼트를 큰 세그먼트로 합칩니다.
이 구조에는 물리적 상한이 있습니다. 단일 Lucene 인덱스는 내부 정수 식별자 한계 때문에 최대 2,147,483,519개 문서까지만 담을 수 있습니다(Lucene 인덱스의 구조적 상한). 따라서 데이터를 한 샤드에 몰지 않고 여러 샤드로 나누는 일이 용량 설계의 출발점입니다.
프라이머리 샤드와 레플리카 샤드
분산 시스템에서는 노드 장애가 언제든 일어납니다. Elasticsearch는 원본인 프라이머리 샤드(Primary Shard)를 그대로 복제한 레플리카 샤드(Replica Shard)를 만들어 가용성을 확보합니다. 레플리카는 같은 물리 노드에 배치되지 않도록 스케줄링되므로, 한 서버가 죽어도 다른 노드의 레플리카가 프라이머리로 승격됩니다.
레플리카는 장애 대비 복제본이면서 읽기 부하도 나눠 받습니다. 검색 같은 읽기 요청은 프라이머리와 레플리카에서 병렬로 처리됩니다. 그래서 레플리카 수를 늘리면 동시 검색 처리량이 함께 늘어납니다.
| 항목 | 프라이머리 샤드 | 레플리카 샤드 |
|---|---|---|
| 맡는 일 | 원본 데이터 저장 | 복제본, 읽기 분산 |
| 개수 변경 | 인덱스 생성 후 고정 | 운영 중 동적 조정 |
| 쓰기 | 직접 수행 | 프라이머리에서 복제 |
| 배치 규칙 | 노드에 분산 | 프라이머리와 다른 노드 |
표의 핵심은 두 샤드의 변경 가능성이 다르다는 점입니다. 레플리카 수는 읽기 부하에 맞춰 운영 중 자유롭게 늘리거나 줄일 수 있습니다. 반면 프라이머리 개수는 인덱스를 만들 때 고정되며, 그 이유는 다음 절의 라우팅에 있습니다.
문서가 샤드를 찾는 방법
특정 문서가 어느 샤드에 저장될지는 라우팅(Routing) 공식이 결정합니다. 프라이머리 개수가 해싱 함수와 결합되어 고정되는 이유가 여기서 드러납니다.
shard = hash(routing) % number_of_primary_shardsrouting 값은 기본적으로 문서의 _id입니다. 이를 해시한 뒤 프라이머리 개수로 나눈 나머지가 목적지 샤드입니다. 나머지 연산이 프라이머리 개수에 직접 의존하므로, 개수가 바뀌면 기존 문서의 목적지가 어긋납니다. 그래서 프라이머리 개수는 생성 후 변경할 수 없습니다.
검색은 반대로 동작합니다. 코디네이팅 노드가 관련 샤드에 질의를 분산(Scatter)하고, 각 샤드의 부분 결과를 병합(Gather)해 최종 응답을 만듭니다.
위 그림은 검색 한 건이 여러 샤드로 흩어졌다가 한 노드에서 다시 합쳐지는 흐름을 보여줍니다. 인덱싱은 라우팅으로 한 샤드만 고르고, 검색은 여러 샤드를 모읍니다. 이 비대칭이 샤드 설계가 읽기와 쓰기 양쪽에 영향을 주는 이유입니다.
쓰기가 복제되는 순서
읽기와 달리 쓰기는 항상 프라이머리를 먼저 거칩니다. 코디네이팅 노드가 라우팅으로 목적지 프라이머리를 고르면, 프라이머리가 먼저 자기 샤드에 색인합니다. 그다음 같은 데이터를 레플리카로 복제하고, 복제가 확인되면 클라이언트에 성공을 응답합니다.
위 그림은 쓰기가 프라이머리에서 레플리카 순서로 전파되는 경로입니다(Elasticsearch 표준 복제 동작). 그래서 레플리카가 많으면 한 번의 쓰기가 더 많은 노드로 퍼져 쓰기 비용이 늘어납니다. 읽기 처리량과 쓰기 비용이 레플리카 개수를 사이에 두고 맞물립니다.
노드 역할 분리
클러스터(Cluster)는 여러 노드를 하나의 논리 시스템으로 묶은 단위입니다. 각 노드는 부여된 역할에 따라 다른 일을 맡습니다.
| 노드 역할 | 맡는 일 |
|---|---|
| 마스터 노드 | 클러스터 상태와 메타데이터, 샤드 할당·재배치 관리 |
| 데이터 노드 | 문서와 역색인 저장, 검색·집계·인덱싱 처리 |
| 인제스트 노드 | 색인 직전 변환·보강·필터링 전처리 |
| 코디네이팅 노드 | 요청 분산과 응답 병합, 로드 밸런서 |
마스터 노드는 샤드 할당과 클러스터 상태를 통제하므로 데이터 입출력 부하에서 격리하는 편이 안정적입니다. 데이터 노드는 검색과 집계처럼 CPU와 메모리를 많이 쓰는 작업을 떠맡습니다. 작은 환경에서는 한 노드가 여러 역할을 겸할 수 있지만, 운영 환경에서는 마스터와 데이터 부하를 분리하는 편이 장애에 강합니다.
샤드 개수를 정하는 기준
샤드는 많아도 적어도 문제가 됩니다. 너무 적으면 단일 샤드가 비대해지고, 너무 많으면 관리 비용이 불어납니다.
엔터프라이즈 환경의 산업 표준은 단일 샤드 크기를 수십 GB(대략 30~50GB)로 유지하는 것입니다(Elastic 권고 기준). 예를 들어 300GB 데이터를 10개 데이터 노드에 둘 때, 프라이머리를 10개로 잡으면 노드마다 30GB 샤드 하나가 고르게 분산됩니다.
이 결정은 인덱스를 만들 때 두 설정으로 표현됩니다. number_of_shards는 프라이머리 개수라 생성 후 바꿀 수 없고, number_of_replicas는 운영 중 갱신할 수 있습니다.
PUT /logs-2026-06
{
"settings": {
"number_of_shards": 10,
"number_of_replicas": 1
}
}레플리카를 1로 두면 프라이머리 10개에 레플리카 10개가 더해져 전체 20개 샤드가 됩니다. 레플리카 값은 나중에 PUT /logs-2026-06/_settings로 올리거나 내릴 수 있습니다(Elasticsearch 표준 인덱스 설정).
반대 방향의 실패가 샤드 폭발(Shard Explosion)입니다. 데이터가 적은데도 매일 인덱스를 새로 만들어 방치하면 잉여 샤드가 수백 개로 불어납니다. 각 샤드는 독립적인 Lucene 인스턴스라 노드의 CPU, JVM(Java Virtual Machine) 힙, 디스크 I/O를 갉아먹습니다. 그 결과 클러스터 상태 갱신과 노드 재시작이 느려집니다.
시계열 로그처럼 계속 쌓이는 데이터는 인덱스 수명 주기 관리(ILM, Index Lifecycle Management)로 다룹니다. 일정 크기나 기간마다 인덱스를 새로 굴리고(rollover), 오래된 인덱스는 핫-웜-콜드 단계로 옮겨 비용을 낮춥니다. 이렇게 하면 한 인덱스의 샤드가 무한정 커지지 않습니다.
리밸런싱과 장애 동작
샤드를 추가하거나 노드가 빠지면 클러스터는 샤드를 재배치합니다. 이 리밸런싱(Rebalancing)은 데이터를 노드 사이로 실제로 옮기는 작업이라 네트워크와 디스크를 크게 씁니다. 복구 중 리밸런싱이 과도하면 오히려 성능이 급락할 수 있습니다.
장애 시 동작은 앞의 설계에서 그대로 따라옵니다. 노드가 죽어 프라이머리가 사라지면 다른 노드의 레플리카가 프라이머리로 승격됩니다. 반면 마스터가 불안정하면 샤드 할당 자체가 흔들려 클러스터 전체 운영 문제로 번집니다.
그래서 샤드와 레플리카 설계는 평상시 성능만이 아니라 장애 복구 비용까지 함께 봐야 합니다. 레플리카가 없으면 프라이머리 손실이 곧 데이터 유실입니다. 레플리카가 지나치게 많으면 쓰기와 저장 비용이 늘고, 복구할 때 옮길 데이터도 많아집니다.
정리
Elasticsearch의 샤딩 아키텍처는 인덱스를 샤드로 나누는 결정과 그 샤드를 레플리카로 복제하는 결정이 맞물린 구조입니다. 프라이머리 개수는 라우팅 해시에 묶여 생성 후 고정되고, 레플리카 개수는 읽기 부하에 맞춰 운영 중 조정됩니다. 노드 역할을 분리하면 마스터의 통제 부하와 데이터 노드의 입출력 부하가 섞이지 않습니다. 샤드 크기를 30~50GB로 잡고 샤드 폭발과 과도한 리밸런싱을 피하는 일이 안정적인 클러스터 운영의 기준선입니다.