🧬

Elasticsearchのインデックスフィールドを直接削除できない理由と代替手段

に公開

はじめに

Elasticsearchを運用していると、既存のインデックスの構造を変更する必要に迫られることがあります。特に、使用しなくなったフィールドやマッピングの定義ミスがあるフィールドをインデックスから削除したいケースは少なくありません。

しかし、多くのユーザーが直感的に可能だと考えるこの操作が、Elasticsearchでは実行できないことに驚かされます。つまり、すでにデータが格納されているインデックスのマッピングから特定のフィールドを直接削除する機能が提供されていないのです。

この記事では、なぜElasticsearchがこのような制約を設けているのか、その根本的な技術的理由を詳しく解説し、実務で使える代替手段を紹介します。

1. Elasticsearchのマッピング管理ポリシー

Elasticsearchでインデックスの構造を定義する核となる要素はマッピング(Mapping)です。マッピングは各フィールドのデータ型、インデックス方法、保存方法などを指定するスキーマのような役割を果たします。

Elasticsearchのマッピングには、以下のようなフィールドの変更や削除に対する明確な制限があります。

  • フィールドの追加:既存のマッピングに新しいフィールドを追加することは許可されています。
  • 既存フィールドの変更:すでにインデックスされているフィールドのデータ型や主要な属性(analyzer など)を変更することはできません。
  • 既存フィールドの削除:データを含むインデックスのマッピングから特定のフィールド定義を削除することもサポートされていません。

これらの制約は単なるポリシー的な決定ではなく、Elasticsearchの基盤となるストレージエンジンであるLuceneの根本的なアーキテクチャと設計原則に由来しています。

2. Luceneのセグメントと不変性の原則

Elasticsearchの制約を理解するためには、その核となる技術であるApache Luceneの動作原理を知る必要があります。

Luceneのインデックス(Elasticsearchではシャードに相当)は、セグメント(Segment)と呼ばれる複数の独立したサブインデックス構造で構成されています。

  • 各セグメントはインデックス全体のドキュメントの部分集合を含み、それ自体が完全に検索可能な「ミニインデックス」として機能します。
  • セグメントには転置インデックス(inverted index)、保存フィールド(stored fields)、ドキュメント値(doc values)など、検索とデータ取得に必要なすべてのデータ構造が含まれています。
  • データがインデックスされ、メモリバッファからディスクにフラッシュされると、新しいセグメントが作成されます。

ここで最も重要なLuceneの核心原則はセグメントの不変性(Segment Immutability)です。

  • セグメントファイルはディスクに書き込まれると、その内容は決して変更されません
    • ドキュメントの更新や削除でさえ、既存のセグメントファイルを直接修正することはありません。
  • この「書き込み後不変(write-once)」方式はLuceneの基本設計哲学です。

セグメントが不変であるため、Luceneはドキュメントの更新と削除を次のように処理します。

  • 削除(Deletes):ドキュメントはセグメントから物理的にすぐには削除されません。代わりに、そのドキュメントが削除されたことを示す別のセグメントごとのデータ構造(ビットセットや.delファイル)にマークされます。検索時にはこの削除情報を参照し、削除マークされたドキュメントを結果から除外します。
  • 更新(Updates):Lucene(およびElasticsearch)での更新操作は、本質的には既存のドキュメントを削除済みとしてマークし、更新内容を含む新しいドキュメントを挿入する方法で実行されます。この新しいドキュメントは新たに作成されるセグメントに保存されます。

時間が経つにつれ、インデックス操作によって多くの小さなセグメントが生成され、削除マークされたドキュメントが蓄積されます。あまりに多くのセグメントを検索することは非効率的なため、Luceneは定期的にバックグラウンドで複数の小さなセグメントをマージして大きなセグメント一つにするセグメントマージング(Segment Merging)を実行します。

このマージ過程で、削除マークされたドキュメントは新しいマージされたセグメントにコピーされません。これによって初めて、削除されたドキュメントが占めていたディスク容量が解放され、データが物理的に除去されます。

3. フィールド削除が不可能な技術的な理由

前述のLuceneセグメントの不変性原則が、Elasticsearchでインデックスフィールドを直接削除できない最も直接的な技術的理由です。セグメントファイルは一度ディスクに書き込まれると変更できないため、既存のセグメントファイルを開いて特定のフィールドに関連するデータをすべてのドキュメントから削除するという低レベルメカニズムが存在しません。

フィールドデータはセグメント内の様々な構造に保存されます。例えば、検索可能なテキストフィールドは転置インデックスに、ソートや集計に使用されるフィールドはドキュメント値に、元のドキュメント内容を取得するためのフィールドは保存フィールドに格納されます。特定のフィールドを削除するには、これらすべての関連するデータ構造からそのフィールドのデータを削除する必要がありますが、不変性の原則のため、既存のセグメント内でこのような修正を行う方法はありません。

