Relationships
≡ duplicate of #3407#3444 Dashboard: a live run page polls a full workflow.run.search every 2.5 s, and time-range presets refetch the run summary every minute
Opened by hammz · 10/14/2026
Description
Two dashboard request patterns keep resending the expensive run queries from #3443. Every one of those requests reads the run index of every workflow, for all time. These patterns turn that per-request cost into constant load on serve.
1. A live run page polls a full run search every 2.5 s
RunDetail.tsx polls refetchRun every LIVE_POLL_MS = 2500 while the run is live (usePolling(refetchRun, live ? LIVE_POLL_MS : null)). refetchRun refetches three requests:
workflow.history.get: cheap for a full run id (findGlobalById).workflow.history.logs, tail 200.workflow.run.search { workflow, query: runId, limit: 1 }. This reads the run index of every workflow and builds a search item for every run ever kept, only to return one row.queryis a substring post-filter, so the run id does not narrow the read.
So each open tab on a running run triggers a scan of all history every 2.5 s. On a repo with about 100k runs that is about 400 ms of serve's only thread every 2.5 s, per tab (see #3443 for the numbers). Long runs, or several people watching a deploy, add up. useActiveRunsRefetch also calls the same refetchRun when the health snapshot's active runs change.
2. Time-range presets re-key the run summary every minute
useTimeBounds() (client/time_range.ts) rounds Date.now() to the minute during render. The health stream re-renders the app every 5 s, so on the first render after a minute rolls over, the preset's since/until change. The useRequest key changes with them, which sends the request again. Every page using useRunSummary with the dashboard range therefore refetches once a minute while open:
- Workflows:
workflow.run.summary { recentLimit: 20 } - Overview:
{ timeline: true, recentLimit: 0 } - Executions: two summaries
- Approvals, and
trigger_runs.ts(schedules and webhooks)
Each refetch is a full scan. The Workflows page already keeps the previous summary on screen to hide the flash (held in Workflows.tsx), but the server work still happens every time. For a "last 7 days" range, moving the window by one minute almost never changes the result.
Steps to reproduce
- Use a repo with a large run history, about 100k runs.
- Open a run while it is running. Watch serve's
swamp.workflow.run_searchspans: one every 2.5 s per tab. - Leave the Workflows or Overview page open on a preset range: one
swamp.workflow.run_summaryper minute per tab.
Expected
- Live run page: poll only what changes for a live run,
workflow.history.getand.logs. Read the run-search row (trigger, awaiting-resume) once, and again only when the status changes or the health snapshot's active runs change. Or, once #3443 lands, use a by-id lookup that doesn't scan. - Preset ranges: don't re-key a run summary only because the clock moved. Options:
- Send the preset as a relative bound (
since: "7d"; the server already accepts durations) so the key stays stable. - Coarsen the rounding to the range's size, for example an hour for ranges of a day or more.
- Refetch on a deliberate interval, or when the health snapshot shows runs started or finished, rather than on each minute's rollover.
- Send the preset as a relative bound (
- Request volume from an open dashboard tab should not depend on how long the tab stays open.
Related: #3443 (the server-side cost of each of these requests), #3335 (dashboard overhaul).
Triaged
Click a lifecycle step above to view its details.