The dashboard polled /api/monitors every five seconds and re-rendered the whole list on every tick, whether anything had changed or not. We replaced that with Server-Sent Events, and making it work across the default multi-process deployment was the interesting part.
The poll#
Every open dashboard tab hit the API every five seconds and rebuilt the list. That meant up to five seconds of lag before a result appeared, a flicker on every tick, and wasted server CPU on idle dashboards nobody was even looking at.
The stream#
Now each tab opens a single /api/events SSE stream. The server pushes a typed event on every change: an execution row written, a monitor created or deleted, a heartbeat flipping overdue or back. Operators see updates within a tick of the worker recording them, with no polling and no flicker. The stream pauses when the tab is hidden, so a backgrounded dashboard costs nothing.
Across two processes#
The default deployment runs the scheduler and worker as one process and the dashboard as another. An in-process EventEmitter is the obvious way to wire events, but it cannot cross that boundary: an event emitted in the worker never reaches the SSE stream in the dashboard process. Live updates had to work across both.
The fix was to bridge the bus over Redis pub/sub. emit() still delivers to local listeners synchronously, and also publishes to a Redis channel; a subscriber in each process re-dispatches incoming events locally, skipping its own to avoid double delivery.
The lesson#
An in-process event bus is a single-process assumption hiding in plain sight, fine for the single-box first version but not once the deployment has two processes. At that point "just emit an event" needs a real transport. Redis was already running for the queues, so it became the bus too.