レジがない間の末尾にこう書いた。
自動リトライがない。 ハーネスが明示的に繋ぎ直した。実際のアプリなら裏で周期的に試みるべきであり、その周期とバックオフはここで決めなかった。
この編がその周期を決める。そして決めるついでに、周期を間違えて決めると何が起きるかを測る。
受ける側から数える
リトライの記事の大半はかける側の話だ — どうやって自分のリクエストを通すか。
このサンプルのサーバーは反対側に立つ。試行が何回来たか、そしてそのうち何回が同じ200ミリ秒の中に集中したかを数える。
/// That second number is the one that matters. A gateway that is briefly down
/// does not care that you tried again. It cares how many of you tried again at
/// the same instant, because that is what keeps it down.
一時的に落ちたゲートウェイはあなたがもう一度かけたという事実に興味がない。興味があるのは何人が同じ瞬間にもう一度かけたかだ。それが落ちたままにさせるものだからだ。
急いでいるときに書かれるもの
/// Try again immediately. This is what gets written when the retry is added in
/// a hurry, and it is the strategy that turns a short outage into a long one.
class NoBackoff extends Backoff {
@override
int waitBefore(int n) => n == 1 ? 0 : 20;
}

RETRY IMMEDIATELY: 4 callers, 64 attempts total, 4 accepted, peak 18
caller 1 attempt 1: straight away -> DOWN at 12ms
caller 1 attempt 2: after 20ms -> DOWN at 58ms
...
caller 1 attempt 16: after 20ms -> ACCEPTED at 708ms
通った。 700ミリ秒の障害を越え、四人とも決済された。
そしてゲートウェイはその間に64回叩かれた。
倍々に伸ばし、揺らす
@override
int waitBefore(int n) {
if (n == 1) return 0;
final base = 60 * (1 << (n - 2)); // 60, 120, 240, 480 ...
// Full jitter: anywhere in [0, base]. Spreading matters more than being
// punctual — nobody is waiting on an exact millisecond here.
return _rng.nextInt(base + 1);
}

EXPONENTIAL + JITTER: 4 callers, 25 attempts total, 4 accepted, peak 13
caller 1 attempt 1: straight away -> DOWN at 13ms
caller 1 attempt 2: after 53ms -> DOWN at 70ms
caller 1 attempt 3: after 87ms -> DOWN at 159ms
caller 1 attempt 4: after 143ms -> DOWN at 305ms
caller 1 attempt 5: after 389ms -> DOWN at 697ms
caller 1 attempt 6: after 449ms -> ACCEPTED at 1150ms
同じ障害、同じ人数、同じ結果。 四人とも決済された。
試行は64回から25回。 2.6倍少ない。