MoQ for the Curious

Chapter 05

Relays and Distribution

On this page

A relay receives content from a publisher and forwards it to subscribers. It moves the copying work off the publisher’s connection, allowing the same publication to reach more viewers.

How does one copy from the lecturer become a copy for every student? We’ll follow the relay’s subscriptions, look at what it caches, and see how it handles viewers with different connection speeds. We’ll also look at which jobs need other parts of the service.

How does a relay share content?

The relay subscribes to the lecturer’s content, then publishes it to the students. It has a separate connection on each side and reads the MoQ messages to decide what to forward.

flowchart TD
  accTitle: A relay shares one source among several viewers
  accDescr: The publisher sends one audio track to a relay. The relay sends a separate copy to Alice, Bob, and a recorder.
  P["Lecturer publishes audio"] -->|One upstream copy| R["Relay"]
  R --> A["Alice"]
  R --> B["Bob"]
  R --> C["Recorder"]

With several interested viewers, a relay can share upstream content and forward it to each downstream connection. If a second relay is closer to another audience, it can repeat that pattern. Viewers in each region can receive content from their local relay.

Each downstream connection still needs its own bytes. For one 2 Mb/s track and 100 viewers, the payload rates are:

Distribution Lecturer sends Relay sends Each viewer receives
Lecturer connects directly to every viewer 200 Mb/s — 2 Mb/s
Lecturer sends one copy to a relay 2 Mb/s 200 Mb/s 2 Mb/s

Transport overhead adds to these figures. With more relays, we can do the same calculation for each link.

What happens when one relay is full?

Moving the copies to a server gives us somewhere to add capacity. Suppose our 100 students are split between two regions. We could put a relay in each region and have each receive one copy from an upstream relay. The upstream relay sends two copies, and each regional relay sends 50. The lecturer still sends one.

This is how the distribution tree grows: each relay shares the content coming in and makes copies for the viewers or relays below it. We still pay for those outgoing bytes and connections, but adding viewers no longer means asking the lecturer’s laptop and home connection to carry every copy. The service has to provide enough relay capacity and route viewers to it. See the MOQT relay motivation.

Why does everyone connect to a server?

We need a reachable relay even if there are only two participants behind private networks. In this browser setup, WebTransport connects each client to a server. It doesn’t open a direct browser-to-browser connection, and MOQT adds no NAT traversal. The lecturer and student therefore both connect out to the relay, which carries the media between them. The opening comparison explains how this differs from WebRTC’s ICE, STUN, and TURN setup.

WebRTC can use ICE to test paths between the participants, with STUN to discover addresses and TURN to carry traffic if a direct path fails. That gives a two-person call a chance to avoid a media relay. Our browser MoQ setup connects both participants to the relay from the start.

The relay receives those connections and forwards the requested tracks between them. It doesn’t run a NAT traversal procedure on the clients’ behalf. If the network blocks the chosen transport, the client still needs a supported fallback or another way to reach the server.

Can subscriptions reduce encoding work too?

Suppose the lecturer offers high- and low-resolution video, but every viewer chooses the lower resolution. Forwarding only that track saves network traffic. Avoiding the unused high-resolution encoding can also save work at the publisher.

The moq.dev concepts guide describes how subscriptions carry demand back to the publisher. An application using its library can start an encoder for the first subscriber and stop it after the last one leaves. A recorder counts too, so closing every player may still leave someone receiving the track. The application has to implement this behavior; MOQT doesn’t control the encoder.

The moq.dev article On a Boat gives another example: several cameras share a limited uplink, with higher-resolution tracks requested only when needed. Subscriptions can save bandwidth even with a single viewer.

There’s a startup cost to consider. When the first viewer asks for an idle rendition, the publisher needs to start encoding and send the configuration and a picture the viewer can decode. Until then, that viewer has to wait.

What does a relay need to know?

A relay uses names, group boundaries, priorities, and delivery rules. It need not inspect the encoded picture to forward it. The same relay can forward audio, video, and application data.

For this to work, the publisher needs to put the right media at each group boundary. Suppose the relay sends a new viewer a group that depends on pictures from the previous group. The relay has delivered what was requested, but the player still can’t show it.

How does caching help new viewers?

A caching relay can hold recently received objects. A new viewer may obtain a useful group without waiting for the original publisher to send it again. An object is identified by its full track name, group ID, and object ID within a scope. Its payload cannot change, allowing different viewers to reuse the same cached bytes. Some object properties can change under their own rules; immutability does not apply to every cached field.

How much history is available depends on the relay. MOQT allows caching, and each relay decides what to keep within its memory budget. A viewer joining late needs to find out whether the objects it wants are still there.

For the lecture, retaining a few useful recent groups may improve startup. Keeping the entire class for replay is a separate storage feature with different costs and lifetime requirements.

How does delivery differ between viewers?

The lecturer’s connection can be fast while one viewer’s connection is slow. A relay has to make downstream delivery decisions for each path. Slowing every viewer to match the slowest one would defeat the point of independent subscriptions.

Alice’s connection may deliver each new group immediately, while Bob’s connection builds a queue. Their playback delay differs even though both request the same publication. The relay can choose delivery settings for each subscription according to that viewer’s path.

A live viewer and a recorder can also have different needs. The viewer may skip an old group to stay near live, while the recorder waits for it. If the publisher or relay retains the objects and spare bandwidth becomes available, the recorder can catch up later. Once those objects have been discarded upstream, a more patient subscriber cannot recover them from that source.

Measure at the edges, then trace through the hops. A healthy publisher-to-relay connection says little about a congested relay-to-viewer connection.

Which jobs belong to the wider service?

Suppose a student needs low-resolution video, but the publisher only sends high resolution. Someone has to create the smaller version. A transcoder does that by decoding and re-encoding the media. The service might run one alongside a relay, which then forwards the resulting objects.

The service also has to choose nearby relays, connect them, and recover when one fails. The moq.dev project includes clustering features to help with this setup.

Before scaling the lecture, draw the actual publisher, relay, and viewer paths. Mark where content is cached and where authorization is checked.

Sources and further reading