Inference Systems · ML Operations

Automatic Rollback Controllers and the Ways They Misfire

A controller that reverts a deployment on a metric threshold is a feedback loop wrapped around a feedback loop; its failure modes are oscillation, latency in the evidence, and disagreement about what baseline means.

An automatic rollback controller watches a deployment’s metrics and reverts it when they cross a threshold. The description makes it sound like a safety net. It is a control loop whose actuator is a deployment change and whose sensor is a metric produced by the system it is changing, and that structure has known failure modes: it can oscillate, it can act on evidence that arrives after the harm, and it can revert based on a comparison it has defined differently from the team that built the release.

The design question is not whether to automate rollback. It is which signal, which threshold, which baseline, and which rate limit, because each of those choices produces a distinct class of bad behaviour.

The loop, and the delay inside it

The components are a metric stream, a decision rule, and an actuator that reverses the last deployment. The sensor’s delay is the sum of the metric’s aggregation window, the scrape or export interval, and any smoothing the rule applies. The actuator’s delay is the time for the reverted version to become ready and receive traffic.

For a metric aggregated over five minutes and scraped every thirty seconds, the earliest a rule can react to an event is roughly the end of the first aggregation window, so about five minutes after the event began. The rollback then takes as long as a cold start plus drain, which for a large model is minutes more. The total time from onset to restored service is therefore on the order of ten minutes, and no amount of threshold tuning reduces it.

This delay has a consequence for threshold design. A threshold that is tight enough to fire quickly will fire on noise, because the short window that produces quick evidence has high variance. A threshold that survives the noise requires a longer window, which delays the reaction further. The two requirements conflict, and the resolution is to use two rules rather than one: a fast rule on a metric with low variance and high specificity, and a slower rule on a metric with more statistical power.

Low-variance, high-specificity metrics are the ones that are structurally bounded: the fraction of requests returning a server error, the fraction failing schema validation, the rate of process crashes. A single such event is meaningful. High-variance metrics are latency quantiles and business conversion, which need aggregation to say anything.

Which baseline the comparison uses

A rollback rule that compares the current metric against a static threshold answers “is the service bad”. A rule that compares against the previous version’s metric answers “is this version worse”. These are different questions, and a controller configured for one while the team expects the other produces confusing actions.

The static threshold is correct for a service that has an absolute requirement: a payment endpoint that must not exceed a small error rate regardless of what the previous version did. It fires on incidents unrelated to the deployment, which is a false positive from the rollout’s perspective and a correct alarm from the service’s.

The comparative rule isolates the deployment’s contribution and requires a baseline to compare against. The baseline is either the metric’s value from the period immediately before the rollout, or the concurrent metric from replicas still running the previous version. The first is contaminated by anything else that changed in the same period, including traffic patterns and unrelated incidents. The second requires the fleet to be split, which is only true during a partial rollout.

During a canary, the split exists and the comparative rule is well-defined. After full promotion, the split disappears and the comparative rule has nothing to compare against, so it degrades to a static threshold or to a comparison against a historical window that may be from a different time of day. A controller that does not distinguish these two phases applies a rule outside the conditions it was designed for, which is the most common structural error in automatic rollback.

Oscillation between two versions

A controller that reverts on a threshold and then re-promotes when the metric recovers can cycle. The mechanism is straightforward: the reverted version serves traffic, the metric returns to baseline, the controller observes recovery and concludes the deployment is safe, promotes again, and the metric degrades again.

Three mechanisms break the cycle, and a controller needs at least one.

Latching. After an automatic rollback, the controller marks the version as failed and refuses to re-promote it without an explicit external action. The mark is persistent state, keyed by the deployment identity rather than by the version string, so that a rebuild with the same tag is not automatically blocked. This is the simplest mechanism and the most common.

Cooldown. The controller refuses to act within a fixed period after any action. Cooldowns suppress oscillation at the cost of responsiveness: a genuine second incident during the cooldown is handled manually.

Rate limiting. The controller permits at most $k$ actions in a rolling window, and after $k$ it stops and alerts. This bounds the damage of a badly tuned rule without preventing a single correct rollback. The parameters are chosen from the incident rate the service can tolerate, not from the metric.

The latching approach is the one that matches the semantics: an automatic rollback is evidence that the deployment is not safe, and that evidence does not expire because the metric recovered after the deployment was removed.

Evidence that arrives after the decision

