Elasticsearchを学び始めた際、「シャードってデータを全部持っているの?」「Kubernetes(k8s)のPodとどう関係するの?」「内部のセグメントって何?」といった疑問にぶつかることがあります。
本記事では、Elasticsearchの階層構造を中心に、シャードの仕組み、k8s上での挙動、セグメントの役割、そして並列検索時の挙動までを一気通貫で整理して解説します。
1. 概念の全貌:Elasticsearchの階層構造
Elasticsearchを理解するには、「物理的な階層(インフラ)」と「論理的な階層(データ構造)」を交差させて把握するのが一番の近道です。
全体の階層関係は以下のようになっています。
【物理構造(インフラ)】 【論理構造(データ)】
Cluster (システム全体)
├── Node (サーバー/Pod)
│ ├── Shard (物理的なデータの分割単位) ◀━━【交差点】━━ Index (論理的なデータの集まり)
│ │ └── Segment (Luceneの物理ファイル)
│ │ ├── 1. 転置インデックス (検索用)
│ │ ├── 2. Doc Values (集計・ソート用)
│ │ └── 3. Stored Fields (原本データ)
│ └── Shard...
├── Node...
各階層の役割一覧
| 階層 | 分類 | 役割と概要 |
|---|---|---|
| Cluster | 物理 | システム全体のまとまり。複数のNodeを束ねる最大の単位。 |
| Node | 物理 | 1台のサーバー(k8s環境では1つのPod)。計算とストレージを担当。 |
| Index | 論理 | RDBのテーブルに相当するデータのまとまり。概念的な枠組み。 |
| Shard | 物理/論理 | 物理と論理の交差点。 Indexを物理的に分割した実体。 |
| Segment | 物理 | Shard内部でLuceneが作成する、変更不可(イミュータブル)なデータファイル。 |
| Document | 論理 | RDBの1行に相当するJSON形式のデータ本体。 |
| Field | 論理 | JSON内の各キーと値のペア(RDBのカラム)。 |
2. インデックス・シャード・Podの関係
1シャードは全データを持っているか?
結論から言うと、1つのシャードはインデックスの一部(細切れ)のデータしか持っていません。
- プライマリシャード(データの分散): Indexを構成するプライマリシャード(Shard A, B, C...)は、それぞれ全く異なるデータを保持しています。これら全てを合計して初めて1つの完全なIndexになります。
- レプリカシャード(データの複製): 特定のプライマリシャードの「完全なコピー」です。障害耐性と読み込みの並列性を高めます。
Kubernetes(k8s)運用での実態
k8s環境において、「1 Pod = 1 Elasticsearch Node」として構成するのが標準的です。
-
k8sのNamespaceでシャードはまとめない
Namespaceはk8s側のリソース分離のための機能であり、シャードの配置管理には関与しません。 -
1 Pod = 1 シャードではない
「1つのPodの中に1つのシャードだけが入る」わけではありません。1つのPod(Node)の中には、複数種類のIndexから生成された数十〜数百のシャードが同居します。 -
シャードの配置はElasticsearchが制御
どのシャードをどのPod(Node)に分散配置させるかは、ElasticsearchのMasterノードが全自動で最適化します。
3. シャードの内部構造「Segment(セグメント)」
Shardのさらに内側には、Segment(セグメント)と呼ばれる物理ファイルが存在します。
Shard
├── Segment 1 (1秒前に作成)
├── Segment 2 (2秒前に作成)
└── Segment 3 (統合された大きなファイル)
セグメントが作られるルール
セグメントは「〇〇件ごと」といったデータ件数ではなく、主に時間(デフォルト:1秒ごと)の単位で作成されます。
- 書き込まれたデータは一度メモリ(Buffer)に入ります。
- 1秒ごとにディスクへフラッシュ(Refresh)され、新しいセグメントファイルが生成されます。
- セグメントは一度作成されると変更不可(イミュータブル)です。追記や上書きは行われません。
増えすぎたセグメントの「マージ(Merge)」
1秒ごとに細かいセグメントが量産されると、検索時のファイル読み込みコストが増大します。そのため、Elasticsearchはバックグラウンドで複数の小さなセグメントを1つの大きなセグメントに自動的に合体させる「Segment Merge」を常時実行しています。
4. セグメント内部のデータ構造
セグメントの中では、受け取ったドキュメントが用途に合わせて主に3つの構造に分解されて保存されます。
-
転置インデックス(Inverted Index):
テキストをトークン(単語)に分割し、「単語 ➔ どのドキュメントに含まれるか」のリストを作成します。爆速な全文検索の要です。 -
Doc Values:
列指向(カラムナ)フォーマットで保持されるデータです。「価格順のソート」や「平均値の集計」などを高速化します。 -
Stored Fields:
元のJSONドキュメントの原本(_source)を圧縮保存するエリアです。検索結果として最終的なレスポンスを組み立てる際に利用されます。
5. シャード・セグメントの「並列検索」メカニズム
検索クエリが実行された際、シャードとセグメントはそれぞれ次のように処理されます。
シャード間:常に「並列(パラレル)」
複数のシャードにまたがる検索は、複数のスレッドやNodeによって同時に(並列で)処理されます。シャード数を増やすことでスケールアウトが可能なのはこのためです。
セグメント間:基本は「直列」、最新では「並列」も選択可能
1つのシャード内部において、複数あるセグメントを探す処理は、伝統的には1つのスレッドが「直列(順番)」に走査します。システム全体のCPUリソース枯渇を防ぎ、同時リクエスト数(スループット)を高めるための設計です。
近年では1回の検索速度(低レイテンシ)を優先するため、Concurrent Segment Search(セグメントの並列検索)の導入が進んでいます。
-
OpenSearch: Index設定(
index.search.concurrent_segment_search.mode)により明示的にON/OFFの設定が可能です。 - Elasticsearch (8.x〜): ユーザーが明示的に設定を書かなくても、リソース状況に応じて自動的に最適化・並列実行されます。
6. 並列検索で結果の「ブレ」は起きないのか?
「複数シャードを並列で検索する場合、処理の早いシャードのデータが優先されて結果がブレるのでは?」という疑問が生じますが、処理スピードの差によって検索結果がブレることはありません。
ElasticsearchはQuery-then-Fetchという2段階のプロセスを踏むためです。
【第1段階:Queryフェーズ】
ユーザー ➔ Coordinating Node ➔ 各シャードへ一斉照会(並列)
※ 全シャードの処理が終わるまで待ち、スコア上位の「IDと順位」だけを集計・統合してソート。
【第2段階:Fetchフェーズ】
Coordinating Node ➔ 確定した上位データを持つシャードへ原本要求
※ 正確なトップN件のJSON原本のみを取得してユーザーに返却。
全シャードからのレスポンスを待ってから上位の全体順位(ソート結果)を確定させるため、各シャードの応答スピードが異なっても常に正しい結果が得られます。
※注意:完全に同スコアのデータがある場合
スコアやソートキーが全く同じドキュメント同士の並び順はブレることがあります。結果を100%完全固定したい場合は、ソート条件の最後に一意な値(_idなど)をタイブレーカーとして指定するのが鉄則です。
おわりに
Elasticsearchのアーキテクチャは、「物理的な分散(Node/Shard)」と「検索エンジンの最適化(Segment/転置インデックス)」が綺麗に組み合わさって成り立っています。
これらの階層構造と並列処理の仕組みを把握しておくことで、パフォーマンスチューニングや障害対応、k8s上でのリソース設計が非常に行いやすくなります。