My zram was bigger than my RAM
My laptop kept freezing on trivial things. Not on compiles, not on Docker builds — on things like clicking a menu. The one that finally made me investigate: I watched an entire movie in SMPlayer without a single stutter, then reached for the volume slider and the whole machine locked up.
That combination is backwards. Heavy work was fine; a volume change was fatal. Whatever was wrong, it was not “the computer is too slow.”
Measuring the freeze instead of guessing
free -h and top are useless here, because by the time you can read them the
freeze is over. What you want is PSI — pressure stall information, in
/proc/pressure/:
$ cat /proc/pressure/memory
some avg10=0.00 avg60=0.00 avg300=0.17 total=2034355
full avg10=0.00 avg60=0.00 avg300=0.17 total=2017374
The averages are a distraction. total= is what matters: cumulative
microseconds that tasks spent stalled. Sample it on a timer and the delta
between two samples tells you what fraction of wall-clock time the machine
spent frozen. full means every task was stalled — that is a freeze, by
definition.
I left a collector sampling every 20 seconds and went back to work. Over 95 minutes it recorded this:
11:27:08 avail=2.96Gi swap=2.69Gi
11:48:05 avail=3.20Gi swap=2.69Gi
12:19:54 avail=3.77Gi swap=3.38Gi
12:51:43 avail=3.19Gi swap=4.31Gi
13:02:20 avail=4.15Gi swap=4.45Gi
Swap climbed 2.69 → 4.45 GB and never came back down. Meanwhile available RAM stayed flat. Even after the largest process on the machine — 1.37 GB — exited, swap went up.
That is not a leak in one program. Memory was going somewhere and not coming back.
Where it was going
$ cat /proc/swaps
Filename Type Size Used Priority
/dev/zram1 partition 5998412 1151552 32767
/dev/zram2 partition 5998412 1153096 32767
/dev/zram3 partition 5998412 1141140 32767
/dev/zram4 partition 5998412 1125820 32767
/swapfile/1 file 524284 0 -2
Four zram devices, 6.14 GB each. That is 22.9 GB of swap on a machine with 15.25 GB of RAM.
This is the whole bug, and it is worth being precise about why it is a bug. zram is not disk. It is a compressed block device that lives in RAM. Swapping a page to zram compresses it and keeps it in memory — cheaper than going to the NVMe, which is exactly why it is a good idea in general.
But sizing it at 150% of RAM inverts the logic. The kernel now believes it has 38 GB of memory to hand out. It does not. Every page it “frees” by swapping is still occupying RAM, just compressed. The pool competes with the very memory it exists to relieve, and the system settles into an equilibrium where a growing share of your RAM is storing a compressed copy of your RAM.
Mine came from the distro default:
$ grep zram_size /usr/share/systemd-swap/swap-default.conf
## zram_size=150% # Virtual disksize (% of RAM). Larger = more data in RAM
I had also set vm.swappiness = 100 myself, months earlier, with a comment in
the file explaining that high swappiness makes good use of fast zram. That
reasoning is sound — but only if zram is sized sanely. At 150% it just fed the
spiral faster.
Why the volume slider, and not the movie
This is the part that explains the symptom, and it took me a while to see it.
Playing a movie is sequential and predictable. The pages it touches constantly stay resident; everything it doesn’t touch gets pushed into zram — including the parts of PipeWire, the Qt widgets and the theme assets that nothing has needed for two hours. Playback stays smooth the entire time.
Then you click the volume control. That demands an immediate round trip through exactly those cold pages, and decompressing them requires free RAM — which is the one thing there isn’t any of. Audio runs on a real-time thread with a hard deadline. When it misses, it does not degrade gracefully; it takes the desktop down with it.
So the rule on a machine in this state is inverted: heavy sequential work is the safest thing you can do. Small interactive actions are what kill it.
The fix
Two lines. /etc/systemd/swap.conf:
zram_size=50%
zram_alg=zstd
and /etc/sysctl.d/99-performance-tuning.conf:
vm.swappiness = 70
50% of RAM is the conventional sizing, and the pool still expands on demand under real pressure — it just no longer pre-claims more memory than the machine physically has.
One trap worth mentioning. Restarting systemd-swap forces everything in zram
back into real RAM, and I had 3.96 GB of uncompressed data sitting in there
against 4.11 GB available. That margin is too thin; it would have OOM-killed
something. So I added a temporary disk swapfile first as a safety net.
On btrfs that has its own catch:
$ sudo swapon /var/tmp/temp.swap
swapon: /var/tmp/temp.swap: swapon failed: Invalid argument
$ sudo dmesg | tail -1
BTRFS warning (device dm-0): swapfile must not be copy-on-write
btrfs swapfiles need chattr +C on the empty file, before you write any data
into it:
sudo truncate -s 0 /var/tmp/temp.swap
sudo chattr +C /var/tmp/temp.swap
sudo dd if=/dev/zero of=/var/tmp/temp.swap bs=1M count=8192
sudo chmod 600 /var/tmp/temp.swap
sudo mkswap /var/tmp/temp.swap && sudo swapon -p 1 /var/tmp/temp.swap
fallocate will not do — it produces an extent layout swap rejects.
The result
Swap total dropped from 22.9 GB to 7.6 GB, and usage collapsed:
before: swap used 4.57 Gi memory pressure climbing all morning
after: swap used 0.21 Gi memory pressure ~0%
The same 1080p clip that I used as a control now plays with 1.51% memory stall and 408 ms of I/O stall across a 20-second run.
MemAvailable reads lower now — 1.69 GB instead of 4.15 GB — and that looks
alarming until you realise it is the point. That data is in real RAM instead of
being compressed inside RAM that was also being counted as free. The earlier
number was double-counting.
What I would do differently
I spent the first part of this convinced it was a video problem, because the freezes happened around VLC and SMPlayer. It wasn’t. Hardware decode was working perfectly the entire time — I confirmed it by playing a clip and watching memory pressure move by a single microsecond.
The media players were victims, not causes. What made them look guilty is that they are memory-hungry enough to push the system over the edge, and they are the app you happen to be touching when it tips.
The lesson I am taking: when a symptom points at an application, measure the application directly before believing it. One 20-second controlled playback would have cleared VLC in the first five minutes. And when a small action freezes a machine that handles large ones fine, stop looking at the application entirely — that asymmetry is a memory-reclaim signature, and it points at the kernel’s idea of how much memory it has.