Kernel Page Cache on a Small VPS: What Is Actually Cached, and How to Prove It

On a 2 GB VPS, the difference between a fast site and a slow one is usually not CPU. It is whether the files your application reads most often are already in RAM when the request arrives. The kernel page cache is what makes that possible, and it is also the most misunderstood subsystem on small instances — the same cache that delivers a 10x speedup on a warm database also causes the mysterious latency spike after a backup job evicts everything. This article explains the mechanism, then gives you measurements that decide whether you need more RAM or better access patterns. If your plan choice is still open, the cloud VPS feature set matters here mainly for one number: how much RAM you can actually allocate to cache.

Read the right numbers in /proc/meminfo

free -h is the tool everyone runs and the tool that misleads most often, because on Linux “used” memory excludes cache — a healthy system shows almost no free RAM by design. Look instead at the breakdown:

grep -E '^(MemTotal|MemFree|MemAvailable|Buffers|Cached|Dirty|Writeback|AnonPages|SReclaimable)' /proc/meminfo
FieldWhat it meansHealthy on a small VPS
MemAvailableRAM obtainable without swapping> 15% of total
CachedPage cache (file-backed pages)Large is good — it is reclaimable
DirtyModified pages not yet written to disk< dirty_ratio × total
WritebackPages actively being flushedBursty; sustained high = disk too slow
AnonPagesProcess RSS that cannot be reclaimedStable; growth means a leak

The practical takeaway: high Cached is not a problem. High Dirty with a slow device is, because those pages must eventually be written, and the flush blocks allocations under memory pressure.

How reclaim actually proceeds when RAM runs low

When a process needs a page and free memory is tight, the kernel reclaims in a defined order. Understanding it prevents you from “fixing” the wrong layer:

  1. Clean page cache — file pages already written to disk. Free to discard. This is why dropping caches is usually a no-op on a healthy system, and why doing it manually is actively harmful: you throw away warm data that took hours to accumulate.
  2. Reclaimable slab (SReclaimable) — inode and dentry caches, filesystem metadata. Cheap to reclaim, cheap to rebuild.
  3. Dirty page writeback — must be flushed before reclaim. This is the step that stalls.
  4. Anonymous pages → swap — process memory pushed to disk. Now latency is measured in tens of milliseconds per access.
  5. OOM killer — a process dies.

Steps 3 through 5 are where small-VPS performance collapses. The goal of tuning is to keep the system operating in steps 1 and 2.

Measure whether your working set fits

This is the single most useful page-cache diagnostic: the ratio of page requests served from cache versus from disk, per process.

# Minor (cache hit) vs major (disk read) page faults for MySQL
ps -o pid,min_flt,maj_flt,cmd -C mysqld

# Watch the delta over 60 seconds instead of the lifetime total
while true; do
  ps -o maj_flt= -C mysqld
  sleep 60
done

maj_flt is the count of faults that required a physical disk read. If that counter is climbing steadily on an otherwise idle system, the cache is being churned and every database read is reaching the device. Watch the delta, not the lifetime figure — the lifetime number is meaningless after a few days of uptime.

Pair it with the kernel’s own pressure signal, which is the best early-warning metric most people have never read:

cat /proc/pressure/memory
# some avg10=0.00 avg60=1.42 avg300=0.61 total=88213
# full avg10=0.00 avg60=0.00 avg300=0.00 total=1993

some means at least one task was stalled on memory. full means all runnable tasks were stalled simultaneously — the system was effectively frozen. Sustained full above 0.5% on a web server is a real problem, and it will not show up in load average or CPU graphs at all.

The dirty page problem on slow storage

Dirty pages generate write latency, and on a shared-tier VPS with an I/O quota, they generate throttling. Two sysctls control when the kernel starts and stops flushing:

sysctl vm.dirty_ratio vm.dirty_background_ratio vm.dirty_expire_centisecs vm.dirty_writeback_centisecs

