$linuxjunkies
>

The Linux Page Cache Explained

Deep dive into Linux page cache mechanics: how reads and writes flow through cache, dirty page writeback, fsync durability guarantees, and vm.dirty_* sysctl tuning.

AdvancedUbuntuDebianFedoraArch12 min readUpdated June 7, 2026

Before you start

  • Root or sudo access to modify sysctl parameters
  • Basic familiarity with Linux memory concepts (virtual memory, RAM)
  • fio installed for benchmarking (apt/dnf/pacman install fio)

The Linux page cache sits between your application and the block device, absorbing reads and batching writes. Understanding it lets you tune throughput, reason about data durability, and diagnose mysterious memory pressure. This guide walks through the mechanics from first principles, then gives you concrete knobs to turn.

What the Page Cache Is

The page cache is a region of kernel-managed RAM that holds file data in 4 KiB pages (on x86-64). When a process calls read(2), the kernel checks whether the requested file pages are already in cache. A hit returns data directly from RAM; a miss triggers a read from the block device and populates the cache for next time. Every mmaped file, every read(2), every sendfile(2) routes through it.

The cache is not a fixed-size buffer. It competes with anonymous memory (heap, stack) under the same memory pressure framework. When RAM fills up, the kernel's reclaim code evicts Least Recently Used (LRU) clean pages without any I/O cost, or writes dirty pages back and then evicts them.

How Reads Touch the Cache

A typical read path looks like this:

  1. Process calls read(fd, buf, n).
  2. VFS layer looks up the file's address space object and checks the page cache for the needed page offset.
  3. Cache hit: data is copied from the kernel page into userspace buf. No disk access.
  4. Cache miss: kernel allocates a page, issues a block I/O read (submit_bio), waits for completion, then copies to userspace. The page stays resident for future hits.

You can watch cache hit/miss rates with cachestat (part of bcc-tools) or the newer pcstat utility. For a quick look at current cache size:

cat /proc/meminfo | grep -E 'Cached|Buffers|Dirty|Writeback'

Output will vary; Cached is the page cache, Buffers is metadata cache, Dirty is data awaiting writeback, and Writeback is data actively being flushed.

How Writes Touch the Cache

By default, write(2) is asynchronous from the application's perspective. The kernel copies userspace data into a cache page, marks that page dirty, and returns to the caller immediately. The application does not wait for any I/O. This is write-back caching and it is why Linux write throughput benchmarks look spectacular — you are often timing RAM speed.

The tradeoff is durability: a power loss before the dirty pages flush to disk loses that data. This matters enormously for databases, journaling systems, and any code that must guarantee persistence.

Dirty Pages and Writeback

The kernel's writeback threads (historically called pdflush, now per-device flusher threads visible as kworker tasks) periodically scan for dirty pages and submit them to the block layer. Two conditions trigger writeback:

  • Age threshold: a page has been dirty longer than vm.dirty_expire_centisecs.
  • Dirty ratio threshold: total dirty memory crosses vm.dirty_background_ratio or vm.dirty_ratio.

Background writeback starts gently when dirty memory exceeds vm.dirty_background_ratio. If dirty memory keeps rising and hits vm.dirty_ratio, the kernel throttles every writing process until writeback catches up — this is the source of latency spikes you see on busy write workloads.

The vm.dirty_* Knobs

All of these live under /proc/sys/vm/ and are tunable at runtime with sysctl.

ParameterDefaultMeaning
vm.dirty_background_ratio10% of RAM; background flush starts here
vm.dirty_ratio20% of RAM; writing processes stall here
vm.dirty_background_bytes0Absolute byte alternative to ratio
vm.dirty_bytes0Absolute byte alternative to ratio
vm.dirty_expire_centisecs3000Age (1/100 s) before a dirty page must flush
vm.dirty_writeback_centisecs500How often flusher threads wake up

Ratio and bytes variants are mutually exclusive; whichever is non-zero takes precedence. On servers with large RAM (256 GiB), a 10% ratio means 25 GiB of dirty data can accumulate — far more than most storage subsystems can flush quickly. Switch to absolute byte values instead:

# View current values
sysctl vm.dirty_ratio vm.dirty_background_ratio vm.dirty_expire_centisecs vm.dirty_writeback_centisecs
# Temporary: cap dirty data to 256 MiB background / 512 MiB hard limit
sysctl -w vm.dirty_background_bytes=268435456
sysctl -w vm.dirty_bytes=536870912

Make changes permanent by writing to /etc/sysctl.d/:

cat > /etc/sysctl.d/60-pagecache.conf <<'EOF'
vm.dirty_background_bytes = 268435456
vm.dirty_bytes = 536870912
vm.dirty_expire_centisecs = 1500
vm.dirty_writeback_centisecs = 250
EOF
sysctl --system

fsync and Data Durability

fsync(2) is the application's escape hatch from write-back caching. When called, it blocks the caller until all dirty pages for that file descriptor have been written to durable storage and the drive's write cache has been flushed (via a FLUSH CACHE command on SATA/NVMe). This is the mechanism databases use to guarantee ACID durability.

