MGLRU on a Small VPS: Generational Page Reclaim Explained and Benchmarked

Multi-generational LRU, merged into the mainline kernel in 6.1, replaced a page-reclaim design that had gone largely unchanged since the early 2000s. The difference is architectural rather than incremental: instead of two lists that approximate recency and frequency with a single reference bit, MGLRU keeps several generations of pages and ages them together, which makes it far better at identifying working sets under memory pressure. On a small VPS this matters directly, because a box with 2 GB of RAM spends a meaningful share of its life deciding which pages to evict, and getting that wrong shows up as wasted I/O and unpredictable latency. This walkthrough covers how the mechanism works, how to confirm what your kernel is using, and how to benchmark the difference with the counters the kernel exposes. For the surrounding memory-management picture, the memory and kernel reference on this site is a useful companion.

The problem the old LRU had

The classical design keeps an active list and an inactive list. A page enters inactive on first access, gets promoted on a second reference, and is demoted when the CPU clears its reference bit during a scan. The scheme is cheap and roughly correct, but it has two structural failings on modern hardware with large page caches: it demotes pages that are still in active use when pressure is bursty, and the cost of scanning grows with the size of the cache.

The visible symptom is re-read I/O. A file-backed working set gets evicted, then immediately faulted back in, and the pattern repeats. On a VPS with a network-attached or throttled disk, that thrashing converts a memory problem into a latency problem within seconds.

How generational reclaim changes the decision

MGLRU partitions pages into multiple generations. Newly faulted pages enter the youngest generation; a scan ages all generations down by one step, but only the oldest is actually reclaimed. Pages that are touched frequently survive many scans before reaching the eviction edge, which approximates frequency without maintaining per-page counters.

The secondary effect is scan cost. Because the reclaim decision looks at a much smaller set of candidates drawn from the oldest generation, the kernel does less work per reclaimed page, which reduces allocation stalls and direct-reclaim latency when memory is tight.

In practice this means a small VPS running a database and a web server can keep a larger effective cache in the same amount of RAM, with fewer of the pathological evict-then-refault cycles that dominated before.

Signal Legacy LRU reading MGLRU reading Interpretation
File refault ratio Often 0.2–0.4 Usually below 0.1 Lower is better
pgscan_direct rate Spiky under pressure Flatter Fewer allocation stalls
Read I/O on cache reload High churn Lower churn Working set respected
p99 latency during pressure Degrades sharply Degrades gradually Predictability

Confirming what your kernel is running

First establish the kernel version and whether the feature is present. MGLRU is controlled by a sysfs knob that may exist while still being disabled, so check both.

uname -r
ls /sys/kernel/mm/lru_gen/
cat /sys/kernel/mm/lru_gen/enabled      # 0x0007 = enabled, 0x0000 = off

If the directory is absent the kernel was built without the feature, which is common on vendor kernels from some VPS providers. If it exists but reads zero, the boot parameter lru_gen=1 enables it on kernels where the default is off. Some distributions expose it only through the kernel command line, so check /boot/grub/grub.cfg or the systemd-boot entry before assuming it can be toggled at runtime.

Confirm the kernel actually booted with reclaim active by reading the generation stats:

cat /sys/kernel/debug/lru_gen
cat /sys/kernel/debug/lru_gen_full | head -40

Building a fair benchmark on a small VPS

Because the feature is selected at boot, the clean method is to boot the instance with and without the parameter, run an identical sequence, and compare the counters.

A simple file-server workload works well because it exercises the page cache without needing a database. Generate a working set larger than RAM so eviction must occur, then access it in a pattern that mimics your production traffic.

fio --name=cachetest --filename=/data/testfile --size=2G --rw=randread \\
    --bs=16k --iodepth=8 --runtime=180 --time_based
grep -E "cache_miss|file_refault|anon_refault" /sys/kernel/debug/lru_gen_full

Run each configuration three times, dropping caches between runs so the start state is identical, and record the refault ratios alongside the throughput. Throughput alone can look identical while the refault ratio tells a very different story about how hard the kernel is working to sustain it.

What to watch under real memory pressure

The clearest signal appears when available memory approaches zero and direct reclaim begins. Compare pgscan_direct and pgsteal_direct in /proc/vmstat, and the allocation stall counters in PSI:

grep -E "^(pgscan_direct|pgsteal_direct)" /proc/vmstat
cat /proc/pressure/memory

MGLRU typically reduces direct reclaim activity for the same external workload, which shows up as lower stall time in the PSI figures. If the two configurations produce identical PSI values, the pressure is coming from anonymous memory your application is allocating rather than from a page-cache working set, and the reclaim algorithm is not the lever you need — the memory limit is.

Interaction with swap, zram, and containers

On an instance without swap, reclaim can only evict clean file pages or trigger the OOM killer, which makes the generational decision more consequential than on a box with a swap device. If you run zram-backed swap, evicted anonymous pages are compressed rather than written to disk, and MGLRU’s better selection of what to evict reduces the compressed-working-set churn the compressor sees.

Under cgroup v2, reclaim operates within the memcg as well as globally. Container hosts benefit because each workload’s generations are tracked inside its own cgroup, reducing the chance that a noisy neighbour’s working set is evicted on a global scan.

Deciding whether to enable it

For most small VPS workloads the answer is yes when the kernel supports it, because the failure modes are milder and the file-refault ratio drops noticeably. Enable it, run your own load test for a week, and keep the boot parameter documented so it can be reverted in one edit if anything regresses. Do the comparison on the workload you actually run, not on a synthetic score, and record the counters so the decision is evidence-based rather than taken on reputation. Further measurement-oriented follow-ups on page cache and reclaim behaviour appear in the kernel tuning archive on virtualserversvps.com.

Leave a Reply