Dragonfly 0.9 adds an epoll fallback
Dragonfly can select epoll when io_uring is unavailable, and SADDEX introduces expiring set members with explicitly passive cleanup.
A datastore's client protocol is irrelevant if its process cannot start under the host's I/O restrictions. Dragonfly 0.9.0, released on October 4, adds an epoll backend and automatically selects it when io_uring is unavailable. The release also lets operators force that choice. This removes a deployment dependency that the initial version treated as mandatory.
The change addresses more than old kernel versions. The startup code also checks whether io_uring initialization succeeds on a kernel that is new enough to offer it. Availability depends on the running environment, including resource limits, so the kernel version alone cannot establish that the preferred I/O engine will initialize.
I find this relevant to Dragonfly's appeal as a cache that scales within one machine. A simpler process topology is useful only if the process can run under the environment's actual constraints. Supporting another Linux I/O engine broadens that choice without requiring a different client API.
Startup selects an available engine
The epoll implementation change shows the selection procedure. An explicit force flag takes precedence. Otherwise, kernels older than the previously required threshold select epoll immediately. On newer kernels, the process attempts to initialize an io_uring queue and inspects the result.
A successful probe keeps io_uring. A failed probe causes the server to select epoll, with messages distinguishing some failure causes. The code handles an unsupported syscall interface and insufficient memory, and its memory-related diagnostic mentions limits on locked memory as one possible explanation.
That is a more complete availability test than looking at a version string. A recent kernel may still run a container whose restrictions prevent a particular initialization. The fallback allows the server to continue through a supported alternative rather than exiting solely because that probe failed.
An operator can choose the alternative directly.
dragonfly --force_epoll
The flag controls the server's underlying I/O engine. It does not select a different datastore format or change the client command spelling. The startup code constructs an epoll pool or an io_uring pool and passes the selected pool to the same accept-server and engine setup.
The release notes connect the fallback to reduced friction in Kubernetes, including avoiding the need for a privileged-container workaround for this I/O requirement. That statement has a specific scope. It does not establish that every possible deployment restriction disappears or that the binary now runs on every operating system.
The accompanying README change still identifies Linux as the platform and recommends a recent kernel while allowing older ones. A defensible claim is that io_uring is no longer the sole runtime I/O path. The release's broad wording about all Linux kernels should not be read as a complete platform-support specification.
Networking was not the only io_uring dependency
The patch also changes file handling. A fallback that accepted connections but could not save or load data would leave a substantial part of the server tied to the old assumption. The implementation therefore supplies fiber-aware file operations for the epoll path.
For snapshot output, the server can create a write file through a fiber queue thread pool when using epoll. The io_uring path continues to use its Linux file wrapper. Both feed the snapshot saver through the appropriate output interface, with direct-I/O handling selected according to the path.
Loading a snapshot likewise chooses a fiber read file when the fallback's thread pool exists and the io_uring read path otherwise. This is an architectural consequence of the fallback. Changing the socket event mechanism requires accounting for persistence operations that also relied on the original I/O facilities.
The patch also replaces some connection-level casts to an io_uring-specific socket with a common Linux socket interface. Polling and cancellation can then operate through the abstraction shared by the backends. That keeps connection management from assuming the concrete engine selected at startup.
These details explain why epoll support is more than a conditional around one initialization call. The server has to carry the selected I/O model into the parts of its lifecycle that previously knew only one backend. The common interface makes those parts usable after either startup decision.
INFO reports the selected mechanism
The server's INFO output changes with the selected backend. The patch computes the multiplexing API from the active proactor kind and reports either epoll or io_uring through the corresponding field. That gives operators a way to identify the runtime decision through the server.
This is useful because automatic fallback can make a server start successfully while choosing a different path than expected. The operator needs an observation of the chosen path, not just evidence that the process has opened its port. The INFO field supplies that specific piece of information.
The release separately reports lower idle CPU usage. It does not provide a direct benchmark comparing epoll and io_uring for every workload, so those statements should not be combined into a claim that the fallback itself always reduces CPU or improves latency. Startup compatibility and workload performance remain different questions.
I prefer this explicit selection and reporting to treating a successful launch as proof that every low-level assumption held. The fallback can recover from an unavailable engine, and the server can state which engine it actually uses. That is a useful operational contract for a deployment whose restrictions differ from a developer's machine.
SADDEX changes the unit of expiration
The same release adds the Dragonfly-specific SADDEX command. Pull request 348 describes adding members to a set with expiry measured in seconds. The timeout belongs to members added through the operation, rather than necessarily expiring the whole set key.
SADDEX recent 60 alice bob
The example follows the documented key, seconds, and members arrangement. It asks the server to add the named members with a sixty-second lifetime. A positive seconds value is required by the tagged parser, which also imposes a maximum supported timeout.
The set implementation at v0.9.0 registers SADDEX when the newer set representation is enabled. That detail matters because the command depends on storage support for member-level expiry. A protocol extension cannot provide those semantics without a data structure that retains the relevant timing information.
The timeout parser accepts an unsigned value, rejects zero, and rejects values greater than two to the twenty-sixth power in seconds. A failed timeout check returns an invalid-integer error before scheduling the set update. That is a command-input limit independent of the passive cleanup policy that governs members after insertion.
The earlier set-expiry implementation adds expiration-aware traversal and lookup behavior to the dense set. Iteration checks expiry as it encounters entries, and insertion-related paths can also recover expired space. The change therefore reaches below command parsing into how members are stored and visited.
Passive expiry affects counting and reclamation
The SADDEX patch states two limits that deserve to accompany any example. Expiry is passive, so an untouched set can retain expired members physically. SCARD uses a cached size and does not scan every member to determine whether it has expired, making its result an upper bound for sets containing expiring members.
That differs from the usual expectation of an exact set cardinality for a normal set. The patch explicitly says ordinary sets retain exact counting. The changed behavior is associated with the new expiry semantics, so a caller should not generalize it to every set operation or every stored set.
The distinction between logical expiry and physical reclamation is especially relevant to caches. A member's deadline can make it unsuitable to return while its allocation remains until an operation encounters it. Merely waiting for the deadline does not establish when memory is reclaimed under this passive implementation.
SADDEX is also an extension, so using it creates a dependency on Dragonfly behavior beyond the common command set. An existing client may be able to send arbitrary commands, but another server implementing the ordinary set API need not understand this one. The convenience comes with a specific portability tradeoff.
The October release consequently broadens two different boundaries. Epoll provides another way for the Linux server to perform its I/O, including persistence work. SADDEX provides a new way for applications to describe member lifetimes, with passive cleanup and an explicitly qualified cardinality result. The tagged implementation states both the added capability and the observable semantics a caller must use.