policybook / retry / constant

Constant

Wait the same amount every time. Fastest to notice a short outage, and the purest form of the thundering herd: every client comes back at the same instant, forever.

Wait the same amount before every retry. sleep(100) in a loop is this policy, and it is what you get by accident when nobody has thought about the question.

It is the baseline the others are measured against, and it is here to be measured rather than recommended.

When to use it

  • A handful of clients, and short outages. With a known, small number of callers there is no herd to disperse, and a fixed short delay notices a recovery faster than any backoff curve.
  • When the failure is not a capacity problem. A lock contended for microseconds, a leader election settling, a file briefly locked: retrying at a steady tempo is right when trying again does not itself cost the other side anything.
  • Inside a bounded loop you control end to end, where the total patience is the thing you are configuring and the shape of the delay is irrelevant.
  • As a benchmark reference. Knowing what the naive answer scores is what makes the others’ numbers mean something.

When not to use it

  • Anywhere many clients can fail together. With no randomness anywhere, every client that failed at the same moment retries at the same moment, forever. This is the thundering herd in its purest form, and on the canonical workload it puts about 14% of all retries into a single 10 ms window.
  • When the service is down because it is overloaded. The load this policy applies does not fall as the outage continues. A struggling service gets no relief and stays struggling.
  • When outages can be long. The total patience is baseMs x (maxAttempts - 1): at the defaults, seven hundred milliseconds. On our thirty-second outages that succeeds under 2% of the time, which is not a broken benchmark but an accurate description of a sleep(100) loop.
  • Almost anywhere else. exponential-full-jitter costs one extra line and fixes both problems.

How it works

if not error.retryable:      return null
if attempt >= maxAttempts:   return null
return baseMs

There is no state, no draw, and no dependence on the attempt number. Asking twice gives the same answer, which is exactly what synchronises a fleet, and a test pins that, because it is the property the policy is here to demonstrate.

Tie-breaking. A non-retryable failure gives up regardless of the attempt budget: nothing is gained by retrying a failure the server calls permanent.

Parameters

baseMs may be zero: retrying immediately is a legitimate, if aggressive, configuration, and a vector covers it.

There is no capMs, because there is nothing to cap.

Complexity

O(1) time and O(1) space. Nothing is cheaper, which is the only axis on which this policy wins.

Source

Folklore, and the default behaviour of a great many retry loops that were never consciously designed.

Its nearest neighbour is exponential, which differs only in letting the delay grow. The comparison is instructive: exponential succeeds far more often on a long outage, and is just as synchronised: backing off exponentially converts a continuous herd into a periodic one rather than dispersing it. Dispersal needs randomness, which is exponential-full-jitter.

Notes

No patents known.

The Rng is supplied at construction rather than passed to nextDelay (see the domain interface for why). This policy never draws from it, which is precisely its defining weakness.

Parameters

Name Type Default Description
baseMs number 100 The delay before every retry, in milliseconds.
maxAttempts number 8 Give up after this many attempts.