Claude Media
Claude CodeでElasticsearchのILMを設計する — rolloverと保持期間の逆算

Claude CodeでElasticsearchのILMを設計する — rolloverと保持期間の逆算

ログの保持要件からILMのrollover条件とdeleteのmin_ageを逆算し、テスト用のデータストリームで遷移を確かめるまでをClaude Codeと進める手順です。

ElasticsearchのILM(Index Lifecycle Management)は、ログindexをrolloverで区切り、古いものを段階的に消すための仕組みです。ポリシーのJSONは短いのに、数字の決め方で保持期間が変わります。

数字を先に決めてから書くのが近道です。この記事では「ログを何日残すか」という保持要件からmax_ageとmin_ageを逆算します。ポリシーを書く作業と、テスト用のデータストリームで遷移を確かめる作業は、Claude Codeに手伝わせる前提で進めます。Elasticsearchへ接続して調べる方法はElasticsearch MCPサーバーの使い方にまとめています。

保持要件を設定値に直す

ILMのdeleteフェーズで使うmin_ageは、indexの作成時刻ではなくrolloverした時刻からの経過時間です。rolloverしたindexでは、min_ageの計算に作成日ではなくrollover時刻が使われます。この1点が、保持期間の計算を左右します。

writeしている間のindexには、古い文書と新しい文書が混ざっています。rolloverまでの最長がmax_ageなので、1つのindex内の文書は次の範囲で消えます。

  • いちばん新しい文書: rollover直後から数えてmin_ageだけ残る
  • いちばん古い文書: max_ageにmin_ageを足した期間だけ残る(ILMの実行間隔ぶんの遅れは別)

この範囲は上記の仕様から出した算術です。保持要件を2つに分けると、設定値が決まります。

要件設定する値決め方
最低でも60日は検索できる設定する値deleteのmin_ageは60日以上決め方最短の文書でもmin_ageは残る
90日を超えて保持しない設定する値max_age + min_age ≦ 90日決め方最長の文書は両方の合計
rolloverは7日刻み設定する値hotのmax_ageは7d決め方7日ならmin_ageは83dまで取れる

3行目の7日を決めると、min_ageは60日以上83日以下になります。この記事では上限に近い83日を使います。max_ageを短くするほど、最短と最長の差が縮まります。反対にmax_ageを30日にすると、同じ90日の上限ではmin_ageは60日までです。

ポリシーJSONを書く

hotでrolloverし、warmへ移し、83日でdeleteするポリシーです。rolloverの条件は、primary shardのサイズとindexの年齢のどちらかです。

