Frontend Labs
⋯

29. Control Theory Basics (Feedback Loops, PID)

Level: Expert You'll use this for: understanding autoscalers, rate limiters, and TCP congestion control as the same underlying pattern.

The idea

A control system continuously measures an output, compares it to a target ("setpoint"), computes an error (error = setpoint - measured), and adjusts an input to drive that error toward zero. Your home thermostat, a cloud autoscaler, and TCP congestion control are all the same mathematical pattern.

PID controller — the workhorse of feedback control

output = Kp·error + Ki·∫error dt + Kd·(d(error)/dt)
  • P (Proportional): react proportionally to the current error — bigger error, bigger correction.
  • I (Integral): react to accumulated past error — corrects persistent small biases (steady-state error) that pure proportional control leaves behind.
  • D (Derivative): react to the rate of change of error — dampens oscillation/overshoot by anticipating where the error is heading (this literally reuses the derivative from Ch. 12).
class PIDController:
    def __init__(self, kp, ki, kd, setpoint):
        self.kp, self.ki, self.kd = kp, ki, kd
        self.setpoint = setpoint
        self.integral = 0
        self.prev_error = 0

    def step(self, measured_value, dt=1.0):
        error = self.setpoint - measured_value
        self.integral += error * dt
        derivative = (error - self.prev_error) / dt
        self.prev_error = error
        return self.kp*error + self.ki*self.integral + self.kd*derivative

# Autoscaler: target 60% CPU utilization, adjust replica count
pid = PIDController(kp=0.5, ki=0.1, kd=0.05, setpoint=60)
current_cpu = 85
adjustment = pid.step(current_cpu)
print(adjustment)   # negative -> scale up (reduce per-replica load)

Tracing two PID steps by hand, kp=0.5, ki=0.1, kd=0.05, setpoint=60, starting with integral=0, prev_error=0:

Step 1 — measured CPU = 85:
  error      = setpoint - measured = 60 - 85 = -25
  integral  += error × dt = 0 + (-25×1) = -25
  derivative = (error - prev_error) / dt = (-25 - 0) / 1 = -25
  output = kp·error + ki·integral + kd·derivative
         = 0.5×(-25) + 0.1×(-25) + 0.05×(-25)
         = -12.5   + -2.5        + -1.25
         = -16.25                                <- negative: scale up

Step 2 — CPU eased to 70 (prev_error is now -25):
  error      = 60 - 70 = -10
  integral  += -10  ->  integral = -25 + -10 = -35
  derivative = (-10 - (-25)) / 1 = 15              <- error is shrinking, so this is positive (damping)
  output = 0.5×(-10) + 0.1×(-35) + 0.05×15
         = -5        + -3.5      + 0.75
         = -7.75                                  <- smaller correction: still scaling up, but less aggressively

Notice the integral term keeps accumulating (-25 then -35) even as the error shrinks — that's the "remembers persistent past error" behavior described below — while the derivative term flipped positive the moment the error started improving, pulling the output back toward zero before the P-term alone would.

