TT Lab
はじめる
学ぶ 学習パス コース

ログパイプラインの設計

インデックスを日付で分ける理由

TT Labで続きを見る

一言でいうと

ログのインデックスをlogs-2026.08.21のように日付で分ける理由は、削除するためです。ドキュメントを1件ずつ削除するのは遅いですが、インデックスごと削除するのはファイルの削除なので、即座に終わります。

なぜ必要なのか

検索エンジンでドキュメントを削除するのは、実際に消すことではありません。「削除済み」の印を付けておき、あとでセグメントをマージするときに実際に取り除かれます。そのため、古いログ1億件をdelete_by_queryで削除すると数時間かかり、その間クラスターが遅くなります。

インデックスを日付で分けておけば、「8月1日分の削除」はDELETE logs-2026.08.01の1回で済みます。ディレクトリを削除するのと同じコストです。

どう動くのか

インデックステンプレートが、新しく作られるインデックスに設定を自動で適用します。

PUT _index_template/labhub-logs
{
  "index_patterns": ["labhub-*"],
  "template": {
    "settings": {
      "number_of_shards": 1,
      "number_of_replicas": 1,
      "refresh_interval": "10s"
    },
    "mappings": {
      "dynamic": false,
      "properties": {
        "@timestamp": { "type": "date" },
        "level":      { "type": "keyword" },
        "message":    { "type": "text" },
        "trace_id":   { "type": "keyword" },
        "pod":        { "type": "keyword" }
      }
    }
  }
}

"dynamic": falseがこの設定の核心です。理由は下で説明します。

ISM(ライフサイクル管理)が削除の作業を自動化します。

hot (7일)  →  warm (23일, 복제본 0, 강제 병합)  →  delete

マッピング爆発

dynamicを有効にしておくと、新しいフィールドが入ってくるたびにマッピングが自動で作られます。ところがアプリケーションがログにユーザーIDをフィールド名として入れたり(user_12345: "...")、エラーオブジェクトをまるごとJSONで入れたりすると、フィールドが数千個になります。

フィールドが増えるとクラスター状態(cluster state)が大きくなり、それはすべてのノードに複製されるため、クラスター全体が遅くなります。ひどい場合はマスターノードが落ちます。これをマッピング爆発(mapping explosion)といいます。

防ぐ方法は3つあります。

  1. "dynamic": false: 定義していないフィールドは保存はするが、インデックス化しません(検索はできませんが、取得はできます)
  2. "dynamic": "strict": 定義していないフィールドが来たら拒否します
  3. index.mapping.total_fields.limit: 上限を設けます(デフォルト1000)

strictは安全に見えますが危険です。コレクターが想定外のフィールドを1つ付けるだけで、ドキュメントがまるごと拒否され、ログが失われます。実際にFluent Bitが内部フィールド_pを付けて400エラーが無限リトライで回り続け、ログがまるごと止まったことがあります。そのため、ログのインデックスにはfalseのほうがよいです。

よくある勘違い

シャードを多く置けば速いという考え: シャード1つはLuceneインデックス1つで、それぞれがファイルハンドルとメモリを使います。1日数GB程度のログにシャード5つは無駄です。経験則はシャード1つあたり10–50GBで、それより小さければシャードを減らします。

textとkeywordを混同すること: textは分析してトークンに分割するため全文検索はできますが、集計とソートができません。keywordはそのまま保存するため集計・ソートはできますが、部分検索ができません。レベル・Pod名・トレースIDはkeyword、メッセージ本文はtextです。

保持期間とコストを設計に入れる

ログインデックス設計の半分は検索性能で、残りの半分はいつ何を捨てるかです。後者を決めておかないと、ある日ディスクがいっぱいになり、そのときは慌てて削除するあまり、必要なものまで消してしまいます。

段階に分けます。最近のものは速いディスクに、古いものは遅いストレージに、さらに古いものはオブジェクトストレージに置き、最後に削除します。インデックスライフサイクル管理(ILM)がこの遷移を自動で行います。

段階 期間(例) 何をするか
hot 0–3日 書き込みと検索の両方が行われます
warm 3–14日 読み取り専用です。セグメントをマージし、レプリカを減らします
cold 14–90日 遅いノードやオブジェクトストレージへ移します
delete 90日以降 削除します

シャードを細かく分けすぎることが最もよくある失敗です。シャード1つごとにメモリとファイルハンドルが付くため、小さなシャードが数千個あるとクラスターが遅くなります。シャード1つあたり10–50GBを目標にし、1日分がそれより小さければ、インデックスを日別ではなく週別にするか、データストリームのロールオーバー条件(max_primary_shard_size)に任せます。

すべてのフィールドを検索可能にしません。検索はせず表示するだけのフィールドはindex: falseにしておくと、インデックス化のコストと保存容量が減ります。逆に、集計だけに使う数値はdoc_valuesがあれば十分です。

形の異なるログを1つのインデックスに入れません。フィールド名が重なっていて型が異なるとインデックス化が拒否され、そのドキュメントは黙って失われます。サービスごとにインデックスを分け、共通フィールドは命名規約を決めておきます。

コストは多くの場合、検索ではなくインデックス化から生じます。毎秒のドキュメント数がそのままCPUになります。デバッグレベルのログを本番でそのまま送ると、そのコストを毎日支払うことになります。サンプリングをかけるか、正常なリクエストはトレースに任せ、ログは異常な場合にだけ残す方向へ移します。

実務で本当に大切なこと

ログにトレースIDを入れることがオブザーバビリティの半分です。それがあれば、「この遅いリクエストが残したログ」を一度に探せます。なければ、時刻とPod名で手探りすることになります。

フィールド名は最初から標準に従うのがよいです。ECS(Elastic Common Schema)は@timestamp、log.level、service.name、trace.idのような名前を定めています。あとから変えるには、古いインデックスをすべて再インデックスする必要があります。