Multi-link scheduling

Documents the atlasd v0.2 release lineView as Markdown

The scheduler is where Atlas earns its keep: continuous measurement of every link, and a per-packet placement decision that shifts traffic away from trouble gradually — not on a binary up/down trigger that flaps. This page covers how links are measured, the eight strategies you can choose from, and the knobs that make asymmetric bearers behave.

Probes: how a link earns trust

Every (peer, link) pair is probed on its own clock — [link_monitor] probe_interval_ms, default 250 ms. Each probe updates an RTT EWMA, a windowed jitter figure and a windowed loss rate. Two principles govern the bookkeeping:

  • Aliveness requires evidence. A link is never considered alive before its first completed round trip, and is declared dead after dead_probe_count (default 5) consecutive misses.
  • A late probe is not proof of life. Past unusable_rtt_ms (default 10 000; per-link override available) a probe that does return stops counting as evidence the link works — so a link buried under its own transmit backlog reads as down, not as "up at 40 s RTT with 0% probe loss". The measured RTT still reaches your dashboards.

Per-link overrides matter on mixed bonds: [[link]] probe_interval_ms lets a cellular link answer every 250 ms while a geostationary VSAT (~600 ms RTT) is probed every 1000 ms, each with timeouts scaled to its own cadence. And because sparse control probes underestimate loss on full-size packets, data_loss_probe_every_n (default 10) piggybacks loss accounting on every Nth real data fragment — the loss figure the scheduler acts on is measured on traffic shaped like yours.

The eight strategies

Set [scheduler] strategy to one of:

StrategyPlacement ruleReach for it when
lowest_delivery_timeDefault. Per fragment, minimise size/capacity + RTT — capacity- and size-aware.General bonding; links of unequal speed.
weighted_round_robinSpread packets proportionally to [[link]] weight.Deliberate load ratios across similar links.
broadcastEvery fragment on every alive link; the receiver's anti-replay window deduplicates.N× bandwidth is cheaper than any loss; survives N−1 failures.
priorityStrict failover through priority_order, a list of link names.A cheap primary and an expensive standby (e.g. satellite backup).
shortest_pathSPF on hop counts; local link choice falls back to lowest_delivery_time.Multi-hop meshes where route stability beats RTT-chasing.
adaptive1→2→3-link redundancy driven by cost ratios between the best links.Redundancy that scales with how bad things are.
loss_adaptiveSame escalation, driven purely by measured loss (enter at 20% / 50%).Loss-dominated bearers.
latency_adaptiveSame escalation, driven by best-link RTT (enter at 40 ms / 100 ms).Latency-sensitive traffic where rising RTT is the early warning.

The three adaptive strategies share an anti-flap discipline: escalation is instant, de-escalation waits (adaptive_dwell_ms, default 5000) and the enter/exit thresholds form a hysteresis gap, so a link hovering at a threshold cannot make the bond oscillate. For the non-adaptive strategies, redundancy_factor = N tops up any placement to the best N links at once.

[scheduler]
strategy = "latency_adaptive"
latency_mirror_ms = 40.0        # ≥ this best-link RTT: send on 2 links
latency_triplicate_ms = 100.0   # ≥ this: send on 3
adaptive_dwell_ms = 5000        # de-escalation dwell; escalation is immediate

Gradual by construction

Link scores update on every probe: loss inflates a link's cost geometrically (1/(1−loss)), jitter inflates it further, and cost-based strategies shift traffic as the numbers move — long before anything is "down". The only binary events are the ones that should be binary: a dead link leaving the candidate set, and the adaptive strategies stepping their redundancy level. By the time a failing link is formally dead, it was already carrying almost nothing.

Predictive failover

[link_monitor] prediction_sourcenone (default), rssi, position, or both — lets the scheduler act on where a link is heading:

  • rssi: the signal-strength trend from radio telemetry is projected rssi_lookahead_secs ahead against rssi_floor_dbm; a link fading toward the floor has its perceived loss inflated (capped at 75%) so traffic drains off it before it dies.
  • position: peers advertise position and velocity; the projected distance is scored against each radio's range envelope ([[link]] max_range_m, warning ramp from range_warn_m) and stretched links get expensive — locally and in mesh route costs.

Prediction only ever biases scheduling. It never declares a link dead — probes remain the sole authority — and dashboards keep showing measured values, never predicted ones.

Metered links

A link with a data budget declares it: quota = "20 GB" with quota_period (monthly, weekly, daily, rolling-30d), an anchor day, and a warning threshold (quota_warn_pct, default 80). Accounting is honest — wire bytes, including framing, probes, handshakes and redundancy copies — and persists across restarts. An over-budget link drops out of the scheduler's candidate set, but a quota never takes down the last usable path: when the expensive link is the only link, it carries the traffic and the journal records the crossing.

Mirrors, stripes and aggregation

In the transport-class path, each class's multipath policy compiles to a typed send plan: Mirror (a copy on every listed link — redundancy) or Stripe (each packet on exactly one link, weighted by effective goodput capacity × (1 − loss) — aggregation). Striping is what makes two 20 Mbps bearers carry materially more than one: measured 36.5 Mbps across two such links under 60 Mbps offered load, 91% of the theoretical sum, versus 19.2 Mbps when the same traffic was mirrored. Links that miss a class's latency budget are excluded from its stripe set before weights are computed, and an emptied set falls back to the single best link rather than dropping traffic.

A tuning checklist for asymmetric bonds

  • Declare honest capacities per link (capacity = "64 kbps""1.5 Gbps") — the default assumption is 80 Mbps, and cost functions divide by it.
  • Slow the probe cadence on high-RTT bearers (probe_interval_ms per link) and raise their unusable_rtt_ms.
  • Budget metered links with quota so the scheduler, not an invoice, discovers the ceiling.
  • Give narrowband radios a slower mesh advertisement cadence — see route flooding on narrow bearers.
  • Prefer an adaptive strategy when link quality varies faster than a human can retune; prefer priority when the economics, not the physics, should decide.
Esc
↑ ↓ navigateEnter — openEsc — close