Deep Engineering
Advanced·Published·50 MIN

Two ways to build Valkey out of several nodes: Cluster and Sentinel

The difference is not fault tolerance — both have it. Four things separate them at once: how the data is laid out, who decides on a failover, who answers the client's question of where to send the command, and what the client itself has to be able to do. Plus a fifth dimension people forget most often: the version. The rule "a cluster has exactly one database" is true for Redis and for Valkey before 9.0, and false for Valkey 9 — and it is re-measured here on two clusters differing by a single config line.

Full technical treatment

TL;DR

"Valkey clustering" means two different things, and the choice between them is usually made on fault tolerance — which both have. The choice should be made on something else: who answers the client's question of where to send the command.

  • A native cluster answers with an address. A key maps to one of 16,384 slots, a slot belongs to a node, and a node whose slot it is not returns not data but MOVED 12182 127.0.0.1:48711. Any node will do: every one of them knows the whole map.
  • Sentinel is not a cluster but a watcher. The data sits on an ordinary primary with replicas; there is no sharding at all. A data node answers CLUSTER INFO with ERR This instance has cluster support disabled. The primary's address is known to a separate service, and the client must ask it.
  • Sharding changes the set of usable commands. In a cluster MSET foo 1 bar 2 is CROSSSLOT Keys in request don't hash to the same slot: a command runs entirely on one node, and the keys landed in different slots. Under Sentinel there is one node, and the same command works.
  • On numbered databases the received wisdom is out of date. "A cluster has exactly one database" is the rule of Redis and of Valkey before 9.0. In Valkey 9 a cluster does have databases; their number is set by cluster-databases, and it defaults to one — which is why the old rule still looks true. Re-measured on two clusters differing by a single config line.
  • And different behaviour under failure. In a cluster the client gets a meaningful answer and the slot's new address from any live node. Under Sentinel the old address simply goes silent, and nobody will tell the client the new one.
  • The most dangerous place in Sentinel mode is the old primary coming back. Measured: for ten seconds it comes up as a primary and answers OK to writes, and once demoted to a replica the resync erases them. The client sees no error at all.
  • Found along the way: "the cluster is up" and "the cluster survives a failure" are different states. 6.07 seconds apart, and a primary that dies inside that window is never replaced — at the default value of cluster-replica-validity-factor.
  • Quorum and majority are different numbers. Five sentinels, quorum 2, two alive: quorum satisfied, no failover, and -failover-abort-not-elected in the log. SENTINEL CKQUORUM answers that question in advance.
  • The version is a fifth dimension of the comparison, and the one forgotten most often. Numbered databases in a cluster arrived in Valkey 9.0; atomic slot migration in Valkey 9.0 and in Redis 8.4. Any rule inherited from knowledge of Redis Cluster has to be tagged with a version — or checked.

Everything was taken on Valkey 9.1.2 built from source; the whole runs are in bench/valkey-cluster/runs/.

One question, answered by two different parties

Nearly every comparison of Cluster and Sentinel starts from a table reading "sharding: yes / sharding: no". That is true, but it is a consequence rather than a cause, and none of the practical differences follow from it — not why MSET stops working, not why the client needs a special library, not where the window comes from in which writes go nowhere.

Four things separate the two modes at once, and conflating them is expensive:

DATA LAYOUT     a cluster shards across 16,384 slots; Sentinel does not,
                all the data sits on one primary
WHO DECIDES     cluster — the nodes themselves, by a majority of primaries;
                Sentinel — a separate service, by a majority of watchers
WHO ADDRESSES   cluster — the data node, with an ordinary command;
                Sentinel — that same service, with a command of its own
WHAT THE CLIENT each mode needs its own support in the library,
CAN DO          and those two abilities are independent

The first line gives rise to the set of usable commands, the second to the behaviour under failure, the third to the connection protocol, the fourth to whether you can take the mode at all with your library. They are taken in turn below, and the third is the place to start: it explains fastest why the two modes get confused in the first place.

So: the client has a question — where do I send this command. The two modes answer it from different sides.

native clusterSentinel
who is askedany data nodea separate service
what comes back"not my slot, go there: 127.0.0.1:48711""the primary is 127.0.0.1:41979"
what the data node doesnames the right address itselfruns the command, knows nothing of the topology
where knowledge of the topology livesinside the databeside the data, in another service

The rest of the article is consequences of that fork, and each of them is checked by a run rather than argued. Arguing them would not work anyway: the data layout and the version add conditions of their own, and the single question "who answers with an address" does not explain every difference.

How a key becomes an address

Cluster addressing takes two steps, and it is worth holding both in mind because it is precisely these two that get conflated. The first step, from key to slot, is pure arithmetic:

HASH_SLOT = CRC16(key) mod 16384

Valkey — Cluster specification