# Tighter bounds for a slow or metered device (2 GB RAM example):
vm.dirty_background_ratio = 5     # start background flush at ~100 MB
vm.dirty_ratio = 15               # force synchronous flush at ~300 MB
vm.dirty_expire_centisecs = 1500  # flush pages older than 15 s
vm.dirty_writeback_centisecs = 500

The trade-off is explicit. Higher dirty_ratio batches writes efficiently — better throughput — but creates a large reservoir of unflushed data that turns into a multi-second stall when it must be written all at once. On a device with a hard IOPS ceiling, the batching benefit is illusory because you cannot drain the reservoir faster than the throttle allows. Lower values keep write latency smooth at the cost of slightly more, smaller write operations.

Pair this with vm.swappiness. The default of 60 is tuned for general-purpose systems; on a database host where you want file pages kept and anonymous pages pushed out only under genuine pressure, values of 10–20 are more appropriate. The interaction is covered in our swappiness tuning article — the short version is that swappiness affects the page cache/anon balance, and on a small VPS that balance is the difference between cached reads and disk reads.

What not to do

  • Do not run echo 3 > /proc/sys/vm/drop_caches on a production VPS. It empties the cache instantly, then every subsequent read hits disk. If you are running it because the system feels slow, you are treating a symptom by making the cause worse. The one legitimate use is before a controlled benchmark where you need comparable cold-cache numbers.
  • Do not add a swap file on fast NVMe as a substitute for cache. Swap pages are anonymous memory, not file cache. A read that comes from swap costs a disk I/O anyway; the working set still does not fit. Swap is a safety net against OOM, not a cache extension.
  • Do not use vmtouch style pre-warming without measuring first. Pinning files with mlock prevents reclaim of pages you might not actually need, and on a 2 GB instance that rigidity is what pushes you into step 4 of the reclaim sequence.

A measurement routine that fits in ten minutes

# 1. Baseline the cache composition
grep -E '^(MemTotal|MemAvailable|Cached|Dirty|AnonPages|SReclaimable)' /proc/meminfo

# 2. Is anything stalled on memory right now?
cat /proc/pressure/memory

# 3. Is MySQL reaching the disk?
ps -o maj_flt= -C mysqld; sleep 60; ps -o maj_flt= -C mysqld

# 4. What is the cache actually holding?
#    (requires vmtouch: apt install vmtouch)
vmtouch -v /var/lib/mysql/ibdata1

# 5. After your nightly backup, repeat step 1 and compare Cached

Step 5 is the one that finds the real problem in most cases. If Cached drops by hundreds of megabytes when your backup window opens and does not recover for an hour, you have a cache eviction problem, not a RAM shortage. The fix is to make the backup reader yield to interactive I/O — ionice -c2 -n7 on the backup process — rather than buying more memory.

When the arithmetic says the working set simply does not fit

Do this sum: your dataset size (the indexes plus hot rows of your database, or the sum of your hot static files) versus your total RAM minus the fixed cost of the OS and processes. If the ratio is much worse than 2:1, no sysctl will save you — random reads will reach the device, and on shared storage random reads are where throughput guarantees stop mattering.

At that point the decision is a plan decision. Two levers: more RAM in the same product line, or moving the cache-hungry component to its own instance so its page cache is not competing with the web server’s. Comparing the second approach against a bigger single box is a straightforward cost-and-contention calculation, and it is exactly the analysis in the VPS versus dedicated server comparison — dedicated boxes win when the working set is genuinely large and steady, VPS wins when it is bursty.

If you are in the “measurement says I need more RAM” branch, the practical next step is pricing memory per dollar and confirming the plan does not hide an I/O quota behind the bigger RAM figure. InterServer’s VPS plans publish both, and their storage-backed tiers are a reasonable baseline for a cache-heavy workload. For teams that would rather hand the whole memory-tuning problem to someone else, a managed platform like Cloudways removes the page-cache tuning layer entirely, at a price premium that is worth it only if you are not running a database you need to tune.

The summary of the whole article: page cache is not a leak, high Cached is not a warning, and the only two numbers worth alerting on are MemAvailable trending down and /proc/pressure/memory full being non-zero. Everything else is a clue about which of the five reclaim steps you are about to enter.

Leave a Reply