<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Meshack Bahati</title>
    <description>The latest articles on DEV Community by Meshack Bahati (@meshackbahati).</description>
    <link>https://dev.to/meshackbahati</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4119282%2F2a9681bd-c4ad-4528-9dbd-7a738916e513.png</url>
      <title>DEV Community: Meshack Bahati</title>
      <link>https://dev.to/meshackbahati</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/meshackbahati"/>
    <language>en</language>
    <item>
      <title>Diagnosing zram Device Initialization Races on Linux: A Direct Kernel Interface Approach</title>
      <dc:creator>Meshack Bahati</dc:creator>
      <pubDate>Tue, 15 Sep 2026 07:11:46 +0000</pubDate>
      <link>https://dev.to/meshackbahati/diagnosing-zram-device-initialization-races-on-linux-a-direct-kernel-interface-approach-4pgl</link>
      <guid>https://dev.to/meshackbahati/diagnosing-zram-device-initialization-races-on-linux-a-direct-kernel-interface-approach-4pgl</guid>
      <description>&lt;p&gt;If you've ever had a Linux box with limited RAM grind to a halt under load, you know the feeling. Browser tabs pile up, an IDE starts indexing, a build kicks off, and suddenly the cursor lags, audio crackles, and the terminal output comes in stutters. The hardware isn't dying. The kernel is just thrashing to disk swap.&lt;/p&gt;

&lt;p&gt;zram helps with that. It creates a compressed block device in RAM, so cold pages get compressed on the way out and decompressed on the way back in. On a machine that's short on memory, it can be the difference between a usable system and a slideshow.&lt;/p&gt;

&lt;p&gt;The usual advice is to install &lt;code&gt;zram-generator&lt;/code&gt; and let systemd handle it. I've had nothing but trouble with that thing. It races the kernel, fails to set the device size, and leaves you with a broken swap unit. So I do it manually now. It's not many steps, and you can actually see what's going on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What zram actually does
&lt;/h2&gt;

&lt;p&gt;zram gives you a compressed block device backed by RAM. You format it as swap, turn it on with a high priority, and the kernel sends cold pages there before touching your disk.&lt;/p&gt;

&lt;p&gt;One thing that trips people up: the size you set is the maximum &lt;em&gt;uncompressed&lt;/em&gt; data the device can hold. It is not the amount of RAM it will use. Every compressed page still lives in physical RAM. If you size it too aggressively, zram starts competing with your working set. That's how you end up with a system that swaps into its own memory and still feels starved.&lt;/p&gt;

&lt;p&gt;A correct setup looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ swapon --show
NAME       TYPE      SIZE USED PRIO
/dev/zram0 partition 3.9G   0B 32767
/dev/nvme0n1p2 partition 4G 0B -2

$ zramctl
NAME       ALGORITHM DISKSIZE  DATA  COMPR  TOTAL STREAMS MOUNTPOINT
/dev/zram0 zstd        3.9G  1.2G  380M  410M       4 [SWAP]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DATA&lt;/code&gt; is the uncompressed size currently stored. &lt;code&gt;COMPR&lt;/code&gt; is the compressed size. &lt;code&gt;TOTAL&lt;/code&gt; includes metadata. The ratio between &lt;code&gt;DATA&lt;/code&gt; and &lt;code&gt;COMPR&lt;/code&gt; tells you how well compression is working. &lt;code&gt;DISKSIZE&lt;/code&gt; is the uncompressed budget you configured. &lt;code&gt;PRIO 32767&lt;/code&gt; means the kernel fills zram before it touches disk swap.&lt;/p&gt;

&lt;p&gt;If you skip the priority, or make the device too big, you get one of two symptoms: disk swap activity while zram sits empty, or a working set that starves even though you supposedly have plenty of memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the zram-generator keeps breaking
&lt;/h2&gt;

&lt;p&gt;The distribution path is &lt;code&gt;zram-generator&lt;/code&gt;. It's a systemd generator. In theory it sets everything up for you. In practice it has a nasty race condition.&lt;/p&gt;

