You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
moq-subscriber-count: use a fire-and-forget message, not a parameter
Restructure the moq-transport extension from a Message Parameter on
SUBSCRIBE/REQUEST_UPDATE to a dedicated SUBSCRIBER_COUNT control
message on the subscription's request stream.
In moq-transport a REQUEST_UPDATE consumes a Request ID (Section 10.1)
and obliges the receiver to answer with REQUEST_OK/REQUEST_ERROR
(Section 10.9). Refreshing a once-per-second telemetry value through
it would be a request/response transaction whose stated purpose is to
modify the subscription -- a poor fit. The dedicated message rides the
existing request stream, consumes no Request ID, and elicits no
response. (Request ID exhaustion is not the concern: MAX_REQUEST_ID /
REQUESTS_BLOCKED were removed in -18.)
The asymmetry with moq-lite is deliberate: moq-lite's SUBSCRIBE_UPDATE
is already fire-and-forget (no response, no ID), so the field-based
design there is fine and is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: draft-lcurley-moq-subscriber-count.md
+45-37Lines changed: 45 additions & 37 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -23,8 +23,9 @@ informative:
23
23
24
24
--- abstract
25
25
26
-
This document defines a Subscriber Count parameter for MoQ Transport {{moqt}}.
27
-
The parameter carries the number of subscribers a subscription represents and telescopes up the relay fan-out tree, so a publisher can learn its total downstream audience across any number of relay hops by reading a single value on its upstream subscription.
26
+
This document defines a SUBSCRIBER_COUNT message for MoQ Transport {{moqt}}.
27
+
The message carries the number of subscribers a subscription represents and telescopes up the relay fan-out tree, so a publisher can learn its total downstream audience across any number of relay hops by reading a single value on its upstream subscription.
28
+
It is a fire-and-forget telemetry message that consumes no Request ID and elicits no response.
28
29
29
30
--- middle
30
31
@@ -37,14 +38,15 @@ A publisher in {{moqt}} often wants to know how many subscribers are receiving a
37
38
This is straightforward when a subscriber connects directly, but {{moqt}} is designed around relays: a relay aggregates many downstream subscriptions for the same Track into a single upstream subscription toward the origin (its "fan-out" tree).
38
39
The origin sees one upstream subscription per relay, not the individual subscribers behind it, so it cannot count its true audience without out-of-band coordination.
39
40
40
-
This document defines a Subscriber Count parameter that rides on the subscription itself.
41
-
Each subscription reports how many subscribers it represents.
41
+
This document defines a SUBSCRIBER_COUNT message that reports, per subscription, how many subscribers it represents.
42
42
A relay sets the count on its single upstream subscription to the **sum** of the counts of the downstream subscriptions it aggregates.
43
43
Because the count is a sum reduced up the existing subscription tree, it telescopes for free: at the origin, the count on each upstream subscription is the total number of subscribers reachable through that relay, transitively, across any number of hops.
44
44
45
-
The count is a property of the subscriber side of the subscription.
46
-
In {{moqt}} a relay already merges subscriber-supplied parameters when it aggregates downstream subscriptions (subscriber properties fan *in*), so this parameter follows the existing aggregation path rather than introducing a new one.
47
-
It changes as subscribers join and leave and is refreshed with REQUEST_UPDATE ({{moqt}} Section 10.9).
45
+
The count changes as subscribers join and leave, so it is sent repeatedly over the life of a subscription.
46
+
This is pure telemetry: the count never modifies the subscription, and the publisher only observes it.
47
+
For that reason it is carried in a dedicated, fire-and-forget message rather than in REQUEST_UPDATE ({{moqt}} Section 10.9).
48
+
A REQUEST_UPDATE consumes a Request ID ({{moqt}} Section 10.1) and obliges the receiver to answer with a REQUEST_OK or REQUEST_ERROR ({{moqt}} Section 10.9) — a request/response transaction whose purpose is to *modify* the subscription, which is a poor fit for a value pushed once per second that changes nothing.
49
+
The SUBSCRIBER_COUNT message instead rides the subscription's existing request stream, consumes no Request ID, and elicits no response.
The extension is available on a hop only if both endpoints on that hop included this option.
63
-
The extension is negotiated independently on each hop: a relay MAY support it upstream but not downstream, or vice versa, and MUST NOT forward the parameter onto a hop that did not negotiate it.
65
+
The extension is negotiated independently on each hop: a relay MAY support it upstream but not downstream, or vice versa.
64
66
65
-
This extension only adds an optional parameter to existing messages, so a peer that receives the parameter without having negotiated the extension MUST ignore it rather than treating it as an error.
66
-
Ignoring an un-negotiated count simply collapses that subscription's contribution to the default of `1` (see [Semantics](#semantics)); it does not break delivery.
67
+
Negotiation is mandatory before the message is sent.
68
+
{{moqt}} (Section 10) requires an endpoint that receives an unknown control message type to close the session, so — unlike an optional parameter, which can be ignored — a SUBSCRIBER_COUNT message cannot be sent speculatively.
69
+
An endpoint MUST NOT send SUBSCRIBER_COUNT on a hop that did not negotiate this extension.
67
70
68
71
69
-
#Subscriber Count Parameter
70
-
This document defines a new parameter for use in the Parameters field of the SUBSCRIBE ({{moqt}} Section 10.7) and REQUEST_UPDATE ({{moqt}} Section 10.9) messages, encoded as a Key-Value-Pair ({{moqt}} Section 1.4.3).
72
+
#SUBSCRIBER_COUNT Message
73
+
This document defines a new control message, sent on a subscription's request stream ({{moqt}} Section 3.3) by the endpoint that opened it (the subscriber, which for an upstream subscription is the relay).
71
74
72
75
~~~
73
-
SUBSCRIBER_COUNT Parameter {
74
-
Type (vi64) = 0xC0116
75
-
Value (vi64) ; Subscriber Count - 1
76
+
SUBSCRIBER_COUNT Message {
77
+
Type (vi64) = 0xC0117
78
+
Length (16)
79
+
Subscriber Count (vi64) ; encoded as count - 1
76
80
}
77
81
~~~
78
82
79
-
The Type `0xC0116` is even, so per {{moqt}} Section 1.4.3 the Value is a single varint with no length prefix.
83
+
The message MUST NOT be the first message on the request stream; it follows the SUBSCRIBE ({{moqt}} Section 10.7) that opened the stream.
84
+
It consumes no Request ID ({{moqt}} Section 10.1), and the receiver MUST NOT respond to it.
85
+
A subscriber MAY send it any number of times over the life of the subscription to refresh the count.
80
86
81
-
**Value**:
87
+
**Subscriber Count**:
82
88
The number of subscribers this subscription represents, including the subscriber itself, encoded as the count minus one.
83
89
The subscription is the implicit `1`, so the wire value is `Subscriber Count - 1`: a leaf encodes `0`, and a relay encodes its summed total minus one.
84
90
85
91
Encoding `count - 1` rather than the count itself makes a count of `0` impossible to represent on the wire: the minimum encodable value is `0`, which decodes to a count of `1`.
86
92
A subscription can never report fewer subscribers than itself.
87
93
88
-
A subscriber MAY include this parameter in SUBSCRIBE and in any REQUEST_UPDATE.
89
-
If the parameter is absent, the wire value is treated as `0`, so the count for that subscription is `1`.
90
-
A leaf subscriber therefore reports `1`, whether by sending a wire value of `0` or by omitting the parameter.
94
+
Until a SUBSCRIBER_COUNT message is received for a subscription, its count is `1`.
95
+
A leaf subscriber that represents only itself therefore need not send the message at all.
91
96
92
97
93
98
# Semantics
94
99
The count is a reduction up the subscription tree.
95
100
96
101
A **leaf subscriber** (one that is not a relay) represents itself: a count of `1`.
97
-
It MAY omit the parameter to mean the same thing.
102
+
It need not send the message to mean this.
98
103
99
-
A **relay** that aggregates one or more downstream subscriptions for a Track into a single upstream subscription sets the SUBSCRIBER_COUNT on that upstream subscription to the **sum** of the counts of the downstream subscriptions, treating an absent downstream parameter as `1`.
100
-
When a downstream count changes, or a downstream subscription is added or removed, the relay recomputes the sum and, if it changed, refreshes the upstream value with REQUEST_UPDATE.
104
+
A **relay** that aggregates one or more downstream subscriptions for a Track into a single upstream subscription sets the count it reports on that upstream subscription to the **sum** of the counts of the downstream subscriptions, treating a downstream subscription that has not reported as `1`.
105
+
When a downstream count changes, or a downstream subscription is added or removed, the relay recomputes the sum and, if it changed, sends a SUBSCRIBER_COUNT message upstream with the new total.
101
106
102
107
A relay MUST report at least `1` for any upstream subscription it is holding open, even if it currently has no live downstream subscribers (for example, a subscription briefly retained for reuse).
103
108
The `count - 1` encoding enforces this floor automatically: a sum of `0` still encodes a wire value of `0`, which decodes upstream as `1`.
104
109
A held subscription cannot represent fewer subscribers than itself.
105
110
106
-
Because each relay reports the sum of its subtree, the value telescopes: at the origin, the SUBSCRIBER_COUNT on a given upstream subscription is the total number of leaf subscribers reachable through that subscription, across any number of relay hops.
111
+
Because each relay reports the sum of its subtree, the value telescopes: at the origin, the count on a given upstream subscription is the total number of leaf subscribers reachable through that subscription, across any number of relay hops.
107
112
A publisher reads its total audience for a Track as the sum of the counts of the subscriptions it is serving.
108
113
109
-
This parameter alters no delivery behavior.
114
+
The message alters no delivery behavior.
110
115
It MUST NOT influence prioritization, caching, congestion response, or any other distribution decision; it is informational telemetry carried alongside the subscription.
111
116
112
117
113
118
# Rate Limiting
114
-
Subscriber churn can change the count rapidly, and at a busy relay each change would otherwise produce an upstream REQUEST_UPDATE.
119
+
Subscriber churn can change the count rapidly, and at a busy relay each change would otherwise produce an upstream SUBSCRIBER_COUNT message.
115
120
116
-
A relay SHOULD rate-limit REQUEST_UPDATE messages whose only change is the SUBSCRIBER_COUNT, coalescing changes that occur within a short window (on the order of a second) and then forwarding the latest sum.
117
-
Because the parameter carries the current count rather than a delta, a change that reverts within the window — a subscriber that joins and leaves, or leaves and returns — requires no upstream message at all.
121
+
A relay SHOULD rate-limit SUBSCRIBER_COUNT messages per subscription, coalescing changes that occur within a short window (on the order of a second) and then sending the latest sum.
122
+
Because the message carries the current count rather than a delta, a change that reverts within the window — a subscriber that joins and leaves, or leaves and returns — requires no upstream message at all.
118
123
119
-
A relay MUST NOT coalesce a SUBSCRIBER_COUNT change with, or delay it behind, a REQUEST_UPDATE that also changes delivery-affecting fields; those are forwarded according to {{moqt}} without additional delay.
124
+
Because the message is independent of REQUEST_UPDATE, this rate limiting never delays a genuine subscription change: delivery-affecting updates are forwarded according to {{moqt}} without regard to the count's window.
120
125
121
126
122
127
# Security Considerations
@@ -133,7 +138,8 @@ A relay aggregates the values it receives but cannot attest to the honesty of it
133
138
134
139
**Churn amplification.**
135
140
A subscriber that rapidly joins and leaves could attempt to amplify control traffic toward the origin by forcing repeated count updates.
136
-
The rate-limiting in [Rate Limiting](#rate-limiting) bounds this: the upstream message rate per subscription is capped regardless of downstream churn, and reverts within the window are suppressed entirely.
141
+
The rate-limiting in [Rate Limiting](#rate-limiting) bounds this: the upstream SUBSCRIBER_COUNT rate per subscription is capped regardless of downstream churn, and reverts within the window are suppressed entirely.
142
+
Because the message consumes no Request ID and elicits no response, this churn cannot exhaust an identifier space or force the origin into matching replies.
137
143
138
144
This extension introduces no other security considerations beyond those described in {{moqt}}.
139
145
@@ -152,15 +158,17 @@ A high, distinctive value is chosen to avoid the low ranges reserved by {{moqt}}
152
158
|:------|:-----|:----------|
153
159
| 0xC0117 | SUBSCRIBER_COUNT | This Document |
154
160
155
-
## MOQT Message Parameters
161
+
## MOQT Message Types
156
162
157
-
This document requests a registration in the "Message Parameters" registry ({{moqt}} Section 15.7).
158
-
The value is **even** so that, per the Key-Value-Pair encoding ({{moqt}} Section 1.4.3), the count is carried as a single varint with no length prefix.
159
-
A high, distinctive value is chosen to avoid the low ranges reserved by {{moqt}} and to minimize collisions with provisional registrations by other extensions; it also avoids the greasing pattern (`0x7f * N + 0x9D`).
163
+
This document registers a control message type.
164
+
{{moqt}} does not yet establish an IANA registry for message types, so this is a provisional codepoint pending such a registry; the value is chosen to be high and distinctive to avoid the low ranges {{moqt}} assigns and to minimize collisions with provisional registrations by other extensions, and it avoids the greasing pattern (`0x7f * N + 0x9D`).
165
+
This is the same value as the SUBSCRIBER_COUNT Setup Option above; Setup Options and message types are independent namespaces, so the shared value is unambiguous.
160
166
161
-
| Value | Name | Reference |
162
-
|:------|:-----|:----------|
163
-
| 0xC0116 | SUBSCRIBER_COUNT | This Document |
167
+
The Stream column has the meaning defined by {{moqt}} Section 10: "Request"indicates the message is carried on a bidirectional request stream. The message is not marked "First": it never opens a request stream.
168
+
169
+
| Value | Name | Stream | Reference |
170
+
|:------|:-----|:-------|:----------|
171
+
| 0xC0117 | SUBSCRIBER_COUNT | Request | This Document |
0 commit comments