Why pure proportional control isn't enough (and why over-tuning causes oscillation)

  • P-only control typically leaves a persistent steady-state error (it never quite reaches the setpoint, because the correction shrinks to zero as the error shrinks to zero — a vanishing feedback signal, similar in spirit to Ch. 21's vanishing gradient problem).
  • Too-aggressive gains (Kp too high) cause overshoot and oscillation — the exact failure mode of an autoscaler that thrashes (scale up, overshoot, scale down, undershoot, repeat) instead of converging smoothly. This is mathematically the same instability pattern as a too-high learning rate in gradient descent (Ch. 21) — both are discrete-time feedback systems that can overshoot and diverge if the step size relative to the system's sensitivity is too large.

TCP congestion control as control theory

Revisiting Ch. 25's AIMD from a control-theory lens: TCP's congestion window is a feedback-controlled variable, where packet loss is the "error signal" telling the sender it exceeded the network's capacity. The conservative asymmetry (slow additive increase, fast multiplicative decrease) is a deliberately stable control law, chosen specifically so that thousands of independent, uncoordinated TCP senders across the global internet converge toward fair bandwidth sharing without a central controller — a remarkable real-world distributed-control-theory result.

Stability, derived: when does a feedback loop converge?

Model the simplest loop in discrete time. Each control tick, a P-controller removes a fraction K of the current error:

e_{t+1} = e_t - K · e_t = (1 - K) · e_t        ->       e_t = (1 - K)ᵗ · e₀
Symbol Meaning
e_t error at tick t (e.g. CPU % above target)
K loop gain: controller gain × how strongly the system responds
1 - K the factor applied to the error every tick

That's a geometric sequence (Ch. 6), so it converges if and only if |1 - K| < 1, that is, 0 < K < 2. It's the identical condition to gradient descent's η < 2/λ in Ch. 21. A training loop and an autoscaler are the same mathematical object. With error e₀ = 10:

K factor Error over ticks Behaviour
0.5 0.5 10, 5, 2.5, 1.2, 0.6 … smooth convergence
1.0 0 10, 0, 0, 0 … "deadbeat": fixed in one step
1.5 -0.5 10, -5, 2.5, -1.2 … overshoots, but settles
2.5 -1.5 10, -15, 22.5, -33.8 … unstable: the thrashing autoscaler

For systems with many interacting state variables, the factor becomes a matrix and the rule becomes Ch. 11's: stable if and only if every eigenvalue has magnitude below 1. Control engineers call these eigenvalues the system's poles.

Delay halves the stable gain

Real loops act on stale measurements: metrics scrape intervals, aggregation windows, pod start-up time. Add a delay of just one tick, so the controller corrects based on the error it saw last time:

e_{t+1} = e_t - K · e_{t-1}

Try a solution e_t = zᵗ. Substituting gives the characteristic equation z² - z + K = 0. For K > 1/4 the roots are complex with magnitude √K (the product of the roots is K), so the loop is stable only if K < 1. The stability limit halves from 2 to 1:

K No delay One tick of delay
0.5 10, 5, 2.5, 1.2 … converges 10, 10, 5, 0, -2.5, -2.5, -1.2 … converges with a wobble
1.0 10, 0 … perfect 10, 10, 0, -10, -10, 0, 10 … oscillates forever
1.5 settles 10, 10, -5, -20, -12.5, 17.5, 36 … diverges

The gain that was ideal without delay (K = 1) causes a permanent oscillation with delay. This is the most common cause of autoscaler flapping and of "retry storms" that build in waves: the controller keeps acting on a picture of the world that's already out of date. Longer delays shrink the stable gain further, so the fix is lower gain, a shorter feedback path, or both.

Steady-state error, and why the I-term exists

Suppose a constant disturbance D keeps pushing the system off target, for example a steady background load the autoscaler doesn't see directly. With P-only control the loop settles where the controller's push balances the disturbance:

e_ss = D / (1 + K)

With D = 20 and K = 4, the loop settles with a permanent error of 20/5 = 4. Higher K shrinks that error but, as shown above, moves you toward instability. The integral term Ki · Σ e keeps growing as long as any error remains, so the only place the loop can come to rest is e = 0. That's the mathematical reason the I in PID exists, and also why an unbounded integral causes windup when the actuator saturates (for example, already at max replicas). Production controllers clamp the integral.

The Kubernetes HPA, read as a controller

The Horizontal Pod Autoscaler's core formula is a proportional controller in multiplicative form:

desiredReplicas = ceil( currentReplicas · currentMetric / targetMetric )

Example: 4 replicas running at 90% CPU against a 60% target: ceil(4 · 90/60) = ceil(6) = 6 replicas. Each mechanism the HPA adds around that formula maps onto the maths above:

HPA feature Default Control-theory role
tolerance 10% (ignore ratios within 0.9–1.1) deadband: don't react to noise
scale-down stabilization window 300 s (uses the highest recommendation in the window) hysteresis: avoid flapping on stale or noisy signals
scale-up/down policies (pods or % per period) configurable rate limiting: caps the effective gain K
metrics resolution and pod readiness delay ~15–60 s the dead time that halves the stable gain

When an HPA flaps, the diagnosis is the same as for any controller: either the gain is too high (policies too aggressive), the delay is too long (slow metrics, slow pod start-up), or both. KEDA, Karpenter and cloud autoscalers are all variations on this loop.

Real-life engineering ties

  • SWE/SRE/Network: autoscalers, rate limiters, adaptive timeout/retry systems, and load-shedding controllers are all PID-flavored (even when implemented as simpler heuristics, they're approximating the same feedback-control idea, often badly, which is why they sometimes oscillate in production).
  • Network: TCP congestion control (AIMD, Ch. 25), Wi-Fi's CSMA/CA backoff, and Active Queue Management (CoDel, RED) are all feedback control systems reacting to a congestion signal.
  • AI: reinforcement learning control policies for physical/robotic systems build directly on control theory; some learning-rate schedulers are explicitly PID-inspired ("adjust the learning rate based on the rate of change of the loss").
  • Security: adaptive rate-limiting against brute-force/DDoS attempts is a feedback controller reacting to a suspicious-traffic "error signal" — poorly tuned gains here cause exactly the oscillation problem above (over-aggressive throttling that also blocks legitimate users in bursts, then relaxes too far, repeating).

More real-life examples:

  • Bitcoin's mining difficulty adjusts automatically roughly every two weeks specifically to keep the average block time near 10 minutes regardless of how much total mining power joins or leaves the network — a real, deployed feedback controller operating at global scale with no central operator.
  • Cruise control (and adaptive cruise control / lane-keeping in modern cars) is a textbook PID controller: it measures actual speed (or lane position), compares it to the target, and adjusts throttle (or steering) — the same three-term structure as this chapter's code example.
  • Data center cooling systems use classic industrial PID controllers to hold server-room temperature at a setpoint despite constantly varying heat load from compute racks — the literal thermostat analogy from this chapter, at industrial scale, and a meaningful lever in a data center's energy efficiency (PUE).
  • Kubernetes' Horizontal Pod Autoscaler and most cloud autoscalers are, in effect, simplified proportional (P-only) controllers — exactly why they're prone to the oscillation ("flapping") failure mode described in this chapter, and why production setups add cooldown windows as a crude substitute for proper derivative damping.

Common mistake

Tuning a feedback system (autoscaler, rate limiter) by trial and error in production without understanding why it oscillates — the oscillation is a textbook symptom of gains that are too aggressive relative to the system's response lag, and the fix (add damping via a derivative term, or simply reduce the proportional gain and increase the reaction window) is well understood control theory, not guesswork.


Generated from real-life-maths-guide.md · 8 Oct 2026 16:36