| Internet-Draft | MoQ-LB | November 2026 |
| Nandakumar & Jennings | Expires 5 May 2027 | [Page] |
This document describes an architecture for load balancing Media Over QUIC Transport (MOQT) requests across a pool of MOQT relays. It introduces a MOQT-aware Load Balancer (MoQ-LB) that places requests on relays according to their Track Namespace, and describes the control-plane, state, and failure-handling requirements needed for a horizontally scaled deployment.¶
This note is to be removed before publishing as an RFC.¶
The latest revision of this draft can be found at https://suhashere.github.io/moq-lb/. Status information for this document may be found at https://datatracker.ietf.org/doc/draft-nandakumar-moq-lb/.¶
Discussion of this document takes place on the Media Over QUIC mailing list (mailto:moq@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/moq/. Subscribe at https://www.ietf.org/mailman/listinfo/moq/.¶
Source for this draft and an issue tracker can be found at https://github.com/suhashere/moq-lb.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 5 May 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Media Over QUIC Transport (MOQT) [MoQTransport] defines a publish/subscribe protocol in which clients establish a single QUIC [QUIC] or WebTransport [WebTransport] session to a relay and perform subscribe, fetch, or publish operations on one or more tracks identified by a Full Track Name (a tuple of a Track Namespace and a Track Name).¶
Larger deployments typically run a horizontally scaled pool of relays fronted by a load balancer. Existing layer-4 load balancers and QUIC load balancers [QUIC-LB] route QUIC packets based on connection identifiers; they are oblivious to MOQT namespaces and tracks. As a result, they cannot:¶
steer MOQT requests for a given Track Namespace to a relay chosen for that namespace;¶
keep a publisher and all of its subscribers converging on the same relay so that fan-out is efficient;¶
move individual requests when relay membership or health changes.¶
This document defines the MoQ Load Balancer (MoQ-LB) architecture. A MoQ-LB is a MOQT-aware entity that sits between MOQT clients and a pool of MOQT relays. It inspects MOQT control messages, consults a namespace-to-relay Routing Table, and either forwards or redirects each MOQT request to the appropriate relay. The MoQ-LB does not define new wire-format messages; it reuses existing MOQT control messages and parameters and specifies how they MUST be used to achieve namespace-aware load balancing. The architecture complements connection-level load balancing (such as QUIC-LB) with namespace-aware routing at the MOQT layer.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
This document uses the following terms:¶
The set of MOQT relays behind a MoQ-LB.¶
A byte string that identifies a relay within a Relay Pool.¶
The set of Namespace Routes (Section 3.2) that maps Track Namespaces to relays.¶
The control-plane component that provisions MoQ-LB instances with the Routing Table, the Relay Pool, and relay health (Section 7).¶
A relay that is Healthy (Section 4.3) and, for a hashed route, has a positive weight.¶
The relay that the MoQ-LB chooses for a request (Section 4).¶
A deployment-configured duration after which a proxy-mode MoQ-LB moves or ends requests that remain on a relay no longer selected for them (Section 4.3.1).¶
+---------------------+
| Configuration Agent |. . . . . . . . . .
+----------+----------+ .
| .
| versioned health .
| configuration: monitoring .
| routes, relays, .
| health, keys .
v .
+--------+ +-----------+ proxy mode: +-------+--+
| | MOQT | | backend MOQT | Relay |<-+
| Client +-------->| MoQ-LB +--------------->| R1 | |
| | | | sessions +----------+ | relay
| | +-----------+ +----------+ | net-
| | redirect mode: client connects to | Relay | | work
| +------------------------------------->| R2 |<-+
+--------+ Selected Relay directly +----------+
A MoQ-LB operates on MOQT sessions established over QUIC or WebTransport. It is reachable at one or more addresses discoverable by clients (e.g., via the mechanisms described in [MOQ-URI]). After the session is set up, the MoQ-LB inspects MOQT control messages that carry a Track Namespace or Full Track Name and consults the Routing Table to select a relay for each request.¶
The relays behind a MoQ-LB are assumed to form a relay network in which any
relay can serve any request: relays exchange PUBLISH_NAMESPACE, PUBLISH,
and subscriptions among themselves so that a subscriber on one relay receives
the namespaces, tracks, and objects published on another. How relays do this
(Section 7 of [MoQTransport]) is outside the scope of this document. Relay
selection therefore does not decide whether a request can be served, only
where. The MoQ-LB places publishers and subscribers of the same namespaces on
the same relay, which reduces relay-to-relay traffic and latency, and spreads
load across the pool according to the Routing Table.¶
The Configuration Agent is authoritative for routing and health state; MoQ-LB instances act on the configuration it distributes (Section 7).¶
A MoQ-LB operates in one of two modes:¶
The MoQ-LB terminates the client's MOQT session and opens one or more MOQT sessions to the Selected Relays. All data objects are forwarded through the MoQ-LB (Section 5).¶
The MoQ-LB redirects each routable request to its Selected Relay with a
REQUEST_ERROR carrying the REDIRECT error code. The client sends the
request directly to the relay, so the MoQ-LB is never in the data path, and
keeps its session with the MoQ-LB for placing later requests
(Section 6).¶
Each client session is served in exactly one mode for its whole lifetime. A MoQ-LB MAY support both modes, for example on different service URIs, but it does not switch a session from one mode to the other.¶
A conforming MoQ-LB MUST implement every normative requirement of the mode(s) it supports. Section 3, Section 4, and Section 7 apply to both modes; Section 5 applies only to proxy mode, and Section 6 only to redirect mode.¶
Below is a non-normative illustration of the end-to-end behavior. It shows a
client subscribing to a track whose Track Namespace matches a hashed route
resolving to relay R2, and then to a second track whose namespace resolves
to R1, with the MoQ-LB in redirect mode. Hashed routes are specified in
Section 3.2.2 and redirects in Section 6.¶
Client MoQ-LB R1 R2 Config | | | | | | |<------------- Routing Table (v=42) ---------| | | | | | | QUIC+WT handshake | | | |<--------------->| | | | | SETUP | | | | |---------------->| | | | | SETUP | | | | |<----------------| | | | | SUBSCRIBE(ns=live/sports/game-7, name=audio) | | |---------------->| | | | | | hash(ns) -> R2 | | | | REQUEST_ERROR(REDIRECT, Connect URI=https://r2.example/moq) | |<----------------| | | | | (connect to R2) SETUP | | |<---------------------------------------------->| | | SUBSCRIBE (retried) | | |----------------------------------------------->| | | SUBSCRIBE_OK | | |<-----------------------------------------------| | | SUBSCRIBE(ns=live/news/ch-2, name=video) | | |---------------->| | | | | | hash(ns) -> R1 | | | | REQUEST_ERROR(REDIRECT, Connect URI=https://r1.example/moq) | |<----------------| | | | | (connect to R1) SETUP | | | |<-------------------------------->| | | | SUBSCRIBE (retried) | | | |--------------------------------->| | | | SUBSCRIBE_OK | | | |<---------------------------------| | |
The MoQ-LB selects a relay for each MOQT request from its routing key. Track Namespaces are tuples; prefix matching is performed tuple by tuple.¶
| MOQT Message | Routing Key |
|---|---|
| PUBLISH_NAMESPACE | Track Namespace |
| SUBSCRIBE_NAMESPACE | Track Namespace Prefix |
| SUBSCRIBE_TRACKS | Track Namespace Prefix |
| SUBSCRIBE | Track Namespace of Full Track Name |
| PUBLISH | Track Namespace of Full Track Name |
| FETCH | Track Namespace of Full Track Name |
| TRACK_STATUS | Track Namespace of Full Track Name |
| SETUP, GOAWAY | None |
| All other messages | The request stream they arrive on |
The messages in the first group start requests and are called routable
requests. SUBSCRIBE_NAMESPACE and SUBSCRIBE_TRACKS are routed as specified
in Section 3.3.¶
SETUP and GOAWAY are session- or request-local and are not routed;
GOAWAY handling is specified in Section 4.4.¶
All other messages (SUBSCRIBE_OK, FETCH_OK, REQUEST_OK, REQUEST_ERROR,
REQUEST_UPDATE, PUBLISH_STATE_NOTIFY, PUBLISH_DONE, PUBLISH_SKIPPED,
NAMESPACE, and NAMESPACE_DONE) are not routed by any field they carry but
by the request stream on which they arrive. In proxy mode the MoQ-LB resolves
them through the per-session request mapping (Section 5.2); in
redirect mode they flow directly between the client and the relay.¶
The Routing Table is the authoritative input to relay selection. It is a set of Namespace Routes, whose structure is described using Concise Data Definition Language (CDDL) [CDDL]. The order of routes is not significant.¶
NamespaceRoute = {
prefix: [* bytes], ; Track Namespace prefix
(
StaticRoute //
HashedRoute //
$$NamespaceRouteKind
),
? attributes: { * text => any } ; implementation-defined
}
StaticRoute = (
kind: "static",
rid: bytes ; Relay Identifier
)
HashedRoute = (
kind: "hashed",
? profile: text, ; default "HRW-SHA256"
? key_fields: uint .ge 1, ; default: all fields
rids: [+ (bytes / { rid: bytes, weight: 0..16 })]
)
¶
The kind field discriminates route types. Additional route kinds can be
registered (Section 10.2); a MoQ-LB MUST reject a Routing Table
containing a kind it does not support. attributes is an open extension
point for implementation-defined metadata.¶
A namespace matches a route when its leading tuple fields are equal, field
by field, to the route's prefix (an empty prefix matches every namespace).
The MoQ-LB SHALL select the matching route with the longest prefix. A
Routing Table containing duplicate prefixes is invalid. Route selection MUST
be deterministic for a given valid Routing Table.¶
A static route (kind: "static") pins a namespace or prefix to the relay
identified by its rid field. If that relay is not Eligible, the route has no
Selected Relay; requests matching it are rejected (Section 4.1) rather than
placed on another relay.¶
A hashed route (kind: "hashed") maps every namespace that matches prefix
to a relay selected by the weighted rendezvous hashing algorithm of
Section 3.2.2.1 over the Eligible Relays among its rids.¶
Each entry in rids is either a plain RID (shorthand for weight 1) or a map
carrying an explicit weight. Candidate RIDs MUST be unique within a route.¶
The optional key_fields field sets how many leading fields of a namespace
form its hash key, the part of the namespace that is hashed to select a
relay. All namespaces that share the same first key_fields fields
therefore select the same relay. When key_fields is absent, the hash key
is the whole namespace. A namespace with fewer than key_fields fields uses
the whole namespace as its hash key. key_fields MUST NOT be less than the
number of fields in prefix.¶
key_fields lets a deployment choose the unit that is kept together on one
relay. For example, an application whose namespaces have the form
(domain, session, participant) can use key_fields: 2 so that all
participants of a session reach the same relay, while different sessions
are still spread across the pool.¶
Weights bias the share of namespace keys assigned to each relay. A relay with
weight 2 receives approximately twice the share of a relay with weight 1 when
the keyspace is sufficiently large. Weights are provisioned by the
Configuration Agent and SHOULD reflect measured sustainable capacity, not
instantaneous load. Sixteen levels (0 to 16) are enough to express relay
capacity ratios in practice and keep the comparator inexpensive. Deployments
SHOULD express capacity ratios using the smallest integer weights that yield
the intended distribution, since comparator cost grows with weight magnitude.¶
A weight of 0 excludes a relay from selection. It is the planned way to drain a relay: it is changed in a new Routing Table version and follows the rules of Section 7.1. The Draining health state (Section 4.3) has the same effect on selection but is an operational state that can change without a route change. A route with no positive-weight relay is invalid.¶
For a given Track Namespace and set of Eligible candidates, all MoQ-LB instances MUST select the same RID. A change to the candidate set, or to any weight within it, can change the selection outcome for subsequent assignments. The procedure specified in this section is deterministic and is independent of platform floating-point behavior.¶
For each candidate relay, implementations compute a per-candidate hash as follows:¶
Take the hash key of the Track Namespace (Section 3.2.2) and encode it as a Track Namespace, as specified in Section 8.7 of [MoQTransport].¶
Encode the RID as its length, an MOQT variable-length integer (Section 8.1 of [MoQTransport]), followed by its bytes.¶
Compute H = HASH(namespace-encoding || rid-encoding) using the hash
algorithm of the route's hash profile (Section 3.2.2.2) and interpret
the result as an unsigned big-endian integer of b bits.¶
For two positive-weight candidates A and B with hash integers H_A,
H_B and weights w_A, w_B, let D be the modulus of the route's hash
profile. Candidate A wins over candidate B if and only if:¶
(H_A + 1)^(w_B) * D^(w_A) > (H_B + 1)^(w_A) * D^(w_B)¶
All operations MUST use exact non-negative integer arithmetic; floating-point evaluation and truncation of intermediate values are prohibited. When the two sides are equal, the candidate with the lexicographically smaller RID byte string is the winner. The relation is a strict total order over the positive-weight candidates. Implementations MAY use any procedure that returns its first-ranked candidate, and the result is independent of iteration order.¶
Each side of the comparison is bounded above by 2^((b + 1) * (w_A + w_B)).
Under the weight ceiling of 16, each side therefore occupies at most 8,224
bits (1,028 bytes) for HRW-SHA256 and 2,080 bits (260 bytes) for
HRW-SHA256-64.¶
The comparator is an exact integer form of weighted rendezvous hashing
[WEIGHTED-HRW] with score -ln((H + 1) / D) / w, where the lowest score
wins. Over a large keyspace, each relay i receives approximately the
share w_i / sum(w_j).¶
A hash profile specifies a hash algorithm together with its output width in
bits b and the associated modulus D = 2^b + 1. Profiles are registered in
the registry defined in Section 10.1. This document defines two:¶
HRW-SHA256: H is the full SHA-256 digest; b = 256,
D = 2^256 + 1.¶
HRW-SHA256-64: H is the first 8 bytes of the SHA-256 digest; b = 64,
D = 2^64 + 1. Its comparator operands are about a quarter of the size
of those of HRW-SHA256, which makes it the better choice where
selection cost matters.¶
Implementations MUST support both profiles. Both use the same hash function, so supporting the second adds only a truncation.¶
Hash profiles are used only by the Configuration Agent and MoQ-LB
instances. Relays and clients never compute selections, and nothing about
the profile appears in MOQT messages. Each hashed route names its profile
in its profile field, and HRW-SHA256 is used when the field is absent.
A MoQ-LB MUST reject a Routing Table that names a profile it does not
support. Changing a route's profile remaps its namespaces in the
same way as replacing its relays, and is applied through configuration
rotation (Section 7.1).¶
SUBSCRIBE_NAMESPACE and SUBSCRIBE_TRACKS carry a Track Namespace Prefix
P rather than a single namespace. The MoQ-LB routes P as if it were a
namespace: it selects the matching route with the longest prefix and, for a
hashed route, computes the hash key of P as described in
Section 3.2.2. More-specific routes under P are not consulted. If no
route matches P, the request is rejected (Section 4.1). In particular, a
request with an empty P (all namespaces) is rejected unless the Routing
Table contains a route with an empty prefix.¶
When P has at least key_fields fields, every namespace under P has the
same hash key as P, so the request reaches the relay that serves those
namespaces. Otherwise, namespaces under P can be placed on other relays,
and the Selected Relay learns of them through the relay network
(Section 2).¶
The following Routing Table is shown in CBOR diagnostic notation (Section 8 of [CBOR]), in which single-quoted strings are byte strings.¶
[
{ "prefix": ['conf', 'example.com'],
"kind": "static",
"rid": 'r-east-07' },
{ "prefix": ['live', 'sports'],
"kind": "hashed",
"rids": [ {"rid": 'r-east-01', "weight": 2},
{"rid": 'r-east-02', "weight": 2},
{"rid": 'r-east-03', "weight": 1} ] },
{ "prefix": [],
"kind": "hashed",
"rids": ['r-default-01', 'r-default-02'] }
]
¶
When a client opens a MOQT session to a MoQ-LB, the MoQ-LB completes the QUIC
or WebTransport handshake and the MOQT SETUP exchange
(Section 6.3 of [MoQTransport]). It then selects a relay for each routable
request (Section 3.1) under the Routing Table and health state in effect
when the request arrives, and forwards (Section 5) or redirects
(Section 6) the request to it.¶
A MoQ-LB rejects a routable request with REQUEST_ERROR
(Section 9.4 of [MoQTransport]) in the following cases:¶
No route matches the request's routing key: DOES_NOT_EXIST for
SUBSCRIBE, FETCH, TRACK_STATUS, SUBSCRIBE_NAMESPACE, and
SUBSCRIBE_TRACKS; UNINTERESTED for PUBLISH and PUBLISH_NAMESPACE.¶
The matching route has no Eligible Relay, or the configuration is too stale
to place the request (Section 7): INTERNAL_ERROR with a non-zero
Retry Interval.¶
The client is not authorized for the request
(Section 9): UNAUTHORIZED.¶
A configured resource limit is reached: EXCESSIVE_LOAD.¶
A MoQ-LB MUST NOT use an error code that is not registered for MOQT (Section 12.3 of [MoQTransport]).¶
Requests for the same namespace reach the same relay because selection is deterministic (Section 3.2.2.1): every MoQ-LB instance, for every session, selects the same relay under the same Routing Table and health state. A client that reconnects after the Relay Pool or the Routing Table has changed can be routed to a different relay.¶
In proxy mode, the MoQ-LB additionally binds each namespace to a relay for the lifetime of a client session, so that later requests on that namespace stay on the relay that already serves the session's earlier requests (Section 5.3). In redirect mode, the MoQ-LB keeps no per-request or per-namespace state; each request is redirected to its current Selected Relay.¶
Each relay in a Relay Pool has one of three health states. The Configuration Agent is authoritative for them and distributes them to every MoQ-LB.¶
Healthy: eligible for new requests.¶
Draining: not eligible for new requests, but still serving existing ones (for example, before maintenance).¶
Unhealthy: not expected to serve traffic.¶
A proxy-mode MoQ-LB also changes its local view of a relay on two events: it
marks the relay Draining when the relay sends GOAWAY on a backend control
stream, and Unhealthy when a backend session to the relay closes abruptly. It
keeps that view until the Configuration Agent reports a newer state. A
redirect-mode MoQ-LB has no backend sessions and only uses the states it
receives. Because local changes are not seen by other instances, instances
can briefly select different relays for the same namespace; a MoQ-LB SHOULD
report local changes to the Configuration Agent so that it can distribute a
consistent view.¶
New requests MUST NOT be assigned to relays that are not Eligible. For hashed routes, those relays are excluded from selection, and namespaces whose Selected Relay is unaffected keep it. For static routes, see Section 3.2.1.¶
A request is displaced when the relay it is on is no longer its Selected Relay: because a new Routing Table version maps its namespace elsewhere (Section 7.1), or because the relay becomes Draining.¶
Displaced requests MAY continue on their relay until the Drain Deadline. A proxy-mode MoQ-LB SHOULD move them (Section 5.7) before it.¶
At the Drain Deadline, or when the relay closes the session or the
GOAWAY Timeout expires, whichever comes first, a proxy-mode MoQ-LB moves
or ends the remaining requests.¶
A redirect-mode MoQ-LB holds no requests and cannot move them. When a relay
becomes Draining, the relay moves its clients with GOAWAY
(Section 6.1). A request displaced by a new Routing Table version
while its relay remains Healthy stays on that relay until it ends, the client
re-issues it, or the relay sends GOAWAY; the Drain Deadline does not apply
to it. Its relay still serves it through the relay network
(Section 2). A deployment that needs such requests moved can have the
relay send GOAWAY for them by means outside the scope of this document.¶
Requests on an Unhealthy relay cannot make progress. A proxy-mode MoQ-LB moves them at once, without waiting for the new relay to accept before switching; in redirect mode, the client detects the failure and returns to the MoQ-LB.¶
Requests that are ended because they could not be moved are ended with MOQT
mechanisms and registered codes: an established subscription with
PUBLISH_DONE and status GOING_AWAY (Section 12.4 of [MoQTransport]), a
pending request with REQUEST_ERROR and code GOING_AWAY, and any other
request stream with a stream reset using GOING_AWAY.¶
Relays use the MOQT GOAWAY message (Section 9.2 of [MoQTransport]) to
move work away from themselves, for example before maintenance or under
overload. A GOAWAY on the control stream moves the whole session; a
GOAWAY on a request stream moves only that request. In both modes, the
Routing Table, not the relay, decides where moved work goes: a relay does
not know the current Routing Table or what the client is authorized to
reach. Mode-specific handling is specified in Section 5.6 and
Section 6.1.¶
In proxy mode, the MoQ-LB terminates the client's MOQT session, opens backend MOQT sessions to Selected Relays, and forwards traffic in both directions.¶
When responding to the client's SETUP, the MoQ-LB responds on behalf of the
pool, advertising only the MOQT version and extensions
(Section 6.3.2 of [MoQTransport]) supported by every relay the client could
be routed to. A deployment that cannot guarantee a non-empty intersection
MUST partition the pool so that each MoQ-LB fronts only relays with
compatible capabilities. Limits that the MoQ-LB advertises to the client
(such as MAX_REQUEST_UPDATES) MUST NOT exceed what it can honor given the
limits its backends advertise.¶
The MoQ-LB does not open a backend MOQT session until it receives a routable
request, at which point it opens a session to the Selected Relay (if not
already open) and forwards the request. The backend SETUP MUST offer the
MOQT version and extensions already agreed with the client. Session-scoped
Setup Options that identify the endpoint or the client (AUTHORITY, PATH,
AUTHORIZATION TOKEN, MAX_AUTH_TOKEN_CACHE_SIZE, MOQT IMPLEMENTATION)
are set by the MoQ-LB for the backend session, following the authorization
model of Section 9. If a relay does not accept the agreed
version and extensions, the MoQ-LB rejects the request (Section 4.1) and
SHOULD report the mismatch to the Configuration Agent, since it indicates a
misconfigured pool.¶
Authorization token aliases (Section 8.9 of [MoQTransport]) are scoped to a session. The MoQ-LB MUST NOT forward an alias from one session to another; it resolves a client's alias to the token and either sends the token or registers its own alias on the backend session.¶
A MoQ-LB MAY multiplex multiple namespace routes onto one client-facing session by maintaining backend sessions to multiple relays. It MUST bound open backend sessions, concurrent requests, and per-session state; when a limit is reached, it rejects new requests (Section 4.1).¶
A proxy-mode MoQ-LB MUST maintain, per client-facing session, a table of request mappings. Each mapping binds exactly one client-facing request stream to exactly one backend request stream. The MoQ-LB creates an entry:¶
when it forwards a request from the client (PUBLISH, PUBLISH_NAMESPACE,
SUBSCRIBE, SUBSCRIBE_NAMESPACE, SUBSCRIBE_TRACKS, FETCH, or
TRACK_STATUS) to the Selected Relay; and¶
when a relay opens a request stream toward the MoQ-LB on a backend session,
such as a PUBLISH resulting from a SUBSCRIBE_TRACKS
(Section 4 of [MoQTransport]) or a PUBLISH_NAMESPACE within a
SUBSCRIBE_NAMESPACE prefix. The MoQ-LB opens a request stream toward the
client and forwards the request on it.¶
Each entry records:¶
the paired client-facing and backend request streams;¶
the Request ID translation between the client-facing and backend sessions;¶
the Track Alias translation between the two sessions, where the request type carries a Track Alias;¶
the original routing key (Section 3.1); and¶
the backend MOQT session carrying the pair, and therefore the Selected Relay.¶
Request ID and Track Alias values are per-session and MUST be kept independent across sessions. An entry is released only after both stream directions and all associated request state have completed.¶
Once a proxy-mode MoQ-LB has selected a relay for a Track Namespace within a
client session, it MUST keep using that relay for every later request on that
namespace in the session, unless the relay's requests on that namespace are
displaced or the relay is Unhealthy (Section 4.3). Affinity applies to every
request type and to the request's data streams and datagrams. It keeps
REQUEST_UPDATE messages and request cancellations
(Section 9.5 of [MoQTransport], Section 6.4.2.3 of [MoQTransport]) on the
relay that holds the request state, and keeps publishers and subscribers of a
namespace on the same relay.¶
The MoQ-LB MUST forward every control message on a mapped request stream to
the paired stream, in both directions, translating Request IDs and Track
Aliases (Section 5.2). It keeps both directions open for as long as
the peers do: except for TRACK_STATUS, which ends after its response,
requests carry asynchronous messages for their whole lifetime, such as
NAMESPACE and NAMESPACE_DONE on a SUBSCRIBE_NAMESPACE,
PUBLISH_SKIPPED on a SUBSCRIBE_TRACKS, PUBLISH_STATE_NOTIFY and
PUBLISH_DONE on subscriptions, and REQUEST_UPDATE from the request's
sender or, on a PUBLISH, from its subscriber. Relay-initiated GOAWAY is
handled as specified in Section 5.6.¶
Object data flows from a subscription's publisher to its subscriber on
unidirectional streams and, where used, datagrams. For a SUBSCRIBE or
FETCH from the client, and for a relay-initiated PUBLISH, it flows from
the relay to the client; for a PUBLISH from the client, it flows from the
client to the relay. For each data stream it receives on one leg, the MoQ-LB
opens a corresponding stream on the paired leg. A client that both publishes
and subscribes MUST be supported with independent data paths per request;
flow in one direction MUST NOT block flow in the other.¶
The MoQ-LB identifies the request a data stream or Object Datagram
(Section 11 of [MoQTransport]) belongs to from its header: a Subgroup
Header stream or an Object Datagram belongs to the subscription with that
Track Alias, and a FETCH Header stream belongs to the FETCH with that
Request ID. When forwarding, the MoQ-LB MUST translate the Request ID and
Track Alias using the request mapping and MUST keep everything else,
including the objects, intact.¶
A stream reset or FIN on one leg MUST be propagated to the paired stream. The MoQ-LB SHOULD forward datagrams in the order received and MAY drop them under congestion, as a relay would. Padding streams and datagrams (Section 11.5 of [MoQTransport]) are not forwarded; however, the MoQ-LB MAY send its own padding on either leg.¶
By default, a proxy-mode MoQ-LB forwards each client request as its own backend request and opens backend sessions per client session. An implementation MAY reduce the load it places on relays with the two optimizations below.¶
A MoQ-LB MAY carry requests from several client sessions on one backend
session to the same relay. It MUST do so only for client sessions that
agreed the same MOQT version and extensions as the backend session. Because
relay-initiated requests on a shared session cannot be attributed to one
client, a backend session that carries SUBSCRIBE_NAMESPACE or
SUBSCRIBE_TRACKS MUST NOT be shared.¶
A MoQ-LB MAY serve several client SUBSCRIBE requests for the same Full
Track Name, routed to the same relay, with a single backend SUBSCRIBE,
copying each delivered object to every coalesced client subscription.¶
Both optimizations apply after relay selection and never change it. Requests are shared or coalesced only when the current Routing Table selects the same relay for each of them; a new subscription is not coalesced onto a backend subscription that is displaced (Section 4.3.1). Both are subject to the authorization requirements in Section 9.¶
When coalescing subscriptions, the MoQ-LB:¶
MUST give each client subscription its own mapping entry, Request ID, and Track Alias (Section 5.2);¶
MUST coalesce a subscription only if the shared backend subscription
delivers every object the client's subscription would receive, considering
its location filter, subgroup, object ID, priority, and property filters,
FORWARD state, and group order; otherwise the subscription gets its own
backend SUBSCRIBE;¶
MUST answer a subscription that joins an existing backend subscription with
a SUBSCRIBE_OK of its own, using the Track Properties of the backend's
SUBSCRIBE_OK and the Largest Object it has observed;¶
MUST apply each subscriber's priority on its own client-facing leg;¶
MUST keep cancellations independent: a cancellation from one subscriber
MUST NOT affect the others, and the backend SUBSCRIBE is cancelled when
the last coalesced subscriber leaves;¶
MUST NOT let a REQUEST_UPDATE from one subscriber change what the
others receive; if the shared backend subscription cannot satisfy the
update, the MoQ-LB moves that subscriber to its own backend SUBSCRIBE;
and¶
MUST copy the relay's asynchronous messages on the shared backend
subscription (PUBLISH_STATE_NOTIFY and PUBLISH_DONE) to every coalesced
client subscription, each carrying that subscriber's Request ID.¶
When a coalesced backend subscription is moved (Section 5.7), it is moved once on behalf of all its subscribers.¶
A proxy-mode MoQ-LB receives a relay's GOAWAY on its backend session. It
MUST NOT forward the relay's New Session URI to the client or use it for
routing. On a GOAWAY on the control stream, the MoQ-LB marks the relay
Draining (Section 4.3) and moves every request on that backend session; on
a GOAWAY on a request stream, it moves only that request.¶
To move a request, the MoQ-LB selects a new relay under the current Routing Table and health state, and either re-issues the request itself or asks the client to re-issue it:¶
SUBSCRIBE: the MoQ-LB MAY re-issue it, following the approach of
Section 7.4.1 of [MoQTransport]. Let G be the group after the latest
group it has forwarded to the client, or the client's original start if it
has forwarded none. The MoQ-LB sends a SUBSCRIBE for the same Full Track
Name to the new relay, with the client's parameters and a filter that
starts at G. It forwards objects of groups before G only from the old
relay and objects of group G and later only from the new relay. Once the
new relay accepts and the old relay's streams for groups before G have
ended, the MoQ-LB cancels the old subscription. The client keeps its
Request ID and Track Alias and sees no change other than the group
boundary. If the new relay rejects the request, the MoQ-LB asks the client
to move instead, so that the client receives the relay's response itself.¶
TRACK_STATUS: not moved; it ends after its response.¶
FETCH: not moved. It completes on the old relay; if the old relay ends
it early, the MoQ-LB ends the client's FETCH with an error, and the
client may retry.¶
PUBLISH, PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE, and
SUBSCRIBE_TRACKS: the MoQ-LB asks the client to move.¶
To ask a client to move a request, the MoQ-LB sends a GOAWAY on the
client-facing request stream with a zero-length New Session URI, so that
the client re-issues the request on the same session
(Section 9.2 of [MoQTransport]), where the MoQ-LB routes it under the
current Routing Table. A MoQ-LB MAY do this for any request type; an
implementation that never re-issues requests is conformant. If every
request on a client session must move, the MoQ-LB MAY instead send one
GOAWAY on the client-facing control stream, also with a zero-length
New Session URI, so that the client reconnects to the MoQ-LB.¶
Moving a request does not transfer relay state. Objects in flight when the
old relay stops can be lost, a re-issued SUBSCRIBE resumes at a group
boundary, and a publisher that is asked to move re-publishes to the new
relay. Implementations SHOULD use the graceful switchover procedures in
Sections 7.4.1 and 7.6.1 of [MoQTransport] where clients and relays
support them. Requests that cannot be moved are ended as specified in
Section 4.3.1.¶
Client MoQ-LB R2 | | | | QUIC+WT handshake | |<--------------->| | | SETUP (versions, exts intersected over pool) | |<--------------->| | | SUBSCRIBE(ns=live/sports/game-7, name=audio) | |---------------->| | | | hash(ns) -> R2 | | | open backend session | | |<-------- QUIC+WT --------->| | | SETUP (same caps) | | |<-------------------------->| | | SUBSCRIBE (translated IDs) | | |--------------------------->| | | SUBSCRIBE_OK | | |<---------------------------| | SUBSCRIBE_OK | | |<----------------| | | | <objects on new stream> | | |<===========================| | <objects on paired stream, IDs translated> | |<================| |
In redirect mode, the MoQ-LB never opens backend sessions. It redirects each routable request (Section 3.1) to its Selected Relay instead of serving it:¶
The MoQ-LB selects the relay (Section 4) and rejects the request with
a REQUEST_ERROR carrying the REDIRECT error code and a Redirect
structure (Sections 9.4.1 and 12.3 of [MoQTransport]).¶
The Redirect's Connect URI is the URI of the Selected Relay from the
authenticated configuration (Section 7). The Redirect target is
the request's own Track Namespace and Track Name, with an empty Track Name
for PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE, and SUBSCRIBE_TRACKS.
The MoQ-LB chooses where a request goes, not what it asks for.¶
The Retry Interval is 0, so that the client does not retry the request
on the MoQ-LB, and a relay acting as client does not cache the redirect.¶
The client connects to the relay and retries the request there. Its session with the MoQ-LB stays open, and the client sends its later requests to the MoQ-LB, which redirects each of them the same way.¶
The MoQ-LB redirects each request separately, so every request reaches the
relay that the Routing Table selects for its namespace, even when a client's
namespaces map to different relays. In that case, the client has one session
to the MoQ-LB and one session to each relay that its requests were redirected
to. If the client already has a session to the Connect URI, it SHOULD retry
the request on that session instead of opening a new one.¶
A client can also send a request directly to a relay on a session it already has. The relay serves the request through the relay network (Section 2). However, the MoQ-LB does not see the request, so the Routing Table does not choose its relay, and the publishers and subscribers of that namespace might be on different relays.¶
A redirect adds one round trip to the MoQ-LB before the request reaches the relay. If the client does not yet have a session to that relay, it also has to set one up. After the MoQ-LB redirects a request, it keeps no state for it. A MoQ-LB MAY close a client session that has been idle for a configured time; the client opens a new session to the MoQ-LB when it has another request to send.¶
A MoQ-LB MUST NOT use its own service URI as the Connect URI, because
that would redirect the client back to itself. A client SHOULD limit how
many redirects it follows for a single request.¶
In redirect mode the client receives the relay's GOAWAY itself. Relays
fronted by a MoQ-LB SHOULD be configured to send the MoQ-LB service URI as
the New Session URI, so that the client re-issues the affected requests to
the MoQ-LB, which redirects each of them under the current Routing Table.
This is relay configuration only; relays need nothing specific to MoQ-LB.¶
When a relay fails, the client detects the failure of its session to the relay and re-issues its requests to the MoQ-LB. The MoQ-LB MUST NOT redirect them to a relay that is not Eligible.¶
The Configuration Agent is responsible for:¶
maintaining the Relay Pool membership and relay endpoints;¶
publishing configuration snapshots with monotonically increasing version
numbers;¶
monitoring relay health;¶
distributing authorization material shared with relays (for example, CAT keys per [CAT-4-MOQT] if used).¶
A configuration snapshot has the following structure, using the
NamespaceRoute defined in Section 3.2:¶
ConfigSnapshot = {
version: uint, ; monotonically increasing
relays: [+ Relay],
routes: [+ NamespaceRoute], ; the Routing Table
? attributes: { * text => any }
}
Relay = {
rid: bytes, ; Relay Identifier
uri: text, ; MOQT URI of the relay
health: "healthy" / "draining" / "unhealthy",
? attributes: { * text => any }
}
¶
RIDs MUST be unique within a snapshot and SHOULD be stable across versions.
Every RID referenced by a route MUST appear in relays. The uri is the
endpoint the MoQ-LB connects to in proxy mode and the Connect URI it sends
in redirect mode. A change in health only is still published as a new
version; it is handled by Section 4.3, not as a remap.¶
A MoQ-LB and the Configuration Agent MUST validate a snapshot in full and MUST reject an invalid one, including one that violates any constraint in Section 3. The encoding of snapshots on the wire is out of scope, provided byte strings are preserved exactly.¶
The transport between the Configuration Agent and MoQ-LB instances is out of scope, but it MUST authenticate both ends, protect integrity and confidentiality, and deliver complete snapshots inside an integrity-protected signature or equivalent authenticated envelope. Snapshots MUST be applied atomically.¶
MoQ-LB instances MUST retain a last-known-good snapshot and report their
configuration age and active version to the Configuration Agent, so that
version skew across instances can be detected. A deployment MUST define a maximum
configuration age. Beyond that age, an instance MUST NOT place new requests
that depend on the stale health state, and rejects them (Section 4.1).¶
When a MoQ-LB loads a snapshot with a higher version, it:¶
validates it and atomically activates it for new requests;¶
treats existing requests whose relay is no longer selected for their namespace as displaced (Section 4.3.1).¶
A MoQ-LB MUST NOT apply a snapshot whose version is less than or equal to
that of its active snapshot. Rollback MUST use a new, higher version that
contains the desired prior contents and an authenticated operator action.¶
Adding or removing a relay changes assignments for new requests under the new
snapshot. With equal weights across n relays, adding one relay assigns
approximately 1/(n+1) of keys to it; removing a relay changes only the keys
that were assigned to that relay. With unequal weights, the moved fraction
depends on the weights. Existing requests are handled as displaced requests
(Section 4.3.1). Planned removals SHOULD first set the relay's weight to 0 or
mark it Draining, and remove it from the snapshot only after its requests
have drained.¶
A MoQ-LB operates above the QUIC layer. MoQ-LB instances do not replicate session or request state. When MoQ-LB instances are replicated behind a connection-level load balancer, that load balancer MUST deliver all packets of a QUIC connection to the instance holding its state; QUIC-LB [QUIC-LB] is one way to achieve this. The RID used by the MoQ-LB is unrelated to the server identifier used by QUIC-LB.¶
When an instance fails, its client sessions end. Clients reconnect and re-issue their requests, which are routed under the current Routing Table; because selection is deterministic (Section 3.2.2.1), they reach the same relays as before unless the pool has changed.¶
If a backend relay is itself fronted by a per-relay QUIC load balancer, the MoQ-LB can list the URI of that load balancer as one relay only if the servers behind it behave as a single relay for MOQT purposes, so that publishers and subscribers of a namespace still meet. Otherwise each server SHOULD be listed as a separate relay with its own RID and URI.¶
The MoQ-LB is a security boundary: it terminates client sessions in proxy
mode and observes namespace-bearing control messages in both modes. In proxy
mode, it MUST enforce the same authorization policy as the relays for
PUBLISH, PUBLISH_NAMESPACE, subscriptions, and fetches before forwarding
them. In redirect mode, the relay authorizes each request it receives, and the
MoQ-LB authorizes requests before redirecting them as described in
Section 9.4. Routes MUST be isolated by tenant or authority where
required.¶
The Configuration Agent can redirect traffic across the deployment. Its credentials and updates MUST be protected against forgery, replay, rollback, and unauthorized route changes (Section 7). Stale routing state and split-brain route decisions should be treated as security and availability failures.¶
Hashed routing uses an unkeyed deterministic hash. A party that can choose
Track Namespaces can search for names that map to a particular RID in order
to concentrate load on one relay. This search is cheap whatever the hash
profile: on average, a relay with weight w out of a total weight W is
selected by one name in every W / w tried, so the choice of hash does not
protect against it. The defenses are authorization and resource limits.
Namespace-bearing requests are accepted only after authentication and
authorization, so an attacker must hold valid credentials and act within an
authorized namespace scope. Deployments that admit untrusted publishers or
tenants SHOULD apply per-tenant quotas and admission control, and MUST NOT
rely on hashed routing to prevent targeted load concentration.¶
A redirect reveals the URI of a relay. A MoQ-LB SHOULD authorize a request
before redirecting it, so that unauthorized clients cannot map the Relay Pool
and cannot use the MoQ-LB to steer load. The Connect URI MUST be taken from
the authenticated configuration, never from the request or from a relay, so
that the MoQ-LB cannot be used as an open redirector. Each relay MUST present
a TLS identity and MOQT authority valid for the service. Relays MUST still
authorize every redirected request themselves; a redirect is not an
authorization decision. Redirect loops are prevented by the rules in
Section 6.¶
MoQ-LB implementations MUST bound parsing work, route lookups, per-session stream mappings, fan-out, queued bytes, and concurrent backend connections. They SHOULD expose limits and overload behavior to operators, apply admission control before resource exhaustion, and avoid logging authorization tokens or untrusted full namespace values by default.¶
This document requests that IANA create two registries in the "Media over QUIC" registry group.¶
The "MoQ-LB Hash Profiles" registry lists hash profiles for hashed routes
(Section 3.2.2.2). The registration policy is Specification Required
(Section 4.6 of [RFC8126]). Each entry has a Name (a text string), a hash
algorithm, an output width b in bits, and a Reference. The initial
contents are:¶
| Name | Hash Algorithm | b | Reference |
|---|---|---|---|
| HRW-SHA256 | SHA-256 | 256 | This document |
| HRW-SHA256-64 | SHA-256, first 8 bytes | 64 | This document |
The "MoQ-LB Route Kinds" registry lists values of the kind field of a
Namespace Route (Section 3.2). The registration policy is
Specification Required (Section 4.6 of [RFC8126]). Each entry has a Kind
(a text string), a description, and a Reference. The initial contents are:¶
| Kind | Description | Reference |
|---|---|---|
| static | Pins a prefix to one relay | This document |
| hashed | Weighted rendezvous hashing over relays | This document |
A meeting application gives each participant a namespace of the form
(example.com, <meeting>, <participant>). A participant finds the others with
SUBSCRIBE_NAMESPACE (example.com, <meeting>) and learns one participant's
tracks with SUBSCRIBE_TRACKS (example.com, <meeting>, <participant>).¶
The deployment uses this route:¶
{ "prefix": ['example.com'], "kind": "hashed", "key_fields": 2,
"rids": ['r1', 'r2', 'r3'] }
¶
With key_fields: 2, every namespace in a meeting has the same hash key, so
all of the meeting's publishers and subscribers end up on the same relay,
which is where fan-out happens. Different meetings still spread across the
pool. Both prefix requests above have at least two fields, so they reach
that relay too (Section 3.3). In redirect mode, the flow could
look like this:¶
Alice Bob MoQ-LB r2 | | | | | PUBLISH_NAMESPACE (ex,m1,alice) | |--------------------------->| | | | | hash(ex,m1) -> r2 | | REDIRECT (r2) | | |<---------------------------| | | PUBLISH_NAMESPACE, then media | |-------------------------------------------------->| | | SUBSCRIBE_NAMESPACE (ex,m1) | | |------------->| | | | REDIRECT (r2)| | | |<-------------| | | | SUBSCRIBE_NAMESPACE | | |------------------------------------>| | | NAMESPACE (ex,m1,alice), media | | |<------------------------------------|
Each request is redirected to the meeting's relay with a REQUEST_ERROR
carrying REDIRECT, and the client retries it there. Bob's later
SUBSCRIBE for Alice's tracks goes through the MoQ-LB the same way; it is
redirected to r2 as well and reuses Bob's existing session to r2. A
participant who also joins a second meeting, hashed to another relay, gets
that meeting's requests redirected there. In proxy mode, the same requests
would flow through the MoQ-LB instead, which could coalesce subscriptions
from its clients (Section 5.5).¶
An encoder publishes each camera of a live event as
(sports.example, <event>, <camera>), and a large audience subscribes to
individual renditions, sometimes using FETCH to rewind a little.¶
Routing viewers by namespace would put every viewer of a popular event on
one relay, so in this case the MoQ-LB is placed only in front of an origin
tier, using the same kind of hashed route with key_fields: 2. Viewers
connect to edge relays chosen by location or load, and the edge relays act
as MOQT clients of the MoQ-LB: each edge keeps one session to it and follows
the redirect of each event's requests to that event's origin. MOQT allows a
relay to forward a REDIRECT to its downstream subscribers instead of
following it (Section 12.3 of [MoQTransport]); edges in this deployment
are configured to follow it themselves, since forwarding it would send
viewers to the origin tier.¶
Encoder Viewers (many)
| | | |
| PUBLISH_NAMESPACE (sp, ev7, cam-1) v v v
v +-----------+
+--------+ REDIRECT (O2) | Edge E7 | chosen by
| MoQ-LB |<-----------------------------+-----------+ location/load
+--------+ SUBSCRIBE on cache miss |
| |
| hash(sp, ev7) -> O2 |
v v
+--------------------------------------------------+
| Origin O2 (home relay of event ev7) |
+--------------------------------------------------+
All cameras of an event share one origin relay, and each edge pulls a track
from it once however many viewers it serves, so the origin's load grows
with the number of edges rather than the number of viewers. A request that
spans events, such as SUBSCRIBE_NAMESPACE (sports.example), is sent to
the relay selected for that prefix, and the relay network delivers the
namespaces of every event to it.¶
The design borrows from the QUIC-LB architecture [QUIC-LB].¶