* feat(nodepool): node groups, per-node caps, and local transcode fallback control Group co-located transcode and proxy nodes so transcoded streams are served by a proxy on the same host/LAN instead of bouncing across the internal network (fixes #93): - New nodepool.Planner is the single selection entry point: it picks the transcode node and its group's proxy together (round-robin within the group), replacing the independent ProxyPool.Pick/TranscodePool.Acquire calls scattered across the native and jellycompat handlers, and absorbs the duplicated soft-affinity pick logic. - A group is only eligible while all of its enabled members are healthy; ungrouped nodes keep the historical behavior. - New per-node max_jobs cap (transcodes for transcode nodes, streams for proxies; NULL = unlimited), enforced via health-reported job counts plus short-lived reservations that expire once fresher health data arrives. Proxy health now reports real stream counts, including HLS sessions via idle-expiry tracking. - New playback.local_transcode_fallback setting (default on) lets admins refuse API-server transcoding when no eligible node exists. - Health checks now publish updated node copies under the pool lock instead of mutating shared structs in place, fixing a data race. - Admin UI: group + cap fields on the node form, group/cap columns, and the new fallback toggle in playback settings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(nodepool): proxy bandwidth measurement and egress caps Proxy nodes now measure their stream egress (rolling 60s average over everything under /stream) and report it via the health endpoint. A new per-proxy max_bandwidth_kbps cap lets the planner route new streams away from saturated proxies: - Admission combines the measured egress with the estimated bitrate of the new stream (transcode target bitrate, or source bitrate for direct play/remux) so a stream is only admitted where it fits. - Recently admitted streams are bridged as bandwidth reservations for the meter window, since the rolling average only converges on a new stream's rate gradually. - A group whose proxies lack bandwidth headroom is treated as full: its transcode nodes are skipped, same as the job cap. - Admin UI: per-proxy "Max Egress Bandwidth (Mbps)" field and a live egress column; manual health checks return the measured rate. Active streams are never interrupted - the cap only gates new admissions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * perf(playback): trim node-mode time-to-stream-start Distributed playback paid several avoidable costs before the first frame that integrated mode doesn't have. This trims the safe ones: - Web player preconnects to the stream origin (the proxy node) as soon as /playback/start returns, overlapping DNS/TCP/TLS handshakes with the transcode dispatch instead of paying them at the first manifest fetch. - The transcode node no longer blocks its 202 on monitoring work: the Redis session-track write moves off the request path, and a replaced session's segment directory is renamed aside and deleted in the background instead of synchronously (RemoveAll of a long session can take seconds on slow disks during quality switches). - The proxy's node-facing HTTP client gets a tuned transport: a larger idle-connection pool (Go's default of 2 per host causes connection churn and TLS re-handshakes when many viewers stream through one proxy->node pair) and a response-header timeout so a hung transcode node can no longer hang client requests indefinitely. - jellycompat's remote transcode dispatch gains the same 10s timeout the native path has had; an unreachable node previously hung the compat manifest request until the OS gave up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
36 lines
1.4 KiB
SQL
36 lines
1.4 KiB
SQL
-- +goose Up
|
|
-- +goose StatementBegin
|
|
ALTER TABLE stream_nodes
|
|
ADD COLUMN node_group text,
|
|
ADD COLUMN max_jobs integer,
|
|
ADD COLUMN max_bandwidth_kbps integer,
|
|
ADD COLUMN egress_kbps integer NOT NULL DEFAULT 0;
|
|
|
|
COMMENT ON COLUMN stream_nodes.node_group IS
|
|
'Optional co-location group. Nodes sharing a group are assumed to be on the '
|
|
'same host/LAN. A group is only eligible for selection while every enabled '
|
|
'member is healthy; transcoded streams are served by a proxy from the same '
|
|
'group as the chosen transcode node.';
|
|
|
|
COMMENT ON COLUMN stream_nodes.max_jobs IS
|
|
'Maximum concurrent jobs for this node (transcodes for transcode nodes, '
|
|
'streams for proxy nodes). NULL = unlimited.';
|
|
|
|
COMMENT ON COLUMN stream_nodes.max_bandwidth_kbps IS
|
|
'Maximum egress bandwidth for proxy nodes in kilobits/s. New streams are '
|
|
'not admitted once the measured egress would exceed this. NULL = unlimited.';
|
|
|
|
COMMENT ON COLUMN stream_nodes.egress_kbps IS
|
|
'Last health-reported egress bandwidth (rolling average, kilobits/s). '
|
|
'Currently only proxy nodes report a non-zero value.';
|
|
-- +goose StatementEnd
|
|
|
|
-- +goose Down
|
|
-- +goose StatementBegin
|
|
ALTER TABLE stream_nodes
|
|
DROP COLUMN IF EXISTS node_group,
|
|
DROP COLUMN IF EXISTS max_jobs,
|
|
DROP COLUMN IF EXISTS max_bandwidth_kbps,
|
|
DROP COLUMN IF EXISTS egress_kbps;
|
|
-- +goose StatementEnd
|