Skip to content

Peer topology

System overview

    graph LR
    Publisher["Publisher<br/>(Browser/WebTransport)"]
    Hub["Hub Relay<br/>(qumo relay)"]
    EdgeA["Edge Relay A<br/>(qumo relay)"]
    EdgeB["Edge Relay B<br/>(qumo relay)"]
    Subscriber["Subscriber<br/>(Browser/WebTransport)"]

    Publisher -->|"QUIC/MoQ<br/>WebTransport"| EdgeA
    EdgeA <-->|"ANNOUNCE_PLEASE<br/>QUIC peer"| Hub
    Hub <-->|"ANNOUNCE_PLEASE<br/>QUIC peer"| EdgeB
    EdgeB -->|"QUIC/MoQ<br/>WebTransport"| Subscriber
  

A node’s role (--role hub, --role edge, or unset for a flat/standalone relay) is a CLI flag on qumo relay — see CLI → relay.

Peer discovery

There is no runtime discovery service — on startup, each relay dials the fixed, comma-separated list of addresses in PEERS: e.g. an edge’s hub(s), or any relay it peers with. Each host is resolved to all its addresses and every address is dialed, so a name for a group of relays (a Consul DNS name, a Docker network alias) connects to each of them. See Nomad for a worked example on Nomad.

--role hub/--role edge is a CLI flag on qumo relay; it is an operator-facing label logged at startup for visibility only and has no effect on which addresses are dialed.

Peers authenticate each other with mutual TLS under a relay CA (CA_FILE, PEER_CERT_FILE, PEER_KEY_FILE); a relay with PEERS refuses to start without them. See TLS & mTLS. A relay whose PEERS resolve to itself drops that session. Announcements flow both ways over one peer session, so one side’s PEERS naming the other is enough for both to relay each other’s broadcasts; listing every relay on every relay only saves the dial from depending on which started first.

Each connection dials QUIC with ALPN moqt, exchanges ANNOUNCE_PLEASE / ANNOUNCE, and registers the peer’s tracks on the local TrackMux. On disconnect the connection is retried with exponential backoff (1s base, 30s cap, ±25% jitter).

    graph TD
    Start["Relay Startup"]

    Start -->|"for each PEERS address"| ALPN

    ALPN["QUIC dial (ALPN: moqt)"] --> Announce["ANNOUNCE_PLEASE / ANNOUNCE"]
    Announce --> TrackMux["Register tracks on local TrackMux"]
    TrackMux --> Serve["Serve subscribers"]

    ALPN -->|"failed"| Retry["Backoff → retry"]
    Serve -->|"disconnected"| Retry
    Retry --> ALPN
  

Route election

When more than one peer path can serve the same broadcast, the relay elects one active route and keeps the losers as retained alternates, promoted if the incumbent’s announcement ends.

Replacement is not a plain better/worse comparison. Liveness and hop count are structural, so they decide outright — a live route always beats a dead one, and fewer hops wins. Bitrate and RTT are noisy, so they only displace an incumbent by clearing a hysteresis margin:

  • Bitrate breaks a hop-count tie. The candidate must reach at least 120% of the incumbent’s estimated bitrate.
  • RTT is consulted only when estimated bitrate is equal (or unknown on either side). The candidate must be lower by at least 5 ms or be under 80% of the incumbent’s RTT — either margin suffices.

Without those margins every edge converges on whichever hub momentarily measures best, and the mesh flaps. A candidate that is genuinely better but sits inside a margin does not take over, and is counted as a rejection.

This is visible via the qumo_relay_route_replacements_total, qumo_relay_route_rejections_total, qumo_relay_routes_retained, and qumo_relay_route_promotions_total metrics — see Observability.

Graceful migration

Route/subscription migration (make-before-break via route election) is the primary mobility mechanism. GOAWAY_REDIRECT_URI is an escape-hatch: on shutdown, it redirects clients/peers to a successor relay. See Configuration.

Related