JusDB
Free DB Audit
NoSQL Databases

Replacing a Failed Valkey Cluster Node Without Wiping the Shard

Replace a failed Valkey Cluster node without promoting an empty replica: how elections pick a winner, what 9.0 to the 9.2 RC changed, and a runbook that checks offsets first.

JusDB Team
Published November 4, 2026
Updated November 10, 2026
18 min read

A Valkey Cluster node dies, you add a replacement replica, and before it finishes its initial sync the shard's primary fails too. The new replica has a replication offset of 0, yet nothing in the election stops it. The primaries vote for it, it promotes itself, and the shard comes back online serving zero keys. CLUSTER INFO reports cluster_state:ok, and when the old primary returns it rejoins as a replica of the empty node and throws away its own copy. Valkey 9.0, 9.0.5 and the 9.2 release candidates each narrow this window. This guide explains the failure mode, how to remove a dead node ID, and a replacement runbook that checks offsets before any failover.

TL;DR
  • Primaries do not compare data when they vote. A replica at replication offset 0, one that has never completed a sync, can win the election and turn its shard into an empty primary.
  • Valkey 9.0 adds a best-effort 500 ms election delay for empty replicas, and 9.0.5 and 9.1.0 fix a stale-packet bug that could create an empty primary. Run 9.1.3 or 9.0.7.
  • The 9.2 release candidates add cluster-replica-no-failover if-empty, which stops an empty replica from starting an automatic failover. As of November 2026 it is in no GA release.
  • Remove a dead node with valkey-cli --cluster del-node on 8.0.10, 8.1.8, 9.0.5, 9.1.0 or later. On older patches, run CLUSTER FORGET on every node within cluster-blacklist-ttl (60 seconds by default).
  • Order: check the shard still has data, remove the dead ID, add the new replica, wait until its offset matches the primary's, then fail over.

Scope and review date: November 2026. This post covers self-managed Valkey Cluster on 8.0, 8.1, 9.0 and 9.1, with notes on the 9.2 release candidates (9.2.0-rc2, published 4 November 2026, is the newest). Versions, defaults and log messages were checked against Valkey's documentation, GitHub release notes and source on 10 November 2026. The commands were not run in a lab, so rehearse them on a staging cluster first.


How can an empty replica win a Valkey Cluster election?

When a primary is marked FAIL, each of its replicas schedules an election and asks every slot-owning primary for a vote. The Valkey Cluster specification is blunt about how votes are granted: "Primaries make no effort to select the best replica in any way." A primary votes for the first replica that asks, as long as that replica's primary is in FAIL and it has not already voted in this round.

Only timing favours the replica with the most data: replicas rank themselves by replication offset, and the best-ranked one asks first. That ranking is best effort. A replica that joined seconds ago may not know the others yet, and a lone surviving replica has nobody to outrank it. The pull request that added the 9.2 guard, #4425, says empty-replica promotion "is especially likely when it is the only surviving replica". That is exactly the state a shard is in halfway through a node replacement.

How an empty replica takes over a shard

Follow the numbers; nothing in steps 2 to 4 compares how much data the replica holds

Empty replica promotion in Valkey Cluster Step 1: the shard's primary fails and is marked FAIL. Step 2: the only surviving replica, added recently and still at replication offset 0, schedules an election. Step 3: the primaries vote for it without comparing data. Step 4: it becomes primary of the shard's slots with no keys. Step 5: the old primary returns and rejoins as its replica. Step 6: a full sync from the empty primary discards the old primary's data. Primary fails, marked FAIL New replica offset 0, still syncing Election votes, no data check Empty primary owns slots, 0 keys Old primary rejoins as replica Full sync old copy discarded 1 2 3 4 5 6 after step 4: every slot covered, cluster_state ok, shard data gone

Silent by design: after step 4 the cluster looks healthy, so no availability alarm fires. If you catch it there, keep the old primary offline; its data files may be the only copy left.

Each election attempt is logged with the offset the replica used to rank itself:

bash
# On each replica (or former replica) of the shard that failed over
grep -E "Start of election delayed|Failover election won|Currently unable to failover" \
    /var/log/valkey/valkey-server.log | tail -n 5

Interpretation: in 9.1 the line reads "Start of election delayed for N milliseconds (rank #R, primary rank #P, offset O)". If the node that logged "Failover election won: I'm the new primary." shows offset 0, the shard is now empty. The log path follows your logfile setting.

