Area: streaming · follow-up to #583 story 2 · decided with Eric, 2026-09-21
stream.keepalive_interval and stream.keepalive_buckets are per tenant, since every tenant folder carries a full config.json. The keepalive wheel (stream.Heartbeater) is still one per process. Story 2 keeps the single wheel and runs it at the shortest keepalive_interval among the serving tenants: the interval is an upper bound on how long a quiet stream goes unwritten, so the shortest one satisfies every tenant.
That is a stopgap. One tenant sets the cadence for all of them: a tenant asking for 1 second makes every tenant's streams write a keepalive every second. The wheel should respect each tenant's own interval and bucket count.
Options to weigh:
- a wheel per tenant, started with the tenant's first stream and stopped after its last
- one wheel per distinct (interval, buckets) pair, shared by the tenants that use it
Either way the stream handler has to know which tenant a connection belongs to. It does not read the request's tenant today; story 5 (hub subscriptions keyed by tenant) is where it starts to, so this fits after story 5.
Done when a stream opened under tenant A is kept alive at A's keepalive_interval, and a reload of tenant B's settings does not change A's cadence.
Area: streaming · follow-up to #583 story 2 · decided with Eric, 2026-09-21
stream.keepalive_intervalandstream.keepalive_bucketsare per tenant, since every tenant folder carries a fullconfig.json. The keepalive wheel (stream.Heartbeater) is still one per process. Story 2 keeps the single wheel and runs it at the shortestkeepalive_intervalamong the serving tenants: the interval is an upper bound on how long a quiet stream goes unwritten, so the shortest one satisfies every tenant.That is a stopgap. One tenant sets the cadence for all of them: a tenant asking for 1 second makes every tenant's streams write a keepalive every second. The wheel should respect each tenant's own interval and bucket count.
Options to weigh:
Either way the stream handler has to know which tenant a connection belongs to. It does not read the request's tenant today; story 5 (hub subscriptions keyed by tenant) is where it starts to, so this fits after story 5.
Done when a stream opened under tenant A is kept alive at A's
keepalive_interval, and a reload of tenant B's settings does not change A's cadence.