Swap on a VPS has a bad reputation, and on spinning disks or network-attached storage that reputation is deserved. Compressed swap changes the arithmetic entirely: instead of writing evicted anonymous pages to a block device, zswap intercepts them before they reach the disk, compresses them into a RAM pool, and only writes to swap when that pool is full. On a small instance with a fast CPU and a slow disk, this can turn a latency cliff into a gentle slope. This guide covers how zswap works, how to choose between its zbud and zstd compressors, and how to measure whether it is actually helping on your workload. For broader memory-tuning context, the memory management reference on this site is a useful companion.
Where zswap sits in the write path
When the kernel decides to reclaim an anonymous page, zswap first checks whether the swap device is a rotational disk or slow device. If it is, the page is compressed and stored in a RAM-backed pool. Only if the pool hits its limit does zswap write out the least-recently-used compressed pages to the real swap device, where they stay in compressed form.
The consequence is a two-tier swap: a fast in-RAM tier absorbing most of the traffic, and a slow disk tier acting as overflow. For workloads that touch a modest working set periodically — a database cache warming cycle, a build process with a large heap — the RAM tier absorbs nearly all of it.
This differs from zram, which creates a compressed block device and replaces the swap target entirely, with no disk backing and therefore no overflow. On a VPS with a modest swap file, zswap is usually the better fit.
Prerequisites and how to check
zswap needs a swap device configured and the kernel module available. Most mainline kernels include it, but vendor kernels vary.
grep -i zswap /boot/config-$(uname -r)
ls /sys/module/zswap/parameters/
cat /sys/module/zswap/parameters/enabled
cat /sys/module/zswap/parameters/compressor
If the parameters directory exists, the module is available and can be enabled at runtime. Enable it at boot rather than only at runtime, and confirm after the next restart that it took effect.
Choosing a compressor: zbud versus zstd
The compressor determines both the compression ratio and the CPU cost per page. zbud is the oldest option and is deliberately simple: it stores pages in small buddy pairs and only compresses if the result fits in a single page. Its CPU cost is negligible, but it stores many pages uncompressed.
zstd compresses every page, achieving ratios in the region of 2:1 to 3:1 on typical application memory, at a moderate CPU cost. On a modern vCPU this is a few percent of one core under typical reclaim load.
lz4 is the middle option: near-zbud CPU cost with better ratios, and the sensible default for most VPS workloads.
| Compressor | Typical ratio | CPU cost | Best for |
|---|---|---|---|
| zbud | 1.2–1.6:1 | Very low | CPU-constrained instances |
| lz4 | 2:1 | Low | General default |
| lzo-rle | 2:1 | Low–moderate | Legacy, still common |
| zstd | 2.5–3:1 | Moderate | Severe memory pressure |
Sizing the pool and the acceptance threshold
The pool limit defaults to a small fraction of RAM. On a 4 GB instance, that leaves most of the benefit untapped. Set it to roughly 15–25% of total RAM, which is enough to hold the compressed form of a large fraction of swap-resident pages without competing with the page cache.
echo zstd > /sys/module/zswap/parameters/compressor
echo 20 > /sys/module/zswap/parameters/max_pool_percent
echo Y > /sys/module/zswap/parameters/enabled
The accept_threshold_percent parameter controls which pages are stored at all: pages whose compressed size exceeds a fraction of the original are written straight to disk instead. Leaving it near the default is fine for zstd; raising it slightly lets more pages into the pool at the cost of using RAM on poorly compressible data.
Measuring whether it works
The critical counters live in the debug filesystem. pool_total_size tells you how much RAM the pool occupies and stored_pages how many pages are compressed there. The ratio of pages written to disk versus pages stored is the real indicator of effectiveness.
grep -E "pool_total_size|stored_pages|pool_limit_hit|written_back_pages" /sys/kernel/debug/zswap/*
cat /proc/vmstat | grep -E "pswpout|pgmajfault"
If written_back_pages stays near zero while stored_pages climbs, the RAM tier is absorbing the traffic as intended. If pool_limit_hit grows steadily, the pool is too small and pages are being forced out to disk — but at least they leave in compressed form, so the disk write volume is still reduced.
Compare pswpout with and without zswap enabled for the same workload. A well-tuned configuration on a memory-constrained instance typically cuts swap-out volume by 60–80%, with the remainder being pages that were evicted from the pool.
The latency question
Compressed swap is not free. A page fault that hits the zswap pool costs a decompression, which is on the order of microseconds, contrasted with a disk read at hundreds of microseconds for local NVMe and milliseconds for a network volume. The difference is not subtle, and it is why zswap changes the behaviour of memory-constrained workloads so dramatically.
The measurement that matters is page-fault latency, not throughput. Use perf or the fault counters in /proc/vmstat alongside a workload that touches more anonymous memory than the instance has.
perf stat -e page-faults,major-faults -a sleep 60
awk '/pgmajfault/ {print $2}' /proc/vmstat
Interaction with databases and the OOM killer
Databases generally perform worse when their buffer pool spills, and compressed swap does not change that: the fix is a bigger instance or a smaller buffer pool, not swap tuning. Where zswap helps is with application-tier processes whose memory grows in bursts — JVM heaps, PHP-FPM children, build jobs.
It also buys the OOM killer some headroom: more time to reclaim before it decides to kill, which turns a hard out-of-memory event into a period of degraded performance.
Keep vm.swappiness at a moderate value of roughly 60. Very low values suppress swapping and leave zswap with nothing to do.
A reasonable configuration to start from
Enable zswap with lz4, set the pool to 20% of RAM, keep the acceptance threshold default, and ensure swappiness is at the kernel default. Measure swap-out volume and major fault rate for a week, then try zstd if the pool is consistently full. On a small instance with slow storage, the change is often the difference between a workload that stays responsive under memory pressure and one that stalls for seconds at a time.
As with every kernel tuning change, keep a note of what you configured, why, and what the counters looked like before the change. That record is what makes the next adjustment a measurement rather than a guess. For more memory and swap tuning experiments, see the kernel tuning guides on virtualserversvps.com.



Leave a Reply
You must be logged in to post a comment.