Skip to content

Raspberry Pi Memory Pressure, ZRAM, and OOM Diagnosis

A low free value does not prove that a Raspberry Pi needs more RAM. Linux uses unused memory for cache and reclaims it when applications need space. Diagnose sustained stalls, swap activity, and out-of-memory kills before changing ZRAM or vm.swappiness.

Quick diagnosis

Run these commands while reproducing the slow workload:

1
2
3
4
5
free -h
swapon --show --output=NAME,TYPE,SIZE,USED,PRIO
zramctl
vmstat 1
watch -n 1 'cat /proc/pressure/memory'

Then check whether the kernel killed a process:

journalctl -k -b --no-pager | grep -Ei 'out of memory|oom-kill|killed process'
Evidence Meaning Next action
Low available, no PSI stalls, no swap Memory is busy but not necessarily constrained Keep observing the real workload
some PSI rises briefly At least one task waits for memory Correlate with latency and swap
full PSI rises All non-idle tasks are stalled together Reduce working set or test ZRAM
Continuous si/so in vmstat Pages move between RAM and swap Measure task latency and swap medium
OOM entry in kernel journal Kernel could not satisfy allocation Identify the killed cgroup/process
ZRAM DATA much smaller than COMPR Data compresses poorly or overhead dominates Reduce/disable ZRAM for this workload

Read free correctly

Use the available column as the practical estimate of memory that can be supplied without swapping. Do not treat used as application-only memory because it includes reclaimable cache.

free -h
grep -E 'MemTotal|MemAvailable|Cached|SwapTotal|SwapFree' /proc/meminfo

For a process-level view:

ps -eo pid,user,comm,rss,vsz,%mem --sort=-rss | head -20

RSS can be counted in more than one process when pages are shared. Use smem when proportional set size matters:

1
2
3
sudo apt update
sudo apt install smem
sudo smem -tk

Measure pressure, not just capacity

Linux Pressure Stall Information reports how much time tasks lose while waiting for resources:

cat /proc/pressure/memory

Example format:

some avg10=0.25 avg60=0.10 avg300=0.04 total=123456
full avg10=0.02 avg60=0.01 avg300=0.00 total=1234

These are observations, not universal pass/fail thresholds. Record them beside application response time. A batch workload may tolerate pressure that makes an interactive desktop unusable.

Save a one-second sample without inventing benchmark results:

mkdir -p "$HOME/memory-bench"
duration=300
output="$HOME/memory-bench/pressure.csv"

printf 'timestamp,mem_available_kib,swap_free_kib,psi_some_avg10,psi_full_avg10\n' > "$output"
for _ in $(seq 1 "$duration"); do
  timestamp=$(date +%s)
  available=$(awk '/MemAvailable:/ {print $2}' /proc/meminfo)
  swap_free=$(awk '/SwapFree:/ {print $2}' /proc/meminfo)
  some=$(awk '/some/ {sub("avg10=", "", $2); print $2}' /proc/pressure/memory)
  full=$(awk '/full/ {sub("avg10=", "", $2); print $2}' /proc/pressure/memory)
  printf '%s,%s,%s,%s,%s\n' "$timestamp" "$available" "$swap_free" "$some" "$full" >> "$output"
  sleep 1
done

Run the same workload before and after one controlled change. Keep the workload, OS, cooling, storage, and starting temperature constant.

Inspect ZRAM efficiency

zramctl reports the uncompressed data size, compressed size, and total memory consumed:

zramctl --output NAME,ALGORITHM,DISKSIZE,DATA,COMPR,TOTAL,STREAMS,MOUNTPOINT
cat /sys/block/zram0/mm_stat

Do not compare only DATA and COMPR; allocator overhead is represented in TOTAL. A high configured disk size is only a limit and does not mean that amount of RAM is preallocated.

Check swap priority:

swapon --show --output=NAME,TYPE,SIZE,USED,PRIO

If both ZRAM and disk swap exist, ZRAM normally needs the higher priority to be selected first. Confirm the actual output rather than assuming a configuration was applied.

Identify an OOM kill

Show kernel OOM messages from the current boot:

journalctl -k -b --no-pager | grep -Ei -C 5 'oom-kill|killed process|out of memory'

For a systemd service, check its cgroup memory counters:

1
2
3
systemctl status example.service
systemctl show example.service \
  -p MemoryCurrent -p MemoryPeak -p MemorySwapCurrent -p OOMPolicy

A service may hit a configured cgroup limit while the machine still has memory. Inspect its unit and drop-ins:

systemctl cat example.service
systemctl show example.service -p MemoryMax -p MemoryHigh -p MemorySwapMax

Fix a leak or oversized workload before treating swap as the solution. For image, AI, database, or browser workloads, reducing concurrency, resolution, cache size, model size, or batch size often helps more predictably.

A/B test ZRAM safely

Use the ZRAM setup guide to select one implementation. Do not install both zram-tools and systemd-zram-generator.

For each state, record at least five runs:

Metric No ZRAM ZRAM configuration
Workload completion time, seconds
Application p95 latency, ms
Peak RSS or cgroup MemoryPeak, MiB
Peak PSI some / full, %
Swap-in / swap-out, KiB/s
ZRAM DATA / COMPR / TOTAL, MiB n/a
OOM kills
Maximum temperature, °C

Stop the test if the device becomes unreachable, repeatedly invokes OOM, corrupts the workload, or shows power/thermal throttling. Keep console or physical recovery access when testing a remote device.

When to choose another solution

  • Reduce the workload when it exceeds RAM by design.
  • Use a Raspberry Pi with more RAM when predictable latency matters.
  • Keep a small disk swap fallback only when the workload can tolerate storage latency and wear.
  • Add a systemd memory limit when one service must not starve the whole device.
  • Use monitoring and restart policy for a confirmed leak while fixing the application.

FAQ

Is 90% used RAM bad?

Not by itself. Check MemAvailable, PSI, application latency, swap activity, and OOM messages.

Does ZRAM double Raspberry Pi memory?

No. The effective gain depends on how well the actual pages compress, and compressed pages plus metadata still consume physical RAM.

What vm.swappiness value should I use?

There is no universal value. Keep the OS default for the baseline, change it only as a separate experiment, and compare pressure and application latency.

Why was one service killed when memory remained?

It may have reached a systemd/cgroup memory limit. Inspect MemoryMax, MemoryHigh, MemorySwapMax, and the kernel journal.