Valkey 8.1 RC1 puts keys inside value objects
The first Valkey 8.1 candidate changes hash-table layout and adds native conditional string updates. Both changes reduce work around operations that applications already perform.
An in-memory database spends memory on the structures that locate values as well as on the values themselves. For short keys, that bookkeeping can be a substantial part of the allocation. Valkey's first 8.1 candidate addresses this with a new hash table and a revised object layout. The implementation in pull request 1186 embeds the key, and optionally its expiration timestamp, in the value object. Its author estimates roughly 20 bytes saved per short string key.
The 8.1 RC1 release is public on February 16. The notes also contain an inconsistent February 11 heading, so the publication timestamp is the date used here. This is a candidate release with low upgrade urgency, containing substantial internal changes alongside new commands and configuration behavior. It provides a concrete view of the proposed implementation before a stable release.
I prefer Valkey where Redis semantics are wanted, so improvements that preserve familiar data operations have particular relevance. Lower per-key overhead changes the cost of a dataset without requiring its owner to choose a different representation. That is an appealing direction for a compatible server.
A bucket contains more useful information
The new table organizes entries into cache-line-oriented buckets. Each entry has a small amount of metadata, and a full bucket can use its last entry pointer to link to a child bucket. The design uses chaining after an earlier probing-based proposal. It retains operations such as scanning and incremental rehashing which the rest of the database needs.
The user of the table defines the entry itself. For database keys, the entry is the value object, with the key embedded alongside its metadata. The keys table and expiration table can refer to that object. This arrangement changes allocation ownership as well as reducing space, and the implementation notes explicitly describe adjustments to functions which add keys or set expiration.
The practical effect depends on the dataset. Saving a fixed amount per key has greater proportional value when values are small. A database dominated by large payloads spends more of its memory on those payloads, so the same bookkeeping improvement represents a smaller fraction of total use. The per-key estimate should be understood in terms of its stated short-key case.
Iterator prefetching in pull request 1501 complements the layout change. When iteration begins on one bucket, the implementation prefetches entries from the following bucket and the structure of the bucket after that. It gives memory reads time to complete before the iterator needs their results.
That patch reports improvements for key iteration and snapshot saving in its particular test setup. The mechanism explains why operations that traverse many entries can benefit even when command semantics remain identical. The useful distinction is between locating one requested entry and repeatedly advancing through a table. Prefetching has specific opportunities in the latter case because the next locations become known in advance.
Conditional string updates become one command
The candidate also includes conditional SET support from pull request 1324. The new IFEQ argument compares the existing string with a supplied value and applies the replacement only when they match. A small example shows the intended operation without introducing a scripting runtime.
SET document-state reviewing
SET document-state approved IFEQ reviewing
The comparison is part of the update operation. An application that reads a value and later sends an unconditional replacement otherwise has a gap in which another writer can change it. IFEQ makes the expected old value explicit in the command sent to the server. It is useful when the application already possesses the value against which it wants to compare.
The interaction with GET needs attention. The patch specifies that GET returns the previous value regardless of whether it matched the comparison. A caller must compare that returned string with its expected value to determine whether the update occurred. Combining IFEQ with either NX or XX is a syntax error, so those flags do not supply extra conditions for this operation.
This is value comparison, and its meaning follows the values supplied by the application. It does not independently prove that no intervening writer has changed a value and restored the same bytes. Applications that need to distinguish such histories need to encode that distinction in the compared value. The command supplies an atomic comparison and replacement, while the application supplies the state model.
Command cost includes request and reply size
COMMANDLOG, introduced in pull request 1294, addresses a limitation of recording execution time alone. A command can require little CPU during processing while receiving a large request or producing a large reply. The patch uses large string SET and GET operations to explain the difference between execution work and network or buffer consumption.
The command keeps separate logs for slow execution, large requests and large replies. For example, these requests inspect the two size-oriented logs.
COMMANDLOG GET 10 large-request
COMMANDLOG GET 10 large-reply
Each log has its own threshold and retained-entry limit. Request and reply thresholds use bytes, while the execution-time threshold uses microseconds. This permits an investigation to distinguish a command that occupies processing time from one whose payload consumes bandwidth. A low execution duration alone cannot establish that a command was inexpensive for the whole service.
I find that addition as useful as the memory changes. A server that improves internal processing still needs to explain where an application's costs occur. Size-based command records provide information that a CPU-oriented slow log cannot supply on its own.
Maintenance work has a scheduling cost
The active-defragmentation refactor in pull request 1242 reduces the duration of individual cycles and runs them more frequently. Its description gives a 500-microsecond cycle and reports reduced tail latency in its tests. It also states that lower-level functions without time bounds remain outside that change's scope.
That qualification matters to the mechanism. Smaller scheduled slices reduce how long one maintenance invocation occupies processing, but they do not prove that every nested operation obeys the same limit. The candidate introduces more precise control through active-defrag-cycle-us. Its value is the ability to schedule maintenance with a clearer time budget while retaining the work needed to repair fragmentation.
TLS offloading in pull request 1271 addresses another source of main-thread work. It moves error-queue clearing closer to actual SSL calls and records pending TLS data after reads, allowing the main thread to use a flag. These changes reduce SSL-related work left on the main thread after I/O offloading. Together with the table changes, they show an 8.1 candidate concerned with specific remaining costs in existing operations.