Tuning CCR checkpoint retention to avoid remote resync

When a Cross-Cluster Replication (CCR) follower briefly disconnects, whether it resumes from where it left off or re-copies the entire index across the link is decided by one thing: whether the leader still retains the operations the follower last acknowledged. This procedure sizes leader soft-deletes and translog retention so a short partition costs an incremental catch-up, not a full remote bootstrap.

Prerequisites

Confirm these before tuning — retention that is too small silently degrades to a resync, and retention that is too large wastes leader disk on operations no follower will ever request.

How retention decides resume versus resync

CCR is a translog-following mechanism: the follower pulls operations from the leader starting at the checkpoint sequence number it last acknowledged. The leader can only serve an operation that still exists — soft-deletes and translog retention are what keep historical operations alive past the point they would normally be merged away. If the follower reconnects while its last checkpoint is still retained, replication resumes from that sequence number. If the checkpoint has aged out, CCR has no incremental starting point and falls back to a full remote bootstrap, re-streaming every segment.

The retention you must keep is the number of operations that can accumulate during the longest partition you want to survive. With a peak write rate RR in operations per second and a target partition window TT in seconds, the minimum operation count to retain is:

Nops=R×T×(1+β)N_{\text{ops}} = R \times T \times (1 + \beta)

where β\beta is a safety margin (typically 0.20.20.50.5) covering burst above the measured peak. Retention is configured as a byte budget, so convert with the mean operation size sˉ\bar{s} in bytes:

Sretain=Nops×sˉ=R×T×(1+β)×sˉS_{\text{retain}} = N_{\text{ops}} \times \bar{s} = R \times T \times (1 + \beta) \times \bar{s}

The incremental resume path holds only while the follower’s disconnection stays inside the retained window. Expressed as lag, if the follower is behind by LL operations when it reconnects, resume succeeds precisely when LNopsL \le N_{\text{ops}}; beyond that boundary the operations it needs are gone and a resync is unavoidable.

Follower lag versus retention budget over a partition Line chart. The x-axis is time since the partition began; the y-axis is follower lag in operations. A horizontal dashed line marks the retained-operations budget. Follower lag rises linearly from the origin. Below the budget line the region is labelled resume zone (incremental catch-up). Above it the region is labelled resync zone (full remote bootstrap). The lag line crosses the budget at the maximum survivable partition window, marked on the x-axis. time since partition → follower lag (ops) retained-ops budget N_ops follower lag = R × t T_max resync zone · full bootstrap resume zone · incremental

The crossing point Tmax=Nops/RT_{\text{max}} = N_{\text{ops}} / R is the maximum partition you can survive on the current budget — the single number this whole exercise produces. Sizing retention is the recovery-cost half of the fencing discipline in Preventing split-brain during CCR network partitions; a clean fenced failover is wasted if reconnection then forces a full resync across an already-stressed link.

Step-by-step procedure

1. Measure the peak write rate and mean operation size

Pull indexing throughput and document counts so RR and sˉ\bar{s} are measured, not guessed.

Shell
GET logs-000001/_stats/indexing,store
Text
"indexing" : { "index_total" : 41880000, "index_time_in_millis" : 6980000 },
"store" : { "size_in_bytes" : 268435456000 }
# R  ≈ index_total / uptime_seconds  (ops/s at peak, not average)
# s̄ ≈ size_in_bytes / index_total    (mean bytes per operation)

Gotcha: use the peak rate, not the lifetime average from _stats — a nightly batch load can be ten times the daytime mean, and retention sized to the average resyncs every time the batch window overlaps a partition.

2. Compute the retention byte budget

Apply the formula with your target window TT and safety margin β\beta. For R=6,000R = 6{,}000 ops/s, T=1800T = 1800 s (30 min), β=0.3\beta = 0.3, and sˉ=512\bar{s} = 512 bytes: Nops=6000×1800×1.31.40×107N_{\text{ops}} = 6000 \times 1800 \times 1.3 \approx 1.40 \times 10^{7} ops, so Sretain1.40×107×5127.2S_{\text{retain}} \approx 1.40 \times 10^{7} \times 512 \approx 7.2 GB.

Text
N_ops     = 6000 * 1800 * 1.3      = 14,040,000 ops
S_retain  = 14,040,000 * 512 bytes ≈ 7.2 GB
T_max     = 14,040,000 / 6000      = 2340 s (39 min headroom incl. margin)

