CPU quota: a shared time budget rather than dedicated cores
Relate cgroup v2 quota and period to aggregate CPU time, bursty parallel consumption, hierarchical throttling, and measured scheduling behavior.
On this page
The question for today
What you’ll understand
- Convert a cpu.max quota/period pair into a CPU-time bandwidth ratio.
- Explain why multiple runnable threads can exhaust a shared budget early.
- Distinguish quotas, weights, CPU placement, and ancestor constraints.
A service with a quota equivalent to one CPU can still run several threads simultaneously on several CPUs and then be throttled. Its budget limits aggregate CPU time over the configured accounting period; it does not reserve a physical core or guarantee smooth execution. This lesson concerns Linux 6.12 cgroup v2 cpu.max for fair-class workloads, with burst accumulation disabled in the simplified examples. Real-time scheduling, newer scheduler integrations, per-CPU accounting slices, and ancestor policies require their own documented treatment.
Quota and period define a bandwidth budget
cpu.max supplies a maximum CPU-time budget and a period, conventionally expressed in microseconds by the interface. A quota of 150000 over period 100000 corresponds to 1.5 CPU-seconds per wall-clock second in the ideal average interpretation. It does not identify which CPUs will provide that time. The value max means no local quota restriction, but does not override an ancestor's finite budget, affinity restrictions, or competition. Read the effective hierarchy before concluding that an apparently unlimited child has unlimited execution capacity.
Threads in the same cgroup share the aggregate budget. If enough CPUs are available, parallel threads can consume several units of CPU time during one unit of elapsed time. Once the applicable budget is exhausted, runnable work can be throttled until replenishment. Actual accounting uses implementation mechanisms such as per-CPU runtime slices, so a simple rectangular budget picture is a teaching approximation. Short bursts, alignment, and configured burst behavior can differ from a naive exact cap on every arbitrary measurement window.
Parallelism and hierarchy change the latency pattern
For an idealized fresh period with p continuously running threads on p available CPUs, a shared quota Q can be consumed in Q/p elapsed time. This estimate ignores slices, preemption, system overhead, and other constraints. If the interval is shorter than the period, later arrivals can wait behind throttling even when a whole-second utilization graph looks modest. A burst of request workers can therefore worsen tail latency without increasing the configured average budget. Measure the scheduling pattern before blaming the application's lock implementation.
CPU weight distributes contested fair-class capacity relative to siblings; it is not a hard ceiling or an idle-machine reservation. CPU placement and cpuset rules constrain where execution can occur, but selecting CPUs alone does not necessarily make them exclusive or eliminate shared-cache interference. Ancestor quota exhaustion can throttle a child that has local runtime remaining. With different periods, transient details require the real hierarchy, while steady aggregate budget reasoning still rules out interpreting a generous child limit as a promise of independent capacity.
Use counters with a workload-specific experiment
Read cpu.stat counters and their units, including usage and throttling information, across a defined interval. A high ratio of throttled periods is not automatically the same percentage of wall time lost, and summing per-thread waiting can exaggerate a single request's delay. Correlate request arrival, runnable threads, CPU pressure, ancestor usage, and runtime versions. Compare a lower worker count, a different period, or an adjusted budget one at a time with the same workload. A quota change cannot promise a proportional application speedup if memory or I/O dominates.
The queue lab illustrates only offered work versus average service. Reset, explicitly set arrival four and service three, predict growth, then Play or Step. For a separate stable run Reset and explicitly set arrival two and service three before predicting and playing. The rates are normalized work items per second, not cpu.max fields. Edits preserve elapsed time and recompute from four initial jobs. This fluid curve cannot show quota-period bursts, throttled intervals, CPU weights, placement, or scheduler accounting.
Work it through
Spend one budget with three parallel workers
In an idealized fresh accounting period, quota Q is 150 ms of aggregate CPU time and period P is 100 ms. Three workers can each run continuously on separate available CPUs. Assume zero configured burst, no ancestor restriction, no contention, and ignore slice/granularity effects.
The quota ratio is 150/100=1.5 CPU-equivalents on average. This is not a reservation of one and a half particular physical cores.
Three simultaneous workers consume three milliseconds of aggregate CPU time per elapsed millisecond. They spend 150 ms of budget in 150/3=50 ms of wall time in this simplified model.
If they remain runnable, the ideal picture then has 50 ms until the next 100 ms period boundary without new budget. Actual timing can differ under the omitted scheduler accounting and workload behavior, so use counters and traces to test the real latency pattern.
The same average quota can produce a burst followed by throttling. Parallel execution and a CPU-time budget are compatible, and neither implies dedicated cores.
The living notebook
Explore the model
Interactive lab · queue
Average capacity cannot show quota-period jitter
Compare separate normalized fixed-rate runs using Reset → explicitly set both λ and μ → predict → Play or Step. Use (4,3), then Reset and (2,3). Edits retain elapsed t and recompute from four initial jobs. The fluid model has no cpu.max period, runtime slices, throttling, CPU affinity, or weights.
- 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 forAverage offered load matters, but a smooth backlog model cannot explain the period-level latency caused by a shared CPU quota.
Pause & reason
Check your understanding
Your turn
Try it yourself
A child has finite quota/period ratio 2.0 while its ancestor's ratio is 1.0, shared with other work. What steady upper bound follows from these ratios? In a separate ideal period, 25 ms quota over 100 ms period is consumed by four fully parallel workers; when is it exhausted?
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
A child's budget cannot remove an ancestor constraint. Divide aggregate quota by parallel workers only under the stated ideal assumptions.
Compare with the solution
The child's steady quota upper bound is at most min(2.0,1.0)=1.0 CPU-equivalent, and competition for the ancestor budget can reduce its actual share. This is not a guaranteed allocation. In the separate fresh-period example, four workers consume 25/4=6.25 ms of wall time to spend 25 ms of aggregate CPU budget. Real accounting slices, preemption, and available CPU placement can change observed timing; the arithmetic is not an exact scheduler simulation.
Keep the idea
Take it with you
- Quota divided by period is an aggregate bandwidth ratio, not dedicated cores.
- Parallel workers can exhaust a shared budget early and create throttling latency.
- Weights, CPU placement, ancestors, and actual counters must be interpreted separately.
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.