Designing ISM policies for CCR follower indices
This walkthrough authors a single follower-only Index State Management (ISM) policy from scratch: one that manages a read-only Cross-Cluster Replication (CCR) follower’s lifecycle using only allocation and metadata actions, and gates its final delete on replication having been stopped first. It is the hands-on procedure behind the design principles in Follower Policy Design, aimed at operators who need a follower cluster to tier and expire its replicas without ever attempting a write the follower will reject.
The goal is a policy you can attach to a logs-follower-* index and trust to progress to Completed rather than parking in Failed on the first rollover or force_merge. Every step below is written so the policy contains zero write actions and the only shard-topology change — deletion — happens after the follower is no longer a follower.
Prerequisites
Confirm each item before authoring the policy — a missing write alias is irrelevant here, but an unstopped-replication assumption or a leader-shaped template is the usual reason a first attempt fails.
How the policy stays follower-safe
The policy’s safety comes from what it omits. A follower’s write block rejects any action that mutates segments or shard layout, so the entire policy is built from allocation (routing metadata), read_only (a metadata block), and a delete reached only after an intermediate stop state. The diagram traces the one authoring decision that matters at each action: is this write-safe on a follower?
The one subtlety the diagram hides is precedence. When both an ism_template on the follower cluster and an inherited plugins.index_state_management.policy_id setting (replicated from the leader’s index settings) point at a policy, the explicitly-attached policy wins, but a leaked leader policy_id in the replicated settings is a common way a leader-shaped policy silently re-attaches after a follower is re-bootstrapped. The procedure below removes that ambiguity by attaching the follower policy explicitly and scoping it with a high-priority template, so neither an inherited setting nor a broad catch-all template can reintroduce a write action.
Step-by-step procedure
The steps build the policy in the order you reason about it: first inventory what the leader does, then translate only the follower-safe actions into tiering states, add the delete gate, assemble and scope the document, and finally attach it to indices that already exist.
1. Inventory the actions your leader policy uses
List the actions in the policy attached to the leader so you know which ones must stay on the leader and which have follower-safe equivalents. Anything that writes segments or shard topology is disqualified from the follower policy.
curl -s "https://<leader>:9200/_plugins/_ism/policies/logs_leader_policy?pretty" \
| grep -oE '"(rollover|force_merge|shrink|allocation|snapshot|delete|read_only)"'
"rollover"
"force_merge"
"allocation"
"delete"
Gotcha:
rolloverandforce_mergein this output stay on the leader — CCR replicates their results down. Onlyallocationand a gateddeletecarry over to the follower policy.
2. Draft the follower states with allocation only
Write the two tiering states. Each uses allocation with wait_for: false so a temporarily full tier does not block the ISM worker; the next sweep re-attempts placement. This is the same non-blocking approach used for leaders, but here it is the only work these states do.
{
"states": [
{
"name": "replicating",
"actions": [
{ "allocation": { "require": { "data": "warm" }, "wait_for": false } }
],
"transitions": [
{ "state_name": "cold_hold", "conditions": { "min_index_age": "14d" } }
]
},
{
"name": "cold_hold",
"actions": [
{ "allocation": { "require": { "data": "cold" }, "wait_for": false } }
],
"transitions": [
{ "state_name": "stop_follower", "conditions": { "min_index_age": "90d" } }
]
}
]
}
Gotcha: do not add a
force_mergetocold_hold“to save space” — it is the single most common way a follower policy fails. Merge on the leader; the merged segments replicate down to the follower for free.
3. Add the stop-follower gate and the delete state
The stop_follower state parks the index for a settling day and sets a read_only metadata block (safe on a follower). The delete gate is enforced by the controller from Follower Policy Design, which stops replication for any follower in this state before ISM’s delete transition fires.
{
"name": "stop_follower",
"actions": [ { "read_only": {} } ],
"transitions": [
{ "state_name": "delete", "conditions": { "min_index_age": "91d" } }
]
},
{
"name": "delete",
"actions": [ { "delete": {} } ]
}
Gotcha: the one-day gap between day 90 and day 91 is not cosmetic — it is the window the replication-stop controller needs to run and confirm the write block is gone. Collapse it and
deletecan fire while the follower is still replicating.
4. Assemble, register the policy, and scope it with an ism_template
Combine the states into a full policy document and PUT it, then bind it to the follower pattern with an ism_template so a leader-shaped policy can never auto-attach to a follower.
PUT _plugins/_ism/policies/follower_safe_policy
{
"policy": {
"description": "Follower-only lifecycle: allocation tiering + replication-gated delete",
"default_state": "replicating",
"ism_template": { "index_patterns": ["logs-follower-*"], "priority": 50 },
"states": [ ... states from steps 2 and 3 ... ]
}
}
Gotcha: give the follower template a higher
prioritythan any broad catch-all template, or a genericlogs-*policy will win the match and attach leader actions to your follower.
5. Attach to existing followers and confirm the initial state
ism_template only auto-attaches on index creation, so any follower that was already replicating when you PUT the policy is unmanaged until you add it explicitly. Batch-add across the whole pattern in one call:
POST _plugins/_ism/add/logs-follower-*
{ "policy_id": "follower_safe_policy" }
{ "updated_indices": 7, "failures": false }
Gotcha:
updated_indicesshould equal the number of live followers. A lower count means some followers already carry a differentpolicy_id(often a leaked leader policy) and were skipped — remove that policy first withPOST _plugins/_ism/remove/<follower>, then re-add.
Verification
Confirm the policy attached and that its first action is a clean allocation, not a failed write.
curl -s "https://<follower>:9200/_plugins/_ism/explain/logs-follower-000007?pretty"
{
"logs-follower-000007": {
"index.plugins.index_state_management.policy_id": "follower_safe_policy",
"state": { "name": "replicating" },
"action": { "name": "allocation", "failed": false },
"step": { "name": "attempting_to_apply_allocation", "step_status": "completed" }
}
}
Cross-check that replication is still healthy — the policy must not have disturbed it:
curl -s "https://<follower>:9200/_plugins/_replication/logs-follower-000007/_status?pretty"
{ "status": "SYNCING", "leader_alias": "dc-primary", "leader_index": "logs-000007" }
A healthy result is failed: false on the allocation action and SYNCING (or SYNCED) replication status. If the action shows failed: true naming rollover or force_merge, a leader-shaped policy is still attached — remove it and re-add the follower policy.
Common failures
| Symptom | Root cause | Fix command |
|---|---|---|
Action failed: true on rollover/force_merge |
Leader-shaped policy attached to the follower | POST _plugins/_ism/remove/<follower> then re-add follower_safe_policy |
| Policy never auto-attaches to new followers | Missing or low-priority ism_template |
Re-PUT policy with ism_template.priority above the catch-all template |
Follower UNASSIGNED after cold_hold |
Cold tier full or missing data: cold nodes |
PUT <follower>/_settings {"index.routing.allocation.include.data":"warm,cold"} |
delete transition never completes |
Replication still active; write block present | Stop replication via the controller, then POST _plugins/_ism/retry/<follower> |
security_exception attaching the policy |
Service account lacks cluster:admin/opendistro/ism/* |
Grant the ISM action per Security & Access Boundaries |
Frequently asked questions
Can I reuse my leader policy on the follower if I just delete the write actions?
You can, and that is effectively what this procedure produces — but do it deliberately, not by trimming a copy. Start from the follower-safe vocabulary (allocation, read_only, snapshot, gated delete) so you never accidentally leave a force_merge behind. A single stray write action parks the whole managed index in Failed.
Why `read_only` in the stop_follower state if the follower is already write-blocked?
read_only sets an ISM-owned metadata block that persists after replication is stopped and the engine’s own write block is lifted. It keeps the index read-only during the settling day between stopping replication and deleting, so nothing writes to the briefly-writable index before it is removed.
Does the follower policy need to match the leader's retention exactly?
No. The follower can retain longer (for local DR) or shorter (to save follower-cluster disk) than the leader. Only the delete gate is non-negotiable: it must sit behind a stopped-replication check regardless of the retention numbers you choose.
Related
- Follower Policy Design — the overview, state machine, and the replication-stop controller this policy depends on.
- Resolving ISM write-block conflicts on follower indices — what to do when an action has already failed against the follower write block.
- CCR Setup and Bootstrap — establishing the remote connection and starting the replication this policy manages.