Reliability and resources

Control CPU, memory, and tasks

Use cgroup-backed unit controls to contain one service without confusing a limit with capacity planning.

systemd places every service in its own control group, a kernel feature that tracks and limits a set of processes together. That is what makes resource limits practical. A limit on the unit applies to the whole process tree, including children and anything they spawn. A worker pool cannot escape it by forking.

The four controls

[Service]
MemoryHigh=400M
MemoryMax=512M
CPUQuota=50%
TasksMax=64

MemoryHigh= is the pressure line. Above it, the kernel throttles the service and reclaims its memory aggressively, but the process survives.

MemoryMax= is the hard boundary. Above it, processes in the group get killed. Setting both gives the service a warning zone before the cliff.

CPUQuota= limits CPU time. 50% means half of one CPU. 200% would mean two full CPUs.

TasksMax= bounds process and thread creation. This is your defense against fork bombs and runaway worker pools.

Put these in a drop-in with sudo systemctl edit demo-api.service, not in the vendor unit. Then restart the service so the new limits apply.

Verify the limit and the current use

Ask systemd where the service lives and what it is using right now:

systemctl show --property=ControlGroup,MemoryCurrent,TasksCurrent demo-api.service
ControlGroup=/system.slice/demo-api.service
MemoryCurrent=61128704
TasksCurrent=11

That is roughly 58 MB and 11 tasks. Well under the limits we set, which is what you want in normal operation.

systemd-cgtop gives you the same numbers live, across all services, sorted by usage. I run it when a box feels slow and I want to know who is eating what.

A limit is not capacity planning

Hitting a limit is still a failure. If the service breaches MemoryMax=, the journal shows the kill:

demo-api.service: A process of this unit has been killed by the OOM killer.
demo-api.service: Main process exited, code=killed, status=9/KILL

and systemctl show demo-api.service -p Result reports oom-kill. Notice that the service did not get slower. It died. With Restart=on-failure it comes back, and if the workload has not changed it dies again.

If that keeps happening, the answer is a bigger budget or a smaller workload. It is not deleting the limit. The limit is what kept the rest of the machine alive while one service misbehaved. Without it, the kernel picks the victim, and it might pick PostgreSQL.

So limits need monitoring. Alert on Result=oom-kill and on MemoryCurrent sitting near MemoryHigh= for a long time.

Try this on one real service. Run the systemctl show command above a few times during the day and note the peak. Propose one limit from that: MemoryHigh= around the peak, MemoryMax= comfortably above it. Then apply it in a drop-in and watch the numbers for a week before tightening.

Lesson completed

Take this course offline

Get every free book, course edition, and software download.

Get the download library →