The second step, from slot to node, is not arithmetic but cluster state: slots belong to someone, and that ownership changes. Hence a property that is otherwise hard to explain: moving data between nodes renames no key. The slot changes owner; the formula stays the same.

This is checked with one command sent to each of six nodes in turn. The client has no -c flag — it is deliberately "dumb" and follows nothing, so that what the server actually said is visible:

nodeanswer to SET foo 1
44997 (primary)MOVED 12182 127.0.0.1:48711
53953 (primary)MOVED 12182 127.0.0.1:48711
48711 (primary)OK
39529 (replica)MOVED 12182 127.0.0.1:48711
54583 (replica)MOVED 12182 127.0.0.1:48711
51665 (replica)MOVED 12182 127.0.0.1:48711

MOVED is a protocol error — a redirection error: the command was not executed, and the node returned not data but the address of whoever will execute it. It does not usually become an application failure, though: a cluster client repairs its slot map on such an answer and retries by address. Note that primaries and replicas answered alike: all of them know the map.

The difference between "not executed and redirected" and "not executed, outcome unknown" matters here, and it should be kept apart from a retry after a network failure. On MOVED a retry is safe: the node definitely did nothing. If the connection broke after the command was sent, the client does not know whether it ran — and there the retry is a question of idempotence, not of routing.

What a real client actually does

