Converting Cold Indices to Searchable Snapshots via ILM
Add a searchable_snapshot action to the cold (and optionally frozen) phase of an existing ILM policy so metrics-* indices that have already aged onto the cold tier convert themselves into repository-backed searchable snapshots — automatically, on the next lifecycle step, with no manual mount.
Doing it by hand with the mount API works for one-off promotion, but it does not scale to a fleet of aging indices or survive the next new index that rolls over. Baking the conversion into the policy makes it a standing rule: every index that reaches the cold phase snapshots into your repository and remounts as a restored- index, shedding its primary-storage footprint while staying fully queryable. This page is the automatic counterpart to mounting a searchable snapshot to the frozen tier; the storage-mode mechanics it relies on are covered under searchable snapshots and the frozen tier, within the wider Snapshot Lifecycle Management & searchable snapshots topic.
Prerequisites
- Elasticsearch 8.x with
data_coldnodes (anddata_frozennodes, if you add a frozen phase) carrying the right roles; frozen nodes additionally needxpack.searchable.snapshot.shared_cache.sizeset. cold-archive-repoalready registered and reachable — verify withPOST _snapshot/cold-archive-repo/_verifybefore editing the policy, or the searchable-snapshot step will fail at runtime.elasticsearch-pyv8.0+ forclient.ilm.put_lifecycle.manage_ilmcluster privilege on the account applying the policy.
Implementation
The change is a phase-level edit: add a searchable_snapshot action to the cold phase (and, if you want a deeper tier, the frozen phase) of the policy that already manages your metrics-* indices. Because PUT _ilm/policy replaces the whole policy, include the phases you want to keep — this is not a partial patch.
PUT _ilm/policy/metrics-archive
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": { "max_primary_shard_size": "50gb" }
}
},
"cold": {
"min_age": "30d",
"actions": {
"searchable_snapshot": {
"snapshot_repository": "cold-archive-repo",
"force_merge_index": true,
"storage": "full_copy"
}
}
},
"frozen": {
"min_age": "90d",
"actions": {
"searchable_snapshot": {
"snapshot_repository": "cold-archive-repo",
"storage": "shared_cache"
}
}
},
"delete": {
"min_age": "365d",
"actions": { "delete": {} }
}
}
}
}Two behaviours make this work cleanly. First, once the cold-phase snapshot is taken and mounted, ILM deletes the original index and continues managing the mounted restored-metrics-* copy in its place — you do not end up with two copies of the data. Second, because the cold and frozen phases name the same snapshot_repository, ILM reuses the snapshot the cold phase already wrote when the index later reaches the frozen phase, remounting it as partial-metrics-* with shared_cache storage rather than snapshotting a second time. Pointing the two phases at different repositories throws that saving away.
The same edit from Python v8, applied idempotently:
import logging
from elasticsearch import Elasticsearch, ApiError
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("ilm_convert")
def add_searchable_snapshot(es: Elasticsearch, policy_id: str, repo: str) -> bool:
"""Apply a metrics-archive policy whose cold and frozen phases mount searchable snapshots."""
policy = {
"phases": {
"hot": {"actions": {"rollover": {"max_primary_shard_size": "50gb"}}},
"cold": {
"min_age": "30d",
"actions": {
"searchable_snapshot": {
"snapshot_repository": repo,
"force_merge_index": True,
"storage": "full_copy",
}
},
},
"frozen": {
"min_age": "90d",
"actions": {
"searchable_snapshot": {
"snapshot_repository": repo,
"storage": "shared_cache",
}
},
},
"delete": {"min_age": "365d", "actions": {"delete": {}}},
}
}
try:
es.ilm.put_lifecycle(name=policy_id, policy=policy)
logger.info("Policy '%s' updated with cold + frozen searchable_snapshot.", policy_id)
return True
except ApiError as exc:
logger.error("Failed to update policy (%s): %s", exc.status_code, exc.info)
return False
if __name__ == "__main__":
es = Elasticsearch(
"https://es-cluster-01:9200",
api_key="YOUR_BASE64_ENCODED_API_KEY",
verify_certs=True,
)
add_searchable_snapshot(es, "metrics-archive", "cold-archive-repo")A policy update does not retroactively rewind an index that has already moved past the cold phase, and it does not interrupt one mid-step. Each managed index picks up the new phase definition at its next lifecycle step evaluation — so an index currently sitting in the cold phase adopts the new cold actions when it next advances, and indices still in hot simply carry the updated definition forward. There is no need to reattach the policy; managed indices always read the current version.
Verification
Confirm an index is actually running the searchable-snapshot conversion by asking ILM where it is. The explain output names the phase, action, and step:
GET metrics-000031/_ilm/explain{
"indices": {
"metrics-000031": {
"managed": true,
"policy": "metrics-archive",
"phase": "cold",
"action": "searchable_snapshot",
"step": "mount-snapshot"
}
}
}A step of mount-snapshot (or the surrounding wait-for-green / copy-execution-state steps) means the conversion is in flight. Once it completes, the original metrics-000031 is gone and a mounted index appears under the tier’s prefix — restored- for the cold full_copy mount, partial- for a frozen shared_cache mount. List them:
GET _cat/indices/restored-metrics-*,partial-metrics-*?v&h=index,health,status,store.sizeSeeing restored-metrics-000031 (or partial-metrics-000031 after it reaches the frozen phase) confirms the conversion landed, and the reduced store.size reflects that the data is now served from the repository rather than held on primary storage.
Gotchas and edge cases
force_merge_indexinflates the first snapshot. Withforce_merge_index: true(the default) ILM force-merges the index toward a single segment before snapshotting, which speeds later queries but rewrites every segment — so the snapshot copies everything, not just changed segments. If a warm phase already force-merged the index, that cost is spent; if not, budget for a large first write tocold-archive-repo.- You cannot change
storageafter the mount. The storage mode is baked into the mounted index at mount time. Editing the policy fromfull_copytoshared_cachewill not convert an index that has already mounted asfull_copy; only indices that reach the phase after the edit use the new mode. To change an existing mount you must remount it. - The update applies at the next step, not immediately. A policy change never yanks an index out of its current step. Indices adopt the new phase definitions when they next advance, so do not expect an already-cold index to convert the instant you press save — it converts on its next lifecycle evaluation, bounded by
indices.lifecycle.poll_interval. - The repository must exist before the step runs. ILM does not validate the repository when you save the policy — it fails at the
mount-snapshotstep at runtime ifcold-archive-repois unregistered or unreachable, leaving the index stuck in anERRORstep. Verify the repository first, and register it read-write for the ILM account.
FAQ
Will updating the policy convert indices that are already cold?
Yes, but on their own schedule. A managed index reads the current policy version and picks up the new cold-phase searchable_snapshot action at its next lifecycle step evaluation, so an index already sitting in the cold phase converts when it next advances — not the instant you save. Indices still in hot or warm carry the updated definition forward and convert when they reach the cold phase.
Does ILM keep the original index after mounting the snapshot?
No. Once the cold-phase snapshot is taken and successfully mounted, ILM deletes the original index and continues managing the mounted restored- copy in its place. You do not end up storing the data twice. The underlying snapshot in the repository is what backs the mounted index from then on.
Do the cold and frozen phases take two separate snapshots?
Not when they name the same snapshot_repository. ILM reuses the snapshot the cold phase already wrote when the index later reaches the frozen phase, remounting it with shared_cache storage instead of snapshotting again. Pointing the two phases at different repositories forces a second, redundant snapshot, so keep them on one repository.
Related
- Mounting a Searchable Snapshot to the Frozen Tier — the manual counterpart for one-off promotion of a snapshot.
- Building Custom ILM Policies via the API — authoring and versioning the policy-and-template pair this action lives in.
← Back to Searchable Snapshots & the Frozen Tier · Snapshot Lifecycle Management & Searchable Snapshots