Preventing split-brain during CCR network partitions

This runbook keeps exactly one writer alive while a Cross-Cluster Replication (CCR) link is partitioned, then promotes the follower only after the old leader is provably fenced — the sequence that stops a partition from forking one index into two divergent histories. It is the step-by-step operator procedure behind the model in Split-Brain Prevention.

Prerequisites

Confirm each item before a partition happens — this runbook is executed under pressure, and a missing prerequisite is discovered at the worst possible moment.

Decision flow

The whole runbook is one decision: promote the follower only if the old leader is fenced, otherwise wait. The flow below is the shape of that decision — every step in the procedure maps to one node in it.

CCR partition decision flow: fence before promote Flowchart. Partition detected leads to a decision on whether the old leader is still reachable by application traffic. If reachable, apply the fence (write block plus drop seeds) and loop back to re-check. Once unreachable or fenced, a second decision checks whether the follower is caught up within the lag budget. If not caught up, wait and do not promote. If caught up, stop replication, repoint the write alias to the former follower, and serve writes. On link heal, re-bootstrap the old leader as a follower. Partition detected Old leader still reachable? Fence old leader write block + drop seeds Follower caught up in budget? Wait · do not promote let follower catch up Stop & promote follower repoint write alias On heal: old leader → follower yes no / fenced no yes

Step-by-step procedure

1. Detect the partition and freeze the promotion instinct

Before touching anything, confirm the partition is real and not a slow link. Check replication status from the follower side and the remote connection health.

Shell
GET _plugins/_replication/logs-000001/_status
GET _remote/info
Text
# follower _status during a partition
"status" : "SYNCING",
"syncing_details" : { "leader_checkpoint" : 90418, "follower_checkpoint" : 90418 }
# _remote/info
"dc-primary" : { "connected" : false, "num_nodes_connected" : 0 }

Gotcha: a follower still reports SYNCING immediately after a partition because it does not yet know the leader is gone — "connected": false from _remote/info is the authoritative partition signal, not the replication status.

2. Fence the old leader before promoting anything

If the old leader is still reachable by application traffic, write-block it locally so it cannot accept another write. This is settable from the leader’s own side without the follower’s participation.

HTTP
PUT logs-000001/_block/write
Text
{ "acknowledged" : true, "shards_acknowledged" : true, "indices" : [ { "name" : "logs-000001", "blocked" : true } ] }

Gotcha: if the old leader is unreachable from your operator station too, fencing by write block is impossible — rely on the dropped-seeds step on the follower side and your arbiter’s decision, and never promote on the assumption that “unreachable to me” means “writing to no one”.

3. Confirm the follower is caught up within the lag budget

Promoting a lagging follower loses acknowledged writes. Check the checkpoint gap and only proceed if it is within your budget.

Shell
GET _plugins/_replication/logs-000001/_status
Text
"syncing_details" : { "leader_checkpoint" : 90418, "follower_checkpoint" : 90418 }
# gap = 0 ops -> zero-loss promotion is possible

Gotcha: the last checkpoint the follower saw is the last one before the partition — a zero gap here means no acknowledged writes are lost, but writes the old leader accepted after the partition are gone unless the fence let you recover them. That is expected and correct: those post-partition writes are exactly what fencing discards to keep one history.

4. Stop replication and promote

With the leader fenced and the follower caught up, stop replication to make the former follower writable, then repoint the write alias.

HTTP
POST _plugins/_replication/logs-000001/_stop
{
  "leader_alias": "dc-primary",
  "leader_index": "logs-000001"
}
HTTP
POST _aliases
{
  "actions": [
    { "add": { "index": "logs-000001", "alias": "logs-write", "is_write_index": true } }
  ]
}

Gotcha: the _stop body must repeat the exact leader_alias and leader_index the replication was started with; a mismatch leaves the follower write-blocked and un-promotable, which under partition pressure looks identical to a hung cluster.

5. Reconcile the old leader as a follower after heal

When the link returns, the old leader rejoins as a follower against the new leader. Drop its stale role and start replication in the reverse direction.

HTTP
POST _plugins/_replication/logs-000001/_start
{
  "leader_alias": "dc-standby",
  "leader_index": "logs-000001",
  "use_roles": { "leader_cluster_role": "ccr_leader", "follower_cluster_role": "ccr_follower" }
}

Gotcha: if retention on the new leader still covers the old leader’s last checkpoint, this _start resumes incrementally; if not, it silently falls back to a full remote bootstrap. Watch the status for BOOTSTRAPPING versus SYNCING to tell which path you got.

Verification

Confirm exactly one writer exists and replication now flows the new direction.

Shell
# Only the promoted cluster should own the write alias
GET logs-write/_alias
# promoted cluster: { "logs-000001": { "aliases": { "logs-write": { "is_write_index": true } } } }
# fenced cluster:   write block present, alias NOT is_write_index
Shell
# Reverse replication is healthy
GET _plugins/_replication/logs-000001/_status
# "status": "SYNCING" with a shrinking checkpoint gap

A correct end state has the write alias resolving to one cluster only, the other cluster either write-blocked or replicating as a follower, and a bounded, shrinking checkpoint gap.

Common failures

Symptom Root cause Fix command
Both clusters own logs-write Follower promoted without fencing the old leader PUT logs-000001/_block/write on the losing side; discard its post-partition writes
_stop returns 400 leader_alias/leader_index in the body mismatch the original _start Re-issue _stop with values from the original replication _status
Reverse _start triggers full bootstrap Retention on the new leader aged out the old leader’s checkpoint Accept the bootstrap now; raise index.plugins.replication.translog.retention_size for next time
Follower promoted but writes rejected Alias repoint missed is_write_index: true Re-run POST _aliases with is_write_index: true on the promoted index
Old leader keeps taking writes after heal Its write block was never set during the partition PUT logs-000001/_block/write then reconcile from the fenced side

Frequently asked questions

What if I cannot reach either cluster to fence the old leader?

Then you are on the minority side of the partition and must not promote. Fencing requires that you can either set a write block on the old leader or provably cut it off (dropped seeds plus an arbiter decision). If you can do neither, promoting the follower risks a live old leader still taking writes — the exact split-brain condition. Wait for reachability or defer to the arbiter that names the authoritative side.

How current does the follower have to be before I promote it?

Within your lag budget, expressed as a checkpoint gap. A zero gap means no acknowledged writes are lost. Any writes the old leader accepted after the partition are discarded by design — fencing chooses one history, and the follower’s is the one that survives. If your tolerance for lost post-partition writes is zero, you cannot fail over during an active partition at all; you must wait for the link to heal.

Why repoint a write alias instead of just writing to the promoted cluster directly?

Because the alias is the single point that defines “the writer”. If applications write to named cluster endpoints, a partition leaves half of them still writing to the old leader — that is split-brain by configuration. Routing every write through logs-write means promotion is one atomic repoint and there is never a moment when two endpoints are both “the writer”.

Up: Split-Brain Prevention