Some failures are visible immediately in error rates. Others are visible only in outcomes that take time to accumulate: a conversion rate that shifts by a fraction of a percent, a model that is slightly worse in a way that shows up over thousands of interactions.

For the second class, a rollback controller with a short window sees nothing and does not act, while a controller with a long window acts minutes or hours after the harm began. Neither is wrong; they detect different things. The problem is a single controller trying to do both, because the thresholds that make a long-window rule sensitive also make it slow, and the operator cannot tell from the alert which class of failure was detected.

The workable structure separates fast guardrails from slow statistical monitors. Fast guardrails act automatically and are limited to metrics that are unambiguous when they move. Slow monitors produce a signal for a human, or for a controller with a much longer decision horizon and a much higher confidence requirement. The statistical machinery for the slow path is the same as for canary promotion, described in Canary Promotion as a Repeated Statistical Test, and it has the same problem with repeated evaluation.

The metric the controller reads is produced by the system it changes

There is a feedback path that is easy to overlook. A rollback changes the traffic distribution: the reverted version may be slower, so the load balancer’s queue grows, so the measured latency of the whole service rises even though the version that was reverted was not the cause. A controller watching service-level latency can therefore observe degradation caused by its own action and revert again.

The defence is to make the metric attributable to the version. This requires the version identity to be a label on every measurement, which in turn requires the measurement to be taken at a point that knows the version. Server-side metrics naturally carry it; load-balancer and client-side metrics usually do not. A controller that reads only unlabelled metrics is reading a mixture and cannot separate the deployment’s effect from the fleet’s state.

Attribution also requires that the metric’s denominator be stable. A version that fails fast produces many short error responses, which can raise a throughput metric and lower a latency metric while the service is broken. Any rule that uses latency without a companion error rate is exploitable by exactly this behaviour, and it is not hypothetical: a server that crashes on startup and is restarted in a loop produces no latency samples at all, so a latency-based rule sees a perfectly healthy absence of slow requests.

Acting on a subset

A rollback controller that reverts the whole fleet at once is fast and destroys the evidence. The logs, the metric window, and the running processes that would explain the failure are gone by the time anyone investigates.

Reverting a fraction first preserves evidence and bounds the blast radius, at the cost of leaving some traffic on a version already known to be bad. The fraction is a judgement about how much continued harm is acceptable in exchange for a diagnosis, and it is not a decision a controller can make from the metric alone. Making the first step a partial revert, and the second step a full revert after a short observation, gives the controller a way to be wrong once.

The evidence preservation has a concrete form: before the reverted version’s replicas are terminated, the controller snapshots the metric window, the version’s logs for the period, and a sample of the inputs and outputs that produced the failing measurements. Without the input sample, the failure can be reproduced only by waiting for it to recur.

What the controller cannot decide

An automatic rollback is a statement that the deployment is worse than its predecessor on the monitored dimension. It is not a statement that the predecessor is good, and reverting to a version that was already degrading a metric returns the service to a worse state than the one it was in before the rollout began, if the rollout was itself a fix.

This is the case that produces the most confusing incidents: a rollback triggered by a guardrail during a deployment that was intended to repair a pre-existing problem. The service returns to the pre-existing problem, the guardrail clears, and the incident is recorded as resolved. The controller did what it was configured to do; the configuration encoded “revert is safe” without checking that the reverted state was acceptable.

The check that closes this gap is a pre-deployment condition rather than a runtime one: a rollout is only eligible for automatic rollback if the baseline it would revert to was within its own guardrails at the time the rollout started. A deployment whose predecessor was already failing should be manual, because neither outcome is good and the choice between them needs a human. Deciding whether the predecessor was acceptable is the same comparison a canary makes against its incumbent, described in Canary Promotion as a Repeated Statistical Test.

Beyond that, the controller’s scope ends at restoring the previous behaviour. Diagnosing why the new version was worse, deciding whether to fix it or abandon it, and updating the guardrail thresholds so that the same failure is caught earlier are all outside the loop. A controller that is expected to do them is a controller that will be blamed for not doing them, which is the reason the boundary is worth drawing explicitly before the first automatic rollback fires.

References

  1. Site Reliability Engineering: How Google Runs Production Systems — Beyer et al., O'Reilly 2016
  2. Kubernetes documentation — Deployments — Kubernetes project
  3. Control Theory: A Guided Tour — James Ron Leigh, Institution of Engineering and Technology 2004
  4. SRE Workbook: Canarying Releases — Google SRE