March 17, 2025·6 min read·engineeringdragonflypubsubreplicationclustering

Dragonfly 1.28 connects Pub/Sub routing to slot ownership

Dragonfly 1.28 adds sharded Pub/Sub support and repairs Streams replication. Migration cleanup and transaction wakeups show where command compatibility depends on lifecycle handling.

A sharded channel needs an owner even though it is not an ordinary database key. Dragonfly 1.28, published on March 17, adds Redis 7-style sharded Pub/Sub support and the routing checks needed to connect a channel name to cluster placement. It also repairs subscription cleanup after migration. These changes make channel ownership part of operating the cluster, alongside the keys stored there.

The release includes Streams replication corrections and several transaction deadlock fixes. Their common concern is the state surrounding an operation. Moving data must update subscriptions, a replica needs the result actually chosen by the primary, and a blocked command must wake when its condition becomes true. Returning the right result in one isolated invocation does not establish those properties.

I use Dragonfly's multicore design as a reason to consider a larger single server before distributing a cache. Cluster support adds another operational option, but its value depends on preserving command behavior while ownership changes. The sharded Pub/Sub work gives that requirement a concrete form.

Channel names participate in routing

The sharded Pub/Sub implementation recognizes SPUBLISH, SSUBSCRIBE and SUNSUBSCRIBE as a separate command group. It identifies their channel arguments for cluster ownership checks even though ordinary command-key metadata does not describe them as database keys. The patch also applies channel authorization checks to the new group.

That is necessary because the server needs to decide whether it can serve the channel on the current node. A command without a stored key can still have a cluster placement rule. Using ordinary absence of key arguments as a reason to skip routing would miss the channel's ownership requirement.

For applications, adopting the sharded operations is an explicit protocol choice. A publisher and subscriber need to use the matching channel operation family, with routing consistent with its cluster placement. Merely replacing the backend process does not rewrite an application's publish calls into the new family.

The feature's usefulness is therefore tied to client support as well as server parsing. A client needs to follow the relevant cluster routing and interpret subscription responses. The server-side patch supplies the command distinction and ownership checks that make those client behaviors meaningful.

Migration changes the subscription's valid location

Subscription cleanup after slot migration removes subscriptions associated with slots the source no longer owns. Its tests move ownership, verify a redirect from the old owner and observe the unsubscribe notification. Subscription state must change with placement, rather than remaining attached indefinitely to the connection's original node.

The implementation also captures subscriber information before dispatching work across threads. The patch comments identify subscriber lookup as unsafe across the relevant preemption boundary. Looking up a channel's subscribers and later using their references are different moments when other work can intervene. Preparing the information before that boundary repairs a race in the cleanup path.

This illustrates a practical difference between transferring key bytes and completing a resharding operation. Connections can retain state associated with the old placement. Correct migration requires either moving or invalidating that state under a defined protocol. An unsubscribe notification gives the client an observable transition instead of leaving a stale subscription silently attached.

For a subscriber, the notification means its prior subscription state has ended on that connection. What the application does next depends on its client and desired behavior. The release's correction establishes the server transition, without implying that a publish-subscribe channel has become a persistent message queue.

Introspection follows the sharded command family

Pull request 4702 adds PUBSUB SHARDCHANNELS and PUBSUB SHARDNUMSUB. The former lists active shard channels, optionally filtered by a pattern. The latter returns subscriber counts for specified shard channels. Those names distinguish them from the ordinary channel introspection commands.

PUBSUB SHARDCHANNELS
PUBSUB SHARDNUMSUB updates

These operations make the new subscription state inspectable. A deployment can distinguish the channel family it intends to use when observing active subscriptions. That is especially relevant during migration, since the existence of an application connection alone does not establish that it still has the expected channel subscription.

The patch also keeps the cluster-mode restrictions for ordinary Pub/Sub introspection separate from the sharded variants. This avoids treating the addition as a blanket claim about every existing publish-subscribe command in cluster mode. The implemented command family and its administrative views have specific scope.

Replicas need resolved trimming boundaries

The Streams replication repair changes how XADD and XTRIM record their effects. For a nonempty trimmed stream, the journal can use the new first entry's ID as the trimming boundary. For an empty result, it records a trim to length zero. XADD replication also uses the generated entry ID selected by the primary.

These details matter because replication needs to reproduce the primary's outcome. Replaying a request which originally depended on generated IDs or approximate trimming can leave room for a different decision at the replica. Recording the resolved boundary makes the intended resulting stream explicit in the journal.

The empty-stream case has its own representation. A last-deleted ID is not always a useful boundary for expressing that no entries remain. A trim to zero states that result directly. The added tests compare primary and replica data after trimming all streams, checking the stored outcome rather than assuming the original command text is sufficient.

This is a useful example of replication carrying a deterministic description of an effect. The client request describes what to do under current conditions. Journal records need enough information to establish what the primary actually did. The two can differ while remaining faithful to the same application operation.

Journaling state must reset between commands

A second Streams-related fix resets an automatic-journaling flag when a transaction switches commands. The regression scenario combines a stream trim with another operation whose journaling policy differs. State enabled for the first operation must not incorrectly influence how the later one enters the replication log.

The correction is small because the incorrect assumption lies in reuse of transaction state. An object which coordinates several commands has both transaction-wide fields and fields whose meaning belongs to one command. Switching the current command requires resetting the latter. Otherwise a later operation can inherit bookkeeping which describes its predecessor.

For an application, both commands may appear to complete normally on the primary. The failure becomes visible in what the replica receives, so testing only immediate replies would miss the affected behavior. The patch's test specifically checks journal handling across the command switch.

Blocked list operations need their wakeup

Pull request 4590 repairs list movement on a single shard when another transaction waits on the destination. Creating the destination list must notify the blocking controller. Its test waits on one list, moves an element into it and verifies both the received value and the removal of waiting state.

The destination acquiring an element is only one half of that operation's effect. A waiting consumer also needs a scheduling event that allows it to proceed. If the storage update succeeds without that event, the database can contain the required value while the consumer remains asleep. The fix connects those two facts.

The release also disables a single-shard scheduling optimization in the transaction path associated with deadlock investigation. That is a concrete correctness tradeoff. An optimized shortcut must preserve progress as well as the eventual values an operation would produce if it ran.

Another deadlock fix involving REPLICAOF inside MULTI selects non-atomic execution when the queued commands include administrative work. The patch's tests repeatedly submit transactions containing a replication-role change. This is a material limit to assumptions about transaction behavior, because administrative operations interact with the engine's own control flow. Treating every queued command as ordinary key access can create a circular dependency. The correction makes the selected execution mode reflect that administrative role instead of relying on the mode used for normal data commands.

I regard these supporting repairs as part of making the larger cluster story usable. Channel routing, replica state and blocked-consumer wakeups each carry information beyond a key's current value. Dragonfly 1.28 adds the sharded Pub/Sub interface while correcting how several existing operations maintain that surrounding information across execution and migration.