# Simulate what a database does: write then sync
strace -e trace=fsync,fdatasync dd if=/dev/zero of=/tmp/test bs=4k count=256 conv=fsync 2>&1 | tail -5

fdatasync(2) is a lighter variant — it flushes data pages but skips metadata (mtime, size changes) unless they are needed to read the data back correctly. Use fdatasync when you control the file size and only need the payload durable.

O_SYNC / O_DSYNC: Opening a file with these flags makes every write(2) behave like a synchronous write — no batching. Throughput drops to raw storage speed. Use only when per-write durability is mandatory (WAL segment files, audit logs).

O_DIRECT: Bypasses the page cache entirely. I/O goes straight from the userspace buffer to the device. Requires aligned buffers and sizes. Databases like PostgreSQL and MySQL InnoDB use this to manage their own buffer pools and avoid double-caching.

Inspecting the Cache in Practice

See how much of a file is cached

# Install on Debian/Ubuntu
apt install pcstat

# Install on Fedora/RHEL
dnf install pcstat   # may need EPEL

# Install on Arch
pacman -S pcstat
pcstat /var/log/syslog

Drop the page cache (testing only, never on production under load)

# Flush dirty pages first, then drop clean cache
sync
echo 1 > /proc/sys/vm/drop_caches   # drop pagecache
echo 3 > /proc/sys/vm/drop_caches   # drop pagecache + dentries + inodes

Live dirty page monitoring

watch -n 1 'grep -E "Dirty|Writeback" /proc/meminfo'

Tuning Guidance by Workload

  • Latency-sensitive databases (PostgreSQL, MySQL): Lower vm.dirty_background_bytes and vm.dirty_bytes aggressively (e.g., 64 MiB / 128 MiB). Use fsync. Consider O_DIRECT if the DB supports it.
  • High-throughput sequential writes (video ingest, log aggregation): Increase ratios or byte limits to allow large write batches. Accept higher data-loss window.
  • Mixed workloads on NVMe: NVMe flushes fast; keep ratios near default or use byte caps scaled to your device's sequential write bandwidth (device MB/s × 5 s is a reasonable dirty_bytes ceiling).
  • Containers / VMs with shared host memory: Each guest competes for host page cache. Lower dirty limits inside containers to avoid one guest triggering host-level writeback stalls.

Verification

After applying tuning, verify the values loaded correctly:

sysctl vm.dirty_background_bytes vm.dirty_bytes vm.dirty_expire_centisecs vm.dirty_writeback_centisecs

Then benchmark write latency before and after with fio:

fio --name=writeback-test --rw=write --bs=4k --size=1g \
    --numjobs=4 --runtime=30 --time_based \
    --filename=/tmp/fio-test --output-format=normal

Focus on the lat (usec) p99 and p999 percentiles in fio output — these expose the stall events the default dirty limits cause.

Troubleshooting

  • Write latency spikes periodically: Almost always dirty ratio throttling. Check Dirty in /proc/meminfo during spikes. Lower vm.dirty_bytes or increase storage bandwidth.
  • fsync takes seconds: Your storage device's write cache is large and the flush command drains it. Use a device with a battery-backed write cache, or check for a failing disk (smartctl -a /dev/sdX).
  • Memory pressure despite low application footprint: The page cache is consuming RAM that the kernel will reclaim on demand. This is normal. If reclaim is too slow, check vm.vfs_cache_pressure (default 100; raise it to reclaim cache more aggressively relative to anonymous memory).
  • drop_caches didn't free memory: Dirty pages are never dropped — only clean ones. Run sync first, then drop. If dirty pages remain after sync, a device may be stalled or a filesystem has a stuck transaction.
tested on:Ubuntu 24.04Fedora 40Debian 12Arch rolling

Frequently asked questions

Is the page cache the same as the buffer cache?
Historically they were separate. Since Linux 2.4 the two were unified into the page cache. The Buffers field in /proc/meminfo now refers specifically to block device metadata pages, not a separate buffer cache.
Will the page cache steal memory from my application?
Yes, but the kernel reclaims clean cache pages on demand at no I/O cost. If reclaim latency causes problems, raise vm.vfs_cache_pressure above 100 to make the kernel more aggressive about freeing cache relative to anonymous memory.
Does echo 3 > /proc/sys/vm/drop_caches harm a running system?
It drops all clean cache, forcing the next reads to hit disk. This causes a temporary performance hit as the cache is rebuilt. Never do this on a production system under load — it is useful only for benchmarking to get a cold-cache baseline.
Why does my database recommend disabling the page cache with O_DIRECT?
Databases maintain their own in-process buffer pools tuned to their access patterns. Without O_DIRECT, the same data sits in both the database buffer pool and the kernel page cache, wasting RAM and causing unnecessary memory pressure.
What happens to dirty pages during a crash before fsync?
They are lost. The kernel provides no durability guarantee for dirty pages that have not been explicitly flushed with fsync or written with O_SYNC. Filesystems with journaling (ext4, XFS) protect metadata consistency but not uncommitted application data.

Related guides