본문 바로가기
J-H-T/OpenSearch

[🔍 OpenSearch 내부 구조 쉽게 이해하기] Index → Document → Shard → Replica → Node

by METAVERSE STORY 2026. 8. 9.
반응형

 

 

이번에는 OpenSearch 내부 구조를 “로그 한 건이 들어와서 실제로 어디에 저장되는지” 기준으로 아주 쉽게 연결해서 설명해볼게요. 😊

🔍 OpenSearch 내부 구조 쉽게 이해하기

Index → Document → Shard → Replica → Node

먼저 전체 관계부터 보면 이렇습니다.

OpenSearch Cluster
│
├─ Node 1
│   ├─ Primary Shard 0
│   └─ Replica Shard 1
│
├─ Node 2
│   ├─ Primary Shard 1
│   └─ Replica Shard 2
│
└─ Node 3
    ├─ Primary Shard 2
    └─ Replica Shard 0

그리고 그 Shard 내부에 실제 Document들이 저장됩니다.

즉 핵심 관계는:

Index
 ↓
Shard로 분할
 ↓
각 Shard 안에 Document 저장
 ↓
Shard는 여러 Node에 분산
 ↓
Replica가 Shard 복사본 보관

한 문장으로 줄이면:

Index라는 논리적인 데이터 공간을 Shard로 쪼개고, 그 Shard를 여러 OpenSearch Node에 분산해서 저장하며 Replica가 복사본을 보관합니다.


1️⃣ Document부터 이해하자 📄

OpenSearch에서 가장 작은 데이터 단위가 Document입니다.

예를 들어 Kubernetes GPU 서버에서 이런 로그가 발생했다고 해보겠습니다.

2026-08-09 14:30:10 ERROR gpu-node01 GPU3 Xid 43

Fluent Bit이 Parsing하면:

{
  "@timestamp": "2026-08-09T14:30:10",
  "level": "ERROR",
  "hostname": "gpu-node01",
  "gpu": "GPU3",
  "xid": 43
}

이 데이터 한 건이 바로:

📄 Document 1개

입니다.

로그가 하나 더 발생하면:

{
  "@timestamp": "2026-08-09T14:31:22",
  "level": "WARN",
  "hostname": "gpu-node02",
  "gpu": "GPU5",
  "message": "ECC Correctable Error"
}

또 하나의 Document가 됩니다.

따라서:

로그 한 건
   ↓
Parsing
   ↓
JSON 데이터
   ↓
Document 1개

라고 보면 됩니다.


2️⃣ Index는 Document를 모아놓는 공간 📚

Document가 수백만 개 발생하면 어딘가에 모아야겠죠.

그 논리적인 저장공간이 Index입니다.

예를 들어 Index 이름이:

gpu-logs-2026.08.09

라면:

Index
gpu-logs-2026.08.09

├─ Document 1
│  └─ GPU3 Xid 43
│
├─ Document 2
│  └─ GPU5 ECC Error
│
├─ Document 3
│  └─ GPU1 Temperature Warning
│
├─ Document 4
│  └─ GPU7 NVLink Error
│
└─ ...

처럼 생각할 수 있습니다.

쉽게 비유하면:

Index = 책장 📚
Document = 책 한 권 📄

입니다.


3️⃣ 그런데 Index가 너무 커지면? 🤔

예를 들어 하루에 로그가 10억 건 발생한다고 생각해보겠습니다.

gpu-logs-2026.08.09

Document
Document
Document
Document
...
10억 건

이걸 서버 한 대가 모두 처리한다면 성능과 장애 측면에서 문제가 생깁니다.

그래서 OpenSearch는 Index를 여러 조각으로 나눕니다.

이 조각이 바로:

🧩 Shard

입니다.


4️⃣ Shard가 뭐야?

예를 들어 gpu-logs Index를 3개의 Primary Shard로 구성했다고 해보겠습니다.

Index : gpu-logs

        ↓ 분할

┌────────────┐
│ Shard 0    │
└────────────┘

