01 Deep dive

Idle-capacity harvesting under tail-latency constraints

Latency-critical Kubernetes nodes are typically provisioned with substantial idle headroom because packing is driven by declared CPU requests rather than usage. We evaluate whether batch work can occupy that headroom without degrading a protected service's tail latency, using memcached and redis primaries under background-load ladders on GKE and EKS. With kernel-enforced scheduling tiers, primary p99 remained flat at node utilizations of 0.81–0.95 where CFS baselines degraded 3–12×; the same enforcement enabled +72% pod placement under request overcommit and a 3→2 node consolidation.

Background: request-based packing and idle headroom

Kubernetes packs nodes by declared CPU requests. Requests are routinely padded two to four times above observed usage because the request is a pod's only protection at admission time: once pods share a node, the Linux scheduler — not the request — arbitrates every microsecond of CPU. Operators compensate by provisioning latency-critical services with headroom that sits idle most of the time.

Idle-capacity harvesting places Background-tier batch work (builds, encoding, analytics) into the idle cycles of protected nodes, under the constraint that batch work yields immediately when the protected tier has demand. The constraint is the operative requirement: if batch work can delay even one wakeup of the protected service, the headroom was cheaper than the colocation.

Scheduling mechanism

CFS arbitrates with proportional weights (cpu.weight, the successor of cpu.shares): a Guaranteed pod outweighs a BestEffort spinner by a large ratio, and over a scheduling period each side receives CPU in that ratio. Proportional sharing is not preemptive priority, with three consequences relevant here:

Temper's node engine replaces this arbitration with explicit layers in the kernel's scheduling path (scx_layered on sched_ext): the Critical tier gets a protected, fenced layer; Background gets an Open layer that runs only on CPUs the fence is not using; and the protected-while-busy policy governs the loan — a protected layer's CPUs are loaned out only when that layer is idle everywhere, and loaned CPUs are preempted back at the layer's idle→demand transition, so batch occupancy does not add a slice-length delay to the woken owner.

Multi-node results

memcached p99 vs. batch-filler density, multi-node Temper flat · CFS 3.1×

3-node GKE cluster (e2-standard-4), two memcached primaries under memtier load, batch-filler ladder 0→18 plus 6 background spinners. Node utilization 0.81–0.92.

CFS (stock kube-scheduler) Temper (scx_layered)
0 0.5 1.0 1.5 p99 (ms) 0 4 8 12 18 batch fillers on 3 nodes 1.52 ms (3.1×) 0.48 ms — flat, node util 0.81–0.92

Single run per arm; ±0.2 ms is within observed noise, and the flat-vs-3.1× delta is far outside it. The second memcached instance tracked the CFS arm (~1.6 ms) in this GKE run; the asymmetry is unexplained and did not reproduce on EKS, where both instances held near-flat (1.2–1.3× vs CFS ~3×). source: docs/training-artifacts/binpack/REPORT.md · EKS replication: docs/training-artifacts/binpack/records/eks/

The same ladder was replicated on EKS. Two instrumentation corrections were required for a valid run: the memtier client was CFS-throttled by its own 500m CPU limit, masking the signal, and the initial nodegroup spanned availability zones, introducing a ~1 ms cross-AZ RTT floor that dominated a sub-millisecond effect. With single-AZ placement and an unthrottled client, the EKS run shows the flat line directly: p99 held 0.343–0.399 ms from node utilization 0.36 to 0.95 while background work delivered ~3.9 cores of a 4-vCPU node; CFS incurred a +33% tail increase for the same reclaim. The earlier EKS run with the defective instrumentation is retained in the record set, marked invalid.

Operating-point sensitivity

The cost of colocation under CFS depends on how hard the primary itself is driven. A sensitivity sweep on EKS quantifies this. With a nearly idle primary (1 thread × 4 connections), both schedulers are flat — parity within noise; there is no tail to protect. At the default operating point the difference is −23% p99 at the top of the ladder. At the heavy point (4 threads × 8 connections, the primary saturating its allocation), CFS degrades +256% across the ladder while Temper holds within +70% of its own baseline — 3.3× lower p99 at the top step, −70%. The advantage therefore scales with primary load; all three operating points are reported.

memcached, heavy operating point, GKE dedicated cores −88% p99 at the knee

Saturating memcached primary (4t×8c) vs. background-spinner ladder. 2× c2-standard-4, 2026-07-02.

