0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

セグメントの裏側から考える、本当にスケールするElasticsearchインデックス設計

0
Posted at

セグメントの裏側から考える、本当にスケールするElasticsearchインデックス設計

Elasticsearchのインデックス設計に向き合うと、多くのエンジニアが同じジレンマに直面します。

「検索は速くしたい。でも、更新時のパフォーマンスも落としたくない。」

この2つを両立させる設計の答えは、refresh_interval や number_of_shards といった表面的な設定値をいじることだけでは見つかりません。本当の答えは、Elasticsearch(そしてその裏側で動くLucene)の根幹をなす 「セグメント」 の仕組みを理解したときに、初めてはっきりと見えてきます。

この記事では、データの保存単位である「セグメント」の内部構造をひも解き、そこから導かれる 「成長に耐えるインデックス設計」 の原則を整理していきます。


1. セグメントの中身は「適材適所」で分かれている

Elasticsearchは、インデックスされたデータを セグメント という単位でディスクに保存します。ここで多くの人が誤解しがちなのが、「セグメント=すべてのデータを一つの箱に詰め込んだもの」というイメージです。

実際は違います。セグメントは、検索の用途に合わせてデータ構造を分離して管理しています。主に次の3つを押さえておけば十分です。

データ構造 役割 得意な検索
転置インデックス どの単語がどのドキュメントに含まれるかを引く「辞書」 全文検索(キーワード検索)
ドキュメント情報(_source) 元のJSONをそのまま保持する領域 検索結果としてデータを返す
ベクトルデータ 近似最近傍探索(kNN)用の独立したグラフ構造 意味検索(セマンティック検索)

一つのセグメントの内部を模式化すると、次のようなイメージになります。

なぜベクトルデータは「別ファイル」なのか

特に注目すべきはベクトルデータです。ベクトル検索は、多次元空間上の距離を計算し、近いものを辿っていくという、テキストの辞書引き(転置インデックス)とはまったく異なるアルゴリズムで動きます。

そのため、ベクトルデータは転置インデックスとは 物理的にも論理的にも切り離された専用ファイルとして管理されます。「一つの箱に雑多に詰め込む」のではなく、それぞれの検索手法が最大限のパフォーマンスを発揮できる形にデータ構造を最適化しているわけです。

この「適材適所」の思想を知ると、Elasticsearchのアーキテクチャの奥深さが見えてきます。そして同時に、**「セグメントに書き込むコストは、含まれるデータ構造の種類と量に比例して重くなる」**という、設計上とても重要な事実にも気づけるはずです。


2. 更新処理の裏側に潜む「不変」という大原則

インデックス設計を考えるうえで、絶対に外せない前提があります。それが、

セグメントは不変(イミュータブル)である

という大原則です。

「ドキュメントの一部を更新する」とき、Elasticsearchはディスク上のデータを直接書き換えてはくれません。内部で実際に起きているのは、次のような処理です。

処理の流れを整理すると、次の3ステップです。

  1. 古いドキュメントに 「削除フラグ」 を立てる(物理削除ではなく論理削除)
  2. 変更を適用した 新しいドキュメントをメモリ上で作り直す
  3. それを丸ごと 新しいセグメントとしてディスクに書き出す

「たった1文字」が高くつく理由

ここに落とし穴があります。更新量がどれだけ小さくても、書き込まれるのはドキュメント全体だということです。

たった1文字の修正でも、フラグを1つ立て直すだけでも、その裏では「ドキュメント全体の読み込み」と「新規書き込み」が毎回発生します。

  • ドキュメントが小さければ、このコストは無視できます。
  • しかし、巨大なJSONやベクトル情報を含む重いドキュメントを頻繁に更新すると、そのたびにCPUとI/Oが激しく消費されます。

さらに、論理削除されたドキュメントは即座にディスクから消えるわけではなく、後続の マージ処理で不要領域として回収されます。更新が多いほどマージの負荷も増え、リソースをじわじわと圧迫していきます。

つまり、「ドキュメントは小さく保つ」ことが、更新性能を守る第一の防御線になるのです。


3. nested 型の罠 ── 便利さの裏で起きていること

この「更新コスト」の問題が最も顕著に現れるのが、配列データを扱う nested オブジェクトの設計です。

親ドキュメントの中に子データを配列で入れ子にできる nested は、直感的で便利に見えます。しかし裏側では、**nested 配列の一要素ごとに「独立した隠しドキュメント(hidden document)」**として保存されています。

ここで、更新の大原則(=セグメントは不変)を思い出してください。親と子は物理的に同じセグメントに束ねられているため、子を1つ触るだけで親を含めた全体が書き直されます。