Why can restarting a node with an empty data directory wipe a shard?

Valkey Cluster tells nodes apart by node ID, not by address. The 40-character ID is generated the first time a node starts and saved in its cluster config file (nodes.conf by default). It lasts as long as that file does, unless you run CLUSTER RESET HARD.

The replication documentation describes the danger: a primary "that crashes and comes back as the same instance with an empty dataset" makes its replicas "faithfully replicate that empty dataset and discard their own copies". Valkey Cluster avoids this when a replacement starts without cluster configuration: it gets a fresh ID and joins as a new, empty node, even on the old IP address, so the surviving replicas keep their data.

The risk sits between those cases: a node that keeps its nodes.conf but loses its data, for example a Kubernetes pod whose volume keeps nodes.conf while persistence is off.

What comes backNode IDDatasetWhat the cluster does
Same host, data and nodes.conf intactSameLoaded from diskRejoins; becomes a replica if it was failed over
New node, empty directory, no nodes.confNewEmptyJoins as an empty node; safe once added as a replica and synced
Old nodes.conf, empty data directorySameEmptyTreated as the old node; back as primary before FAIL, its replicas copy the empty dataset
bash
valkey-cli -h 10.0.1.14 CONFIG GET appendonly save dir cluster-config-file

Interpretation: if appendonly is no and save is empty, the node writes no data to disk. Its nodes.conf (relative to dir unless the path is absolute) is then the only thing that survives a restart, and a fast restart in place brings back the old ID with no data.

No persistence means no restart in place

The replication page lists three ways out: turn persistence on, do not restart a crashed instance automatically, or replace the failed node instead of restarting it. If you run cluster nodes without persistence, make every restart a replacement: start with no nodes.conf, so the node gets a new ID, and add it back with the runbook below.

What did Valkey 9.0, 9.1 and the 9.2 release candidates change?

Four changes touch this failure mode; the cluster tutorial mentions none of them.

  • 9.0: a delay tied to the node timeout, plus a penalty for new replicas. PR #2449 sets the fixed election delay to min(500, node_timeout / 30) ms, which is 500 ms at the default 15,000 ms timeout. It also adds 500 ms for a replica at offset 0: "This avoids the scenario where a new replica with no data wins the failover." The cluster spec page still shows the older formula.
  • 9.0.5 and 9.1.0: stale cluster packets. PR #2811 fixes a race where a replica acted on an old packet, re-attached itself to the failed primary and won an election as an empty primary. The PR says the 8.x backport was skipped.
  • 9.1: the best-ranked replica skips the delay. PR #2227 lets the best-ranked replica start at once. In the 9.1.3 source, a replica at offset 0 never takes this shortcut.
  • 9.2 release candidates: if-empty. Covered in the next section.

Empty-replica protection by Valkey version

Switch versions to see which guards and fixes your cluster has

Valkey 8.0 and 8.1

