Linux Dirty Pages, Writeback, and fsync
Linux's page cache is the part of RAM where the kernel keeps file data, and a dirty page is cached file data that has been changed but not yet written to the storage device. Background writeback eventually sends those changes out; fsync makes a program wait for that process to reach a defined completion point.
That distinction explains why an ordinary write can be fast while an fsync can be slow, and why a successful write does not necessarily mean data will survive a sudden power loss.
The problem the page cache solves
Storage is much slower than RAM, especially when an application performs many small reads and writes. Without caching, reading a file would repeatedly wait for the storage device, and each write would have to wait for the device before the application could continue.
The page cache lets Linux treat file data as memory first:
- A read can be served from RAM if the requested data is already cached.
- A write can update RAM and return before the storage device finishes.
- Multiple small writes can be combined into larger, more efficient I/O operations.
A page is a fixed-size block of virtual memory, commonly 4 KiB on Linux. For file-backed data, the kernel associates each cached page with a position in a file. The cache is therefore not just a general pool of bytes: it knows which file and which file offset each page represents.
Clean and dirty file-backed pages
When Linux reads a missing part of a file, it loads that data into the page cache. The page is initially clean, meaning its contents agree with the copy represented by the storage device.
When a process changes the cached contents—for example, through write(2) or a writable mmap mapping—the kernel marks the corresponding page dirty. Dirty does not mean corrupted or invalid. It means:
The newest version exists in memory, while storage still has an older version.
An ordinary file write commonly follows this path:
- The process enters the kernel with data to write.
- Linux copies or incorporates that data into the file's cached pages.
- Those pages are marked dirty, and file metadata such as the size may also change.
- The write call can return without waiting for the physical device.
At this point, other processes reading the file through the normal filesystem path will generally see the updated page-cache contents. That is visibility, not durability. The update is visible to software but remains vulnerable to a crash or power loss.
How background writeback works
Writeback is the kernel's process of submitting dirty cached pages to the filesystem and block device. Linux performs it in the background, using kernel worker threads and filesystem-specific writeback code.
The kernel does not wait until RAM is completely full. It tracks the amount of dirty memory and starts or intensifies writeback when configured thresholds are reached. If a process keeps dirtying memory faster than storage can absorb it, Linux eventually throttles that process: its writes begin waiting for dirty pages to be written out. This prevents one writer from consuming nearly all memory and leaving no room for other work.
Writeback usually works like this:
- The kernel selects dirty pages for a file.
- The filesystem prepares write requests for the relevant storage blocks.
- The block layer submits those requests to the device.
- After successful completion, the pages can be marked clean and reclaimed later if memory is needed.
A page is not clean merely because writeback started. It becomes clean only after the kernel has received the appropriate completion from the lower layers. If an I/O error occurs, the filesystem must retain or report that failure rather than treating the old storage contents as current.
Background writeback improves throughput and spreads I/O over time, but it does not provide a per-operation durability promise. A process may continue for seconds after a write returns while its changes are still dirty—or while they are sitting in a device's volatile write cache.
What fsync changes
fsync(fd) is an explicit request from a process to make the changes associated with an open file descriptor durable, then wait until that request completes. In response, Linux and the filesystem arrange for the file's dirty data to be written and for relevant filesystem metadata to be committed. The call does not normally return until the required I/O has completed or an error is reported.
This makes fsync a durability boundary:
- Before it completes successfully, a recent write may exist only in memory or in a volatile device cache.
- After it completes successfully, the operating system has obtained the storage device's acknowledgement for the requested data and ordering.
fdatasync is a related operation intended to flush file data and only the metadata needed to access that data, avoiding some metadata work that fsync may include. If an operation creates, renames, or removes a directory entry, syncing the file alone may not be enough; applications that need that name change to survive a crash commonly also sync the containing directory.
fsync does not mean “flush every dirty page on the machine.” Its scope is tied to the file and filesystem operation being synchronized. It can still cause surrounding filesystem work because filesystems often group updates into transactions or journal commits.
Why latency and durability are separate
A normal write often has low latency because it measures the cost of copying data into RAM. An fsync measures the much less predictable cost of getting data through the filesystem, block layer, device firmware, and sometimes flash translation or persistent media. Queueing and device cache flushes can make that cost especially visible.
That leads to a familiar performance pattern: an application appears fast until dirty-memory limits force it to wait, or until a database, log writer, or message queue calls fsync for each committed record. Batching several logical updates before one synchronization can reduce overhead, while omitting synchronization can improve latency at the cost of a larger crash-loss window.
Finally, fsync depends on the storage stack honoring flushes and write-ordering commands. A device with a volatile write cache that lies about completed writes can still lose data. Devices with power-loss protection are designed to preserve acknowledged writes, but the operating system cannot manufacture that protection. The page cache therefore gives applications speed, while fsync gives them an explicit point at which they ask the full stack for durability.