Scaling and the poller
The poller lives in the Node process (src/lib/poller/registry.ts). It is
correct and efficient for a single app instance: one poller per dashboard,
shared by all subscribers on that instance.
What breaks with multiple replicas
Section titled “What breaks with multiple replicas”Running multiple app replicas would create one poller per replica:
- duplicated polling load on TimescaleDB, multiplied by replica count
- no cross-instance delta sharing, so each replica keeps its own cursor
- subscribers on different replicas can see different data
getPoller keys pollers by [dashboardId, timeRange.from, timeRange.to] in a
process-local Map, so “one poller per dashboard” holds only within a
process.
What extraction would take
Section titled “What extraction would take”To scale horizontally, the poller moves behind a shared runtime — a dedicated poller service, or a pub/sub fan-out such as Redis — with web instances subscribing rather than polling directly.
The code is already shaped for this: computeDelta is pure, and the
PanelExecutor abstraction is injectable, which is what makes the extraction
straightforward rather than a rewrite.
The open work, in order:
| Issue | Work |
|---|---|
| #40 | Decide the topology — standalone service versus pub/sub fan-out |
| #41 | Delta fan-out so replicas share one producer |
| #42 | Distributed lease so exactly one process polls a dashboard |
| #43 | SSE resume via Last-Event-ID |
| #44 | Poller backoff and circuit breaker |
Other known limits
Section titled “Other known limits”- No rate limiting on the LLM routes yet (#18) — one editor can burn unbounded provider spend.
- No subscriber caps per dashboard or per instance (#48).
- New connection per query.
executePlanopens a freshpg.Clientper execution with no pooling or concurrency ceiling (#13). - No published load numbers yet (#49); the caveat above is qualitative until they exist.