{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": {
            "max_primary_shard_size": "50gb",
            "max_age": "7d"
          }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": {
          "set_priority": { "priority": 50 }
        }
      },
      "delete": {
        "min_age": "83d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

max_primary_shard_sizeは、いちばん大きいprimary shardがこのサイズに達するとrolloverを起こします。似た名前のmax_sizeはindex全体のprimaryの合計で判定しますが、Elastic Stack 9.3で非推奨になりました。新しく書くならmax_primary_shard_sizeを使います。

条件はmax_*のいずれかが満たされれば発火します。max_*を1つも書かない空のrolloverは無効です。

warmのset_priorityは、再起動後に回復する順番を下げる設定です。hotに100、warmに50という値が例として挙がっています。warm用のノードを用意していない構成では、warmフェーズごと省いても構いません。ポリシーは、使うフェーズだけを書けます。

shardサイズと文書数の目安

50GBという値は、シャードサイズの推奨範囲(10〜50GB)の上端に合わせたものです。ログが少ないクラスターでは50GBに届く前にmax_ageが先に効きます。逆に量が多いと、7日より前にサイズで切れます。どちらで切れても、min_ageはrollover時刻から数えます。

文書数にも上限があります。ILMは、いずれかのshardが2億文書に達すると、条件に関係なくrolloverします。サイズの小さい文書が多いデータでは、50GBより先にこちらに当たることがあります。

テンプレートとデータストリームを用意する

ログにはデータストリームを使います。新規の構成ではデータストリームが推奨されていて、rolloverの設定が少なくて済みます。

{
  "index_patterns": ["logs-app-*"],
  "data_stream": {},
  "template": {
    "settings": {
      "number_of_shards": 1,
      "number_of_replicas": 1,
      "index.lifecycle.name": "logs-app-policy"
    }
  }
}

ポリシーは、テンプレートのindex.lifecycle.nameで結び付けます。時系列のindexでは、テンプレートに紐付けないとrollover後の新しいindexにポリシーが渡りません。手でポリシーを当てたindexは、rolloverしても新しいindexへ引き継がれません。

従来のエイリアス方式にも対応しています。その場合はindex名がmy-index-000001の形で、index.lifecycle.rollover_aliasを設定し、is_write_index: trueのindexが必要です。要件が増えるので、既存の構成を引き継ぐ場合に限る選択になります。

テスト用に遷移を数分へ縮める

本番と同じ日数では、結果が出るまで3か月かかります。検証用のポリシーは、日数を分に置き換えて作ります。構造を本番と揃えて、時間だけを縮めます。

ILMは周期的に条件を調べます。indices.lifecycle.poll_intervalは既定で10分です。10分周期のままだと、1分刻みの条件は意味を持ちません。テスト用のクラスターでだけ、この値を縮めます。

# テスト用クラスターだけ: ILMの確認周期を10秒にする
curl -s -X PUT localhost:9200/_cluster/settings \
  -H 'Content-Type: application/json' \
  -d '{"persistent":{"indices.lifecycle.poll_interval":"10s"}}'
 
# 検証用ポリシー(日数を分に置き換えた構造コピー)
curl -s -X PUT localhost:9200/_ilm/policy/logs-app-policy-test \
  -H 'Content-Type: application/json' \
  -d @policy-test.json
 
# 検証用テンプレート: logs-app-test*だけに検証用ポリシーを結び付ける
curl -s -X PUT localhost:9200/_index_template/logs-app-test-template \
  -H 'Content-Type: application/json' \
  -d '{
    "index_patterns": ["logs-app-test*"],
    "priority": 300,
    "data_stream": {},
    "template": {
      "settings": {
        "number_of_shards": 1,
        "number_of_replicas": 1,
        "index.lifecycle.name": "logs-app-policy-test"
      }
    }
  }'

policy-test.jsonは、max_ageを1m、warmのmin_ageを2m、deleteのmin_ageを4mにしただけのコピーです。検証用ポリシーは、テンプレートで結び付けて初めてデータストリームに効きます。logs-app-*の本番用テンプレートはlogs-app-testにも一致するので、検証用テンプレートのpriorityを本番用より高くして、そちらが選ばれるようにします(priorityが同じでindex_patternsが重なるテンプレートは作れません)。テンプレートはデータストリームを作る前に登録します。登録済みのデータストリームには遡って効かないので、すでにlogs-app-testを作ってしまった場合は、データストリームを削除してから作り直します。explainの応答のpolicyにlogs-app-policy-testと出ていれば、紐付けは成功です。rolloverは空のindexをスキップするので、文書を1件入れてから待ちます。

# 文書を1件入れてデータストリームを作る
curl -s -X POST localhost:9200/logs-app-test/_doc \
  -H 'Content-Type: application/json' \
  -d '{"@timestamp":"2026-10-28T00:00:00Z","message":"ilm test"}'
 
# 遷移の状況を見る
curl -s 'localhost:9200/.ds-logs-app-test-*/_ilm/explain?pretty'

explainの応答で見る項目は、phase、action、step、ageです。rolloverを待っている間は、stepがcheck-rollover-readyで止まります。ILMは1つの処理を完了するのに複数回の周期を使うことがあります。poll_intervalが10分でも、rolloverの完了まで20分かかる場合があると説明されています。

待ちたくないときは、rolloverだけをAPIで直接起こせます。

curl -s -X POST localhost:9200/logs-app-test/_rollover

rolloverすると、旧indexのmin_ageの起点がrollover時刻へ移ります。explainでageが0近くに戻っていれば、起点の仕様どおりです。

テストが終わったら、poll_intervalを元に戻します。nullを指定すると既定値に戻ります。

curl -s -X PUT localhost:9200/_cluster/settings \
  -H 'Content-Type: application/json' \
  -d '{"persistent":{"indices.lifecycle.poll_interval":null}}'

