15 KiB
N=3 SWIM retuning against deployed latency — behavioral spec
Companion to N3_POSTMORTEM_2026-05-25_1779733878.md,
N3_COVERAGE_EXTENSION_SPEC.md, and the simulator's
SWIM_TUNING_REPORT.md. This document is the contract for a SWIM
retuning pass that uses the 1779733878 deployment's observed
latency and churn data as the evidence the tune is calibrated
against — rather than the simulated 60 ms latency the prior tune
used.
This is a behavioral spec. It names the targets the retuning must hit, the evidence each target is calibrated against, and the prerequisites that must be in place before a retuning pass can be evidence-driven rather than guess-driven. It does not prescribe specific knob values.
0. Motivation
SWIM_TUNING_REPORT.md documented a prior tuning pass against the
§10.3 gossip-flap property and the three N3 calibration scenarios.
That pass collapsed self_incarnation_peak from 86–94 to 7–10 — a
significant win — but its calibration latency was 60 ms RTT with
15 ms jitter (per §2 of that report). The simulator's calibration
scenarios used these numbers because the live bundles available at
the time did not surface per-SWIM-probe RTT.
The 1779733878 run exposes a different reality:
- Tier-2 (host-level) UDP echo RTT: orchestrator 293 ms, stage-0 181 ms, stage-1 405 ms, stage-2 184 ms. p99 spread is multi-hundred-millisecond and asymmetric across peers.
- Topology: every peer connection is
conn_type=Relay. No hole-punching succeeded. SWIM probes ride a relay-mediated path whose RTT is strictly higher than the tier-2 floor and is subject to relay-side HOL queueing. - Observed churn: 1701
SwimTransitionevents over a ~7-minute run while iroh continued to exchange messages (connect-timeout count = 0). The chosenprobe_timeout = 15 ticks = 3 swas selected against 60 ms RTT; against a relay-mediated path with p99 multi-hundred-millisecond tier-2 floor and load-driven queueing on top, that budget may be marginal or worse. - Configuration: the prior tune's knobs landed at
probe_interval=10, probe_timeout=15, suspicion_timeout=75, indirect_probes=2, dead_reprobe_interval=50ticks plusmax_piggyback=6. These are committed defaults; the question this spec opens is whether they hold under the observed deployment shape, not whether the prior tuning method was correct.
The previous postmortem (run 1779720002) could not have driven
this retune: its bundle lacked the data the observability upgrade
landed afterward. The 1779733878 bundle is the first one rich
enough to retune against. This spec captures the contract that
retuning must satisfy.
1. Prerequisites
Retuning is not evidence-driven until the data the tune calibrates against is in the bundle. Three prerequisites are explicit.
1.1 Coverage 2.6 from N3_COVERAGE_EXTENSION_SPEC.md
Per-SWIM-probe RTT events and the post-processor's ## Probe RTT distribution section must land in production and in the sim
adapter. Until this coverage is in place:
- The deployed evidence is tier-2 RTT (UDP echo to the collector), which understates the relay-mediated SWIM RTT by an unknown factor.
- The simulator's
no_flap_while_probes_okassertion isInconclusiveon every SWIM scenario (perSWIM_TUNING_REPORT.md§6.3), so the assertion can neither pass nor fail the retune.
A retuning pass that lands without 2.6 is a guess against tier-2 latency — the same mistake the prior tune made against 60 ms simulated latency, only with a different proxy for the real number.
1.2 Determinism fix from SWIM_TUNING_REPORT.md §6.7
MemberList's HashMap<NodeId, _> randomises iteration order per
process; the prior tune reports ±20 % run-to-run variance as a
result. A retuning pass that has to average across five samples
per grid point to estimate variance is exactly five times slower
and five times noisier than one against deterministic substream
selection. HashMap → BTreeMap is the one-line fix the prior
report names; it must land before retuning, not after, so the
retune's results have signal-to-noise high enough to read.
1.3 The Layer-B1 refute-on-stale-Suspect bug (SWIM_TUNING_REPORT.md §6.1)
crates/distribution/src/swim/node.rs::apply_membership_update
refutes against self_id() whenever
update.state ∈ {Suspect, Dead} regardless of whether
update.incarnation is current. This creates a non-zero floor on
self_incarnation_peak that no tuning can collapse. A retuning
pass against the 1779733878 shape, where the relay-mediated path
keeps stale Suspect entries in the dissemination queue for many
probe cycles, will hit this floor and conclude — incorrectly —
that further tuning gain is unavailable.
The one-condition gate the prior report names is the priority-1 follow-up the prior tune deferred. It is a prerequisite for evidence-driven retuning against this deployment, not a downstream cleanup.
2. Calibration data the retune is driven by
The retuning pass's evidence comes from the 1779733878 bundle
(and any subsequent N=3 deploy bundles that land before the
retune). Three numbers anchor the calibration:
2.1 Observed tier-2 RTT distribution
| node | RTT (ms) | echo success |
|---|---|---|
| orchestrator | 293 | 34/35 |
| stage-0 | 181 | 55/55 |
| stage-1 | 405 | 27/28 |
| stage-2 | 184 | 38/38 |
The tier-2 echo path is collector-bound, not peer-bound. It establishes the floor below which a relay-mediated SWIM probe cannot land.
2.2 Per-SWIM-probe RTT distribution (post coverage 2.6)
After coverage 2.6 lands, the bundle will carry per-probe RTT
distributions per (observer, target) pair, plus per-bucket
distributions over the run window. The retune calibrates
probe_timeout such that the configured budget exceeds the
observed p99 of legitimate (non-failure) probe RTT with a margin
the retune explicitly justifies. Until 2.6 is collected against a
live run, the retune uses §2.1 as a lower-bound proxy and is
explicit about that.
2.3 SWIM churn and dial outcomes
SwimTransition: 1701 across a ~7-minute run is the load-bearing
churn signal. The retune is calibrated such that a scenario
configured to mirror the 1779733878 shape produces a churn
count within a stated factor (target: <300, an order-of-magnitude
collapse comparable to the prior tune's self_incarnation
collapse).
Per-peer dials from the postmortem (orchestrator 7/11 timeout, inter-stage 19/19 success) are the discriminator the retune must not undo: a retuned SWIM that makes inter-stage probes flap is a regression even if it makes orchestrator-bound probes more stable.
3. Retuning targets
Six, ordered by load-bearing impact.
3.1 probe_timeout against relay-mediated p99 RTT
Target: probe_timeout exceeds the bundle's observed p99 of
legitimate probe RTT (post-2.6) by a margin the retune justifies
in prose — the margin must account for relay-side HOL queueing
peaks the steady-state distribution does not capture.
Anti-target: the budget cannot be set so high that suspicion
takes longer than the operator's deadstop threshold. The
postmortem named ~7 min as the operator's deadstop budget; SWIM's
detection time (probe_timeout + suspicion_timeout) must remain
well under that, with a documented headroom.
Evidence: per-probe RTT histogram from §2.2; SwimTransition churn count from §2.3.
3.2 suspicion_timeout under relay-mediated reachability
Target: a peer whose relay-mediated path is intermittently
unreachable (the 1779733878 shape — repeated probe failures
interleaved with successes) does not flap between Alive and
Suspect more than the prior tune's bound on the gossip-flap
property, when the scenario mirrors the deployment's latency
distribution.
Anti-target: a peer whose path is genuinely dead is not falsely held Alive past the operator's deadstop window.
Evidence: §2.3 churn count; the no_flap_while_probes_ok
assertion (now resolvable post-2.6) against the calibration
scenario.
3.3 indirect_probes count against relay HOL behavior
Target: indirect probes still provide redundant coverage when
the direct probe times out, but their cumulative bandwidth
contribution to the relay's egress queue does not push the
relay_queue_depth_bounded assertion to fail under own-relay
policy.
Anti-target: dropping the count below the prior tune's 2 collapses indirect coverage, which the prior tune's §5 already documents.
Evidence: relay_queue_depth_bounded under own-relay calibration;
churn count from §2.3.
3.4 probe_interval against the dial-rate signal
Target: probe rate is set such that the orchestrator-bound
dial failures the 1779733878 run exhibited (7/11 timeout) do
not bottleneck convergence beyond a tolerance the spec names.
Anti-target: probe rate is not lifted so high that
message_size_bounded regresses against own-relay policy.
Evidence: per-peer dial table from §2.3; piggyback byte totals from the bundle.
3.5 max_piggyback against observed gossip-receipt sizes
Target: piggyback gossip stays within the
message_size_bounded envelope under own-relay policy, given
the 1779733878 per-node piggyback byte totals (806–1589
piggybacks per node, 194–522 KB total).
Anti-target: lowering max_piggyback below the prior tune's
6 stops convergence within the property's window
(SWIM_TUNING_REPORT.md §5).
Evidence: gossip-receipt totals from the postmortem's "Gossip
receipts" section; message_size_bounded assertion under
own-relay.
3.6 LifeguardConfig wiring (formerly out of scope)
Target: the dynamic suspicion-timeout formula in
crates/distribution/src/swim/lifeguard.rs is wired into
SwimNode's suspicion state machine. Until wiring lands, the
constants in lifeguard.rs have no observable effect — per
SWIM_TUNING_REPORT.md §6.5, the prior tune could not sweep "the
lifeguard band" because it was dead code.
This target is the only one that requires code beyond a knob
change. It is included here because the prior tune named it as a
priority follow-up and because the 1779733878 data motivates
adaptive suspicion: a path whose RTT varies 2× under load benefits
from adaptive timeouts more than a static budget can capture.
Anti-target: landing the wiring without sweeping its parameters reproduces the prior tune's dead-code condition for the new fields. The wiring must come with a sweep against the calibration scenarios.
Evidence: the new dynamic-suspicion code path is exercised by at least one scenario whose assertion verdict changes when the multiplier changes.
4. Calibration scenario updates
The three N3 calibration scenarios under
crates/simulation/scenarios/calibration/ were last updated to
mirror the prior tune's defaults at the scenario's 200 ms tick
(SWIM_TUNING_REPORT.md §3). The retune updates these scenarios
along two axes:
- Latency distribution: per-link latency is set against the
1779733878per-peer tier-2 RTT distribution, not the prior 60 ms baseline. Heavy-tailed perSIM_HARDENING_SPEC §8(the prior battery spec's reference); the distribution's median, p95, and p99 fall within tolerance of the live bundle's after coverage 2.6 lands. - Topology: every host-to-host link is routed through the
relay vertex (
via = Rin scenario syntax). The1779733878shape hadconn_type=Relayeverywhere; the calibration scenarios must reflect that to be evidence-faithful.
The scenarios' kind_config blocks are updated to the retune's
chosen operating point. The current calibration block (per the
prior report) gives probes a 333 ms budget against 60 ms RTT;
against multi-hundred-millisecond relay-mediated RTT, the same
budget under-budgets by an order of magnitude. The retune's new
budget is the §3.1 target.
5. Acceptance
The retune is complete when:
- Every prerequisite in §1 is in place (coverage 2.6, the determinism fix, the Layer-B1 gate).
- Each target in §3 has a chosen operating point and a one-line prose justification anchored to the §2 evidence.
- The
1779733878calibration scenario, configured to mirror the deployment's latency and topology, produces fewer than 300SwimTransitionevents in a 7-minute virtual run (an order-of-magnitude reduction from 1701). - Inter-stage dial outcomes in the calibration bundle remain at
the
1779733878shape (≥95 % success on inter-stage edges) — the retune does not improve orchestrator-bound stability at the cost of inter-stage flakiness. - The §10.3 gossip-flap property's
self_incarnation_peakdoes not regress from the prior tune's 7–10 band. - A retuning report (a successor to
SWIM_TUNING_REPORT.md) documents the new operating point, the evidence each knob choice was calibrated against, the before/after numbers across every calibration scenario, and the limits the retune could not move.
6. Out of scope
- Adding new SWIM features. The retune adjusts existing knobs and lands the Layer-B1 gate / Lifeguard wiring the prior report named. New algorithmic features (push-pull anti-entropy, alternative failure detectors) are not in scope.
- Relay-side fixes. The
relay_queue_depth_boundedfailure on the canary topology is structurally out-of-reach for SWIM tuning (SWIM_TUNING_REPORT.md§6.2). The retune does not attempt to make canary pass; it does not regress own-relay. - Orchestrator-topology changes. Running the orchestrator on
a reachable host (the
1779733878postmortem's item 6) is a deployment-shape change, not a SWIM-tuning change. The retune is calibrated against the NAT'd-orchestrator shape because that is the deployment we have, but the conclusion may be "even optimally-tuned SWIM cannot stabilize this topology" — that conclusion is a valid retune outcome. - The gossip-flap property's
self_incarnation_boundedassertion. The prior tune collapsed it from 86–94 to 7–10 without removing the non-zero floor; the retune holds that result. Removing the floor is the Layer-B1 fix's job (a §1.3 prerequisite, not a §3 target). - Scenarios beyond the calibration corpus. The reproduction and topology scenarios remain on their current SWIM config. Retuning them is a follow-up that should wait for the calibration retune to converge.
7. References
N3_POSTMORTEM_2026-05-25_1779733878.md— source of the observed latency distribution (§"UDP echo probes"), the churn signal (§"SWIM churn and relay events"), the dial outcomes (§"Per-peer dials"), and the topology context (conn_type=Relayeverywhere, NAT'd orchestrator).N3_COVERAGE_EXTENSION_SPEC.md §2.6— the data surface this spec consumes. §1.1 of this spec is a hard prerequisite.crates/simulation/SWIM_TUNING_REPORT.md— the prior tuning pass. §3 (configuration), §5 (tradeoff curve), §6 (limits) are the load-bearing prior art the retune does not re-derive. §6.1, §6.5, §6.7 limits are §1.3, §3.6, §1.2 prerequisites respectively in this spec.crates/simulation/SIM_SPEC.md— the simulator's calibration contract (§11) and the assertion catalog (§10.1) the retune is scored against.N3_SIM_TEST_BATTERY_SPEC.md— the sim-test battery. A retuned SWIM that regresses any battery family is a retune regression, not a battery regression.