Gotcha: retention is per-shard-aware but you configure a byte budget per index setting; if the index has many primaries, the effective per-shard retention is the budget divided across shards, so size against the busiest shard’s share of RR, not the index total blindly.

3. Apply retention on the leader index

Set the translog retention byte budget and the soft-deletes retention lease period. Both must be generous enough to cover TmaxT_{\text{max}}.

HTTP
PUT logs-000001/_settings
{
  "index.plugins.replication.translog.retention_size": "8gb",
  "index.soft_deletes.retention_lease.period": "45m"
}
Text
{ "acknowledged" : true }

Gotcha: round the byte budget up from the computed SretainS_{\text{retain}} (7.2 GB → 8 GB here) and set the lease period at or above TmaxT_{\text{max}} (39 min → 45 min) so the two limits agree; the tighter of the two is what actually bounds recovery, and a mismatched pair silently caps you at the smaller window.

4. Verify the retention lease is held for the follower

A retention lease is the leader’s promise to keep operations for a specific follower. Confirm the lease exists and its retained sequence number trails the follower’s checkpoint.

Shell
GET logs-000001/_stats?level=shards&filter_path=**.retention_leases
Text
"retention_leases" : { "leases" : [
  { "id" : "peer_recovery/ccr_follower", "retaining_seq_no" : 90190, "source" : "ccr" }
] }

Gotcha: if retaining_seq_no is far behind the leader’s current checkpoint, that is healthy — it means the leader is holding history back for the follower. A retaining_seq_no that jumps forward to near-current means the lease expired and was renewed from scratch, which is the resync signal.

Verification

Prove that a short disconnect resumes instead of bootstrapping.

Shell
# Pause then resume replication to simulate a brief disconnect
POST _plugins/_replication/logs-000001/_pause
POST _plugins/_replication/logs-000001/_resume
GET  _plugins/_replication/logs-000001/_status
Text
"status" : "SYNCING"        <- resumed incrementally; a full resync would show BOOTSTRAPPING
"syncing_details" : { "leader_checkpoint" : 90512, "follower_checkpoint" : 90480 }

A resume shows SYNCING with a shrinking checkpoint gap and no bootstrap phase. If you see BOOTSTRAPPING, retention aged out during the pause — raise the budget and lease period and retest.

Common failures

Symptom Root cause Fix command
Resume triggers BOOTSTRAPPING Retention byte budget smaller than ops accumulated during the gap PUT logs-000001/_settings {"index.plugins.replication.translog.retention_size":"<larger>"}
Lease expired mid-partition index.soft_deletes.retention_lease.period shorter than the partition Raise the period to at least TmaxT_{\text{max}}; one resync re-establishes the lease
Leader disk filling from retention Budget sized to lifetime average with no upper cap during a long follower outage Lower the budget to a real TmaxT_{\text{max}}; stop replication for a permanently-dead follower to release its lease
Per-shard retention too small on a busy shard Budget divided across many primaries under-serves the hot shard Recompute against the busiest shard’s share of RR; raise the index budget
Resume slow but not bootstrapping Recovery chunk size throttled PUT logs-000001/_settings {"index.plugins.replication.follower.recovery.chunk_size":"<larger>"}

Frequently asked questions

What is the cost of setting retention very high to be safe?

Leader disk. Every retained operation is a segment the leader cannot merge away until the follower acknowledges past it or the lease expires. Oversized retention is mostly wasted headroom, and during a long follower outage it can fill the leader’s disk and trip the flood-stage watermark, turning the index read-only. Size to a real maximum partition window rather than an arbitrarily large value, and release the lease by stopping replication for a follower that is never coming back.

Does translog retention or the soft-deletes lease govern the resume window?

Whichever is tighter. Translog retention size bounds the operations kept by byte budget; the soft-deletes retention lease period bounds them by time. CCR needs both to still cover the follower’s last checkpoint, so the effective survivable window is the smaller of the two. Set them as a consistent pair — a byte budget covering thirty minutes paired with a five-minute lease still resyncs after five minutes.

Can I change retention without restarting or re-bootstrapping the index?

Yes. Both index.plugins.replication.translog.retention_size and index.soft_deletes.retention_lease.period are dynamic index settings applied with PUT _settings, and they take effect on the next retention evaluation without a restart. Raising them before a planned maintenance partition is a safe, reversible way to widen the resume window for the duration.

Up: Split-Brain Prevention