Redis 5.0.5 synchronizes AOF data after writes stop
An empty application buffer caused Redis to skip pending AOF synchronization work. Redis 5.0.5 separates the need to write bytes from the need to schedule fsync.
A quiet Redis server can still owe work to its persistence policy. A write may have moved out of the application's append-only buffer while remaining in the operating system's page cache. If the server decides that an empty application buffer means persistence work is finished, a periodic synchronization policy can stop making progress precisely when incoming writes cease.
Redis 5.0.5 corrects that case. Its 15 May release notes classify the update as critical because the everysec AOF policy could fail to synchronize the most recent writes when the write load stopped abruptly. A subsequent failure could then lose writes that should have received the policy's periodic synchronization. The mechanism is an early return that treated two different kinds of pending work as equivalent.
I have used Redis since its release and like the range of systems its command model supports. Persistence behavior is part of choosing where those semantics fit. This correction matters because the relevant workload transition is ordinary, a burst of writes followed by inactivity.
Writing a file and synchronizing it differ
The configuration supplied with 5.0.5 documents three AOF synchronization policies. always synchronizes after writes to the log. everysec schedules synchronization periodically, while no leaves flushing to the operating system. These policies trade different synchronization behavior against the work imposed on the serving process and storage.
The file also makes a separate distinction between enabling AOF and selecting its fsync policy. AOF is disabled in the supplied default configuration even though everysec is the selected AOF synchronization policy. Calling it the default policy must not be confused with saying that every Redis installation has append-only persistence enabled.
An example configuration for enabling that policy is:
appendonly yes
appendfsync everysec
Those lines select behavior. They do not establish that the application waits for each command's data to reach persistent media before receiving a response. The purpose of the periodic policy is to avoid that per-write synchronization requirement. The bug concerns whether the promised periodic work gets scheduled when there is no further application data to append.
This distinction is visible in the AOF implementation at the release tag. Appending buffered bytes and requesting background fsync are separate stages. A successful file write advances one stage, but the synchronization policy still has work to consider. The user-space buffer becoming empty cannot represent both stages at once.
The empty-buffer return skipped another obligation
Before the fix, flushAppendOnlyFile returned immediately when server.aof_buf had no bytes. That is a sensible shortcut if the function's only responsibility is writing newly buffered bytes. It is incomplete when the function also owns the decision to schedule a delayed synchronization for bytes already written.
The AOF correction adds an explicit branch for the empty-buffer case. Under AOF_FSYNC_EVERYSEC, it checks whether the recorded fsync offset differs from the current AOF size. It also checks that the time has advanced beyond the last fsync time and that no AOF fsync job is already pending.
When those conditions hold, execution proceeds to the synchronization section without writing another buffer. The code therefore permits a state in which there are no new bytes to append but existing bytes still require synchronization work. That state was the missing case in the old early return.
The helper aofFsyncInProgress examines pending background jobs of type BIO_AOF_FSYNC. This keeps the empty-buffer path consistent with the existing background-work check. The change does not create an independent fsync operation for every idle event-loop pass. It retains a condition that prevents redundant scheduling while an operation is already outstanding.
Why continuing traffic concealed the problem
With ongoing writes, the append-only buffer regularly becomes nonempty. The function can then pass its initial guard, write the new data and reach the synchronization logic. Under that pattern, the structure of the old function makes the omitted idle case less obvious. New traffic creates opportunities to revisit work associated with earlier traffic.
A burst followed by silence removes those opportunities. Redis can have written the final commands to the file without yet reaching the next periodic synchronization point. Later calls see an empty application buffer. Under the previous guard, they return before deciding whether those already-written commands now need fsync.
The release notes describe the resulting data-loss condition in terms of a write load that drops immediately and a later server failure. The source comment identifies data remaining in the page cache. That distinction matters when describing failure scenarios. Loss of the entire machine's volatile state is different from only restarting a process while the operating system remains healthy.
It would therefore be too broad to say that stopping client traffic always loses data, or that an ordinary Redis restart necessarily loses the final burst. The defect delays the synchronization work that the selected policy should perform. Data loss depends on a subsequent failure that makes the unsynchronized data unavailable.
I find this example useful because it ties durability to a state transition. High traffic is not the only demanding workload. A service can violate a persistence expectation while its request rate is zero, if a time-based obligation incorrectly depends on another request arriving.
Tracking an offset does not make every write synchronous
The patch records aof_fsync_offset when it schedules a background fsync, alongside the current AOF size. That field helps decide whether newly written bytes remain outside the work already requested. It should not be described as a per-command certificate that every preceding byte has completed persistence on the storage device.
The difference between scheduling and completion is visible in the call itself. aof_background_fsync starts work in another thread. The surrounding code checks for existing work and updates its bookkeeping when issuing a new request. The policy remains asynchronous and periodic after the fix.
That is why the update does not turn everysec into always. A client still selects a periodic policy with its associated durability tradeoff. The correction makes the implementation continue that policy when no new commands arrive. It removes an unintended dependency without changing the advertised choice into a stronger synchronization mode.
The same code retains the option that suppresses fsync while background saving or AOF rewriting is active. The supplied configuration documents that option separately as no-appendfsync-on-rewrite, with a default of no. Enabling it deliberately changes synchronization behavior during those operations. It is a different cause of delayed fsync from the empty-buffer bug.
The AOF file can also use an RDB preamble followed by an append-only tail, as the supplied configuration explains. That layout affects how Redis reloads a rewritten file. It does not make a successful append equivalent to a completed fsync. The idle synchronization issue concerns the active file and its pending synchronization work, independently of describing that file as a sequence of commands or a preamble plus tail. Recovery format and synchronization policy are separate parts of persistence behavior, and changing the former is not what this patch does.
The release changes persistence behavior without changing commands
No application command syntax is needed to trigger the corrected path. The relevant inputs are the AOF policy, the persistence state and the timing of writes. This makes the patch materially different from a fix to a rarely used command option. An installation using periodic AOF synchronization can encounter the workload pattern through normal application behavior.
The release also corrects a stream iterator issue and adds module APIs, but neither explains its critical urgency. The AOF correction addresses a durability expectation attached to an existing configuration. Evaluating the urgency from the list of newly added APIs would miss the reason the maintainers recommend this update.
An operator can identify the affected policy from the versioned configuration and understand the changed behavior from the commit. The meaningful question is whether append-only persistence uses everysec, not whether a particular application data structure is present. The early return and the new offset check sit in shared persistence code.
The corrected implementation can reach its synchronization decision with an empty append buffer. It checks whether the file contains bytes beyond the recorded fsync work, whether another job is pending and whether the policy's time condition has been reached. A final burst of writes can therefore receive its periodic fsync scheduling even when no subsequent request adds bytes to the application buffer.