When memory tightens, eligible cold pages may be reclaimed to swap. A zram swap device stores compressed pages in RAM, trading CPU work and memory overhead for potential headroom. The benefit depends on the workload; it is not a fixed amount of extra memory.
A compressed block device in memory
zram is a RAM-backed block device that stores compressed data. Configured as swap, it can retain eligible pages without ordinary disk swap I/O. Optional writeback can use a separately configured backing device, so “never touches a drive” is not universal. Compression may reduce latency, but the algorithm, CPU load and data matter. Logical device capacity and physical RAM consumption must be read separately; neither describes free system memory.
Compression is the space win
Compression is the space win, and it is the reason a small box can appear to hold more memory than it physically has. Whether a page pays off depends on its content: text, zeroed pages and structured data compress well, while already-compressed blobs such as media or archives barely shrink. The honest engineering view is that zram turns spare CPU cycles into apparent memory, so the trade is worthwhile when the workload leans compressible and the extra latency of a real disk swap is exactly what you want to avoid.
It can suit small hosts and phones
The fit shines on hosts with little memory and modest I/O, such as a small VPS, a container host, or a compact device like a phone or an embedded box. It is less obviously right for a server that already has plenty of RAM or that swaps into genuinely cold storage anyway, because a real disk bought with the same spend can hold far more. The practical check is to watch the compression ratio and swap usage, decide whether swapping beats killing the process, and pick zram when resident data stays small and compressible enough to make the CPU cost a fair trade.
When it breaks
Poor compression can erase much of the expected gain. Photos, video, encrypted buffers and archives may barely shrink. Compare orig_data_size with compr_data_size, but also inspect mem_used_total, which includes allocator fragmentation and metadata. Pool occupancy, available RAM, workload demand and pressure still matter. A ratio near one does not determine whether or when an OOM event occurs; cold-page residency and reclaim behavior also affect the outcome.
An expensive compressor can increase CPU use and swap latency. Available algorithms and parameters depend on the kernel build. Compare candidates such as lzo-rle, lz4 or zstd under representative pressure rather than assuming one is always suitable. High system CPU or a busy kswapd thread alone does not identify the compressor as the cause. Record application latency and useful progress alongside compression and reclaim activity before changing policy.
Using zswap with zram can add compression, decompression and accounting overhead. On eviction, zswap reconstructs a page before submitting it to the backing swap device; it does not simply hand an already-compressed blob to zram. A zram backing device may then compress that page again. Whether this arrangement helps requires workload evidence. Do not treat the two names as interchangeable mechanisms or claim that every layered configuration has identical costs.
A fixed disksize can become inappropriate after a machine resize, but it sets logical device capacity rather than immediately reserving that many RAM bytes. The sysfs disksize setting initializes the device before mkswap; mkswap does not set the zram device size. mem_limit is a separate limit on compressed-data memory. Capacity policy should reflect demand, overhead and recovery margin, and be reviewed after resizing rather than automatically enlarged to an arbitrary fraction.
Check it quickly
Save this as zram_ratio.py and run python3 zram_ratio.py without optimization. The standard-library self-test creates temporary files with invented mm_stat snapshots, asserts reports and exit decisions, then removes them. It never discovers devices or reads kernel interfaces. An optional filename, as in python3 zram_ratio.py saved-mm-stat.txt, reads a saved ordinary text snapshot. Use ordinary files only.
#!/usr/bin/env python3
"""Offline mm_stat parser/report demo; no device discovery or kernel access."""
from pathlib import Path
import sys
from tempfile import TemporaryDirectory
def parse_mm_stat(text):
parts = text.split()
if len(parts) < 3:
raise ValueError("need orig, compr and mem_used_total")
if any(not x.isascii() or not x.isdecimal() for x in parts):
raise ValueError("fields must be unsigned decimal integers")
values = tuple(int(x) for x in parts)
if any(x > (1 << 64) - 1 for x in values):
raise ValueError("field exceeds unsigned 64-bit range")
orig, compr, mem_used = values[:3]
if orig == 0 and compr != 0:
raise ValueError("inconsistent zero-orig snapshot")
return orig, compr, mem_used
def report(name, orig, compr, mem_used):
prefix = f"{name}: orig={orig} compr={compr} mem_used_total={mem_used}"
if orig == 0:
return prefix + " ratio=n/a (no stored data)"
if compr == 0:
return prefix + " ratio=n/a (nonempty; zero compressed payload)"
ratio = orig / compr
saved = 100.0 * (1.0 - compr / orig)
return prefix + f" ratio={ratio:.2f} payload_saved={saved:.1f}%"
def inspect_paths(paths):
lines = []
parsed = skipped = 0
for path in paths:
try:
values = parse_mm_stat(path.read_text(encoding="ascii"))
except (OSError, UnicodeError, ValueError) as exc:
lines.append(f"SKIP {path.name}: {type(exc).__name__}")
skipped += 1
continue
lines.append(report(path.name, *values))
parsed += 1
lines.append(f"SUMMARY: parsed={parsed} skipped={skipped}")
return (0 if parsed > 0 and skipped == 0 else 1), lines
def self_test():
if not __debug__:
raise RuntimeError("run without Python optimization")
with TemporaryDirectory(prefix="zram-fixture-", dir=".") as directory:
root = Path(directory)
cases = {
"ratio": ("314572800 104857600 125829120 0 125829120 0 0 0 0\n",
"ratio: orig=314572800 compr=104857600 mem_used_total=125829120 ratio=3.00 payload_saved=66.7%"),
"same": ("4096 0 0 0 0 1 0 0 0\n",
"same: orig=4096 compr=0 mem_used_total=0 ratio=n/a (nonempty; zero compressed payload)"),
"empty": ("0 0 0\n", "empty: orig=0 compr=0 mem_used_total=0 ratio=n/a (no stored data)"),
"overhead": ("0 0 4096\n", "overhead: orig=0 compr=0 mem_used_total=4096 ratio=n/a (no stored data)"),
"plain": ("4096 4096 8192\n", "plain: orig=4096 compr=4096 mem_used_total=8192 ratio=1.00 payload_saved=0.0%"),
}
for name, (text, expected) in cases.items():
path = root / name
path.write_text(text, encoding="ascii")
assert inspect_paths([path]) == (0, [expected, "SUMMARY: parsed=1 skipped=0"])
bad = ["", "1", "1 1", "x 1 1", "1 1 x", "-1 0 0",
"1 -1 0", "1 1 -1", "1 1 1 -1", "0 1 1", "1.0 1 1",
"+1 1 1", "1_000 1 1", "18446744073709551616 1 1"]
invalid_paths = []
for index, text in enumerate(bad):
path = root / f"bad-{index}"
path.write_text(text, encoding="ascii")
invalid_paths.append(path)
assert inspect_paths([path]) == (1, [f"SKIP {path.name}: ValueError", "SUMMARY: parsed=0 skipped=1"])
missing = root / "missing"
assert inspect_paths([missing]) == (1, ["SKIP missing: FileNotFoundError", "SUMMARY: parsed=0 skipped=1"])
assert inspect_paths([]) == (1, ["SUMMARY: parsed=0 skipped=0"])
code, lines = inspect_paths(invalid_paths + [missing])
assert code == 1 and lines[-1] == "SUMMARY: parsed=0 skipped=15"
assert len(lines) == 16 and all(line.startswith("SKIP ") for line in lines[:-1])
code, lines = inspect_paths([root / "ratio", missing])
assert code == 1 and lines == [cases["ratio"][1], "SKIP missing: FileNotFoundError", "SUMMARY: parsed=1 skipped=1"]
print("PASS: offline file fixtures; ratio, nonempty zero payload, empty, invalid/missing, all-skipped and partial results")
def main():
if len(sys.argv) == 1:
self_test()
return 0
code, lines = inspect_paths([Path(name) for name in sys.argv[1:]])
print("\n".join(lines))
return code
if __name__ == "__main__":
raise SystemExit(main())
On 2026-10-19, both complete fences passed offline under Python 3.12.14: assertions enabled, exit 0, empty stderr. PASS covers only file-fixture assertions. The invented 300 MiB / 100 MiB snapshot checks ratio 3.00 and payload saving 66.7%. Tests distinguish nonempty zero payload from no stored data, retain allocation, and reject missing, malformed, negative or inconsistent values. Separate mutation tests detect deliberately broken behavior.
The parser requires three fields and unsigned 64-bit decimal tokens; mem_used_total is reported separately. Same-filled pages may have positive original bytes without compressed payload: the ratio is unavailable, not proof of emptiness. No stored data does not mean zero allocation. Optional-file mode never prints PASS and exits nonzero if nothing parsed or anything was skipped. It cannot infer disksize, pool fullness, trends, CPU cost, OOM timing or tuning remedies. No live-device behavior was tested.
Further reading
Sources
kernel.org: zram block device, the authoritative zram documentation, checked 2026-10-19.
Wikipedia: zram, what zram is and how it works, checked 2026-10-19.
ArchWiki: Zram, retained further reference; access attempted 2026-10-19, but an anti-bot page prevented content verification.
Linux v6.12 zswap source, correction reference, checked 2026-10-18.
Linux zswap documentation, correction reference, checked 2026-10-18.
Linux v6.12 zram source, same-filled page accounting and mm_stat fields, checked 2026-10-19.