&lt;p&gt;The same binary ships under different names depending on your distro. Fedora calls it &lt;code&gt;zram-generator-defaults&lt;/code&gt; or just &lt;code&gt;zram-generator&lt;/code&gt; without defaults. Debian calls it &lt;code&gt;systemd-zram-generator&lt;/code&gt;. Arch has &lt;code&gt;zram-generator&lt;/code&gt; in core and &lt;code&gt;zram-generator-git&lt;/code&gt; in the AUR. Three names, three sets of packaging notes, three bug trackers. That alone makes tutorials annoying.&lt;/p&gt;

&lt;p&gt;The real problem is what happens after installation. You get a config file at &lt;code&gt;/etc/systemd/zram-generator.conf&lt;/code&gt;, reboot, and then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;swap-create@zram0.service: Failed with result 'exit-code'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in the journal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;zram: Cannot change disksize for initialized device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's &lt;a href="https://github.com/systemd/zram-generator/issues/7" rel="noopener noreferrer"&gt;issue 7&lt;/a&gt;, open since January 2020. The generator assumes &lt;code&gt;/dev/zram0&lt;/code&gt; is ready for sizing the moment the device node appears. The kernel isn't ready yet. systemd writes to &lt;code&gt;/sys/block/zram0/disksize&lt;/code&gt; before initialization finishes, and the write fails with &lt;code&gt;Device or resource busy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/systemd/zram-generator/issues/239" rel="noopener noreferrer"&gt;Issue 239&lt;/a&gt; shows the same thing after &lt;code&gt;systemctl soft-reboot&lt;/code&gt;, with repeated &lt;code&gt;Failed to configure disk size into /sys/block/zram0/disksize&lt;/code&gt;. There's also an old mailing list report where the generator says &lt;code&gt;Device zram0 not found&lt;/code&gt; even though &lt;code&gt;ls /dev/zram0&lt;/code&gt; clearly shows the node.&lt;/p&gt;

&lt;p&gt;This isn't a one-distro problem. &lt;a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1026745" rel="noopener noreferrer"&gt;Debian bug 1026745&lt;/a&gt; records &lt;code&gt;systemd-zram-generator: Failed to start Create swap on /dev/zram0&lt;/code&gt;. &lt;a href="https://github.com/coreos/fedora-coreos-tracker/issues/1844" rel="noopener noreferrer"&gt;Fedora CoreOS tracker 1844&lt;/a&gt; shows the generator failing with &lt;code&gt;terminated by signal ABRT&lt;/code&gt; on Rawhide after a version transition. The Arch Wiki documents the generator path but also documents two manual paths that don't hit the race.&lt;/p&gt;

&lt;p&gt;The kernel module itself has been stable since 2014. The bug is in the systemd layer that tries to predict when the kernel is ready, and predicts wrong.&lt;/p&gt;

&lt;p&gt;The manual approach has to respect the same ordering. &lt;code&gt;modprobe&lt;/code&gt; returning doesn't mean &lt;code&gt;/dev/zram0&lt;/code&gt; is usable yet — on fast NVMe machines the node can lag behind the module load. The script polls for it instead of assuming.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;p&gt;No generator package. Just the basics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A kernel with the &lt;code&gt;zram&lt;/code&gt; module. Check with &lt;code&gt;modinfo zram&lt;/code&gt; and &lt;code&gt;zgrep ZRAM /proc/config.gz&lt;/code&gt; or &lt;code&gt;grep ZRAM /boot/config-$(uname -r)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;util-linux&lt;/code&gt; for &lt;code&gt;mkswap&lt;/code&gt; and &lt;code&gt;swapon&lt;/code&gt;. Check with &lt;code&gt;mkswap --version&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kmod&lt;/code&gt; for &lt;code&gt;modprobe&lt;/code&gt;, plus &lt;code&gt;bash&lt;/code&gt;, &lt;code&gt;coreutils&lt;/code&gt;, &lt;code&gt;procps&lt;/code&gt;, and &lt;code&gt;gawk&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sudo&lt;/code&gt; or root access for &lt;code&gt;/sys&lt;/code&gt; and &lt;code&gt;/etc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/proc&lt;/code&gt; and &lt;code&gt;/sys&lt;/code&gt; mounted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're missing something:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;util-linux kmod procps gawk

&lt;span class="c"&gt;# Fedora/RHEL&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install &lt;/span&gt;util-linux kmod procps-ng gawk

&lt;span class="c"&gt;# Arch&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;pacman &lt;span class="nt"&gt;-S&lt;/span&gt; util-linux kmod procps gawk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Doing it by hand
&lt;/h2&gt;

