Using systemd-run to limit something's memory usage in cgroups v2
Once upon a time I wrote an entry about using systemd-run to limit something's RAM consumption. This was back in the days of cgroups v1 (also known as 'non-unified cgroups'), and we're now in the era of cgroups v2 ('unified cgroups') and also ZRAM based swap. This means we want to make some adjustments, especially if you're dealing with programs with obnoxiously large RAM usage.
As before, the basic thing you want
to do is run your program or thing in a new systemd user scope,
which is done with 'systemd-run
--user --scope ...
'. You may wish to give it a unit name as well,
'--unit <name>', especially if you expect it to persist a while and
you want to track it specifically. Systemd will normally automatically
clean up this scope when everything in it exits, and the scope is
normally connected to your current terminal and otherwise more or
less acts normally as an interactive process.
To actually do anything with this, we need to set some systemd
resource limits.
To limit memory usage, the minimum is a MemoryMax=
value. It may also work better to set MemoryHigh=
to a value somewhat below the absolute limit of MemoryMax. If you're
worried about whatever you're doing running your system out of
memory and your system uses ZRAM based swap, you may also want
to set a MemoryZSwapMax=
value so that the program doesn't chew up all of your RAM by
'swapping' it to ZRAM and filling that up. Without a ZRAM swap
limit, you might find that the program actually uses MemoryMax RAM
plus your entire ZRAM swap RAM, which might be enough to trigger a
more general OOM. So this might be:
systemd-run --user --scope -p MemoryHigh=7G -p MemoryMax=8G -p MemoryZSwapMax=1G ./mach build
(Good luck with building Firefox in merely 8 GBytes of RAM, though. And obviously if you do this regularly, you're going to want to script it.)
If you normally use ZRAM based swap and you're worried about the program running you out of memory that way, you may want to create some actual swap space that the program can be turned loose on. These days, this is as simple as creating a 'swap.img' file somewhere and then swapping onto it:
cd / dd if=/dev/zero of=swap.img bs=1MiB count=$((4*1024)) mkswap swap.img swapon /swap.img
(You can use swapoff to stop swapping to this image file after you're done running your big program.)
Then you may want to also limit how much of this swap space the
program can use, which is done with a MemorySwapMax=
value. I've read both systemd's documentation and the kernel's
cgroup v2 memory controller documentation,
and I can't tell whether the ZRAM swap maximum is included in the
swap maximum or is separate. I suspect that it's included in the swap
maximum, but if it really matters you should experiment.
If you also want to limit the program's CPU usage, there are two
options. The easiest one to set is CPUQuota=
.
The drawback of CPU quota limits is that programs may not realize
that they're being restricted by such a limit
and wind up running a lot more threads (or processes) than they
should, increasing the chances of overloading things. The more
complex but more legible to programs way is to restrict what CPUs
they can run on using taskset(1).
(While systemd has AllowedCPUs=
,
this is a cgroup setting and doesn't show up in the interface used
by taskset and sched_getaffinity(2)
.)
Systemd also has CPUWeight=
,
but I have limited experience with it; see fair share scheduling
in cgroup v2 for what I know. You
might want the special value 'idle' for very low priority programs.
Comments on this page:
|
|