The block above shows the protocol but not the behaviour: a real library hides MOVED. What it does is counted by the nodes themselves — from their own processed-command counters, with a control window of equal length and no client (cluster nodes talk to each other constantly, and without a control the client's traffic cannot be told from the background).

The client was given one address out of six, and not the one holding the key:

nodeidle windowwith the clientwhat arrived in the second window
51665 (the address given)27`client

One trip for the slot map (cluster|slots) — and the write went straight to the right node. Hence what people usually mean by "the cluster sorts itself out": the client need not know every node, one live address is enough, and there is no middleman between the client and the data.

Under Sentinel the order is reversed, and the same measurement shows it: first the question to the service, then the command to the data. The data node took no part in finding itself, and could not have:

asked ofanswer
data node 43471: CLUSTER INFOERR This instance has cluster support disabled
data node 43471: rolemaster
sentinel: SENTINEL get-master-addr-by-name cache127.0.0.1:41979

One formulation should be narrowed right here, because it suggests itself in a form wider than what was measured. "A data node knows nothing about the topology" is wrong: it knows its own replication pair perfectly well.

asked of the node under Sentinelanswer
primary: INFO replication → rolemaster
primary: connected replicasconnected_slaves:2, slave0:…port=45047,state=online, slave1:…port=52369,state=online
replica: INFO replication → roleslave
replica: its primary and the channelmaster_host:127.0.0.1, master_port:34351, master_link_status:up
primary: CLUSTER INFOERR This instance has cluster support disabled
primary: SENTINEL get-master-addr-by-name cacheERR unknown command 'sentinel'

What it does not know is the authoritative answer to "who is this service's primary right now". It has an opinion, and during a failover that opinion is sometimes stale; that is exactly what happens in the section on the returning address.

The Sentinel client spec describes the required sequence in plain words — and it has three steps, not one:

it should attempt a connection with the primary, and call the ROLE command in order to verify the role of the instance is actually a primary.

Valkey — Sentinel client spec

The third step — verifying with ROLE — looks like belt and braces right up to the section about the returning address. There it becomes clear what it guards against.

And what it does not give: it is a check, not a proof. If both the service's answer and the node's own opinion are stale — and that is precisely the pair you get in the first seconds after a failover — ROLE will answer master, and the client will take the old address for the right one. The spec calls this case rare and relies on the sentinels tearing such connections down later; it promises no consistency.

What breaks in a cluster

Out of this grows the list of commands that stop working. The list is not arbitrary: a command runs entirely on one node, so a command needing two keys from different slots cannot run anywhere.

slot
CLUSTER KEYSLOT foo12182
CLUSTER KEYSLOT bar5061
CLUSTER KEYSLOT {u}:a11826
CLUSTER KEYSLOT {u}:b11826
commandin a clusterunder Sentinel
MSET foo 1 bar 2CROSSSLOT Keys in request don't hash to the same slotOK
MSET {u}:a 1 {u}:b 2MOVED 11826 127.0.0.1:42549 — that is, it runsOK

The refusal is not about "two writes at once" but about two different slots: the same two writes with a shared brace go through. Braces are not decoration in a name:

If the key contains a "{...}" pattern only the substring between `{` and `}` is hashed in order to obtain the hash slot.
Valkey — Cluster specification

The practical consequence is costliest in hindsight: in a cluster the set of keys you will ever need to touch with one command is decided before the first write. Under Sentinel that decision need not be made at all.

The second item on the list is numbered databases — and this is where the article got it wrong. The mistake is instructive, so it is dealt with not by swapping a line but in a section of its own.

Numbered databases: here the answer depends on the Valkey version

The claim "a cluster has exactly one database" is easiest to check with one experiment:

commandin a clusterunder Sentinel
SELECT 1ERR DB index is out of rangeOK
SWAPDB 0 1ERR SWAPDB is not allowed in cluster modeOK

— and drew from it the conclusion "a cluster has exactly one database". The server's answer is real, the conclusion is not. The only correct conclusion from that single experiment is: THIS cluster is configured with one database. Telling the two readings apart needed a second experiment, and there wasn't one.

Because in Valkey 9.0 numbered databases in a cluster appeared:

Valkey 9.0 adds the ability to have numbered databases on a cluster, changing everything about that advice.

Valkey — Numbered Databases in Valkey 9.0

The number of databases is set by a separate option, cluster-databases, and it defaults to one — which is why a cluster brought up the ordinary way behaves the old way. Two clusters differing by exactly that line:

cluster Acluster B
cluster-databases116
SELECT 0OKOK
SELECT 1ERR DB index is out of rangeOK
SELECT 15ERR DB index is out of rangeOK
SELECT 16ERR DB index is out of rangeERR DB index is out of range

One config line — and SELECT 1 turns from a refusal into OK. The option is immutable (IMMUTABLE_CONFIG in src/config.c), so it is chosen when the node starts and cannot be changed on the fly.

The database number takes no part in addressing

That is the second question, and it is the more interesting one: does the new feature break the two-step addressing the whole cluster rests on. It does not.

answer
node owning the slot of key foo127.0.0.1:51799
in db 0: SET foo db0OK
in db 1: SET foo db1OK
in db 0: GET foodb0
in db 1: GET foodb1
in db 0: CLUSTER KEYSLOT foo12182
in db 1: CLUSTER KEYSLOT foo12182

One key, two values, one slot. The database is added as a third dimension inside the node, and the layout of data across nodes does not depend on it by a single key.

And what it does NOT give you

From "databases exist now" it is just as easy to build the opposite myth — that in a cluster they behave as they do on a single node. They do not:

answer
SWAPDB 0 1ERR SWAPDB is not allowed in cluster mode
three keys written into db 1foo → slot 12182, bar → slot 5061, user:1 → slot 10778
DBSIZE in db 1 on node 371911
DBSIZE in db 1 on node 509911
DBSIZE in db 1 on node 517991
SCAN in db 1 on the owner node0 foo
SCAN in db 1 on node 371910 bar

SWAPDB was forbidden and stayed forbidden. And DBSIZE and SCAN count not the database as a whole but its part on the node the command reached: three keys were written into one database, spread across three nodes, and no node sees all three. The same goes for FLUSHDB. The documentation says so directly:

will return the number of keys in the current database on the connected node

Valkey — Numbered Databases in Valkey 9.0

From the same page, on what numbered databases should not be taken for:

numbered databases do not provide any form of resource isolation

ibid.

The upshot: a numbered database in a cluster is a namespace, not a separate store. Splitting environments across them is fine; expecting isolation, or a cluster-wide view of their contents, is not.

Hence the rule worth taking away from the article as a whole

The error here is not carelessness. It is that the knowledge about clusters came from where everyone takes it: shared memory of Redis Cluster, where there really is one database. On Valkey 9 that rule stopped being true — and still looks true, because the default value confirms it.

capabilityValkey ≤ 8.xValkey 9.0Valkey 9.1
db 0 in a clusteryesyesyes
several dbs in a clusternoyesyes
cluster-databasesnoyes, default 1yes, default 1
SELECT n in a clusteronly 0up to cluster-databases − 1up to cluster-databases − 1
SWAPDB in a clusternonono

The rule is simple: any statement about clusters inherited from knowledge of Redis has to be tagged with a version — or checked. Checking it here cost one config line and two minutes.

A primary fails: one event, two client experiences

Here is the main practical difference, and it is not in the seconds. Comparing durations is pointless: both are configurable, and in the benchmark both were deliberately shortened so the run would fit in minutes. What should be compared is the text the client receives.

In the cluster a live node was polled — not the one that died:

t, swhat the client received
0.00node 36355 killed
0.00MOVED 12182 127.0.0.1:36355
3.39CLUSTERDOWN The cluster is down
3.49MOVED 12182 127.0.0.1:39129
the returned node: role slave, SET fooMOVED 12182 127.0.0.1:39129

Three different answers, all three meaningful: the slot's old address, an honest "the slot is covered by nobody right now", the new address. The client never had to guess — the node it reached knows the state of the whole cluster, because nodes exchange it continuously over the bus. And no external service was consulted: the replica was promoted by the nodes themselves, by a majority of primaries.

Under Sentinel the same event looks different:

t, swhat happened
0.00node 44393 killed
0.00old address: Could not connect to Valkey at 127.0.0.1:44393
2.30the sentinel names 127.0.0.1:41979 as the primary
the new primary answers a write with OK
the other replica: READONLY You can't write against a read only replica.

The old address answered nothing — there is nobody there. No data node named the new address to the client and none could: the nodes do not know about their pair's service identity, and the service that does know does not call. Hence the rule in the client spec:

every time a reconnection is needed, the client should resolve again the address using Sentinels restarting from Step 1.

Valkey — Sentinel client spec

Not "preferably", but every time. Why, is the next section.

The old primary comes back

This is the most dangerous place in Sentinel mode, and it is about recovery rather than failure. The killed primary is brought back — and comes back as a primary: that is what it remembers being, and nobody has told it otherwise yet.

t, srole and answer
0.06role master, write → OK
10.10role slave, write → READONLY You can't write against a read only replica.
right after demotionGET whoold
after the resyncGET who → the key is gone

For ten seconds the node accepted writes and answered OK. The last two rows read together and in that order: right after demotion the write is still there, and after the resync the key is gone — the data was replaced with a copy of the real primary. The client got OK, and the write vanished, without seeing a single error.

In this experiment no such window appeared in the cluster, and that is checked by the same run — the last two rows of the cluster table above. The returned node learned from the bus that its slots belong to another before it took a single command.

"In this experiment" is not politeness. One scenario was checked — killed and brought back up; on start the node read the bus and got a fresh configuration before the client's first command. A network partition is a separate case in the cluster specification, and there a bounded loss of writes is admitted: a client with a stale map writes to a former primary that has not yet learned of its demotion. We did not measure that, and the article will not claim the cluster has no such window at all. What was measured is something else: in a cluster that window closes by itself, and under Sentinel only by the client's own actions.

This is what the ROLE check in the client spec guards against. A client that holds a data address directly and cannot re-ask the sentinel falls into that window whole.

Found along the way: ready to work ≠ ready to fail

This section came out of a bug in the benchmark. The script brought up a cluster, waited for cluster_state:ok on every node and killed a primary. The replica was never promoted — not in twenty seconds, not in a minute, never.

It turned out not to be a bug in the benchmark:

signal of readinesst, s
cluster_state:ok on all nodes (slots covered, requests served)0.01
master_link_status:up on all replicas (someone can be promoted)6.08
the window between them6.07

Inside those six seconds the cluster looks entirely healthy: slots are covered, keys are written and read. But the replicas have not completed a single sync — and a primary that dies here is never replaced:

Currently unable to failover: Disconnected from primary for longer than
allowed. Please check the 'cluster-replica-validity-factor' configuration
option.

Why "never" rather than "until some timeout expires" is visible in the source:

C
if (server.repl_state == REPL_STATE_CONNECTED) {
    data_age = (mstime_t)(server.unixtime - server.primary->last_interaction) * 1000;
} else {
    data_age = (mstime_t)(server.unixtime - server.repl_down_since) * 1000;
}

For a replica that has never connected to its primary, repl_down_since is zero. Its data age comes out measured from the Unix epoch, and no allowance covers an age like that.

That "never" needs narrowing, and it is narrowed by measurement rather than by a disclaimer. The freshness check on a replica's data is governed by cluster-replica-validity-factor; in the experiment above it holds its default. The same experiment with zero:

cluster-replica-validity-factor0
cluster-node-timeout2000 ms
t=0.00primary 34455 killed, waiting up to 40 s
t=3.53cluster state fail, replica role slave
t=4.15cluster state fail, replica role master
replica promotedyes

One config line — and "never" turns into four seconds to promotion. So the correct formulation is: at the default value, a replica that has completed no sync is never promoted; an option turns that off. No advice to do so follows: the check is there for a reason, and a replica that has never synced is promoted empty — a failover of that kind cures availability at the cost of data.

The conclusion is not about Valkey but about operations: "the cluster is up" and "the cluster survives a failure" are different checks. The first answers whether requests are served; the second requires looking at master_link_status on every replica. Automation that rolls out a cluster and immediately tears down the old one passes the first and fails the second.

Quorum and majority are different numbers

In sentinel monitor <name> <ip> <port> <quorum> the last number is read as "how many sentinels suffice to fail over". That is wrong, and the documentation says so directly:

The quorum is only used to detect the failure. In order to actually perform a failover, one of the Sentinels need to be elected leader... and be authorized to proceed. This only happens with the vote of the majority of the Sentinel processes.

Valkey — High availability with Valkey Sentinel

The numbers were chosen so that one condition holds and the other does not: five sentinels, quorum 2, three killed. The quorum is satisfied — and there is no failover:

sentinels in the set5
quorum in the configuration2
majority of five3 (not configurable anywhere)
sentinels alive2
failover within 30 snone
+sdown master cache 127.0.0.1 48147
+odown master cache 127.0.0.1 48147 #quorum 2/2
+new-epoch 1
+try-failover master cache 127.0.0.1 48147
+vote-for-leader c92cd70b21a9dd26ec40e67185371e78ee502f9a 1
-failover-abort-not-elected master cache 127.0.0.1 48147

#quorum 2/2 — the configured condition is met and the primary is declared down. And right after it -failover-abort-not-elected: two votes out of five were not enough to become leader. Bringing back one of the killed sentinels gave a majority, and the failover completed in two seconds — at the very same quorum.

None of this has to be discovered during the incident. One command answers in advance, and it checks both conditions:

sentinels aliveSENTINEL CKQUORUM cache
5OK 5 usable Sentinels. Quorum and failover authorization can be reached
2NOQUORUM 2 usable Sentinels. Not enough available Sentinels to reach the majority and authorize a failover

The refusal names the second condition in so many words: there are not enough watchers to reach a majority and authorize a failover — while the configured quorum is met. That makes it a ready-made monitoring signal: it answers "will we fail over if the primary dies right now", not "is everything fine at this moment".

A practical rule that does not follow from one setting: the system will not survive losing a majority of its sentinels, however low the quorum. Hence the minimum in the documentation:

You need at least three Sentinel instances for a robust deployment.

Valkey — High availability with Valkey Sentinel

How this differs from Redis

A fair question: Valkey inherited both mechanisms from Redis whole, and up to a point this is literally the same code. The boundary is named inside Valkey itself, in the same file as the version number:

C
#define VALKEY_VERSION "9.1.2"
 
/* Redis OSS compatibility version, should never
 * exceed 7.2.x. */
#define REDIS_VERSION "7.2.4"

So Valkey declares compatibility up to 7.2.4 — the version at which the projects parted.

That line cannot be used as proof, though, and it is worth saying so plainly. It means exactly one thing: a shared past up to 7.2.4. It does not follow that everything before that point still matches today, nor that everything after it diverged. Both sides have since changed things that used to be common. The only honest way to compare is to take a capability and read the current documentation of both projects; that is what is done below, and the result was not what the reasoning "it arrived in Valkey, so Redis has not got it" leads to.

The first example is atomic slot migration, introduced in Valkey 9.0.

Valkey 9.0 introduced a option for migrating hash slots known as atomic slot migration, which is faster, more reliable, and has less impact on client applications than the legacy CLUSTER SETSLOT-based migration.

Valkey — Atomic slot migration

What matters here for the reader: the old way of moving slots is the very one that keeps ASK redirection alive in the specification, along with the warning that multi-key operations may answer TRYAGAIN while a slot is on the move. Atomic migration shortens that phase.

But the conclusion "and Redis has nothing of the kind", which suggests itself here, is wrong. Redis Open Source 8.4 gained atomic migration too, under a command of its own:

This command allows you to import slots from other nodes, monitor the progress of migration tasks, and cancel ongoing migrations... Executes on the destination master. Accepts multiple slot ranges and triggers atomic migration for the specified ranges.

Redis — CLUSTER MIGRATION

So as of today both have the capability, while the commands and the protocol differ: in Valkey it is CLUSTER MIGRATESLOTS plus the internal CLUSTER SYNCSLOTS, in Redis CLUSTER MIGRATION. Operationally that is a more practical difference than "present or absent": migration procedures and the tooling around them do not carry between the projects, even where the capability goes by the same name.

capabilityValkeyRedis
shared past up to7.2.47.2.4
several dbs in a clustersince 9.0, cluster-databasesno
atomic slot migrationsince 9.0, CLUSTER MIGRATESLOTSsince 8.4, CLUSTER MIGRATION

Everything else in this article — slots, MOVED, CROSSSLOT, the shape of Sentinel, quorum against majority — came to both projects from a shared past. None of it was re-checked here on Redis, and the article will not claim "Redis does exactly the same": not measured, not stated. That is precisely what undoes that kind of claim.

Which one to pick

The analysis is short, and it is not about throughput.

you need more memory or throughput than one node gives
    → native cluster: it is the only one of the two that shards

the data fits one node and you need fault tolerance only
    → Sentinel stays a candidate

the code leans on multi-key commands over arbitrary keys
    → Sentinel is simpler; in a cluster the keys must be pulled
      into one slot with braces

you need numbered databases
    → Valkey before 9: Sentinel only
    → Valkey 9+: a cluster too, via cluster-databases

you need SWAPDB
    → Sentinel only: in a cluster it is forbidden in 9.x as well

does the client speak the cluster protocol?
    → no: a cluster is out of reach without replacing the client or a proxy

does the client speak sentinel?
    → no: Sentinel is out of reach without replacing the client or a proxy

The last two lines deserve a careful reading: the received "the client can't speak sentinel → take a cluster" leads up to the idea that a cluster forgives a naive client. It does not. An ordinary client that knows nothing of clusters, on receiving MOVED, will simply hand the error text to the application: it keeps no slot map, cannot retry by address, does not know ASK and ASKING, does not handle TRYAGAIN. Both modes require their own support in the client, and the two abilities are independent: a library may have one, the other, both or neither. With neither, nothing works, and the question moves from choosing a mode to choosing a library or a proxy.

What is true and stayed true: a cluster client needs one live address to get the map. In production several are listed anyway — in case that particular node is unreachable at startup.

And one more caveat about multi-key commands that was missing from the first version. The CROSSSLOT refusal is a server contract. Some client libraries offer a convenient multi-key call on top of it, splitting the request into several commands by node. That works as a wrapper, not as the same command: it has no atomicity, its failure model is different (some sub-requests may succeed and others not), and the latency is the sum of several round trips.

What should reproduce, and what should not

The numbers were taken on Valkey 9.1.2 (built from source — the README in bench/valkey-cluster/ says how), on one host, processes on random ports. The scripts and the recorded runs open from here: bench/valkey-cluster/.

The seconds carry nowhere. A node counts as down after one second of silence instead of the default thirty, and cluster-node-timeout is 2000 ms instead of 15,000. That was done so the set would fit in minutes. The ten seconds of the recovery block and the 6.07 seconds of the readiness block will differ on your settings — but that both windows exist does not depend on settings.

The verbatim server replies carry over completely. MOVED, CROSSSLOT, ERR DB index is out of range, READONLY, -failover-abort-not-elected are behaviour rather than measurement, and on the same version they will be the same.

Across versions, not necessarily. Everything was taken on 9.1.2; these runs were repeated neither on 8.x nor on Redis. And the numbered-databases section above is the article's own demonstration of why that caveat is not boilerplate.

Corrections

12 September 2026

Collected here is what this material got wrong, and what settled it. Every entry rests on a run or on a document rather than on our having changed our minds.

Was

"A cluster has exactly one database" — and from it the conclusion that numbered databases do not exist in a cluster at all.

Now

In Valkey 9 a cluster has databases. Their count is set by cluster-databases, and its default is 1 — which is exactly why the old rule still looks right. It has not stopped being the rule of Redis and of Valkey before 9.0; it was never a universal law.

What settled it

Two fresh clusters differing by one line of config: with cluster-databases 1 the command SELECT 1 answers ERR DB index is out of range, with cluster-databases 16 it answers OK. The database number takes no part in addressing: one and the same key holds different values in databases 0 and 1 while its slot stays 12182 (bench/valkey-cluster/databases.py).

Was

"Atomic slot migration arrived in Valkey 9.0 — so Redis has nothing of the kind."

Now

Both have the capability; the protocols differ. In Redis Open Source 8.4 atomic migration arrived under its own command, CLUSTER MIGRATION instead of CLUSTER MIGRATESLOTS.

What settled it

The current documentation of both projects, quoted in the text. This is a correction of the METHOD of comparison: that a capability is announced in one project implies nothing about the other — comparison must go capability by capability against current documentation rather than by dates of arrival. Nothing here was re-measured on Redis, and the article does not claim otherwise.

Was

"A data node under Sentinel knows nothing about the topology."

Now

A node knows its own replication pair perfectly well: a primary sees its replicas, a replica sees its primary. What it does not know is the membership of the whole group and who decides on a failover.

What settled it

INFO replication run on both sides of the pair (bench/valkey-cluster/boundaries.py). The wording is narrowed to what was measured.

Was

"The client can't speak sentinel → take a cluster" — and from it the idea that a cluster forgives a naive client.

Now

It does not. An ordinary client that knows nothing of clusters, on receiving MOVED, hands the error text to the application: it keeps no slot map, cannot retry by address, does not know ASK and ASKING, does not handle TRYAGAIN. Both modes require their own client support, and the two abilities are independent.

What settled it

This is a reading of the contract rather than a measurement: the behaviour of MOVED, ASK and TRYAGAIN is set out in the cluster specification quoted in the text.

Was

The CROSSSLOT refusal was presented with no caveat about client libraries.

Now

The CROSSSLOT refusal is the SERVER's contract. Some client libraries build a convenient multi-key call on top of it by splitting it into several commands across nodes. That is a wrapper, not the same command: it has no atomicity, its failure model differs, and its latency is the sum of several round trips.

What settled it

An added caveat rather than a changed claim: the refusal itself was measured and is unchanged, but without this frame a reader carries the server's contract over to the behaviour of their own library.

Common misconceptions

Claim

Sentinel is Valkey's "lightweight cluster"

Actually

Sentinel is not a cluster at all: it shards nothing. All the data sits on one primary, and replicas exist only so that there is someone to replace it. One command checks this: a node under sentinel answers CLUSTER INFO with ERR This instance has cluster support disabled. Hence the thing these two modes are usually confused over: if the data does not fit one node, Sentinel does not help at all — it solves a different problem, fault tolerance, and solves it without sharding.

Claim

In a Valkey cluster only database 0 is available

Actually

That is the rule of Redis and of Valkey before 9.0, and in Valkey 9 it is wrong. Numbered databases in a cluster arrived in 9.0, and a separate option, cluster-databases, sets how many there are; it defaults to one — which is why a cluster brought up the ordinary way refuses SELECT 1 and the old rule keeps looking true. Re-measured on two clusters differing by that one line: with 16, SELECT 0 through 15 all work. The database number takes no part in addressing — the same key holds different values in db 0 and db 1 at one and the same slot, 12182. The opposite myth is wrong too: SWAPDB stays forbidden, and DBSIZE, SCAN and FLUSHDB see only the part of a database that lies on the node the command reached.

Claim

MOVED is an error and the client should handle it as a failure

Actually

It is a redirection error: the command was NOT executed, but instead of data the node returned the address of whoever will execute it. The node did not refuse: it named the slot and its owner — MOVED 12182 127.0.0.1:48711. A cluster client repairs its slot map on that answer and retries by address, and the retry is safe here: the node definitely did nothing. That has to be told apart from a retry after a broken connection, where the client does not know whether the command ran — and the question becomes idempotence rather than routing. Measured across all six nodes: primaries and replicas answer alike, because every node knows the map.

Claim

Multi-key commands do not work in a cluster

Actually

They do, if the keys are in one slot. The boundary runs by slots, not by key count: MSET foo 1 bar 2 gives CROSSSLOT Keys in request don't hash to the same slot because foo is in slot 12182 and bar in 5061. The same two writes as MSET {u}:a 1 {u}:b 2 go through: braces make only the substring inside them hash, and both keys land in slot 11826. The price of this is not a line of code but a decision about key naming, taken before the first write.

Claim

The number in sentinel monitor ... <quorum> says how many watchers suffice to fail over

Actually

It says only how many are needed to DECLARE the primary down. A failover can be performed only by the sentinel elected by a majority of all known sentinels, and that number is not configurable anywhere. Measured: five sentinels, quorum 2, two alive. The quorum was satisfied — the log shows +odown ... #quorum 2/2 — and right after it -failover-abort-not-elected: two votes out of five were not enough. Bringing back one killed sentinel made the failover complete in two seconds, at the very same quorum. None of this needs to be discovered during the incident: SENTINEL CKQUORUM answers in advance and checks both conditions separately.

Claim

If cluster_state:ok, the cluster is ready

Actually

Ready to work, yes; ready to fail, no — and 6.07 measured seconds lie between those states. Slots are covered almost at once, while the replication link between a primary and its replica takes several more seconds, and all that time master_link_status on the replicas is down. A primary that dies inside the window is NEVER replaced: for a replica that has never connected, the "when did the link go down" mark stayed zero, its data age is computed from the Unix epoch, and it sticks forever on Currently unable to failover: Disconnected from primary for longer than allowed. "Forever" here is about the default value: the same experiment with cluster-replica-validity-factor 0 promotes in four seconds — at the cost of promoting an empty replica.

Claim

After a failover you can simply bring the old primary back up

Actually

You can, but it will come back as a PRIMARY — that is what it remembers being — and it will accept writes until the sentinel notices and demotes it. Measured: for ten seconds SET answered OK. After the demotion a full resync with the real primary erased those writes, and the client saw no error at all. The danger is not the window itself but who falls into it: a client holding a data address directly. In this experiment the cluster had no such window — the returned node answered MOVED to the slot's new owner immediately, having read the bus before its first command.

Claim

Valkey clustering and Redis clustering are the same thing

Actually

Their shared past runs to 7.2.4 — Valkey says so itself, with REDIS_VERSION "7.2.4" sitting next to VALKEY_VERSION "9.1.2". But that line cannot be taken as proof: it does not follow from it that everything before that point still matches today, nor that everything after it diverged. Comparison has to go capability by capability, against the current documentation of both. The example that trips up the reasoning "it arrived in Valkey, so Redis has not got it": atomic slot migration arrived in Valkey 9.0 — and in Redis Open Source 8.4 as well, only under its own command, CLUSTER MIGRATION instead of CLUSTER MIGRATESLOTS. Both have the capability; the protocols differ. Nothing here was re-measured on Redis.

Claim

A cluster forgives a naive client: any node will tell it where to go

Actually

It will — but only a client that speaks the cluster protocol can use that. An ordinary one, on receiving MOVED, simply hands the error text to the application: it keeps no slot map, cannot retry by address, knows nothing of ASK, ASKING or TRYAGAIN. Both modes require their own support in the library, and the two abilities are independent: it may speak cluster, sentinel, both or neither. What is true is that a cluster client needs ONE live address to obtain the map; in production several are listed anyway, in case that particular node is unreachable at startup.

Check yourself

Question 1 of 6

A client sent SET foo 1 to a cluster node that does not own that slot. What does it get?

Sources & further reading

8 SOURCES

  1. Valkey — Cluster specificationOfficial documentation. The addressing rule everything else follows from: "HASH_SLOT = CRC16(key) mod 16384". And the way around it: "If the key contains a '{...}' pattern only the substring between the braces is hashed in order to obtain the hash slot." From the same page, an honest caveat about multi-key operations: "Multi-key operations may become unavailable when a resharding of the hash slot the keys belong to is in progress."https://valkey.io/topics/cluster-spec/
  2. Valkey — High availability with Valkey SentinelOfficial documentation. The four duties of the service, named as one list: "Monitoring, Notification, Automatic failover, Configuration provider." The last is the answer to why a client needs Sentinel at all: clients ask it "for the address of the current Valkey primary responsible for a given service." And the distinction this article checks by measurement: "The quorum is only used to detect the failure. In order to actually perform a failover, one of the Sentinels need to be elected leader... and be authorized to proceed. This only happens with the vote of the majority of the Sentinel processes." Also the minimum: "You need at least three Sentinel instances for a robust deployment."https://valkey.io/topics/sentinel/
  3. Valkey — Sentinel client specOfficial documentation. What a client is obliged to do, and why "just connect to the address" is wrong in this mode. Step by step: "The client should iterate the list of Sentinel addresses", then SENTINEL get-master-addr-by-name, then "it should attempt a connection with the primary, and call the ROLE command in order to verify the role of the instance is actually a primary." And the rule that explains the block about the returning address: "every time a reconnection is needed, the client should resolve again the address using Sentinels restarting from Step 1."https://valkey.io/topics/sentinel-clients/
  4. Valkey — Numbered Databases in Valkey 9.0Official documentation. The feature that forced a whole section to be rewritten: "Valkey 9.0 adds the ability to have numbered databases on a cluster, changing everything about that advice." From the same page, the boundaries without which one gets the opposite myth: DBSIZE "will return the number of keys in the current database *on the connected node*", the same for SCAN and FLUSHDB, and plainly: "numbered databases do not provide any form of resource isolation."https://valkey.io/blog/numbered-databases/
  5. Redis — CLUSTER MIGRATIONOfficial documentation. The source that corrected the section on differences. Atomic slot migration exists in Redis too: "This command allows you to import slots from other nodes, monitor the progress of migration tasks, and cancel ongoing migrations", and on the same page "Executes on the destination master. Accepts multiple slot ranges and triggers atomic migration for the specified ranges." It arrived in Redis Open Source 8.4.0 — so both projects have the capability, and the commands differ.https://redis.io/docs/latest/commands/cluster-migration/
  6. Valkey — Atomic slot migrationOfficial documentation. What arrived in Valkey 9.0: "Valkey 9.0 introduced a option for migrating hash slots known as atomic slot migration, which is faster, more reliable, and has less impact on client applications than the legacy CLUSTER SETSLOT-based migration." It is tempting to draw from it the conclusion "and Redis has nothing of the kind" — which is wrong: Redis Open Source 8.4 gained atomic migration too, under its own CLUSTER MIGRATION command.https://valkey.io/topics/atomic-slot-migration/
  7. valkey/src/cluster_legacy.c — why a replica refuses to be promotedSource code. The condition behind the finding in blocks 11-12. A replica's data age is computed as: "if (server.repl_state == REPL_STATE_CONNECTED) { data_age = (mstime_t)(server.unixtime - server.primary->last_interaction) * 1000; } else { data_age = (mstime_t)(server.unixtime - server.repl_down_since) * 1000; }". For a replica that has never connected to its primary, repl_down_since is zero — so the age comes out measured from the Unix epoch, and no allowance covers it. Hence the permanently stuck "Currently unable to failover: Disconnected from primary for longer than allowed."https://github.com/valkey-io/valkey/blob/9.1.2/src/cluster_legacy.c
  8. valkey/src/version.h — what Valkey states about compatibilitySource code. Two lines side by side, worth knowing before any talk of differences: "#define VALKEY_VERSION \"9.1.2\"" and "/* Redis OSS compatibility version, should never exceed 7.2.x. */ #define REDIS_VERSION \"7.2.4\"". Valkey declares itself compatible up to 7.2.4 — the version at which the projects parted — and nothing guarantees that whatever each added after it still matches.https://github.com/valkey-io/valkey/blob/9.1.2/src/version.h