&lt;p&gt;This is the whole process. You can run each step yourself and check the output as you go. The script I'll show later does the same thing in the same order.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Load the module
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;modprobe zram &lt;span class="nv"&gt;num_devices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1

&lt;span class="c"&gt;# the /dev node can lag behind modprobe on fast machines, so wait for it&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;_ &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;1..5&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; /dev/zram0 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;0.2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done
&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /dev/zram0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That wait loop is the entire generator race, fixed in three lines. If the node isn't there after a second, something is actually wrong and you should stop rather than write to sysfs paths that don't exist yet.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;/dev/zram0&lt;/code&gt; is already being used as swap, turn it off first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; /dev/zram0 /proc/swaps &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;swapoff /dev/zram0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the device has an old size on it, reset it before you change anything. Sizing and algorithm writes fail on an initialized device:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /sys/block/zram0/reset &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;1 | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /sys/block/zram0/reset &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. See what compression algorithms you have
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/block/zram0/comp_algorithm
&lt;span class="c"&gt;# example: lzo-rle lzo lz4 lz4hc [zstd] deflate 842&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The one in brackets is currently selected.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Pick zstd, with fallbacks
&lt;/h3&gt;

&lt;p&gt;zstd compresses better than lz4. On a low-RAM machine, compression ratio matters more than raw CPU speed. A few extra cycles compressing a cold page is cheaper than thrashing that page to NVMe.&lt;/p&gt;

&lt;p&gt;Not every kernel has zstd, though. So fall back to lz4, then lzo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;available&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/block/zram0/comp_algorithm&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;algo &lt;span class="k"&gt;in &lt;/span&gt;zstd lz4 lzo&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  if &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$available&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qw&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$algo&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$algo&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /sys/block/zram0/comp_algorithm &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
    &lt;span class="nb"&gt;break
  &lt;/span&gt;&lt;span class="k"&gt;fi
done
&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/block/zram0/comp_algorithm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That fallback chain is why the same steps work on Arch, Debian, Ubuntu, Fedora, and RHEL without installing extra packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Size the device
&lt;/h3&gt;

&lt;p&gt;On an 8GB desktop running KDE, a browser, and an IDE, I run zram at 100% of RAM — 8G of uncompressed budget holding roughly 2–3x that in effective data once zstd does its thing. The conservative starting point you'll see in most guides is 50%. Both are defensible; what isn't defensible is sizing blindly and never checking the compression ratio afterwards.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;mem_kb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/MemTotal/ {print $2}'&lt;/span&gt; /proc/meminfo&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;size_bytes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; mem_kb &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$size_bytes&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /sys/block/zram0/disksize &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Percentages get converted to raw bytes. Fixed sizes go through as-is — the kernel's sysfs parser accepts suffixes directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 50 percent instead&lt;/span&gt;
&lt;span class="nv"&gt;mem_kb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/MemTotal/ {print $2}'&lt;/span&gt; /proc/meminfo&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="k"&gt;$((&lt;/span&gt; mem_kb &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /sys/block/zram0/disksize &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null

&lt;span class="c"&gt;# or a fixed size, no conversion needed&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;8G | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /sys/block/zram0/disksize &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;numfmt&lt;/span&gt; &lt;span class="nt"&gt;--to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;iec-i &lt;span class="nt"&gt;--suffix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;B &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$size_bytes&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/block/zram0/disksize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One write path, no reformatting in between. If the value is malformed the kernel rejects the write and you see the error immediately instead of debugging a silently wrong size later.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Make swap and turn it on
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;mkswap &lt;span class="nt"&gt;-L&lt;/span&gt; zram-swap /dev/zram0 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
&lt;span class="nb"&gt;sudo &lt;/span&gt;swapon &lt;span class="nt"&gt;--priority&lt;/span&gt; 32767 /dev/zram0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That priority is the highest the kernel accepts. It tells the memory manager to fill zram first and only touch disk swap when zram is genuinely full. Without it, the kernel can happily page to NVMe while your zram device sits empty. That's the exact opposite of what you want, and it's a detail a lot of guides miss.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Tune the VM for compressed swap
&lt;/h3&gt;

