August 14, 2025·6 min read·engineeringvalkeyclusteringexpirationreplication

Valkey 9 RC1 makes slot movement a replication job

Valkey 9's first candidate adds atomic slot migration, hash-field expiration and numbered cluster databases. Each changes an operational assumption around existing data structures.

Moving a cluster slot requires transferring data while accounting for writes that continue during the transfer. Valkey 9's first candidate introduces a source-driven replication procedure for that work. The source sends a snapshot of the selected slots and then a stream of changes, with an explicit handoff before ownership changes. That is the central operational change in the August 14 release candidate.

Hash-field expiration and numbered databases in cluster mode are the other major additions. They address different limitations, but both interact with moving data between nodes. A slot's contents can now span database numbers, and a hash can contain fields with individual expiration state. Migration has to preserve the data model that the receiving server is expected to serve.

I prefer Valkey when Redis semantics are wanted, and cluster operations are a substantial part of that preference. A compatible application interface is useful only if the server's operational mechanisms can move and maintain the data behind it. This candidate gives those mechanisms new responsibilities which deserve a version-specific explanation.

The source controls an asynchronous migration

The atomic migration proposal and implementation introduce migration jobs connecting a source node to a target. A job sends an AOF-formatted snapshot of the selected slots, followed by their replicated changes. The target can replay the snapshot to its own replicas through the same replication forwarding mechanism. An initial command starts asynchronous work whose state remains observable afterward.

The candidate includes CLUSTER MIGRATESLOTS to begin the operation and CLUSTER GETSLOTMIGRATIONS to inspect recent jobs. Cancellation has a separate command. This separation is operationally useful because command acceptance and completed ownership transfer occur at different times. A caller needs the job's result to distinguish a requested migration from a finished one.

The tagged migration implementation contains separate importing and exporting states. They describe connection establishment, snapshot transfer and streaming, followed by pausing and failover coordination. Failure and cancellation are terminal states alongside success. The implementation therefore represents the progress needed to make an atomic handoff possible.

Atomic ownership change does not mean copying an entire slot in a single instantaneous operation. The copy takes time, and writes during that interval need a defined destination and order. Snapshot-plus-stream transfer addresses that interval. The final coordination establishes when the target may assume responsibility for the migrated data.

The source file also validates requested ranges before creating the operation. It rejects ranges whose start exceeds their end, overlapping ranges, unassigned slots and requested slots spanning multiple shards. Those checks establish the ownership context in which the job can operate. A range description alone is insufficient if its slots do not have the required common owner.

On errors, the target marks the import as failed and closes its source connection. The implementation comments require the operator to restart from the source. That is a defined failure result, not a promise that a migration automatically continues through every connection or ownership failure. A controller using the new interface needs to retain that distinction when it interprets job status.

Numbered databases share the cluster's slot map

Multi-database cluster support leaves key hashing unchanged. Identical key names map to the same slot across database numbers. Database selection therefore adds a namespace within the cluster's existing distribution model. It does not create an independent slot map for every numbered database.

The implementation describes cluster management as global, with key enumeration and key counting for a slot using the selected database context. That difference matters to administrative tooling. A command reporting ownership describes the cluster, while a command enumerating a slot's keys needs to know which database's keys it should return.

Migration tooling also has to cover all relevant databases. The contribution updates the CLI's resharding behavior to handle that requirement. A tool which assumes all cluster keys live in database zero can otherwise report or move an incomplete view of the slot's contents. Retaining the hashing algorithm does not remove the need to update database-aware administration.

The tagged atomic-migration code applies importing state across databases. It marks the key and expiration stores, including the store tracking keys with volatile items, for the relevant slots. This shows how the candidate's new capabilities meet inside the implementation. Import visibility is a property of the slot's data throughout the server, not merely one selected database's key list.

Numbered databases should also be interpreted according to what the change actually supplies. They separate key namespaces while sharing the cluster's placement mechanism. The release does not establish independent resource budgets or failure domains for those numbers. Applications needing those properties cannot infer them from the availability of database selection.

Hash fields acquire independent expiration state

The field-expiration contribution adds expiration to individual field-value entries. It explicitly aims to preserve familiar client APIs while extending persistence and defragmentation handling to the new metadata. A field can now have a lifetime independent of other fields stored under the same top-level key.

That is useful when a hash groups related values whose lifetimes differ. Expiring the whole key would remove every field together. Separate keys can express separate lifetimes, but they also change the representation exposed to application code. Field-level expiration lets that lifetime distinction exist inside the existing hash representation.

The HEXPIRE command definition in this candidate makes the field list explicit. For example, a hash can retain a stable identifier while expiring a temporary status field.

HSET job:42 owner alice status running
HEXPIRE job:42 60 FIELDS 1 status

The command returns a result for each specified field, in the order requested. Missing fields and unmet conditions have different result codes from successfully applying expiration. A client operating on several fields must interpret those individual results, rather than treating the command as one undifferentiated success flag.

HSETEX combines writing fields with expiration options. Its candidate schema includes seconds and milliseconds, absolute expiration timestamps and a keep-TTL option. This permits the write to carry its intended lifetime directly, which avoids an application-level gap between storing a value and issuing a separate expiration command.

HSETEX job:42 EX 60 FIELDS 1 status running

These examples describe the candidate's syntax. They do not imply that expiration metadata is free or that every command reports the physical hash contents in exactly the same way. The implementation proposal explicitly discusses the difference between fields whose expiration has elapsed and fields already reclaimed by background expiration work.

Expiration visibility and reclamation are different

The field-expiration design chooses active expiration for memory reclamation at this stage. Its explanation calls out consequences for HLEN and random-field selection when expired fields remain physically present. That is relevant to consumers who use counts or sampling as part of an algorithm. A field's logical lifetime and the moment its storage is reclaimed are related events with different observable effects.

Persistence needs the same distinction. The proposal describes loading fields during RDB loading, including fields whose expiry may already have passed, with later reclamation. That keeps the loading path simpler but means snapshot size and initial in-memory representation can include data whose useful lifetime has ended. It is an explicit implementation tradeoff in the candidate.

I find the combination more interesting than any one added command. Cluster migration, database namespaces and per-field lifetime now meet in the same server. The candidate's implementation has to preserve ownership and expiration metadata across those boundaries, while clients need to understand the specific results of commands they adopt.

The candidate also adds DELIFEQ, which deletes a string key only when its value matches the supplied comparison. Its result distinguishes deletion from leaving the key unchanged. This supports conditional cleanup when the caller already knows the value it expects to remove. Comparing and deleting inside one server operation closes the gap between a separate read and unconditional deletion, during which another writer could replace the value. As with any value comparison, the application still determines what ownership or state that value represents.

Valkey 9 RC1 remains a release candidate with low stated upgrade urgency. Its concrete additions already define what can be evaluated against an application's data model. Numbered databases retain common slot placement, hash fields acquire individual expiration state, and slot migration becomes an observable asynchronous replication job with explicit terminal outcomes.