例えば、1社の企業データに 1,000人分の従業員データが nested で紐づいているとします。

従業員を たった1人追加するだけで、次のことが起こります。

  • 企業本体 + 既存1,000人分 + 新規1人分、合計1,001件超が一度に無効化される
  • そのすべてが 新しいセグメントとしてディスクに書き直される

「1人追加しただけなのに、なぜこんなに重いのか」──その答えは、便利さの裏でセグメント全体が再生成されているから、なのです。


4. 解決策 ── 「勇気を持って」別インデックスに切り出す

では、どう設計すればよいのか。原則はシンプルです。

上限なく増え続ける可能性があるデータや、頻繁に更新されるデータが紐づく場合は、別のインデックスに切り出す。

企業インデックスと従業員インデックスを 物理的に分割してみましょう。

この構成なら、

  • 従業員の追加・更新は 「従業員1件」だけの小さな書き込みで完結する
  • 親である 企業データにはまったく負荷がかからない

というメリットが得られます。

トレードオフを正しく引き受ける

もちろん、分割はいいことばかりではありません。デメリットは大きく2つあります。

① 検索時に2回クエリを投げる必要がある

分割すると、検索時にアプリケーション側で

  1. まず企業を検索する
  2. その company_id を使って従業員を検索する

というように、2回クエリを発行してデータを紐づける手間が発生します。

② 検索パフォーマンス自体は「1インデックスにまとめる」ほうが有利

ここは特に意識しておきたいポイントです。そもそも Elasticsearchは「データの重複を許容してでも、検索を速くする」ことを大事にするツールです。関連するデータが最初から1つのドキュメント(1インデックス)にまとまっていれば、

  • クエリは1回で完結する
  • インデックス間の突き合わせ(アプリ側のJOIN相当)が不要
  • ネットワークの往復も1往復で済む

という理由で、検索そのものは圧倒的に速く・シンプルになります。RDBのように正規化して結合するのではなく、あえて非正規化してデータを持つ(=重複を許容する)ことこそ、Elasticsearchが得意とする設計思想です。

インデックスを分割するということは、この 「検索性能の優位性」をあえて手放し、更新性能と引き換えにするという判断にほかなりません。

では、どう判断するか

つまり選択は「検索性能」と「更新性能」のどちらを優先するかのトレードオフです。

  • データが小さく更新もまれなら、素直に1インデックスにまとめて検索性能を取る(Elasticsearchの本領)
  • 上限なく増える/頻繁に更新されるなら、検索の手間を少し引き受けてでも分割し、更新性能とスケーラビリティを取る

「2回クエリ」と「検索性能の低下」という2つのコストを引き受けてもなお、データ量が数百万・数千万と膨れ上がった未来でシステムを安定稼働させたい――そう判断できる場面でこそ、分割は最強の盾になります。


5. 設計判断の早見表

最後に、迷ったときの判断基準を表にまとめておきます。

データの性質 推奨アプローチ 理由
件数が少なく、めったに更新されない object / nested で内包 更新コストが小さく、1クエリで検索が完結する(検索性能を優先)
頻繁に更新される 別インデックスへ切り出し 親ドキュメント全体の再書き込みを避ける
上限なく増え続ける可能性がある 別インデックスへ切り出し セグメント肥大化と更新コスト爆発を防ぐ
巨大 / ベクトルを含み更新が多い ドキュメントを分割・軽量化 1回の更新あたりのI/O・CPUを抑える
検索速度を最優先したい 1インデックスに非正規化してまとめる データの重複を許容してでも検索を速くするのがESの本領

まとめ

Elasticsearchの設計に、「すべての要件を一度に満たす魔法」はありません。しかし、裏側で動くセグメントの仕組みに寄り添うことで、次のような判断を 確信を持って下せるようになります。

  • なぜドキュメントを小さく保つべきなのか → セグメントは不変で、更新のたびに全体が書き直されるから
  • いつ別インデックスに分割すべきなのか → 更新が頻繁、または上限なく増えるデータが紐づくとき
  • 分割で何を失うのか → 「1クエリで完結する検索性能」。Elasticsearchは本来、データの重複を許容してでも検索を速くするツールであり、分割はその優位性を更新性能と引き換えにする判断

結局のところ、インデックス設計は 「検索性能」と「更新性能」のトレードオフをどう取るかに尽きます。セグメントの構造という「土台」を理解することが、表面的なチューニングを超えた、成長に耐える設計への近道です。次にインデックスを設計するときは、ぜひこの裏側の仕組みを思い出してみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?