┌────────────┐
│ Shard 1    │
└────────────┘

┌────────────┐
│ Shard 2    │
└────────────┘

Document들이 이 Shard들에 분산됩니다.

예를 들면:

gpu-logs Index
│
├─ Shard 0
│   ├─ Document 1
│   ├─ Document 4
│   └─ Document 7
│
├─ Shard 1
│   ├─ Document 2
│   ├─ Document 5
│   └─ Document 8
│
└─ Shard 2
    ├─ Document 3
    ├─ Document 6
    └─ Document 9

중요한 점은:

관리자가 Document를 어느 Shard에 넣을지 직접 선택하는 것이 아닙니다.

OpenSearch가 Routing 계산을 통해 자동으로 결정합니다.


5️⃣ 왜 Shard로 나눌까? 🚀

가장 큰 이유는 분산 처리입니다.

Shard가 하나뿐이라면:

검색 요청
   ↓
Shard 0
   ↓
한 곳에서 전부 처리

하지만 Shard가 3개라면:

             검색 요청
                 ↓
       ┌─────────┼─────────┐
       ↓         ↓         ↓
    Shard 0   Shard 1   Shard 2
       │         │         │
       └─────────┼─────────┘
                 ↓
              결과 병합

여러 Shard가 동시에 검색을 수행할 수 있습니다.

그래서 대량 데이터 환경에서 확장성이 좋아집니다.


6️⃣ OpenSearch Node는 뭐야? 🖥️

Node는 OpenSearch 프로그램이 실행되고 있는 서버 또는 인스턴스입니다.

예를 들어 OpenSearch 서버가 3대 있다면:

OpenSearch Cluster

Node 1
Node 2
Node 3

이 3대가 하나의 Cluster를 구성할 수 있습니다.

그리고 앞에서 만든 Shard를 여러 Node에 나눠 저장합니다.

Index : gpu-logs

Shard 0 → Node 1
Shard 1 → Node 2
Shard 2 → Node 3

즉:

Index
 ↓
Shard
 ↓
Node에 실제 저장

입니다.


7️⃣ 이제 Replica가 등장합니다 🛡️

여기서 문제가 하나 있습니다.

현재:

Node 1 → Shard 0
Node 2 → Shard 1
Node 3 → Shard 2

라고 되어 있는데,

만약 Node 1이 장애 나면 어떻게 될까요?

Node 1 ❌

Shard 0
   ❌

Shard 0에 저장된 데이터를 사용할 수 없게 됩니다.

그래서 OpenSearch는 Replica라는 복사본을 만듭니다.


8️⃣ Replica = Shard의 복사본

Shard 0의 원본을:

Primary Shard 0

라고 한다면 복사본은:

Replica Shard 0

입니다.

예를 들어:

Node 1
└─ Primary Shard 0

Node 2
└─ Replica Shard 0

와 같이 구성할 수 있습니다.

중요한 원칙이 있습니다.

⚠️ Primary와 해당 Replica는 같은 Node에 저장하지 않습니다.

왜냐하면:

Node 1
├─ Primary Shard 0
└─ Replica Shard 0

이렇게 만들면 Node 1이 장애 났을 때:

Primary ❌
Replica ❌

둘 다 사라지기 때문입니다.

그래서:

Node 1 → Primary Shard 0

Node 2 → Replica Shard 0

처럼 분산합니다.


9️⃣ 실제 3 Node 구성을 그림으로 보면 🎯

이제 Primary Shard 3개 + Replica 1개씩 있다고 해보겠습니다.

Index : gpu-logs

Primary Shard
P0
P1
P2

Replica Shard
R0
R1
R2

OpenSearch는 예를 들어 이렇게 배치할 수 있습니다.

┌─────────────────┐
│ OpenSearch Node1│
│                 │
│  P0             │
│  R2             │
└─────────────────┘

┌─────────────────┐
│ OpenSearch Node2│
│                 │
│  P1             │
│  R0             │
└─────────────────┘

┌─────────────────┐
│ OpenSearch Node3│
│                 │
│  P2             │
│  R1             │
└─────────────────┘