&lt;p&gt;The default &lt;code&gt;vm.swappiness=60&lt;/code&gt; is meant for disk-backed swap. With zram it causes a specific failure mode, and low-swappiness advice carried over from the spinning-disk era makes it worse.&lt;/p&gt;

&lt;p&gt;Swappiness controls how eagerly the kernel moves cold pages to swap. A low value tells it to hold off until raw RAM is nearly exhausted. That logic assumes swap is slow and best avoided. But zram swap &lt;em&gt;is&lt;/em&gt; RAM — just compressed. Holding off means cold pages sit around uncompressed until memory is gone, and by then there's no headroom left to compress anything. The kernel goes from "everything is fine" to out of memory with almost nothing in between, and the OOM killer starts shooting your browser tabs while gigabytes of compressible cold pages were sitting right there. On an 8GB box under desktop load, that goes: lag, stutter, dead tabs. The swap was configured and never got used in time.&lt;/p&gt;

&lt;p&gt;High swappiness inverts this. It pushes cold pages into compression early, while there's still plenty of room to absorb them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl &lt;span class="nt"&gt;-w&lt;/span&gt; vm.swappiness&lt;span class="o"&gt;=&lt;/span&gt;150
&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl &lt;span class="nt"&gt;-w&lt;/span&gt; vm.watermark_boost_factor&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl &lt;span class="nt"&gt;-w&lt;/span&gt; vm.watermark_scale_factor&lt;span class="o"&gt;=&lt;/span&gt;125
&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl &lt;span class="nt"&gt;-w&lt;/span&gt; vm.page-cluster&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="c"&gt;# kernel default, included in case something else tuned it lower&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl &lt;span class="nt"&gt;-w&lt;/span&gt; vm.vfs_cache_pressure&lt;span class="o"&gt;=&lt;/span&gt;100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Newer kernels accept swappiness values up to 200. What the rest do: &lt;code&gt;watermark_scale_factor=125&lt;/code&gt; (default 10) widens the background reclaim window so kswapd compresses ahead of emergencies instead of during them; &lt;code&gt;watermark_boost_factor=0&lt;/code&gt; turns off the bursty boost allocator. &lt;code&gt;page-cluster=0&lt;/code&gt; disables swap readahead — single-page I/O, no latency fetching neighboring compressed blocks you never asked for.&lt;/p&gt;

&lt;p&gt;Make it stick:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/sysctl.d
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' | sudo tee /etc/sysctl.d/99-zram-swap.conf &amp;gt;/dev/null
# Optimized for zram on low-RAM systems
vm.swappiness=150
vm.watermark_boost_factor=0
vm.watermark_scale_factor=125
vm.page-cluster=0
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Verify
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;swapon &lt;span class="nt"&gt;--show&lt;/span&gt;
free &lt;span class="nt"&gt;-h&lt;/span&gt;
zramctl
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/sys/vm/swappiness
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/module/zswap/parameters/enabled
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/block/zram0/comp_algorithm
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/block/zram0/mm_stat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want to see &lt;code&gt;/dev/zram0&lt;/code&gt; with &lt;code&gt;PRI 32767&lt;/code&gt; and swappiness at 150. After some pressure, &lt;code&gt;mm_stat&lt;/code&gt; and &lt;code&gt;zramctl&lt;/code&gt; should show non-zero compressed totals. If they stay at zero even under pressure, check the zswap line — if it reads &lt;code&gt;Y&lt;/code&gt;, something is intercepting pages before they reach zram. Details below.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Make it survive a reboot
&lt;/h3&gt;

&lt;p&gt;An fstab line alone doesn't do it. At boot &lt;code&gt;/dev/zram0&lt;/code&gt; exists with &lt;code&gt;disksize 0&lt;/code&gt; until something runs &lt;code&gt;modprobe&lt;/code&gt;, &lt;code&gt;mkswap&lt;/code&gt;, and &lt;code&gt;swapon&lt;/code&gt; — so a fstab-generated swap unit fires before the device is initialized and fails. Everything looks alive after a manual run, then the next reboot has no swap. The script installs a oneshot systemd service instead, which re-runs the whole setup before &lt;code&gt;swap.target&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/usr/local/sbin/zram-swap&lt;/code&gt; — a copy of the script that remembers your size arg&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/etc/systemd/system/zram-swap.service&lt;/code&gt; — enabled, runs before &lt;code&gt;swap.target&lt;/code&gt; on every boot&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/etc/modules-load.d/zram.conf&lt;/code&gt; and &lt;code&gt;/etc/modprobe.d/zram.conf&lt;/code&gt; — module loading&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/etc/sysctl.d/99-zram-swap.conf&lt;/code&gt; — the four VM keys above&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The installer also removes stale &lt;code&gt;/dev/zram0&lt;/code&gt; lines from &lt;code&gt;/etc/fstab&lt;/code&gt;, since they'd race the service. On non-systemd systems it falls back to an fstab entry with a warning, because there's nothing better available there.&lt;/p&gt;

