Skip to content
Closed
23 changes: 14 additions & 9 deletions doc/bin/relay/cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ same way so the cluster converges instead of flapping. Both wire protocols
carry it: natively on moq-lite, and via the [cluster extension](/draft/moq-cluster)
on moq-transport 17+.

Failover routes must carry copies of the same broadcast. For each track, the
Failover routes must carry copies of the same broadcast. A relay moves a
subscription only between sources from the same origin: on moq-lite-07 the one a
source's SUBSCRIBE\_OK or FETCH\_OK names, otherwise the first hop of its route.
A change of origin ends the subscription and the viewer re-subscribes. For each track, the
relay requires matching timescale, retention window, publisher priority, and
group ordering. A source with different properties is refused before its groups
are spliced in. If no compatible source remains, the track fails with
Expand All @@ -43,18 +46,20 @@ link costs 1, which reproduces plain hop counting. Each relay adds the price of
the link an announcement arrived on before forwarding it, so a route's cost is
the sum of what it crossed.

Wildcard advertisements are forwarded and costed the same way as an exact-path
Prefix advertisements are forwarded and costed the same way as an exact-path
route: each hop appends its identity, adds the link price, and passes the
claim on. An advertisement must be contained by one of the publisher's granted
prefixes (`grant/**`); an over-wide pattern is refused rather than clamped.
prefixes (`grant/**`); an over-wide prefix is refused rather than clamped.

Routing prefers the most specific pattern, then a fully identified hop list
Routing prefers the longest covering prefix, then a fully identified hop list
over one that holds a 0 (an anonymous hop) at any depth, then the lowest cost,
then the shortest hop list, breaking any remaining tie toward the newest
announcement so a reconnecting publisher isn't outranked by the session it
replaced. An assigned identity for an anonymous peer is local selection state
and is never written into the hop list. Resolving a non-prefix pattern into a
subscription is not implemented yet.
then the shortest hop list, then a hash of the requested path and the hop list,
breaking any remaining tie toward the newest announcement so a reconnecting
publisher isn't outranked by the session it replaced. Hashing the requested
path spreads equal-cost advertisers of one prefix, such as a transcode pool,
across its paths instead of sending every path to one of them, and every relay
picks the same one for a given path. An assigned identity for an anonymous peer
is local selection state and is never written into the hop list.

```toml
[cluster]
Expand Down
6 changes: 5 additions & 1 deletion doc/concept/moq-lite.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,11 @@ member. The Rust consumer is a `Stream` and the TypeScript one an async iterable

Announcements are hints; requests are the authority. When a subscriber asks
for a covered path the advertiser will not serve, the advertiser refuses that
request rather than narrowing the claim, and no message narrows a route. Token
request rather than narrowing the claim, and no message narrows a route. A
refusal is final: a relay resolves a request against the longest covering
prefix and never retries another advertiser or a broader prefix. An advertiser
running out of capacity withdraws or re-prices its route instead, leaving
headroom for requests already in flight. Token
scope is any pattern union; the session asks for each member's literal head on
the prefix-only wire and filters locally.

Expand Down
20 changes: 5 additions & 15 deletions drafts/draft-lcurley-moq-cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,8 @@ A refusal never falls through to a less specific tier.
Within that tier, a receiver SHOULD prefer a HOP_PATH that contains no 0 entry over one that does, then the lowest ROUTE_COST, breaking ties toward the shorter HOP_PATH and then toward the most recently received.
This is advisory: a receiver MAY apply local policy, such as measured RTT, instead.

NO_CAPACITY ({{iana}}) refuses a request the publisher could serve but has no capacity for now.
It permits ONE re-resolution within the same tier, excluding the refusing advertiser: every route with its non-zero first Hop ID, or its session when that ID is 0.
A receiver that has spent its retry, or has no other candidate, MUST refuse downstream with a code other than NO_CAPACITY, so retries cannot compound hop by hop.
Every other refusal, including an unrecognized code, is terminal.
Every refusal, including an unrecognized code, is terminal.
A publisher signals capacity through its advertisement alone: it withdraws or re-prices it before it runs out, leaving headroom for requests already in flight, since a withdrawal and a request for the slot it gave away can cross.
A receiver SHOULD NOT cache refusals.

A relay MUST NOT advertise a namespace merely because it resolved it: the covering advertisement stays the only one until the publisher advertises the concrete namespace, which it SHOULD do once producing, so a later request finds the running content by its exact namespace instead of resolving a second producer.
Expand All @@ -266,7 +264,7 @@ One rule for advertisement and dispatch keeps advertised paths truthful and prev
Under this extension an advertisement is a path, so a session advertises a namespace at most once, a relay forwards only the best path it knows ({{selection}}), and a subscription is served from one source at a time.

A receiver MAY still hold paths to several publishers of one namespace and choose between them as it sees fit: serve from the cheapest and move to the next when it fails.
A refusal moves to another publisher only as {{selection}} allows: once, and only for NO_CAPACITY.
A refusal never moves to another publisher ({{selection}}).
The advertised path and the served source stay the same publisher: a relay that moves to another MUST withdraw its advertisement and advertise the new path ({{updating}}), so the first Hop ID downstream always names the publisher whose Objects flow.
Moving between distinct publishers is a discontinuity: their groups are not one sequence, so a subscriber sees an unrelated Location, and a FETCH that succeeds against one may fail against the other.

Expand All @@ -283,7 +281,7 @@ Because a relay only appends to HOP_PATH, it cannot make a competing path look s
ROUTE_COST has no such protection: it is a single value the sender chooses, so a relay can advertise 0 for content it is not carrying and attract subscriptions it then has to fetch.
Both cost only a suboptimal path choice, and the latter is self-limiting, since the traffic won this way must then be served.

Implementations SHOULD bound the work started by requests beneath a broad advertisement, using NO_CAPACITY when capacity is exhausted.
Implementations SHOULD bound the work started by requests beneath a broad advertisement, withdrawing it before capacity is exhausted.

A receiver MUST NOT make security decisions based on Hop IDs, and a deployment spanning a trust boundary SHOULD treat a peer's ROUTE_COST as a hint to clamp or ignore rather than an accounting figure.

Expand Down Expand Up @@ -314,21 +312,13 @@ Both are carried in PUBLISH_NAMESPACE, in REQUEST_UPDATE of a PUBLISH_NAMESPACE

The Key-Value-Pair parity is load-bearing: HOP_PATH is odd, so its value is a length-prefixed byte string, while HOP_ID, RELAY_COST, and ROUTE_COST are even, so their values are bare varints.

## MOQT Error Codes

This document requests one registration in the "REQUEST_ERROR Codes" registry.

| Value | Name | Reference |
|:--------|:------------|:--------------|
| 0x40B5A | NO_CAPACITY | This Document |


--- back

# Appendix A: Changelog

## moq-cluster-02
- Defined request resolution against the longest covering prefix and the NO_CAPACITY refusal with its single re-resolution; any other refusal is terminal, including between several publishers of one namespace.
- Defined request resolution against the longest covering prefix; every refusal is terminal, including between several publishers of one namespace, and capacity is signaled only by withdrawing or re-pricing the advertisement.
- A relay does not advertise a namespace because it resolved it; the publisher advertises the concrete namespace once producing.

## moq-cluster-01
Expand Down
Loading