Verdict — keep zswap enabled. Leave LZO, zbud, and the 20% pool threshold alone. Keep /dev/sda2 active, and do not add zram as a higher-priority swap device. The current pool is tiny, and this boot has recorded no pool-limit hits, cached-page writeback, or rejected stores.
The caveat matters. This server runs Linux 5.4. Modern architectural advice about zswap still helps, but current-kernel allocator, shrinker, and cgroup commands do not transfer directly.
Start with the failure mode, not the compressor
Run a low-memory Linux server with real swap and wonder whether zram would be better? Decide from the machine’s topology, kernel version, and live counters instead of copying a tuning recipe.
The reusable question is not “Which compressor benchmarks fastest?” It is “What happens when compressed memory fills?”
Zswap is a compressed cache in front of an ordinary swap device. Suitable pages stay compressed in RAM; pages that bypass the cache or age out can still go to disk. The disk remains a slower capacity tier.
Zram is a compressed RAM block device. When it is used as swap, its storage still consumes finite physical memory. A zram-only design without a configured zram backing device therefore ends at an in-RAM capacity boundary. Adding lower-priority disk swap does not, by itself, make zram an age-aware cache that automatically moves its oldest pages to disk.
Zram can optionally use an explicitly configured backing device and administrator-triggered writeback on kernels built with that feature. That is a separately managed topology: it does not turn ordinary lower-priority swap into automatic zswap-style eviction, nor does it extend zram’s finite logical device size.
That distinction makes zswap the conservative fit for this server: it already has an 8 GiB swap partition, no zram device, and no requirement to keep swap data off persistent storage.
The host already has a coherent zswap setup
The boot command line contains zswap.enabled=1, and the same argument is present in the default grubby kernel stanza. The runtime switch reports Y. The kernel log closes the loop:
zswap: loaded using pool lzo/zbud
Only the enable flag was added explicitly. LZO, zbud, the 20% threshold, and same-filled-page handling are Linux 5.4 defaults exposed through sysfs.
| Property | Observed value | Why it matters |
|---|---|---|
| Operating system | Oracle Linux Server 7.9 | An older server distribution, not a current zram-first desktop setup. |
| Kernel | 5.4.17-2102.206.1.el7uek.aarch64 | Modern zswap commands must be checked against this implementation. |
| Base page size | 65,536 bytes | Every stored_pages unit is 64 KiB, not the 4 KiB common on x86. |
| Physical RAM | 5,903,296 KiB (5.63 GiB) | The 20% pool threshold is about 1.126 GiB. |
| Backing swap | /dev/sda2, 8 GiB, priority -2 | Zswap has a real device for bypass and eviction. |
| Zswap | LZO · zbud · 20% | The pool grows on demand; 20% is not reserved at boot. |
| VM policy | swappiness=60, page-cluster=3 | Distribution defaults remain in place. |
| zram | No device | There is no competing compressed swap tier. |
| Userspace OOM policy | None detected | Neither systemd-oomd nor earlyoom is active. |
The 20% value is easy to misread. Zswap does not seize 1.126 GiB when the machine boots. Its pool grows only as pages are accepted and shrinks when entries are loaded, invalidated, or written to the backing device.
The counters show light use, not a tuning problem
After 6 days and 5 hours, debugfs reported:
| Counter | Value | Interpretation |
|---|---|---|
stored_pages | 89 | Eighty-nine 64 KiB pages currently have zswap entries. |
same_filled_pages | 1 | One page is represented as a repeated value with no compressed payload. |
pool_total_size | 2,883,584 bytes | The zbud pool occupies 2.75 MiB, or 44 physical 64 KiB pages. |
pool_limit_hit | 0 | No store recorded finding the aggregate pool over the configured threshold. |
written_back_pages | 0 | No cached page has been evicted to disk during this boot. |
| All rejection counters | 0 | No poor-compression, allocation, reclaim, or metadata failure was recorded. |
The page size makes the accounting unusually clear:
89 stored pages × 64 KiB = 5.5625 MiB logical pages
2,883,584 bytes of zbud storage = 2.75 MiB physical pool
2.75 MiB ÷ 64 KiB = 44 physical pool pages
(89 entries − 1 same-filled entry) ÷ 44 = 2 ordinary entries per pool page
The readings are consistent with the 88 ordinary entries fitting two per zbud page. Including the same-filled page, zswap represents about 5.56 MiB of logical pages with 2.75 MiB of pool memory—a little over 2:1 before entry metadata.
This is logical-to-pool accounting, not a universal LZO compression ratio. Zbud caps density at two entries per physical page, the debugfs files were read separately rather than atomically, and the counters are approximate under concurrent activity.
The traditional page-swap I/O counters and the partition’s completed writes were also zero since boot. The defensible conclusion is narrow: zswap is active, currently holds a few megabytes, and has not approached a failure boundary. The snapshot does not prove a measurable application speedup.
One swapped page gets a fast path and an escape hatch
A page selected for reclaim follows this path on Linux 5.4:
- The page has or obtains a swap entry identifying a slot on
/dev/sda2. - Zswap gets the first opportunity to keep it in compressed RAM. A same-filled page can be represented by metadata alone; an ordinary page is compressed and placed in zbud if it fits.
- If zswap is disabled, over its limit, unable to reclaim space, or unable to store the compressed result, the normal disk-swap path remains available.
- On a page fault, zswap reconstructs or decompresses the page and removes the cache entry. If the page is no longer cached, ordinary swap-in handles it.
The backing slot still matters while the payload is in zswap: it gives the page a swap identity and reserves somewhere to write it later. That is why swapon --show can report used swap without proving that the same number of bytes reached the disk.
Replacing zswap with zram would change the operating model
A zram conversion is not a drop-in compressor change. It replaces a cache over durable capacity with a block device whose storage consumes the same RAM the system is trying to relieve.
| Layout | What happens under pressure | Fit for this host |
|---|---|---|
| Current: zswap + disk swap | Suitable pages stay compressed in RAM; bypassed or older pages can use the 8 GiB partition. | Keep. It preserves a capacity tier, and the counters show no failure requiring redesign. |
| High-priority zram + lower-priority disk swap | Once zram’s logical swap slots are exhausted, later swap allocations can use disk while older pages remain in zram. A compressed-storage allocation failure is an I/O failure, not automatic reassignment of that page to disk. | Avoid as a casual “faster swap” change. |
| Zram as the only swap device, with no zram backing device | Swap stays in RAM, but the device has finite logical capacity and consumes finite physical memory. | Coherent only when diskless operation, persistence constraints, or deliberate disk-swap avoidance justify the new boundary. |
| No swap | Cold anonymous memory loses its backing tier; file cache and kill policy absorb pressure sooner. | No evidence from this audit supports removing the existing safety margin. |
Zram can be the right design for embedded, diskless, or persistence-sensitive systems. This server has none of those stated requirements. It also lacks a proactive userspace OOM manager, so removing disk-backed capacity would require an explicit pressure and kill policy rather than a one-line swap substitution.
With compressor swap, macOS is topologically closer to zswap than zram
When its backing tier is active, macOS and zswap both keep non-discardable pages compressed in RAM before slower storage. Zram is instead a compressed block device. This is a topology analogy, not an implementation equivalence.
Linux starts with swap identity. A page selected for swap owns a slot on /dev/sda2, and Frontswap gives zswap a chance to cache that page before I/O. The slot remains reserved while its compressed payload lives in zbud. If zswap evicts the entry, Linux reconstructs one full page and writes it through the ordinary swap path.
XNU starts inside the Mach VM object and pager path. Dirty reclaimable pages from internal objects enter the compressor queue; the kernel creates a compressor pager for the object when needed, and vm_compressor_pager_put() stores the page. On a later fault, the pager retrieves and decompresses it. Dirty external-object pages go to their external pager for cleaning; already-clean file-backed pages can normally be reclaimed and faulted back from that pager. Wired pages are not compressor candidates.
The storage geometry is also different. This Linux host’s zbud allocator can place at most two compressed entries in one 64 KiB pool page. XNU packs compressed slots into segments, compacts sparse segments, ages them, and—when its compressor-with-swap mode and backing storage are available—can move eligible segments to swap files. A fault into a swapped segment first brings its populated extent back before the requested slot is decompressed. In short: zswap caches individual pages that already have disk-swap identities; XNU’s compressor pagers map internal-object offsets into a globally managed tier of compressed segments.
| Property | Linux 5.4 zswap on this VM | macOS compressor |
|---|---|---|
| VM integration | Frontswap intercepts the normal swap store and load path. | A Mach memory-object pager is part of pageout and fault handling. |
| In-RAM unit | One compressed page entry in a zpool, tied to one ordinary swap offset. | A pager maps each object offset to a compressor slot; payloads from different mappings are packed into globally managed segments. |
| Backing-storage unit | An evicted entry is decompressed to a full page and written to its reserved swap slot. | Under compressor-swap policy, eligible segments are written and read as their page-rounded populated extent. |
| Allocation geometry | zbud: at most two entries per 64 KiB pool page. | Variable compressed slots packed into compactable segments; no two-entry ceiling. |
| Observed base page size | 64 KiB on the ARM Linux guest. | 16 KiB on the Apple Silicon Mac sampled separately. |
| Compression policy | LZO and zbud are visible, selectable kernel parameters. | Published source defines WKdm-only, LZ4-only, and adaptive hybrid modes. The sampled release kernel did not expose its active mode. |
| Operator interface | Pool limits, compressor, allocator, and debug counters are visible in sysfs and debugfs. | macOS presents memory pressure, compressed memory, wired memory, and swap use; its allocator policy is not a supported tuning surface. |
Two live ratios, but not a benchmark
A separate Apple Silicon snapshot on macOS 26.6.1, captured with sysctl and vm_stat, reported a 16 KiB page size, 620,095 pages stored in the compressor, and 224,248 pages occupied by it:
Linux: 89 × 64 KiB ÷ 2.75 MiB ≈ 2.02:1 logical-to-pool
macOS: 620,095 × 16 KiB ≈ 9.46 GiB stored-page bytes
224,248 × 16 KiB ≈ 3.42 GiB resident compressor-data pages
9.46 GiB ÷ 3.42 GiB ≈ 2.77:1 stored-page bytes / data-page bytes
The Mac simultaneously reported zero current encrypted swap use and zero cumulative swap-in and swap-out events. That does not mean macOS lacks a backing tier: XNU defines both compressor-only and compressor-with-swap configurations. It means only that this snapshot’s stored pages were not disk-resident. In general, vm_stat’s current “stored” count can include logical pages whose segments are in swap, while “occupied” counts resident physical data pages; neither includes every compressor metadata allocation. Those gauges must not be confused with the cumulative compression, decompression, swap-in, and swap-out event counters.
The apparent density advantage is not a codec contest. The samples came from different workloads at different times; their base pages differ by 4×; zbud’s two-entry layout constrains the Linux pool; XNU packs and compacts variable-size slots; and the two kernels account for metadata differently. The Linux numerator includes one same-filled page with no zbud payload, while XNU’s stored-page accounting includes analogous single-value pages. Neither allocator-footprint ratio says which system has lower fault latency or whether either compressor is optimally sized.
Do not copy a macOS result into a Linux tuning target
The useful lesson is architectural, not numerical. macOS makes compression part of an integrated pressure policy, while Linux exposes replaceable components and thresholds. On this VM, a few megabytes in zswap and zero failure events leave no density problem to solve. Application memory, tail latency under real pressure, and the age of the 5.4 kernel remain the higher-value targets.
The source comparison uses XNU 12377.121.6, the nearest published release to the sampled Mac’s later 12377.161.13 kernel. In that tag, ARM macOS defaults the compressor payload buffer to 64 KiB, overridable by a boot argument. This is not a 64 KiB VM page: on the sampled 16 KiB-page Mac, the resident or swapped populated extent is page-rounded and can occupy up to four base pages. Apple’s source describes the size as a tradeoff: larger segments reduce end slack and overhead and improve I/O throughput, while smaller ones reduce unnecessary swap-in of data that is not subsequently accessed.
Linux 5.4 is the boundary on modern advice
Chris Down’s 2026 zswap-versus-zram analysis describes a much newer memory-management implementation, using Linux 6.19 examples. The architecture-level lesson transfers: zswap is a cache in front of disk, while zram is a finite compressed block device. The tuning details do not all transfer.
| Area | This Linux 5.4 host | Modern zswap |
|---|---|---|
| Pool storage | zpool with zbud; at most two compressed entries per physical pool page. | Direct zsmalloc integration can provide denser storage. |
| Pool pressure | A store that finds the static limit exceeded attempts synchronous reclaim and may reject the new cache entry. | Acceptance hysteresis, an optional pressure-aware shrinker, and newer reclaim behavior are available. |
| Cgroup control | No memory.zswap.* interface in the upstream 5.4 path. | Per-cgroup zswap accounting and writeback controls are part of the current design. |
| Operational advice | Use the interfaces present on the running host. | Current commands assume current kernel support. |
Do not switch this kernel to zsmalloc by analogy
The Linux 5.4 documentation explicitly warns that its zsmalloc zpool does not implement compressed-page eviction. Once that pool fills, zswap can only reject new pages. The presence of CONFIG_ZSMALLOC=y therefore does not make zsmalloc the correct pool for this old zswap implementation.
The same rule applies to modern shrinker parameters and per-cgroup writeback controls: do not copy commands for interfaces that do not exist on the running kernel.
The safe plan is keep, measure, then modernize
1. Keep the current topology
- Leave
zswap.enabled=1, LZO, zbud, and the 20% threshold unchanged. - Keep the 8 GiB backing swap partition active.
- Do not add zram beside it as a supposedly faster first swap device.
- Keep the boot argument in the kernel-management path and verify it after maintenance. It appears in the generated
grubbystanza, not/etc/default/grub.
2. Measure a real memory-pressure event
A 2.75 MiB pool with zero failure events is good operational news and poor tuning evidence. The next useful sample is the same counter set captured before, during, and after a real deployment, traffic spike, backup, or worker-memory event—not an artificial swapoff on a remote production machine.
Watch these signals together:
| Observation | Likely reading |
|---|---|
stored_pages and pool_total_size grow; writeback stays flat | The compressed cache is absorbing swap candidates. |
written_back_pages grows without repeated swap-ins | Cold-page tiering may be working normally. |
| Writeback, swap-ins, storage I/O, and tail latency all rise quickly | The host may be thrashing. |
| Pool-limit or rejection counters rise | The pool is reaching a real constraint and tuning evidence now exists. |
Application latency and storage I/O matter more than any one zswap counter. Compare deltas over an interval rather than treating a static value as a performance result.
3. Modernize the platform, not one old knob
The largest technical gap is the 5.4 memory-management implementation. A supported operating-system and kernel upgrade can bring newer zswap storage, cgroup accounting, pressure-aware reclaim, counters, and maintained security fixes together. Validate that change during planned maintenance; do not approximate it by changing the allocator underneath the old kernel at runtime.
Reproduce the audit without changing memory policy
The following commands are read-only:
printf '%s\n' '=== host ==='
uname -a
cat /etc/os-release
getconf PAGE_SIZE
cat /proc/cmdline
printf '%s\n' '=== memory and swap ==='
free -h
cat /proc/swaps
grep -E '^(MemTotal|MemAvailable|SwapTotal|SwapFree|SwapCached):' /proc/meminfo
printf '%s\n' '=== zswap parameters ==='
for file in /sys/module/zswap/parameters/*; do
printf '%s=' "${file##*/}"
cat "$file"
done
printf '%s\n' '=== zswap counters ==='
sudo sh -c '
for file in /sys/kernel/debug/zswap/*; do
printf "%s=" "${file##*/}"
cat "$file"
done
'
printf '%s\n' '=== VM policy ==='
for name in swappiness page-cluster watermark_scale_factor; do
printf 'vm.%s=' "$name"
cat "/proc/sys/vm/$name"
done
printf '%s\n' '=== zram and OOM policy ==='
zramctl 2>/dev/null || true
systemctl is-active systemd-oomd 2>/dev/null || true
systemctl is-active earlyoom 2>/dev/null || true
Read stored_pages using the machine’s actual base page size. Assuming 4 KiB on this ARM host would understate the logical pages by a factor of sixteen. Read pool_total_size as physical bytes used by compressed storage, not logical bytes saved.
What this snapshot cannot prove
- No synthetic pressure was induced on the production server, so the audit does not benchmark a full pool.
- Debugfs event counters are intentionally approximate under concurrent access.
- The snapshot cannot attribute swap candidates to individual processes or cgroups through this old zswap interface.
- There is no zswap-disabled or zram comparison run, so the audit cannot assign a performance gain to the current 2.75 MiB pool.
- The macOS snapshot is a separate machine and workload, so its compressor density cannot benchmark or size this Linux host.
- Upstream Linux 5.4 explains the observed interfaces, but Oracle UEK may carry additional patches; runtime behavior remains authoritative for this host.
The evidence supports a no-change decision, not a claim of optimality. Zswap is enabled, correctly backed, lightly used, and has no recorded failure events. The old kernel is the larger operational constraint, which makes a planned platform upgrade more defensible than changing the compressed-swap topology without a workload requirement.
How Linux 5.4 handles a full zbud pool
Show the full-pool reclaim sequence
Upstream Linux 5.4 checks aggregate zswap pool size before each store. If the pool is already over max_pool_percent, zswap synchronously asks the selected zpool to free one physical page.
With zbud, reclaim starts at the tail of the pool-page LRU and can inspect up to eight candidate pages. For each candidate it tries occupied buddies in order, stopping that candidate at the first failed writeback callback. A page is freed only when both buddies are gone; if the first succeeds and the second fails, the page remains partly evicted and reclaim proceeds to another candidate.
The incoming store is rejected if reclaim fails or the aggregate pool remains over the threshold. Because the check happens before allocation and stores are not serialized around a byte-exact cap, 20% is a store-time threshold rather than a strict ceiling.
Sources
- Chris Down, “Debunking zswap and zram myths”, 24th March 2026.
- Linux kernel 5.4 documentation, “zswap”.
- Linux v5.4 source,
mm/zswap.c. - Linux v5.4 source,
mm/zbud.c. - Current Linux kernel documentation, “zswap”.
- Linux kernel documentation, “zram: Compressed RAM-based block devices”.
- Oracle, Unbreakable Enterprise Kernel Release 6 release notes.
- Apple Support, “View memory usage in Activity Monitor on Mac”.
- macOS
vm_stat(1)manual. - Apple open-source XNU
12377.121.6: pageout, compressor pager, segment management, backing store, and compression algorithms. This is the nearest published source to the sampled12377.161.13kernel, not its exact patch build.