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/_rolloverrolloverすると、旧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に反映されない場合があるので、検証のたびに新しいデータストリームで試してください。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- code.claude.com/docs/en/common-workflows
- elastic.co/docs/manage-data/lifecycle/index-lifecycle-management/inde…
- elastic.co/docs/manage-data/lifecycle/index-lifecycle-management/roll…
- elastic.co/docs/reference/elasticsearch/index-lifecycle-actions/ilm-r…
- elastic.co/docs/manage-data/lifecycle/index-lifecycle-management/tuto…
- elastic.co/docs/reference/elasticsearch/configuration-reference/index…