BehaviourValkey 8.0 and 8.1
Fixed election delay500 ms plus random 0 to 500 ms, plus 1,000 ms per rank
Extra delay for a replica at offset 0None
Stale cluster packet fix (#2811)Not backported
cluster-replica-no-failover valuesno, yes
del-node on an unreachable nodeWorks from 8.0.10 and 8.1.8
Truncated nodes.conf fix (#4489)8.0.12 and 8.1.11

Valkey 9.0 and 9.1

BehaviourValkey 9.0 and 9.1
Fixed election delaymin(500, node_timeout / 30) ms plus a random part, plus twice that per rank; 9.1 skips it for the best-ranked replica
Extra delay for a replica at offset 0500 ms, best effort
Stale cluster packet fix (#2811)9.0.5 and later, 9.1.0 and later
cluster-replica-no-failover valuesno, yes
del-node on an unreachable nodeWorks from 9.0.5 and 9.1.0
Truncated nodes.conf fix (#4489)9.0.7 and 9.1.3

9.2 release candidate

Behaviour9.2.0-rc2
Fixed election delaymin(500, node_timeout / 30) ms plus a random part, plus twice that per rank; skipped for the best-ranked replica
Extra delay for a replica at offset 0500 ms, best effort
Stale cluster packet fix (#2811)Included
cluster-replica-no-failover valuesno (default), yes, if-empty
del-node on an unreachable nodeWorks
Truncated nodes.conf fix (#4489)9.2.0-rc2 (not in rc1)

Minimum patch levels: 9.1.3 or 9.0.7 carries every GA fix in this table. On 8.x, 8.1.11 or 8.0.12 still lacks the stale-packet fix and the empty-replica delay.

bash
valkey-cli -h 10.0.2.15 INFO server | grep valkey_version
valkey-cli -h 10.0.2.15 CONFIG GET cluster-replica-no-failover \
    cluster-replica-validity-factor cluster-node-timeout

Interpretation: read valkey_version, not the redis_version:7.2.4 compatibility field. Check every node: the replica that runs the election is the one whose version counts.

Should you set cluster-replica-no-failover to if-empty?

With if-empty, a replica refuses to start an automatic failover while its replication offset is 0, because it "has never completed an initial sync with its primary", in the words of the 9.2.0-rc2 valkey.conf. "Empty" means no data received from the primary, not zero keys: a replica fully synced with an empty primary has a non-zero offset and can still fail over. The default stays no.

The trade is availability for data. If the only replica left is empty, the shard stays down until a replica with data is available, the old primary returns, or you fail over manually. For a cache you can refill, an empty shard may be acceptable; for data you cannot rebuild, a shard that stays down is easier to recover.

ini
# valkey.conf on every replica: 9.2 release candidates only
cluster-replica-no-failover if-empty

Interpretation: the setting can also be changed with CONFIG SET. When it blocks a failover, the replica logs "Currently unable to failover: Replication offset is 0 and no data has been received from the primary." That makes the refusal visible instead of silent.

Release candidate, not GA

if-empty shipped in 9.2.0-rc1 (16 September 2026) and is still in 9.2.0-rc2. Test it on staging, but do not plan production around it until 9.2.0 GA is out and its release notes keep the option unchanged. A 9.1 node rejects the value at startup.

Manual failovers bypass if-empty, so CLUSTER FAILOVER FORCE or TAKEOVER sent to an empty replica still promotes it. Also check cluster-replica-validity-factor (default 10): it stops a replica that has been disconnected longer than node timeout times the factor plus repl-ping-replica-period. Setting it to 0 drops that data-age check; Valkey's own if-empty test sets it to 0 so the stalled replica becomes a candidate immediately.

How do you check a replica's offset before a failover?

Two commands give you the numbers. CLUSTER SHARDS returns each node's replication-offset and a health of online, fail or loading. In the 9.1.3 source, a replica reports loading exactly when its replication offset is 0, the same condition if-empty tests. INFO replication shows the link state and the offsets on both sides.

bash
# Any replica still at offset 0 shows up as "loading"
valkey-cli -h 10.0.2.15 CLUSTER SHARDS | grep -A1 -E 'replication-offset|health'

Interpretation: every replica you might fail over to should show online and an offset close to its primary's. A replica that shows loading is the one that must not be promoted.

Comparing offsets on a primary and its new replica

Illustrative output shaped like Valkey 9.1, taken after the replica's full sync finished

ops@bastion:~
$ valkey-cli -h 10.0.2.15 INFO replication | grep offsetslave0:ip=10.0.3.9,port=6379,state=online,offset=4821337,lag=0,type=replicamaster_repl_offset:4821337second_repl_offset:4790122repl_backlog_first_byte_offset:3772762$ valkey-cli -h 10.0.3.9 INFO replication | grep -E 'link|sync_in|offset'master_link_status:upmaster_sync_in_progress:0slave_read_repl_offset:4821337slave_repl_offset:4821337master_repl_offset:4821337second_repl_offset:-1repl_backlog_first_byte_offset:4790001

Match before you move: the replica's slave_repl_offset equals the primary's master_repl_offset, the link is up and the primary lists the replica as online. Field names follow the INFO reference.

On a busy primary both numbers keep moving, so also check lag on the primary's slave0 line. During the initial sync, master_link_status stays down.

How do you remove an unreachable node from the cluster?

Until recently, valkey-cli --cluster del-node loaded only reachable nodes and failed on a dead one with [ERR] No such node ID. PR #3209 fixed that in 8.0.10, 8.1.8, 9.0.5 and 9.1.0. The cluster tutorial still tells you not to use del-node for a failed node, which is out of date for those versions.

bash
# Valkey 8.0.10, 8.1.8, 9.0.5, 9.1.0 and later
DEAD_ID=9f2b61c0de5e8a7a4d0c3e1b2f6a9d8c7b5e4f30   # 40-character ID from CLUSTER NODES
valkey-cli --cluster del-node 10.0.2.15:6379 "$DEAD_ID"

Interpretation: on a fixed version the output warns that it "Could not connect" to the dead node and is "unable to send CLUSTER RESET", then ends with [OK] Node ... removed from the cluster. That is expected. If it refuses with "is not empty! Reshard data away and try again.", the node still owns slots: no failover happened, and you need the original node back or a forced failover to a replica that has data.

On older patches, send CLUSTER FORGET to every remaining node yourself. Each node bans the forgotten ID for 60 seconds so that gossip from nodes that still remember it cannot re-add it. Since 8.0 that window is cluster-blacklist-ttl, in seconds, and it can be changed at runtime.

bash
# Older patch levels: forget the dead ID on every node in one pass
valkey-cli --cluster call 10.0.2.15:6379 CLUSTER FORGET "$DEAD_ID"

# Forgetting node by node? Widen the ban window first, then restore it
valkey-cli --cluster call 10.0.2.15:6379 CONFIG SET cluster-blacklist-ttl 300

Interpretation: --cluster call skips nodes it cannot reach, and every other node should reply OK. Forget only a node that no longer owns slots, and note that a replica refuses to forget its own current primary. On 7.2 the window is fixed at 60 seconds.

bash
# Every node should print 0 once the ID is gone everywhere
for h in 10.0.1.11 10.0.1.12 10.0.2.11 10.0.2.12 10.0.2.15; do
  echo "$h $(valkey-cli -h "$h" CLUSTER NODES | grep -c "$DEAD_ID")"
done

Interpretation: if a count returns to 1 later, some node re-learned the ID through gossip; forget it everywhere again. Before reusing the old host, delete its nodes.conf or run CLUSTER RESET HARD on it.

Which patch fixes truncated nodes.conf files?

Replacing a node means fresh nodes.conf writes on every member as the topology changes. PR #4489, released in 9.1.3, 9.0.7, 8.1.11, 8.0.12 and 7.2.15 in late October 2026, fixes three problems in how that file was saved:

  • A write error could go unnoticed, so a truncated nodes.conf was published. The node then panicked while loading it and would not start.
  • After the first save, the startup lock no longer covered nodes.conf, so a second process could start on a config already in use.
  • On SMB file systems such as Azure Files, every save failed and the process exited, which shows up as a pod crash loop.

The lock now lives in a sibling file with .lock appended. The 9.1.3 valkey.conf warns that it must stay creatable, which matters for Kubernetes subPath mounts of only the config file and for directory-cleaning sidecars.

bash
ls -l /var/lib/valkey/nodes*.conf*

Interpretation: a fixed version shows the config file and its .lock sibling. Valkey 9.1 also added cluster-config-save-behavior: the default sync exits when a save fails, while best-effort logs and keeps running, at the risk of a stale config after a restart.

What is the safe order for replacing a failed node?

The runbook covers the common case: a node is gone for good and its shard is one replica short. If the dead node was the primary, step 2 is the one that matters.

Replacing a failed Valkey Cluster node

Eight steps; only step 7 pauses writes, and only if you fail back

  1. Find the failed node and its shard

    Note the dead node's ID and whether a replica now serves its slots.

    valkey-cli -h 10.0.2.15 CLUSTER NODES | grep fail
  2. Check that the shard's primary has data

    Compare key counts per database with what you expect. If it is empty, stop: keep the old primary offline and recover from its files or a backup.

    valkey-cli -h 10.0.2.15 INFO keyspace
  3. Remove the dead node ID

    del-node handles unreachable nodes from 8.0.10, 8.1.8, 9.0.5 and 9.1.0. Older patches: CLUSTER FORGET on every node.

    valkey-cli --cluster del-node 10.0.2.15:6379 \ "$DEAD_ID"
  4. Start the replacement clean

    Empty data directory and no nodes.conf, on the same Valkey version as the cluster. It generates a new node ID.

    valkey-cli -h 10.0.3.9 CLUSTER MYID
  5. Add it as a replica of the shard's primary

    Name the primary. add-node refuses a node that already knows other nodes or holds keys.

    valkey-cli --cluster add-node 10.0.3.9:6379 \ 10.0.2.15:6379 --cluster-replica \ --cluster-master-id "$PRIMARY_ID"
  6. Wait for the full sync to finish

    Until it does, this replica is the empty one that must not be promoted. Then compare offsets as shown above.

    until valkey-cli -h 10.0.3.9 INFO replication \ | grep -q 'master_link_status:up'; do sleep 5 done
  7. Fail back, only if you need to

    Writes pause

    First check that every primary lists the new node as a replica. Without FORCE, writes pause until offsets match, for at most cluster-manual-failover-timeout (5,000 ms by default).

    valkey-cli -h 10.0.3.9 CLUSTER FAILOVER valkey-cli -h 10.0.3.9 ROLE
  8. Check the whole cluster

    Look for "[OK] All 16384 slots covered." and no open slots.

    valkey-cli --cluster check 10.0.2.15:6379

Main risk: between steps 5 and 6 the shard has a primary and an empty replica. If the primary fails in that window, the empty replica can win; on 9.2 release candidates, if-empty on the new replica closes it.

Frequently asked questions

What happens if an empty replica is promoted in Valkey Cluster?

The shard comes back online with no keys and every slot covered, so nothing alerts. When the old primary returns, it rejoins as a replica of the new primary and a full sync discards its data. If you notice first, keep the old primary offline and recover the shard from its data files or a backup.

How do I remove a failed node from a Valkey cluster?

Run valkey-cli --cluster del-node with any live node's address and the dead node's ID. It handles unreachable nodes from Valkey 8.0.10, 8.1.8, 9.0.5 and 9.1.0. On older patches, send CLUSTER FORGET to every remaining node within cluster-blacklist-ttl, 60 seconds by default, for example with valkey-cli --cluster call.

Is cluster-replica-no-failover if-empty available in Valkey 9.1?

No. The if-empty value was added in Valkey 9.2.0-rc1 and is still present in 9.2.0-rc2, but as of November 2026 it is in no GA release. Valkey 9.1 accepts only yes and no. On 9.1, protect a new replica by waiting for its full sync before you rely on it, and run 9.1.3 for the related fixes.

How do I know a new Valkey replica has finished syncing?

Run INFO replication on the replica and look for master_link_status:up and master_sync_in_progress:0, then compare its slave_repl_offset with the primary's master_repl_offset. In CLUSTER SHARDS, a replica still at offset 0 shows its health as loading instead of online.

Key takeaways

  • Primaries vote without comparing data, so a replica at offset 0 can win and empty its shard; the cluster still reports cluster_state:ok.
  • Run 9.1.3 or 9.0.7: they include the empty-replica delay, the stale-packet fix, the del-node fix and the nodes.conf fix. Treat if-empty as a 9.2 release candidate feature.
  • A node without persistence must never come back with its old nodes.conf; replace it with a new ID instead.
  • Remove dead IDs with del-node on fixed versions, or CLUSTER FORGET everywhere within cluster-blacklist-ttl.
  • Add the replacement as a replica, wait until its offset matches the primary's, and fail over only after that, without FORCE.

Replace Valkey Cluster nodes with JusDB

JusDB's Valkey Cluster team can review your replica placement, persistence and failover settings against the failure modes in this post, and write a node-replacement runbook that fits your hosting, from VMs to Kubernetes.

Bring your Valkey versions, your valkey.conf cluster settings, and how nodes are restarted or rescheduled today. Your team approves every production change.

Share this article

Database engineering notes

Articles like this one, in your inbox. No spam, unsubscribe anytime.

JusDB Team

Official JusDB content team

Keep reading

Moving a Multi-Database Valkey Instance into a 9.x Cluster

Move a Valkey instance that uses SELECT 1 to 15 into a 9.x cluster: keep or remap databases, copy each one, verify counts per database, and keep a way back.

Redis18 minNov 10, 2026
Read

Valkey Keyspace Notifications in Production: Expired Events, hexpired and Cluster Mode

Run Valkey keyspace notifications in production: why expired and hexpired events lag the TTL, how to subscribe on every cluster node, and when a stream is safer.

Redis20 minNov 10, 2026
Read

Valkey GLIDE in Production: AZ-Affinity Reads and Migrating from Legacy Clients

Configure Valkey GLIDE AZ-affinity reads on servers and clients, learn what GLIDE 2.5.3 does not support, and migrate from redis-py, Jedis, ioredis, or go-redis one service at a time.

Redis20 minNov 10, 2026
Read