If you're running multiple services on a single Linux host and one starts consuming too much memory or disk IO, it can drag down everything else. I've seen this happen on busy web servers where a misbehaving backup job or log processor starves the frontend of resources. The fix isn't always more hardware — often it's better isolation. In my environment, I use systemd slices combined with MemoryMax and IOWeight to enforce hard boundaries. This keeps critical services responsive even when others misbehave. You don't need containers or VMs for basic isolation; systemd's built-in cgroup v2 controls are enough for many cases.
Why Use systemd Slices for Resource Control
Slices are systemd's way of grouping services into hierarchies that share cgroup limits. Instead of applying limits to each service individually, you can place them in a slice and manage the group as a unit. This is especially useful when you have a set of lower-priority background jobs that should never interfere with core services like a web server or database. I typically create a slice called system-minimal.slice for essential services and another like batch-low.slice for cron jobs or backups. By assigning services to these slices, I enforce policies at the group level — simpler and more scalable than per-service tuning.
Creating a Custom Slice with Memory and IO Limits
First, create a slice unit file. I put mine in /etc/systemd/system/ to keep it persistent across reboots. Here's an example for a low-priority batch slice:
# /etc/systemd/system/batch-low.slice
[Slice]
MemoryMax=2G
IOWeight=100
MemoryMax=2G caps the total memory used by all services in this slice at 2 gigabytes. If they try to exceed it, the OOM killer will start terminating processes within the slice — but not outside it. IOWeight=100 sets their relative IO priority; the default is 100, so this means they get baseline access. To really suppress them, I often drop this to 50 or even 10 when they're running alongside latency-sensitive workloads.
After saving the file, reload systemd and start the slice:
sudo systemctl daemon-reload
sudo systemctl start batch-low.slice
You can verify it's active with systemctl status batch-low.slice.
Assigning Services to the Slice
Now move your services into the slice. For a service like my-backup.service, edit its unit file and add:
[Service]
Slice=batch-low.slice
Reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart my-backup.service
To confirm it's in the right slice, run:
systemctl show my-backup.service -p Slice
You should see Slice=batch-low.slice. You can also check the cgroup path directly: cat /proc/$(pgrep -f my-backup)/cgroup — it should show the slice hierarchy under /sys/fs/cgroup/.
Monitoring and Adjusting Limits
I use systemd-cgtop to get a real-time view of resource usage per slice and service. It's like top but for cgroups:
sudo systemd-cgtop
Watch the MEM% and IO% columns to see if your limits are being hit. If services in batch-low.slice are consistently stalled but not using their full memory allowance, the bottleneck might be IO — try lowering IOWeight further. Conversely, if they're getting OOM killed too often, increase MemoryMax or optimize the service itself.
For logging, I check journal entries for the slice:
sudo journalctl -u batch-low.slice --since "1 hour ago"
This helps correlate limit breaches with service behavior.
Important Caveats and Best Practices
First, these limits only work on cgroup v2. Most modern distros use it by default, but verify with mount | grep cgroup2. If you see cgroup v1 hierarchies, you may need to enable unified mode — though that's beyond this scope.
Second, avoid setting limits too tightly on essential services. I once accidentally put sshd.slice under a low-memory slice and locked myself out during a spike. Now I only apply strict limits to clearly non-critical, restart-safe workloads.
Third, combine this with other isolations. As I mentioned before in my post about using systemd-nspawn for risky command testing, layers help — but slices are great for baseline service hygiene without added complexity.
Finally, document your slice structure. I keep a simple markdown file in /etc/systemd/slice-policy.md listing each slice, its purpose, and its limits. It saves confusion during handoffs or audits.
When to Reach for More
Slices and basic cgroup limits solve 80% of my resource contention issues. But if you need per-device IO throttling (MaxBandwidth) or real-time CPU guarantees, look into full cgroup v2 configuration or consider systemd's Delegate= feature for nested containers. For most bare-metal or VM workloads though, a well-designed slice hierarchy with MemoryMax and IOWeight gives you predictable performance without the overhead.
It’s not flashy, but in production, quiet reliability beats flashy fixes every time.
Cover image: [email protected] · CC0 (Openverse / kamu malı) · https://www.thingiverse.com/thing:712213
