← Control Loops Outside the Lab

Control Loops Outside the Lab · 01

Delayed Feedback and Overcorrection

Why a reasonable correction can destabilize a slow system, and what showers, queues, and retries have in common.

A shower with a long pipe is a fairly effective lesson in feedback control. Turn the valve toward hot, wait a second, and the water still feels cold. Turn it again. By the time the first adjustment reaches you, the second one is already on its way.

The direction of the correction was right. The size and timing were wrong. You were making decisions from a measurement that did not yet include the effects of your previous decisions.

This is worth separating from measurement error. A perfectly accurate thermometer at the showerhead does not remove the travel time through the pipe.

Start with the delay

The first lab uses a deliberately small model. A valve sets the water temperature, a transport delay represents the pipe, and a short thermal lag determines the temperature you feel. Every two seconds, the controller moves the valve by a fraction of the current temperature error.

  • What you dialed
  • What you feel
  • Comfortable
Presets

Compare Long pipe with Long pipe, gentle hand. The pipe is unchanged; only the size of each adjustment changes. The smaller corrections take longer to get started, but they avoid most of the repeated overshoot.

The useful parameter is the correction accumulated during the delay. Halving the adjustment while doubling how often you make it may accomplish very little. A slower response is not automatically stable either; it still needs to be appropriate for the system being controlled.

Delay can enter through measurement, actuation, or both. A one-minute metric window followed by a two-minute provisioning operation is a different loop from a current measurement followed by an immediate action. Control models with explicit delays make that distinction visible.

An autoscaler has pending state

Consider a queue served by a pool of workers. Arrivals add messages; active workers remove them. The controller periodically measures the backlog and computes a desired worker count.

There are two counts to keep track of: workers doing useful work now, and the target that has been requested but has not taken effect. If the controller only looks at the first, it can spend the provisioning interval issuing decisions based on a system it has already asked to change.

  • Backlog
  • Target
  • Active workers
  • Pending capacity target
Presets

The lab sends absolute worker-count targets. A request for 30 followed by another request for 30 does not create 60 workers. Each queued target takes effect after its delay. This is a simplified actuator; real orchestration systems may coalesce requests or cancel obsolete ones.

Even with absolute targets, the loop can oscillate. Capacity arrives after the queue has grown, drains it below the target, then falls after the controller has already decided to scale down. New demand reaches an undersized pool, and the cycle repeats.

The shaded gap shows active capacity versus the last target scheduled to take effect. It is not a count of all instances being provisioned. The presets change several parameters to provide contrasting examples. To isolate the effect of delay, select a preset and change only the delay slider.

An empty queue is not an outage in this model, and the model has no database or connection limit. Those are possible consequences in a real system, not things the chart establishes.

Retrying before the first attempt finishes

Retries can create a related problem. Suppose an operation takes 900 ms under load, but its client times out at 500 ms. If the server continues processing the first attempt, a retry adds work while the original operation is still consuming resources.

A timeout tells the client that it stopped waiting. It does not establish that the operation stopped running or failed to commit.

Backoff and retry budgets limit the additional load. End-to-end deadlines can let the server discard work that is no longer useful. Idempotency addresses repeated effects, but does not necessarily eliminate the cost of processing repeated requests. These mechanisms solve different parts of the problem.

Measure before increasing the gain

For a slow loop, I would start by recording when each decision was made and when its effect became observable. Put pending operations beside active capacity on the same dashboard. Then apply a small change and watch the response through at least one complete delay.

That gives you something to tune against. If the response is already oscillating, increasing the correction usually makes diagnosis harder. Reduce the gain, account for pending actions, or shorten the delay where possible.

Waiting is useful when an earlier action is still in progress. It is less useful when that action failed. A controller needs to distinguish those cases, which is why acknowledgments and completion timestamps can be more valuable than another decimal place in the metric.