ポリシーを直したときの注意

indexがあるフェーズに入ると、ILMはそのフェーズの定義をindexのメタデータへキャッシュします。ポリシーを後から更新しても、すでにそのフェーズへ入ったindexには反映されない場合があります。直したポリシーを試すときは、新しいデータストリームを作り直すほうが確実です。

Claude Codeに任せる範囲を決める

ポリシーの数字は、保持要件から機械的に出せます。Claude Codeには計算と検証の手順を任せ、本番クラスターへの書き込みは任せない形にします。

最初にplan modeで、設計案だけを出させます。plan modeでは、Claudeがファイルを読んで計画を示し、承認するまでファイルを編集しません。

claude --permission-mode plan

指示は、要件と算術の根拠をそのまま渡します。

ログindexの保持要件は「60日は検索可、90日超は保持しない」。
hotのmax_ageを7dとしたとき、deleteのmin_ageの上限と下限を
min_age = rollover時刻からの経過、という前提で計算して。
計算根拠も書いて。まだファイルは変更しない。

承認後は、policy.json・template.json・検証用のpolicy-test.json・確認スクリプトを作らせます。CLAUDE.mdには、次のような規約を置いておくと毎回の説明が減ります。

## Elasticsearch ILM
- ポリシーの変更は必ず policy-test.json で先に検証する
- deleteのmin_age は rollover 時刻からの経過。作成日からではない
- 本番クラスターへ PUT しない。適用コマンドは出力して人が実行する

ILMは、ポリシーを最後に更新したユーザーの権限で動きます。ポリシーを流し込むAPIキーには、必要なindexへの権限だけを持たせます。検証環境と本番環境でキーを分けると、検証用の誤操作が本番へ届きません。

Claude Code側では、本番のURLやAPIキーを含むファイルのReadをdenyルールで止めておきます。許可と拒否の書き方はClaude CodeでCloudWatch Logsの費用を下げるの権限設定と同じ型で、読み取りだけを許可する形です。

つまずきやすい点

落とし穴

保持期間が想定より長くなる

min_ageをrollover時刻ではなく作成日からの経過と読んでいると、最大でmax_ageぶん長く残ります。上の算術のとおり、最長はmax_ageとmin_ageの合計です。

  • rolloverが起きない: 空のindexは、max_ageを過ぎてもrolloverされません。min_docs: 0を条件に入れると上書きでき、クラスター全体ではindices.lifecycle.rollover.only_if_has_documentsをfalseにします
  • 時間刻みのrolloverで小さなindexが増える: 時刻だけで切ると小さなindexが大量にでき、性能と資源に響きます。サイズ・文書数・年齢のいずれかで切るほうが好まれます
  • クラスターがyellowのとき: 未割り当てshardがあっても、indexは次のフェーズへ進みます。ただし一部の後始末はgreenでしかできず、想定外の副作用が出る場合があります
  • searchable snapshotを使うとき: deleteフェーズのdelete_searchable_snapshotは既定でtrueです。falseにするとスナップショットが残るので、別途削除が要ります。deleteの前に手でindexを消した場合も、スナップショットは残ります

まとめ

ILMの設計は、保持要件を「最低何日残すか」と「最長何日で消すか」の2本に分けると決まります。max_ageが短いほど、この2本の差が縮まります。

テスト用のポリシーに日数を分へ置き換えたコピーを使うと、hotからdeleteまでの遷移を数分で確かめられます。直したポリシーは、すでにフェーズへ入ったindexに反映されない場合があるので、検証のたびに新しいデータストリームで試してください。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/common-workflows
  2. elastic.co/docs/manage-data/lifecycle/index-lifecycle-management/inde…
  3. elastic.co/docs/manage-data/lifecycle/index-lifecycle-management/roll…
  4. elastic.co/docs/reference/elasticsearch/index-lifecycle-actions/ilm-r…
  5. elastic.co/docs/manage-data/lifecycle/index-lifecycle-management/tuto…
  6. elastic.co/docs/reference/elasticsearch/configuration-reference/index…
この記事を共有:XはてブLinkedIn