여기서:

P = Primary
R = Replica

입니다.

관계를 연결하면:

P0 → R0

P1 → R1

P2 → R2

이고 각각 다른 Node에 존재합니다.


🔟 Node 1이 장애 나면 어떻게 될까? 🚨

Node 1에는:

P0
R2

가 있었습니다.

Node 1 장애:

Node 1 ❌
├─ P0 ❌
└─ R2 ❌

하지만 다른 Node에:

Node 2
└─ R0

Node 3
└─ P2

가 존재합니다.

특히 P0이 사라졌더라도 Replica 0이 있습니다.

R0
 ↓
Primary로 승격
 ↓
P0

OpenSearch가 Replica를 Primary 역할로 승격시킬 수 있습니다.

그래서 서비스를 계속 유지할 수 있습니다.

이게 바로 Replica의 핵심 목적입니다.

🛡️ Node 장애가 나더라도 데이터를 유지하고 서비스를 계속하기 위해


1️⃣1️⃣ 실제 로그 한 건은 어떻게 저장될까?

이번에는 처음부터 끝까지 연결해보겠습니다.

GPU 서버에서 로그가 발생합니다.

2026-08-09 14:30:10 ERROR GPU3 Xid 43

① Fluent Bit 수집

GPU Server
    ↓
Fluent Bit

② Parsing

{
  "time": "2026-08-09 14:30:10",
  "level": "ERROR",
  "gpu": "GPU3",
  "xid": 43
}

③ OpenSearch로 전송

Fluent Bit
    ↓
OpenSearch

④ Index 결정

예를 들어:

gpu-logs-2026.08.09

⑤ Document 생성

Document #A123

⑥ Routing 계산

OpenSearch가:

Document A123
     ↓
Primary Shard 1

에 저장하기로 결정했다고 가정합니다.

⑦ Primary에 저장

Node 2

Primary Shard 1
    ↓
Document A123

⑧ Replica에 복제

Primary Shard 1의 Replica가 Node 3에 있다면:

Node 2
P1
└─ Document A123

        ↓ 복제

Node 3
R1
└─ Document A123

결과적으로 같은 Document의 데이터가 Primary와 Replica 양쪽에 존재하게 됩니다.


1️⃣2️⃣ 전체 그림으로 한 번에 보기 🧠

Kubernetes Pod / GPU Server
          │
          │ 로그 발생
          ↓
      Fluent Bit
          │
          │ Parsing
          ↓
┌─────────────────────────┐
│ Index                   │
│ gpu-logs-2026.08.09     │
└───────────┬─────────────┘
            │
            │ Sharding
            ↓
 ┌──────────┼──────────┐
 ↓          ↓          ↓
 P0         P1         P2
 │          │          │
Document   Document   Document
 │          │          │
 ↓          ↓          ↓

Node1      Node2      Node3

 P0         P1         P2
 R2         R0         R1

조금 더 정확하게 그리면:

                OpenSearch Cluster

                    INDEX
              gpu-logs-2026.08.09
                       │
          ┌────────────┼────────────┐
          ↓            ↓            ↓
      Primary 0    Primary 1    Primary 2
          │            │            │
      Documents    Documents    Documents


┌────────────────┐
│ Node 1         │
│                │
│ Primary 0      │
│ Replica 2      │
└────────────────┘

┌────────────────┐
│ Node 2         │
│                │
│ Primary 1      │
│ Replica 0      │
└────────────────┘

┌────────────────┐
│ Node 3         │
│                │
│ Primary 2      │
│ Replica 1      │
└────────────────┘

1️⃣3️⃣ 중요한 포인트: Index 자체가 한 서버에 있는 게 아니다 ⭐

초보자 때 가장 많이 오해하는 부분입니다.

예를 들어:

gpu-logs Index → Node 1

처럼 Index 전체가 특정 서버 한 대에 저장된다고 생각하기 쉽습니다.

실제로는:

gpu-logs Index

 ↓

