Linux control groups
Translate CPU and memory policies into measurable resource envelopes, then diagnose when a limit becomes a service bottleneck.
On this page
The question for today
What you’ll understand
- Distinguish CPU weight from a time quota.
- Reason about hierarchical memory pressure and process termination.
- Estimate a service ceiling from CPU demand without promising latency.
A process can report healthy while requests wait for its CPU budget to replenish. Linux control groups provide resource accounting and controls for groups of processes. Platform engineers must choose the envelope, interpret pressure inside it, and connect that pressure to customer experience. Treat each resource limit as an explicit service assumption rather than a magic performance setting.
Read the hierarchy before reading a number
In cgroup v2, processes belong to a hierarchy and controllers distribute or constrain resources along it. A child cannot escape an ancestor's restriction by declaring a larger local allowance. Consequently, inspecting only a container's leaf settings can miss the actual constraint. Sketch the workload, parent group, host services, and physical machine before diagnosing an apparent mismatch between requested resources and measured throughput.
Use a budget ledger to make the hierarchy concrete. Suppose two services each advertise two CPUs, but their shared parent has only three CPUs of quota. Four CPUs of simultaneous demand cannot fit inside that parent envelope. An operational dashboard should show the parent as well as the children and name the team that owns the shared budget. Otherwise two innocent application teams may repeatedly tune their workers against the same undisclosed ceiling.
Separate relative sharing from a hard envelope
CPU weight expresses relative allocation among competing eligible sibling groups; it does not reserve a fixed core count. A CPU quota instead limits aggregate runtime over a period. Several runnable threads can consume that allowance quickly and then wait. If latency rises while CPU usage appears modest over a long averaging window, examine throttling and scheduling intervals rather than immediately assuming the application needs more threads.
Translate an envelope into an application hypothesis using measured CPU demand per successful request. If each request consumes twelve milliseconds of CPU, one sustained CPU-second supports roughly eighty-three such requests. That estimate assumes enough runnable work, unchanged code paths, and no other bottleneck. It does not include queueing delay, network waiting, garbage-collection bursts, or changing instruction efficiency. Test the hypothesis under representative payloads and concurrency before using it for admission policy.
Treat memory pressure as a different failure mode
The memory controller's memory.high setting introduces pressure and reclaim when exceeded; memory.max is a hard boundary that can lead to an out-of-memory action when reclaim cannot satisfy an allocation. Memory accounting includes more than the application's preferred heap metric. Compare the group's actual usage and events with the process heap, file-backed cache, and transient allocation patterns. A CPU throttle and a memory kill require different remediation.
For capacity planning, reserve room for measured transient peaks rather than adding a percentage without a reason. Track the largest request, concurrent model loading, and temporary buffers during rollout. Raising every limit simultaneously can merely move failure to the host. A useful experiment changes one envelope, observes successful throughput and tail latency, and records whether another resource became limiting. Kubernetes resource requests and limits are another policy layer; inspect their effective runtime settings instead of assuming that a scheduling request is a benchmark result.
Work it through
Diagnose a constrained inference gateway
A gateway has a quota of 150,000 microseconds per 100,000-microsecond period. Each request consumes 0.012 CPU-seconds. Traffic is 100 requests/s. Its memory limit is 6 GiB, with 4.8 GiB steady usage and a measured 0.6 GiB rollout burst. Assess both envelopes.
Divide quota by period: 150,000 / 100,000 = 1.5 aggregate CPU equivalents. This is a budget shared by all threads in the group.
Divide 1.5 CPU-seconds/s by 0.012 CPU-seconds/request: the ideal CPU ceiling is 125 requests/s. At 100 requests/s, the demand ratio is 100 / 125 = 0.8.
Add steady and transient memory: 4.8 + 0.6 = 5.4 GiB. The specified scenario leaves 6 − 5.4 = 0.6 GiB below the hard limit.
Neither twenty percent CPU headroom nor 0.6 GiB memory headroom proves safety. Measure quota-period throttling and simultaneous bursts, then load-test the actual latency objective.
The budget is arithmetically feasible for the assumed steady workload, but latency and burst behavior require observation.
The living notebook
Explore the model
Interactive lab · queue
Connect an envelope to backlog
Adjust λ and μ in the fluid backlog max(0, 4 + (λ − μ)t). Treat μ as an estimated service ceiling. The separate M/M/1 residence-time readout assumes Poisson arrivals, exponential service, and one server; this lab does not implement CPU quota periods or Linux scheduling.
- Fluid backlog
- 4
- Net rate · λ − μ
- −1 /s
- M/M/1 mean residence
- 1 s
Fluid backlog starts at 4 and follows max(0, 4 + (λ − μ)t). The separate M/M/1 residence-time formula assumes Poisson arrivals, exponential service and λ < μ. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forA resource envelope matters through effective service capacity and the backlog it permits.
Pause & reason
Check your understanding
Your turn
Try it yourself
A service has 200,000 microseconds of quota per 100,000-microsecond period and uses 0.025 CPU-seconds per request. Estimate its CPU ceiling and utilization at 60 requests/s. Name one observation that could invalidate the estimate.
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Convert quota to CPU equivalents, then divide by measured CPU demand. Keep CPU time distinct from wall-clock request duration.
Compare with the solution
The quota represents two CPU equivalents. Dividing 2 by 0.025 gives 80 requests/s, and demand is 60 / 80 = 75% of that estimate. A single runnable thread, a different payload distribution, a tighter parent quota, memory pressure, or another bottleneck could reduce achieved throughput. Inspect the effective hierarchy and measure completed requests under load.
Keep the idea
Take it with you
- Read resource controls through the complete ancestor hierarchy.
- CPU weights, runtime quotas, and memory boundaries express different policies.
- A calculated capacity ceiling is a hypothesis to validate against successful work and latency.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.