Resource Control with cgroups
systemd in Depth
Chapter 9 · Resource Control with cgroups
perf1's own CPU and Memory Bottlenecks chapters covered diagnosing a resource problem system-wide. This chapter closes the loop — once you know which specific service is the actual cause, systemd's own per-service limits are the concrete tool to contain it.
What cgroups Actually Are
Control groups (cgroups) are a Linux kernel feature that groups processes together and lets the kernel enforce resource limits — CPU, memory, I/O — on the group as a whole, not just individual processes. A genuinely important, often-unknown fact: as PID 1 and the ultimate ancestor of essentially every process on the system, systemd automatically places every unit into its own cgroup, by default, whether or not any explicit limit is ever configured. Cgroup accounting already exists per-unit before you ever write a single CPUQuota= line.
Setting Limits — CPUQuota= and MemoryMax=
CPUQuota=50% means this service can use at most 50% of a single CPU core's worth of processing time — even on a multi-core system, even with idle capacity available elsewhere — genuinely enforced by the kernel, not a polite request. MemoryMax= is a hard ceiling; exceeding it triggers the kernel's own OOM killer specifically against this cgroup, rather than the whole system hunting for an arbitrary victim process. MemoryHigh= is the softer companion — a throttling threshold below MemoryMax= that encourages memory reclaim before the hard kill ever becomes necessary.
Why This Matters — the Direct Tie to perf1
perf1's own bottleneck chapters diagnosed resource problems at the whole-system level — a genuinely common real outcome of that kind of investigation is discovering one specific misbehaving service is the actual cause. This chapter's own limits are the concrete containment tool once that diagnosis is made. Concrete scenario: a runaway process — a memory leak in a custom app — that would otherwise eventually exhaust all system memory, potentially taking down unrelated services too. MemoryMax= turns "the whole system's memory" into "just this cgroup's own memory," genuinely containing the blast radius to the one service actually responsible.
systemd-cgtop — Real-Time Per-Unit Resource Usage
A live, top-style view — but organized by cgroup/unit rather than individual process, showing CPU/memory/IO usage per service, in real time. Immediately actionable alongside this chapter's own CPUQuota=/MemoryMax= settings — observe first, then set a limit informed by real usage.
Verifying a Limit Is Actually Enforced
Confirms the configured value actually took effect. systemd1-3's own daemon-reload gotcha applies directly here again — forgetting to reload and restart after adding a resource limit means the old, unrestricted cgroup settings are still what's actually enforced.
| Behavior when exceeded | |
|---|---|
| MemoryHigh= | Soft — throttling and reclaim pressure encouraged, no kill |
| MemoryMax= | Hard — the cgroup-scoped OOM killer triggers |
systemctl set-property myapp MemoryMax=512M applies a limit immediately, live, with no unit file edit or restart required — genuinely useful for urgent, live containment mid-incident, a direct parallel to grub1-4's own live-edit-vs-permanent-config distinction.
MemoryMax= set without first observing real usage via systemd-cgtop can OOM-kill a service during entirely legitimate, normal peak load — the same "measure before you optimize" discipline perf1 itself would recognize, applied here to setting a hard resource ceiling rather than diagnosing an existing one.
Hands-On Exercises
Write the [Service] section additions needed to limit a unit to 25% of a CPU core and a hard 1GB memory ceiling, and the command to confirm the memory limit actually took effect afterward.
📄 View solutionExplain the real difference in what happens to a service when it exceeds MemoryHigh= versus when it exceeds MemoryMax=.
📄 View solutionA team sets MemoryMax=256M on a service without ever checking its real memory usage first, and the service gets OOM-killed every day during its own normal peak traffic window. Explain what went wrong, and the correct process that should have been followed.
📄 View solutionChapter 9 Quick Reference
- Every systemd unit is placed into its own cgroup automatically, whether or not limits are ever configured
- CPUQuota= — a hard, kernel-enforced percentage-of-a-core cap
- MemoryHigh= (soft throttle) vs. MemoryMax= (hard, cgroup-scoped OOM kill)
- Per-service limits are the concrete containment tool once perf1's own system-wide bottleneck diagnosis points at one specific service
systemd-cgtop— real-time, per-unit resource usage, observe before setting a limitsystemctl set-propertyapplies a limit instantly to an already-running unit, no restart required- Never set a hard memory ceiling without first observing real usage — a real, common cause of killing a healthy service