&lt;p&gt;To remove it later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl disable &lt;span class="nt"&gt;--now&lt;/span&gt; zram-swap.service
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; /etc/systemd/system/zram-swap.service /usr/local/sbin/zram-swap
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;swapoff /dev/zram0
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; /etc/modules-load.d/zram.conf /etc/modprobe.d/zram.conf /etc/sysctl.d/99-zram-swap.conf
&lt;span class="nb"&gt;sudo &lt;/span&gt;modprobe &lt;span class="nt"&gt;-r&lt;/span&gt; zram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The script version
&lt;/h2&gt;

&lt;p&gt;I wrapped all of this into a script: &lt;a href="https://github.com/meshackbahati/zram-swap/blob/main/zram-swap.sh" rel="noopener noreferrer"&gt;zram-swap.sh&lt;/a&gt; in &lt;a href="https://github.com/meshackbahati/zram-swap" rel="noopener noreferrer"&gt;meshackbahati/zram-swap&lt;/a&gt;. It's under 300 lines and does the same steps in the same order.&lt;/p&gt;

&lt;p&gt;It checks for &lt;code&gt;mkswap&lt;/code&gt;, &lt;code&gt;swapon&lt;/code&gt;, &lt;code&gt;modprobe&lt;/code&gt;, &lt;code&gt;sysctl&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, and &lt;code&gt;grep&lt;/code&gt;, verifies &lt;code&gt;/sys&lt;/code&gt; and &lt;code&gt;/proc&lt;/code&gt; are mounted, and checks for the zram module via &lt;code&gt;modinfo&lt;/code&gt;. If something's missing, it tells you what to install per distro. Then it loads the module, polls for the &lt;code&gt;/dev&lt;/code&gt; node, swaps off and resets any existing device, disables zswap, picks the best algorithm with fallback, validates and sets the size (percentages become bytes, &lt;code&gt;K&lt;/code&gt;/&lt;code&gt;M&lt;/code&gt;/&lt;code&gt;G&lt;/code&gt; suffixes pass straight through), runs &lt;code&gt;mkswap&lt;/code&gt;, turns on swap with priority 32767, applies the VM tuning, and installs the systemd service plus the sysctl/modprobe persistence files. Finally it prints &lt;code&gt;swapon --show&lt;/code&gt; and &lt;code&gt;free -h&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/meshackbahati/zram-swap.git
&lt;span class="nb"&gt;cd &lt;/span&gt;zram-swap
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x zram-swap.sh
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./zram-swap.sh          &lt;span class="c"&gt;# 100% of RAM (the 8GB-desktop default)&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./zram-swap.sh 75%      &lt;span class="c"&gt;# 75% of RAM&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./zram-swap.sh 8G       &lt;span class="c"&gt;# fixed size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Percentages outside 10–200% are rejected. Uninstall is the manual steps in reverse, listed above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Details that actually matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sizing
&lt;/h3&gt;

&lt;p&gt;This is the easiest thing to get wrong. Setting zram to your total RAM feels logical until the system starts swapping into its own memory budget and starves the working set. You're competing with yourself.&lt;/p&gt;

&lt;p&gt;So why default to 100%? Because the budget is &lt;em&gt;uncompressed&lt;/em&gt; data, and zstd typically compresses desktop working sets around 2–3:1. An 8G budget on an 8GB box costs roughly 3–4G of physical RAM when full — the rest is compression doing its job.&lt;/p&gt;

