From 56ae3638380bdfd75bdf23b8977b6069903ef4f6 Mon Sep 17 00:00:00 2001 From: AAA <35992542+TNTcraftHIM@users.noreply.github.com> Date: Sat, 22 Aug 2026 22:07:34 +0800 Subject: [PATCH] docs(route): define deployment-wide SFU admission --- .../0005-automatic-hybrid-media-routing.md | 14 ++++++++ docs/deployment.md | 24 +++++++++----- docs/research/low-server-media-routes.md | 33 +++++++++++++++++++ docs/方案设计.md | 2 ++ docs/需求理解.md | 3 +- 5 files changed, 66 insertions(+), 10 deletions(-) diff --git a/docs/adr/0005-automatic-hybrid-media-routing.md b/docs/adr/0005-automatic-hybrid-media-routing.md index b592a5c..c934349 100644 --- a/docs/adr/0005-automatic-hybrid-media-routing.md +++ b/docs/adr/0005-automatic-hybrid-media-routing.md @@ -184,6 +184,20 @@ SFU and TURN remain bounded fallback resources with independent deployment-wide admission. Resource exhaustion produces the next bounded candidate, an explicit wait, or failure; it never creates unbounded central fanout. +For the single-process deployment, SFU admission is one injected in-memory +authority with deployment-wide ingress and egress counters. Enabling LiveKit +requires explicit positive safe-integer `SFU_INGRESS_CAPACITY` and +`SFU_EGRESS_CAPACITY` values; neither has a product default and neither is +derived from endpoint capacity, Viewer admission, or a fixed root count. One +Host publication consumes one ingress unit and every SFU subscription consumes +one egress unit. A concurrently media-producing candidate consumes its actual +units in addition to the committed generation. The controller reserves against +the exact room, share, and publication generation before token issuance, +promotes that reservation at route commit, and idempotently releases it on +abort, timeout, participant loss, share rollover, room stop, or room deletion. +A multi-process application deployment requires a shared atomic admission +authority before it may claim these values are deployment-wide. + ## Current Production Divergence Production release `9461e20` predates this revision. Its exact behavior is owned diff --git a/docs/deployment.md b/docs/deployment.md index 88e107f..4e39efe 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -219,7 +219,8 @@ PEER_ASSISTED_MEDIA=true LIVEKIT_URL=wss://share.example.com LIVEKIT_API_KEY= LIVEKIT_API_SECRET= -MAX_SFU_ROOTS_PER_ROOM=2 +SFU_INGRESS_CAPACITY= +SFU_EGRESS_CAPACITY= ``` The rejected `PEER_ICE_TURN_*` participant-wide tuple is removed; supplying any @@ -247,7 +248,7 @@ errors to the result. `PEER_ASSISTED_ROOM_IDS` is retired. Supplying it, even blank, fails startup so that a stale room-1 deployment cannot silently retain the old scope. With `PEER_ASSISTED_MEDIA=true`, every normal room receives peer-assisted routing, -optional LiveKit fallback, and the same per-room root/fanout/failure guards. +optional LiveKit fallback, and the same deployment resource/fanout/failure guards. Every ordinary peer connection remains STUN-only; selected-edge TURN is still issued only to a current controller-selected edge. There is no browser control, percentage rollout, or second router. @@ -282,14 +283,19 @@ sender atomically on `screener-v6`; restore the exact prior environment and release together when rolling back. The three `LIVEKIT_*` values must either all be absent or all be present, and a -complete tuple requires `PEER_ASSISTED_MEDIA=true`. An empty tuple keeps the -optional SDK and server path dormant. `LIVEKIT_URL` must be a plain `ws:` or -`wss:` origin with no `/rtc` suffix; production requires `wss:`. +complete tuple requires `PEER_ASSISTED_MEDIA=true` plus explicit positive +safe-integer `SFU_INGRESS_CAPACITY` and `SFU_EGRESS_CAPACITY` values. The two +capacities have no defaults and must be selected from the instance's measured +publisher/subscriber/bitrate and accepted concurrency matrix. Supplying either +capacity without the complete fallback configuration fails startup rather than +silently enabling or ignoring a partial policy. An empty tuple with neither +capacity keeps the optional SDK and server path dormant. `LIVEKIT_URL` must be a +plain `ws:` or `wss:` origin with no `/rtc` suffix; production requires `wss:`. `LIVEKIT_API_SECRET` must contain at least 32 bytes and must not reuse -`SITE_ACCESS_PASSWORD`. In that same held release, `MAX_SFU_ROOTS_PER_ROOM` defaults to -2 and accepts only 1 or 2; it is ignored when LiveKit is not configured. These -credentials authorize short-lived LiveKit room tokens and do not provide E2EE: -the LiveKit operator can access ordinary SFU media. +`SITE_ACCESS_PASSWORD`. These credentials authorize short-lived LiveKit room +tokens and do not provide E2EE: the LiveKit operator can access ordinary SFU +media. The current admission owner is one Screener process; run only one +application process until a shared atomic ledger is implemented. For the first candidate canary, use an isolated instance and a protected persistent room whose ID is stable across restarts. Restart the application and diff --git a/docs/research/low-server-media-routes.md b/docs/research/low-server-media-routes.md index b231dff..4db1003 100644 --- a/docs/research/low-server-media-routes.md +++ b/docs/research/low-server-media-routes.md @@ -533,6 +533,39 @@ the server must. Redundancy can improve recovery but costs traffic and coordination; removing redundancy leaves a recovery interval after a relay is lost. +## Deployment-Wide SFU Admission + +LiveKit's official benchmark guidance says each room must fit on one SFU node +and identifies published tracks, subscribers, and bytes forwarded per subscriber +as separate capacity drivers. Its room `maxParticipants` setting is a participant +count guard; it does not express Screener's Host-publication ingress or +per-subscription egress budget. The application therefore needs its own typed +admission boundary instead of treating a root count or the SDK room setting as +resource evidence. + +For the current single application process, one O(1) ledger owns two explicitly +configured positive safe-integer capacities: each Host publication uses one +ingress unit and each authorized SFU subscription uses one egress unit. There is +no built-in default or fixed root count. A candidate reserves its full actual +ingress/egress demand under exact room/share/publication identity before any +token is issued; concurrent old and candidate generations are both charged. +Commit promotes the candidate and releases the superseded generation. Abort, +timeout, disconnect, share rollover, room stop, and room deletion use the same +idempotent release. Capacity exhaustion leaves the current route unchanged and +returns control to the bounded fallback sequence. + +These capacities are operator inputs derived from the actual instance, codec, +representation, bitrate, and accepted concurrency matrix. LiveKit's published +example benchmark cannot supply Screener defaults. A later multi-process +Screener deployment needs a shared atomic ledger; independent process-local +counters would not be deployment-wide admission. + +Primary sources accessed 2026-08-22: + +- [LiveKit benchmarking](https://docs.livekit.io/transport/self-hosting/benchmark/) +- [LiveKit server configuration sample](https://github.com/livekit/livekit/blob/master/config-sample.yaml) +- [LiveKit access tokens and room configuration](https://docs.livekit.io/frontends/reference/tokens-grants/) + ## Route Screening | Route | Where copies are emitted | Endpoint cost | Evidence status | diff --git a/docs/方案设计.md b/docs/方案设计.md index f93eaa4..f5fbce0 100644 --- a/docs/方案设计.md +++ b/docs/方案设计.md @@ -376,6 +376,8 @@ Parent selection 使用硬过滤加字典序,不建立综合质量评分器。 所有非服务器端点共享一个 server-authoritative steady outbound media-copy capacity:唯一配置 `ENDPOINT_MEDIA_COPY_CAPACITY` 默认 `2`,只接受 `1`、`2` 或 `3`,不兼容旧键。peer child 或 Host publication 各占一份,upstream receive 免费,committed selected TURN 只替换同一份 edge transport;SFU subscriber egress 与 TURN allocation 使用独立 server admission。客户端 capability 只能收紧该值,不能按角色、浏览器、UA、设备或页面可见性建立另一套 policy。服务端通过 v6 `authenticated.endpointMediaCopyCapacity` 把最终值交给 Web 与 Native;Native 不从 `maxViewers` 推导发送预算,普通 admission、peer-assisted child assignment 和槽位分配共用该值。代码通过一个 shared accounting guard 执行,不在 Host、Viewer、active、recovery 或 migration 分支重复 magic number;client/server/native wire 随该能力原子升级。首个迁移波次在独立 server admission 就绪前保留现有 SFU subscriber 与 room-wide selected-lease 安全护栏,但不把其固定值当作产品 policy;`C=3` 满载时必须在发出会产生第四份 Host media copy 的 token/grant 前明确等待或失败。 +SFU admission 在应用单进程中只有一个注入实例。LiveKit credential tuple 完整时,`SFU_INGRESS_CAPACITY` 与 `SFU_EGRESS_CAPACITY` 都必须显式配置为正 safe integer;它们没有产品默认值,也不从 Viewer 数、endpoint cap 或历史 root 数推导。账本只保存全局已用计数和按 exact room/share/publication generation 索引的 `reserved | committed` 条目:Host publication 计 ingress `1`,每个订阅计 egress `1`,候选与旧 generation 并存时按实际副本同时计数。route controller 先 reserve,再 prepare 和签发 token;media proof 后 commit candidate 并释放同房旧 generation,任何失败、超时、失联、分享换代、停止或删房都调用同一幂等 release。所有操作是 Map lookup 与整数加减,不扫描房间或参与者。当前切片不实现 TURN allocation admission,不改 parent selection,也不预建多进程协调;多应用进程部署必须先增加共享原子 owner。 + 普通 Web relay 把收到的 remote track 加入独立 downstream `RTCPeerConnection`,因此可能逐跳解码和重编码;浏览器不保证跨连接 shared encode。每条 ordinary child 是独立网络副本,实际 encode time、CPU/GPU、bitrate、loss、frame rate、depth 和 recovery 必须测量。 迁移使用 exact room/share/session/revision/assignment/connection/media-binding identity。旧 route 保持 active,candidate 在 active map 外准备;只有当前代 live track、正 RTP 与 decoded-frame progress 同时成立时才原子 promotion。失败、超时、代次变化或授权撤销销毁 candidate 并保留或恢复旧 route。Make-before-break 的 overlap 由统一端点容量、单个有界 `TransitionOverlapSlot` 和 typed server-resource admission 同时决定。 diff --git a/docs/需求理解.md b/docs/需求理解.md index 187c30e..50f04f4 100644 --- a/docs/需求理解.md +++ b/docs/需求理解.md @@ -52,7 +52,7 @@ SFU/TURN 的 publication、subscription、transport、server-resource accounting - 原生 shared encode relay:具备原生 relay 能力的非服务器端点同样受统一服务端权威下游容量约束,并应复用兼容编码输出而不再次编码。当前普通浏览器 relay 使用逐边重编码的标准 WebRTC 路径,不伪称 shared encode。 - SVC 重开条件:当前标准 Web/LiveKit SVC 已静态拒绝为 `no-go-web-svc-cross-path-hardware-contract`。sender `scalabilityMode` 没有 direct/peer receiver 对应的按层选择或跨 PeerConnection shared-encode 契约;requested/applied mode、Media Capabilities `powerEfficient` 和 RTCStats encoder 字段也不能证明没有静默软件 fallback。Pinned client 2.22.0 还会把 screen-share SVC 覆盖为三层 temporal-only `L1T3`,不满足低分辨率 base 与最多两层。只有未来 native sender 能明确选择硬件编码器、最多发布两层、将同一编码输出复用于 direct/peer/SFU、保持每条路径独立选择并通过真实游戏和亚秒延迟门槛时,才可用新的 capability decision 重开 SVC;不能把浏览器 API 支持或 UA 推断当成采用理由。 - 分包/分层多树实验:把编码包、对象或可独立解码层拆到多个 peer 路径以分散上传,只能在独立 spike 中验证。必须保持亚秒级,处理多父重组、排序、丢包恢复、关键帧、同步和节点退出;未通过门槛前不进入产品路由控制器。 -- SFU/TURN 中央兜底:产品部署可以提供有界 SFU 与可选 TURN 资源,但不要求用户选模式,也不得默认变成整房 always-SFU。只有 Host 拥有一个 authoritative publication;SFU-fed Viewer 具备独立出站证明时才可继续 relay,严格受限 Viewer 保持 leaf。TURN 是某条授权 logical edge 或 Host-SFU ingress 的 selected transport,不是拓扑节点。central egress/admission、temporary overlap 和 exact fallback 由同一个 route controller 与资源账本约束,不能由固定 root 数或普通端点 child 数代替。 +- SFU/TURN 中央兜底:产品部署可以提供有界 SFU 与可选 TURN 资源,但不要求用户选模式,也不得默认变成整房 always-SFU。只有 Host 拥有一个 authoritative publication;SFU-fed Viewer 具备独立出站证明时才可继续 relay,严格受限 Viewer 保持 leaf。TURN 是某条授权 logical edge 或 Host-SFU ingress 的 selected transport,不是拓扑节点。启用 LiveKit fallback 时必须显式提供无默认值的正整数 `SFU_INGRESS_CAPACITY` 与 `SFU_EGRESS_CAPACITY`;一份 Host publication 占一个 ingress,每份 SFU subscription 占一个 egress,实际并行 candidate 另占资源。单进程部署由一个 O(1) 全局账本在发 token 前按 exact room/share/publication fence reserve,commit 后替换旧 generation,abort、timeout、disconnect、share rollover、room stop/delete 都幂等释放。固定 root 数和普通端点 child 数都不能代替该准入;资源耗尽进入下一条有界候选、明确等待或失败。多应用进程部署必须先引入共享原子账本,不能把各进程本地计数相加冒充全局准入。 - 分享端桌面封装:用 Electron 固定 Chromium 版本、改善选源/热键/系统音频体验,同时保留 Web 观看端。 - Windows 应用音频:Windows 11 Native sender 使用本地 opaque window target 绑定 HWND、PID 与进程创建时间,只捕获所选游戏及其子进程的音频,避免把语音聊天或通知声一并分享;“全部系统声音”只能由分享者显式选择。普通 Windows 10 不具备当前 process-loopback API 门槛,必须明确标为未解决/不支持而不是伪兼容。浏览器无法证明或隔离任意进程音频时必须如实提示,原生捕获失败、无声或进程退出不得静默扩大为整机捕获。 - 受限网络增强:短期鉴权 TURN 只有在完整、默认关闭的部署配置和当前 route controller 明确授权时可用;普通 peer PC 从创建起保持 STUN-only,不得向整个 participant、房间或所有 peer connections 预发 TURN candidates。凭据必须绑定 exact room/share/session/route/connection generation,短期有效、内存态、受配额约束且不进入 URL、日志、浏览器持久化或 SQLite。TURN 只替换授权 edge 的 transport;Host 无 direct first-level path 时可作为单条 Host-SFU ingress 的 transport。TURN 不得被描述为更高清的传输方式。 @@ -139,6 +139,7 @@ SFU/TURN 的 publication、subscription、transport、server-resource accounting - Given ADR-0004 的任一硬门槛只有在引入 Encoded Transform/DataChannel/WebCodecs 媒体、自定义拥塞控制、FEC/RTX、多树、综合评分、转码、codec ladder、浏览器 RTP 注入 hack 或放宽 endpoint capacity 后才能满足,When 结束 bounded spike,Then 放弃并删除该浏览器 relay 实验路径,保留标准 P2P 与整体路由模型已接受的 fallback;后续 native shared encode sender 仍按独立 ADR 推进,但不能据此把失败的 relay 拓扑判为通过。 - Given 新观看者会使预计总上行超过安全预算,When 其请求加入,Then 系统只选择整体路由模型已授权且满足配置容量与资源准入的路径;没有合格路线时明确等待或拒绝,而不是创建超过配置容量的 ordinary edge 或持续堆积发送队列。各 transport 的拥塞控制适应该路径,应用证据只改变拓扑资格,不能合成全房降档。 - Given SFU 或 TURN 参与路线,When 计算准入、迁移 overlap 或失败恢复,Then 同时检查统一端点容量和独立的 server ingress/egress、SFU、TURN 资源预算;不得把普通端点 child 数当作 server cap,也不得借兜底形成无界整房 fanout。 +- Given 配置完整 LiveKit credential tuple,When `SFU_INGRESS_CAPACITY` 或 `SFU_EGRESS_CAPACITY` 缺失、非正整数或超出 JavaScript safe-integer 范围,Then 服务必须在启动时 fail closed。Given 两项均有效,When 当前 committed 与 reserved 的 ingress 或 egress 会越过任一部署容量,Then controller 必须在签发任何新 SFU token 前拒绝该 candidate,并保持旧 route 和计数不变。 - Given Host、relay parent 或 leaf 处于受限网络,When direct/peer 路径不足,Then 只执行 ADR-0005 授权的 publisher、subscriber、selected transport 和 fallback sequence;不得靠未授权的固定数量或静态 lease 假设猜测实现。 ### 质量和恢复