CFS Temper
0 1 2 3 p99 (ms) 0 1 2 4 8 background spinners (ladder) 3.23 ms 0.40 ms

−88% p99 at 4 spinners (0.415 vs 3.231 ms), −71% at idle; CFS +202% across the ladder, Temper flat. Cross-cloud: EKS heavy point −70%. One Temper step in this run reported background_cores=0.0 with p99 unaffected; per-tier core attribution is a known open instrumentation item. source: docs/training-artifacts/headroom/gke-c2-mc-heavy/REPORT.md · docs/training-artifacts/headroom/eks-sensitivity/REPORT.md

Redis shows the same operating-point dependence. At the default operating point a single hot redis thread is the most favorable case for CFS weight arbitration, and the difference is modest (−17…−24%; Temper flat, worst step +13% over its own baseline while background reclaims ~3.8 cores at 0.98 node utilization). At the heavy point the difference widens to −55% at the knee and −40% on the idle node — Confined placement isolates the primary from system-pod noise before any background load is added.

Single-node density with tier QoS alone

memcached vs. density, tier QoS only CFS degrades 12.3× · Temper flat

Single node (c3-standard-8), density ladder 0→8. No workload profile — PriorityClass-derived tiers alone.

CFS Temper
0 0.5 1.0 1.5 p99 (ms) 0 2 8 noisy neighbors (density) 1.951 ms (12.3×) 0.183 ms flat

CFS broke the SLO at density 2 (0.335 ms) and reached 1.951 ms at density 8; Temper held 0.183 ms through an up-and-down ladder. At zero load, fenced placement idles ~20% above CFS idle (0.191 vs 0.159 ms), and background was limited to 1.84 cores vs CFS's 6.22 — a strictly protective fence trades reclaim for protection; the reclaim side is addressed by the loan policy in the next section. source: docs/training-artifacts/OVERNIGHT-REPORT.md · docs/training-artifacts/memcached/SUMMARY.md

Capacity reclaim and request overcommit

Holding the tail is one half of the problem; reclaiming the idle cycles is the other. A strictly protective layer strands capacity: under a bursty primary, a fence that held its CPUs unconditionally left background work at 1.89 cores and node utilization at 0.40 while the primary idled 60% of the time. The protected-while-busy loan policy (loans only when the layer is idle everywhere, preemption on demand return) takes the same node to 5.65 background cores and 0.85 utilization at −7% primary throughput (the report's figure; raw samples/s read 25.5→23.3). Steady-state primary throughput was unaffected.

With enforcement in place, the request padding itself becomes recoverable. On the same 3-node cluster, halving the declared requests of non-Critical pods (the opt-in overcommit webhook; Critical requests untouched, originals preserved in annotations) moved the request-packing wall from 18 to 31 fillers — +72% pods placed with p99 still under 1.9 ms. A 16-pod fleet that does not fit on two nodes at stock requests ran entirely on two after overcommit — 3→2 nodes (33%), with a p99 spot-check of 1.56 ms taken with the client co-located with a server, which biases the reading upward. With Karpenter performing placement in both arms, the same load and SLO provisioned −40% vCPU (12 vs 20) with Temper enforcement underneath.

Limitations

Raw records

  • docs/training-artifacts/binpack/REPORT.md
  • docs/training-artifacts/binpack/SAVINGS-REPORT.md
  • docs/training-artifacts/binpack/records/eks/
  • docs/training-artifacts/headroom/gke-c2/REPORT.md
  • docs/training-artifacts/headroom/gke-c2-mc-heavy/REPORT.md
  • docs/training-artifacts/headroom/gke-c2-redis/REPORT.md
  • docs/training-artifacts/headroom/gke-c2-redis-heavy/REPORT.md
  • docs/training-artifacts/headroom/eks-valid/REPORT.md
  • docs/training-artifacts/headroom/eks-sensitivity/REPORT.md
  • docs/training-artifacts/headroom/eks-inconclusive/FINDINGS.md (invalidated run, kept)
  • docs/training-artifacts/karpenter/REPORT.md
  • docs/training-artifacts/memcached/SUMMARY.md
  • docs/training-artifacts/OVERNIGHT-REPORT.md

Committed benchmark records in the product repository; design partners get the full artifact tree. Single-run arms are labeled in each report; anomalies are published, not pruned.