&lt;p&gt;That said, the ratio is workload-dependent. Watch &lt;code&gt;zramctl&lt;/code&gt; over a week: if &lt;code&gt;COMPR&lt;/code&gt; stays close to &lt;code&gt;DATA&lt;/code&gt; (ratio near 1:1, common with already-compressed media or encrypted blobs), your pages aren't compressing and the budget really is costing you RAM. Shrink to 50% in that case. Community consensus lands around 25–50% of RAM for exactly this reason — one thread on older hardware found 25% worked better after 50% felt heavy, and the Arch Wiki says start with half. The 100% default is aggressive for a specific machine profile, not a universal law. The override exists for a reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why zstd
&lt;/h3&gt;

&lt;p&gt;zstd compresses more tightly than lz4. On modern hardware with default options, zstd commonly gets around 3:1 on text and source-heavy working sets. lz4 lands closer to 2–2.5:1. The tradeoff is higher CPU usage during compress and decompress.&lt;/p&gt;

&lt;p&gt;lz4 is faster per operation, but stores less data per byte of physical RAM. On a system where the problem is not enough RAM, compression ratio matters more than per-operation speed. A few extra CPU cycles compressing a cold page is cheaper than thrashing that page to NVMe.&lt;/p&gt;

&lt;p&gt;Not every kernel exposes zstd. Older kernels or minimal builds may only have lzo and lz4. That's why the script probes at runtime and falls back from zstd to lz4 to lzo. It's what lets the same script work across distros without extra packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  zswap will ruin your day
&lt;/h3&gt;

&lt;p&gt;If zswap is enabled, it sits in front of zram as a swap cache and intercepts pages before they reach the compressed device. The Arch Wiki says plainly that zswap prevents zram from being used effectively. The symptom is empty zram counters even when the system is under pressure.&lt;/p&gt;

&lt;p&gt;The script handles this: every run checks &lt;code&gt;/sys/module/zswap/parameters/enabled&lt;/code&gt; and writes &lt;code&gt;0&lt;/code&gt; if it's on — and since the systemd service re-runs the script every boot, the disable persists across reboots in practice. If you want it dead at the kernel level instead, add &lt;code&gt;zswap.enabled=0&lt;/code&gt; to your kernel cmdline: &lt;code&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;/code&gt; in &lt;code&gt;/etc/default/grub&lt;/code&gt; on grub systems, the loader entry &lt;code&gt;options&lt;/code&gt; line on systemd-boot, then regenerate and reboot.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/admin-guide/blockdev/zram.html" rel="noopener noreferrer"&gt;Kernel zram admin guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/admin-guide/sysctl/vm.html" rel="noopener noreferrer"&gt;Kernel vm sysctl docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/systemd/zram-generator" rel="noopener noreferrer"&gt;zram-generator repository and README&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/systemd/zram-generator/issues/7" rel="noopener noreferrer"&gt;zram-generator issue 7: Cannot change disksize for initialized device&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/systemd/zram-generator/issues/239" rel="noopener noreferrer"&gt;zram-generator issue 239: soft reboot Device or resource busy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1026745" rel="noopener noreferrer"&gt;Debian bug 1026745: Failed to start Create swap on /dev/zram0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/coreos/fedora-coreos-tracker/issues/1844" rel="noopener noreferrer"&gt;Fedora CoreOS tracker 1844: Rawhide generator failure SIGABRT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.archlinux.org/title/Zram" rel="noopener noreferrer"&gt;Arch Wiki zram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.archlinux.org/title/Zram#Using_a_udev_rule" rel="noopener noreferrer"&gt;Arch Wiki zram udev rule&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.archlinux.org/title/Zswap" rel="noopener noreferrer"&gt;Arch Wiki zswap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pop-os/default-settings/pull/163" rel="noopener noreferrer"&gt;Pop OS default settings pull 163 on zram tuning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://old.reddit.com/r/Fedora/comments/mzun99/new_zram_tuning_benchmarks/" rel="noopener noreferrer"&gt;Fedora community zram tuning benchmarks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Repository and script: &lt;a href="https://github.com/meshackbahati/zram-swap" rel="noopener noreferrer"&gt;meshackbahati/zram-swap&lt;/a&gt; and &lt;a href="https://github.com/meshackbahati/zram-swap/blob/main/zram-swap.sh" rel="noopener noreferrer"&gt;zram-swap.sh&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>ram</category>
    </item>
  </channel>
</rss>