これはドキュメントの削除や更新の処理方法と対照的です。ドキュメント削除は別の削除マークファイルを通じて行われ、更新は削除マークと新しいドキュメントの追加(つまり、新しいセグメントの作成)によって不変性を回避します。しかし、既存のセグメント内部構造で特定のフィールドだけを「削除済み」としてマークする軽量なメカニズムは存在しません。

結局、既存のセグメントから特定のフィールドのデータを削除する唯一の方法は、そのセグメント全体を読み込み、削除したいフィールドを除くすべてのデータを含む新しいセグメントを作成し、既存のセグメントを破棄することです。これは本質的にセグメントマージプロセスと似ていますが、単一のフィールドを削除するために潜在的に非常に大きなセグメント全体を書き直す作業は、極めて多くのリソース(特にディスクI/OとCPU)を消費し、非効率的です。

4. フィールド削除を許可した場合の潜在的問題

Elasticsearch/Luceneがフィールドの直接削除やセグメント修正を許可したとしたら、次のような問題が発生する可能性が高いです。

  1. データ再構成コスト:フィールドを削除するにはセグメントを書き直す必要があり、これは膨大なI/OおよびCPU負荷を発生させ、インデックスおよび検索性能に深刻な影響を与える可能性があります。

  2. パフォーマンスへの影響:可変データ構造に対する同時読み取り/書き込みはロックメカニズムを必要とし、これはボトルネックを引き起こし、不変性ベースのロックなしの読み取り方式と比べて全体的なスループットを低下させる可能性があります。パフォーマンス予測が難しく、一貫性が低下する可能性があります。

  3. キャッシュ効率の低下:頻繁なデータ直接修正はファイルシステムキャッシュを継続的に無効化させ、キャッシュの効果を低下させディスクアクセス頻度を上げ、性能を低下させます。

  4. 複雑性の増加:複雑なLuceneデータ構造(転置インデックスなど)内のデータを直接修正/削除するロジックを管理することは、Luceneコードベースの複雑性を大幅に増加させ、バグの発生可能性を高め、メンテナンスを困難にします。

  5. スナップショット複雑性:増分スナップショットの実装がはるかに複雑になり、効率性が低下する可能性があります。

5. Elasticsearchの設計思想

これらの利点と欠点を考慮すると、Elasticsearchの設計哲学は明確です。Elasticsearchは検索性能、スケーラビリティ、運用の安定性を最優先の価値としています。不変セグメントの採用は、これらの優先順位を支える核心的な技術的決定です。これによりフィールド削除のようなスキーマ柔軟性はやや制限されますが、その代わりに重要な利点が得られます。

つまり、Elasticsearchの設計はスキーマ変更のためのインデックス再作成の複雑さを受け入れる代わりに、Luceneの不変セグメントがもたらす継続的なパフォーマンスと安定性上の利点を選択した結果です。これは検索エンジンのような読み取り操作が頻繁な(read-heavy)システムでよく見られるエンジニアリングのトレードオフです。スキーマ変更は比較的頻繁に発生しないため、reindexという管理された変更メカニズム(特にAliasと併用する場合)を提供することで、コアパフォーマンスアーキテクチャを損なうことなく実質的なスキーマ進化の要求を満たすことができると判断しています。

6. 使用されないフィールド管理のための代替手段

Elasticsearchでフィールドを直接削除できなくても、不要になったフィールドや誤ったフィールドを管理するための代替戦略がいくつか用意されています。

戦略1: アプリケーションレベルでフィールドを無視する

最も簡単な方法は、アプリケーションのクエリやデータ収集ロジックで該当フィールドを使わないことです。新しいドキュメントをインデックスする際に該当フィールドを含めません。

  • メリット: 実装が最も簡単で、Elasticsearch設定を変更する必要がありません。
  • デメリット: フィールドデータが既存インデックスに残り、ディスク容量を占有し、内部データ構造にオーバーヘッドを引き起こす可能性があります。

戦略2: ドキュメントからフィールドを削除する (Update API / Update By Query)

特定のドキュメントからフィールドを削除するにはUpdate APIを、複数のドキュメントから削除するにはUpdate By Query APIをスクリプト(ctx._source.remove('field_name'))と共に使用します。

  • メリット: 完全な再インデックスなしに特定のドキュメントの_sourceからフィールドデータを削除できます。
  • デメリット: インデックスマッピング自体からフィールド定義を削除することはできません。

戦略3: ignore_malformedの使用 (入力エラー処理用)

フィールドマッピングにignore_malformed: true設定を追加します。この設定は、特定のフィールドに誤ったデータ型の値が入力された場合に、そのフィールドのみをインデックスしないようにします。

  • 主な目的: 既存の正しくマッピングされたフィールドを削除するのではなく、データ収集過程で発生する予測不可能または不正な形式の入力データを処理するための機能です。

戦略4: _sourceフィールドから除外

