April 28, 2026·4 min read·engineeringvalkeyperformanceiomemory

Valkey 9.1 RC2 changes thread handoff and small-string storage

Valkey 9.1 RC2 adds queue-based I/O handoff and expands embedded string storage. It also defers stricter TLS configuration validation because the change breaks compatibility.

The second Valkey 9.1 release candidate changes how work moves between the main thread and I/O threads. It also changes which strings fit inside an embedded allocation. Both changes reduce costs around ordinary commands without requiring an application to express a new operation.

Valkey 9.1.0-rc2, released on April 28, remains a release candidate. Its tagged notes report performance gains from the changes and explicitly revert strict TLS certificate validation at configuration load. That last decision is part of the same release's engineering story, because internal optimization and externally observable compatibility require different treatment.

I default to Valkey for Redis semantics and appreciate that the project can develop its implementation while retaining a familiar interface. These changes are interesting because the mechanism is visible in the code. The reported percentages alone would say much less about which workloads could benefit.

I/O workers pull work from a shared queue

The I/O communication redesign introduces specialized queues for the different directions of communication. The tagged I/O thread implementation has a shared inbox for work sent by the main thread. Multiple I/O threads consume it.

That arrangement lets available workers take work from the same source. A busy worker does not need to receive an equal predetermined share while another has capacity. The queue topology therefore participates in distributing work, rather than merely transporting a fixed assignment.

Completed jobs travel back through a shared outbox with multiple producers and one consumer. The I/O threads produce responses, and the main thread consumes them. Thread-specific work has separate single-producer, single-consumer inboxes.

Those directions explain why the implementation has more than one queue type. A shared worker inbox and a return channel have different producer and consumer relationships. Treating every exchange as the same queue problem would discard information the implementation can use to coordinate access.

The code also retains pending I/O responses locally when the shared outbox is full. Capacity is still bounded even with lock-free communication. Avoiding a conventional lock does not remove the need to account for a destination that temporarily cannot accept more work.

Completion and waiting remain explicit

The released implementation counts submitted and completed jobs. Its drain routine commits pending inbox work and waits for the outstanding count to reach zero. Separate client state records pending reads and writes.

These details limit what should be inferred from the release description. A new queue model does not mean all interactions between threads have become independent or that waiting has disappeared. Some operations still need to establish that work has completed before they proceed.

The release notes report an eight to seventeen percent throughput improvement for the redesign. That is a project-reported result, not a universal multiplier for a deployment. The mechanism suggests an improvement in communication and distribution costs, while the effect on an application depends on how much of its work pays those costs.

A workload dominated by another part of command execution can have a different response. This is why I find the queue directions useful information. They connect a performance claim to the operations the patch actually changes.

The embedded-string limit counts more than value bytes

The embedded-string change raises a threshold from 64 to 128 bytes. The tagged object implementation shows that the comparison uses a computed allocation size.

That size includes the object overhead and string representation. When a key is embedded, its representation contributes to the total. An expiry contributes additional space as well. The threshold does not mean every value shorter than 128 bytes receives the same layout.

The regression examples make this concrete. With a 32-byte key and no expiry, the patch's test expects a 79-byte value to fit while an 80-byte value uses the raw representation. With expiry present, the corresponding examples are 71 and 72 bytes.

The public command has not changed, but key naming and expiry now help determine which side of that internal boundary a stored value occupies. Two applications with the same value-length distribution can therefore have different layout distributions if their keys or expiry policies differ.

Embedded strings place the string storage in the same allocation as the object. The implementation describes that representation as unmodifiable. Expanding the threshold lets more objects use it, but it does not eliminate the need for the other representation.

The release notes report a GET throughput gain for this change. Its most useful practical qualification is the allocation calculation itself. A performance comparison needs to describe the key and value shapes if it is intended to explain the benefit.

TLS startup validation is an observable behavior change

RC1 introduced stricter certificate checks during TLS configuration loading. The RC2 revert removes those checks, and the notes defer that behavior to a future major version because it breaks compatibility.

The removed code checked certificate validity periods and inspected CA certificates during configuration creation. Moving a failure earlier can appear attractive, but it changes whether a process accepts an existing configuration. That can affect a restart even when the application has not changed its commands.

Deferring those checks does not mean TLS has ceased validating connections. The patch concerns the additional fail-fast behavior at configuration load. Conflating configuration acceptance with handshake verification would misstate the change.

The distinction is useful to operators following the candidate sequence. An installation that starts under RC2 has not thereby demonstrated that its certificates will satisfy every connection. The candidate deliberately restores an earlier configuration-loading boundary while retaining the separate TLS connection path.

These changes show Valkey development at two levels. Queue ownership and string layout alter internal costs. TLS configuration validation alters a behavior deployments can observe during startup. RC2 makes both categories explicit, with source-level details that explain why their compatibility implications differ.