Redis 4.0.9 puts AOF synchronization before the write reply
Redis 4.0.9 repairs an event-loop ordering bug that could acknowledge a write before syncing it. The fix connects the append-only file durability policy to socket event scheduling.
A durability setting changes the meaning of a successful response. With the append-only file enabled and appendfsync always, Redis is supposed to synchronize a write to disk before acknowledging it. Redis 4.0.9 repairs a scheduling error that could reverse that order. A command could execute and its reply could leave the server during the same event-loop cycle, before the persistence hook ran.
The March 26 release notes classify the upgrade as critical for users relying on that policy. The version-pinned account says the developers reproduced the condition experimentally. This is a failure of the promised acknowledgement semantics, even when the resulting vulnerability window is short and the disk itself works correctly.
I value Redis because its commands give applications concrete operations with understandable behavior. Persistence has to meet the same standard. A successful reply under an explicit durability policy must mean what the policy says, since another system may act on that reply immediately.
Persistence work happens between event-loop iterations
The implementation divides command processing, persistence, and response transmission across several functions. In the AOF implementation, feedAppendOnlyFile serializes commands into server.aof_buf. Putting bytes in that buffer does not synchronize them to disk. flushAppendOnlyFile performs the file write and applies the configured synchronization policy.
The server loop hook calls flushAppendOnlyFile(0) in beforeSleep. Immediately afterwards, it handles clients with pending output. This arrangement puts the persistence step ahead of the ordinary pending-reply path. It also means the guarantee depends on how other response paths interact with the hook.
Under the always policy, the flush function calls aof_fsync after writing the buffer. The source notes that Linux uses fdatasync for this operation to avoid flushing unnecessary metadata. Under the every-second policy, the implementation instead schedules background synchronization when appropriate. Those paths have different relationships to the point at which a client sees success.
The distinction matters when reading the configuration. Enabling the AOF determines that Redis records operations for persistence. Selecting the synchronization policy determines how Redis requests that those writes reach storage. Neither the in-memory dataset update nor appending bytes to a process buffer alone establishes the always-policy acknowledgement guarantee.
The release does not move every disk operation into every command handler. It preserves the existing flush hook and changes the event scheduler so a response cannot use a conflicting route around that ordering. That is a smaller architectural change than making command execution itself synchronously perform all persistence work.
Readable and writable sockets can bypass the intended sequence
The event-loop implementation explains why the old order was useful. A socket may be ready both for reading a request and for writing a reply. Ordinarily, the loop invokes the readable callback first, then the writable callback. That allows a reply produced while processing input to leave through the writable callback during the same iteration.
For many request-response operations, avoiding another pass through the loop is desirable. For an always-synced write, however, that shortcut can send a newly produced response before the next beforeSleep invocation. Having the correct sequence inside beforeSleep is insufficient if a different callback can send the response earlier.
The failure therefore does not require Redis to forget its persistence policy. Command processing can append to the right buffer, and the next flush can perform the right synchronization. The error is that the response becomes externally visible before those deferred actions finish. An application cannot distinguish that response from one sent after synchronization.
Consider an application that treats a successful write as permission to discard its own recoverable copy. If Redis acknowledges before the AOF synchronization, the application can discard that copy while Redis still lacks the promised durable record. This is an implication of the documented ordering defect, not a claim that every affected installation loses data. The problematic interval exists because two systems assign different meaning to the same response.
A process that continues running can conceal this defect. The next flush may persist the operation, and subsequent reads can return the expected value throughout. Observing a correct value after a write does not establish when the acknowledgement occurred relative to synchronization. The source-level event ordering explains the bug more directly than an ordinary command-and-read example could.
The barrier changes callback order
Redis 4.0.9 adds AE_BARRIER to the event machinery. When that flag is present, the loop processes the writable event before the readable event for the descriptor. A writable callback can send output that already existed, but it cannot send a response to input that the readable callback has yet to process in that iteration.
After the read callback executes a new command, the loop reaches beforeSleep before another writable-event opportunity for that descriptor. The AOF flush can therefore run before the response produced by that read leaves through the writable callback. The barrier establishes an ordering relationship using the structure of the existing loop.
The networking implementation supplies the other half of the repair. handleClientsWithPendingWrites first tries to write buffered output directly. If output remains, it registers a writable event. When AOF is on and its policy is always, that registration includes AE_BARRIER.
That direct-write attempt remains safe in the normal hook sequence because beforeSleep has already flushed the AOF before calling the pending-writes handler. The barrier is needed for the subsequently registered callback, which otherwise participates in the read-then-write order on a later event-loop pass. Understanding both paths explains why the patch belongs in networking as well as in the generic event loop.
The flag is a scheduling constraint. It does not introduce a new lock around the dataset or a new acknowledgement message in the Redis protocol. Clients keep receiving the existing command replies, while the server restores the persistence ordering those replies are intended to represent under this configuration.
The configuration still determines the contract
The configuration shipped with 4.0.9 has AOF disabled by default and uses everysec as its active synchronization-policy line. An installation that requires the always policy must select it explicitly. A minimal relevant fragment is:
appendonly yes
appendfsync always
no-appendfsync-on-rewrite no
The final line matters to the meaning of this example. The configuration documents an option to suppress synchronization while a background save or AOF rewrite performs disk I/O. Setting that option to yes relaxes durability during those operations. Leaving it at no preserves synchronization there, so the example does not quietly combine the always policy with a separate setting that suspends it.
The always policy also has an explicit performance cost. File synchronization participates in the path before the acknowledgement, and the supplied configuration describes it as slower than the alternatives. This release repairs the semantics of that choice. It does not claim to remove the storage latency that the choice accepts.
I prefer an explicit cost when it buys a property the application actually needs. Choosing every-second synchronization can be reasonable when the application accepts its weaker persistence timing. Choosing always and receiving an early acknowledgement defeats the reason for paying that cost in the first place.
The release notes accordingly distinguish affected semantics from affected configuration. They say every-second users do not have this particular guarantee and therefore do not suffer a violation of it through this bug. They also note that an always-policy user who does not rely on acknowledgement-after-sync may not need the urgent upgrade for that reason. The important operational question is what dependent application behavior assumes after success.
Error handling reveals the same dependency
The AOF flush implementation treats a file-write failure under the always policy as unrecoverable and exits. Its comment explains that replies already occupy output buffers while the server owes clients synchronized data for acknowledged writes. Under other policies, the function can retain unwritten data and record an error state for later recovery.
That difference reinforces why response timing is part of persistence design. Once success reaches a client, the server cannot withdraw the meaning the client assigned to it. Buffering the response until the required local persistence step completes is therefore a correctness condition, not merely a throughput decision.
The supplied configuration also describes the operating-system role in flushing data. Redis requests synchronization through the system call, and the behavior of the storage stack remains relevant. The event-loop repair establishes the required ordering inside Redis. It should not be expanded into a claim about surviving every conceivable hardware failure.
Redis 4.0.9 restores the connection between a configured local persistence policy and the time a reply can leave the socket. The fix is specific enough to inspect in the scheduler and networking code. For applications that use a successful Redis reply as evidence that the AOF synchronization has happened, that ordering is the substantive reason to install this release.