マッピング設定で特定のフィールドを保存される_sourceドキュメントから除外するよう構成します("_source": {"excludes": ["field_name"]})。

  • メリット: 保存される_sourceのサイズを縮小し、ディスク容量を節約できます。
  • デメリット: index: falseenabled: falseなどでインデックス自体を無効化しない限り、フィールドは検索可能として引き続きインデックスされる可能性があります。

戦略5: 再インデックス (Reindexing - フィールド削除のための最終解決策)

希望するマッピング(削除したいフィールドを除いた)を持つ新しいインデックスを作成し、Reindex APIを使用して既存インデックスのデータを新インデックスにコピーします。

  • メリット: 結果として生成される新インデックスのマッピングとデータ構造の両方からフィールドを完全に削除する唯一の方法です。同時に他のマッピング変更作業も実行できます。
  • デメリット: 特に大規模インデックスの場合、リソース(CPU、I/O、新インデックス用のディスク容量)消費が大きく、時間がかかることがあります。

7. 再インデックス:フィールド削除のための実践方法

再インデックス(Reindexing)は、Elasticsearchでフィールドを効果的に削除する最も確実な方法です。これは既存インデックスを直接修正するのではなく、データを新しい構造のインデックスに移行(migration)するプロセスです。

Reindex APIの活用

この作業を実行する主要ツールはElasticsearchの_reindex APIです。

まずは新しいインデックスとマッピングを作成します。

PUT /new_index
{
  ...,
  "mappings": {
    "properties": {
      "field_1": { "type": "keyword" },
      "field_2": { "type": "text" },
      "another_field": { "type": "integer" }
      // field_to_deleteは記載しません。
    }
  }
}

次に、Reindex APIを呼び出します。

POST /_reindex
{
  "source": {
    "index": "old_index"
  },
  "dest": {
    "index": "new_index"
  },
  "script": {
    "source": "ctx._source.remove('field_to_delete')"
  }
}

無停止(Zero-Downtime)再インデックス戦略

運用システムでは、サービスを中断せずに再インデックスを実行することが非常に重要です。典型的には以下の手順を踏みます。

  1. 新インデックス準備: 削除したいフィールドが除外された適切な新しいマッピングでnew_indexを作成します。

  2. エイリアス(Alias)使用: アプリケーションが実際のインデックス名(old_index)の代わりにエイリアス(例:my_data_alias)を通じてインデックスと対話するよう構成します。このエイリアスは最初はold_indexを指します。この抽象化層が無停止移行の核心です。

  3. 初期データコピー: _reindex APIを実行してold_indexのデータをnew_indexにコピーします。この間、読み取りリクエストはエイリアスを通じて引き続きold_indexに転送されます。

  4. 進行中の変更処理: 初期再インデックスが進行中にold_indexに新しいドキュメントが追加されたり既存ドキュメントが更新/削除されたりする状況を処理する必要があります。一般的なアプローチには以下のようなものがあります。

    • 二重書き込み(Dual Writing)
    • 増分再インデックス(タイムスタンプベース)
    • 短時間の書き込み停止 / 原本システム活用
  5. エイリアスの原子的切り替え: _aliases APIを使用してmy_data_aliasold_indexではなくnew_indexを指すよう変更します。

  6. 既存インデックス削除: new_indexが正常にサービスされていることを確認した後、old_indexを削除してリソースを回収します。

再インデックスのベストプラクティス

  • リソース管理:

    • 作業中のクラスター状態(CPU、I/O、ヒープメモリ)を監視する
    • Reindex APIのrequests_per_secondパラメータでクラスター負荷を調節する
    • 可能なら、トラフィックの少ない時間帯に再インデックスを実行する
  • パフォーマンス最適化:

    • スライシング: _reindexリクエストにslices=Nまたはslices=autoパラメータを使用して並列処理
    • バッチサイズ調整: source内のsizeパラメータ値を調整
    • Refresh Interval調整: 大量再インデックス中に一時的にrefresh_interval値を増加
  • 長時間実行作業: 大規模インデックスではwait_for_completion=falseパラメータを使用して非同期実行し、Tasks APIで進捗状況を監視する

結論

Elasticsearchでインデックスフィールドを直接削除できない根本的な理由は、Apache Luceneのセグメント不変性原則にあります。この不変性は、フィールド削除のようなスキーマ変更の柔軟性を制限する一方で、キャッシュ効率の最大化、同時実行処理の簡素化、システム安定性の向上、効率的な増分スナップショットなど、検索パフォーマンスと運用安定性の面で大きな利点をもたらす意図的な設計選択です。

より使用されなくなったフィールドを完全に削除し、関連リソースを解放するには、再インデックス(Reindexing)が最も効果的な方法です。特に運用環境では、インデックスエイリアスを活用し、進行中の書き込み処理のための戦略を慎重に計画して、無停止(zero-downtime)で再インデックスを実行することが重要です。

直接的なフィールド削除機能はないものの、再インデックスメカニズムを通じて、Elasticsearchはパフォーマンスと安定性を維持しながら必要なスキーマ進化をサポートしています。

GitHubで編集を提案
nextbeat Tech Blog

Discussion