Shard 0 → Node 1
Shard 1 → Node 2
Shard 2 → Node 3

처럼 Index가 Shard 단위로 여러 Node에 분산됩니다.

따라서:

Index는 논리적인 개념이고, 실제 데이터 저장과 분산의 기본 단위는 Shard입니다.

이 부분이 매우 중요합니다.


1️⃣4️⃣ Primary Shard와 Replica Shard 차이

구분Primary ShardReplica Shard

의미 원본 Shard Primary 복사본
데이터 저장
검색 처리
장애 대응 기준 데이터 Primary 장애 시 승격 가능
같은 Node 배치 - 해당 Primary와 같이 배치하지 않음
목적 데이터 저장 고가용성 + 검색 처리량

특히 Replica는 백업만을 위한 존재가 아닙니다.

Replica도 검색 요청을 처리할 수 있습니다.

즉:

Primary
+ Replica

가 함께 검색 처리에 참여할 수 있습니다.

따라서 Replica는:

🛡️ 장애 대응 + 🚀 검색 성능 확장

두 가지 역할을 합니다.


1️⃣5️⃣ Replica는 백업인가? ❌

이것도 중요합니다.

Replica = Backup

은 아닙니다.

Replica는 Cluster 내부의 실시간 복제본입니다.

실수로 Document를 삭제하면:

Primary에서 삭제
      ↓
Replica에서도 삭제

됩니다.

따라서 진짜 백업은 별도로:

Snapshot
    ↓
S3 / Object Storage

같은 방식으로 구성해야 합니다.

즉:

Replica
= 장애 대응용

Snapshot
= 백업/복구용

이라고 구분하면 됩니다.


1️⃣6️⃣ 아주 쉬운 회사 비유 🏢

OpenSearch 구조를 회사 문서 보관으로 비유해보겠습니다.

Index

GPU 장애 보고서 전체

📚 큰 문서함

Document

GPU3 Xid43 장애 보고서 1건

📄 개별 문서

Shard

문서가 너무 많아서:

문서함 A
문서함 B
문서함 C

로 분산

🧩 문서함 조각

Node

문서함을 실제로 보관하는:

창고 1
창고 2
창고 3

🖥️ 실제 서버

Replica

문서함이 없어질 경우에 대비해서:

원본 문서함 → 다른 창고에 복사

🛡️ 복제본

결과적으로:

GPU 장애 로그
   ↓
Index 📚
   ↓
Shard 🧩🧩🧩
   ↓
Node 🖥️ 🖥️ 🖥️
   ↓
Replica 🛡️

가 됩니다.


🎯 가장 중요한 관계

이 다섯 개를 이렇게 기억하면 됩니다.

Document
= 실제 데이터 한 건

      ↓ 모아서

Index
= 데이터의 논리적인 묶음

      ↓ 나눠서

Shard
= 실제 분산 저장 단위

      ↓ 저장되는 곳

OpenSearch Node
= 실제 서버

      +

Replica
= 다른 Node에 만들어놓은 Shard 복사본

💡 한 문장으로 정리

OpenSearch는 수많은 Document를 Index로 관리하고, Index를 여러 Shard로 나눈 뒤 여러 Node에 분산 저장하며, 각 Shard의 Replica를 다른 Node에 배치해 장애에 대비합니다.

실제 로그 시스템으로 연결하면

Kubernetes Pod
    ↓
Fluent Bit
    ↓
Parsing
    ↓
Document
    ↓
Index
    ↓
Primary Shard
    ↓
OpenSearch Node
    ↓
Replica Shard
    ↓
다른 OpenSearch Node
    ↓
OpenSearch Dashboards에서 검색 🔍

이 구조까지 이해하면 다음 단계에서는 “Primary Shard를 몇 개 잡아야 하는지, Replica를 0/1/2개로 하면 무엇이 달라지는지, Node가 3대인데 Shard를 10개 잡으면 실제로 어떻게 배치되는지”를 배우면 실무적인 OpenSearch 용량 설계로 연결됩니다.

 

 

반응형

댓글