#3407 workflow run search / history search parse every run record on every call, so their cost grows with all history ever kept
Opened by swamp_lord · 10/13/2026· Shipped 10/14/2026
Description
run_search (src/libswamp/workflows/run_search.ts) gets summaries for every workflow (findAllWorkflows then findAllRunsByWorkflowId). Through YamlWorkflowRunRepository.findAllSummariesByWorkflowId, that opens and YAML-parses every workflow-run-*.yaml in each workflow's directory. Only after that does it sort and apply --since, --status, --workflow and --limit. A search for the last hour still reads every run ever kept.
Each workflow already has a run index (workflow_run_index.ts), but this path doesn't use it. #3051 fixed the same pattern for run doctor.
It also hurts everything else on serve. The parsing ties up serve's single process, so unrelated requests queue behind it.
Measured on ops.swamp-club.com
Serve 20261013.004531.0-sha.1373120f, local filesystem datastore. Spans from serve's OTel traces.
- History size: 13,226 run records (239 MB) across 41 workflows after 18 days. 5,196 are from one 5-minute reconcile workflow, 3,547 from an archive sync, and 2,448 from a webhook router at about 64 KB each, since each stores the full webhook payload.
- Quiet server:
swamp.workflow.run_searchp50 2–4 s. - Under load (about 20 calls a minute from the dashboard, bots and CLI polling, 19:30–20:00 UTC on 2026-10-13):
run_search: 610 calls, max 115 s.swamp.workflow.history.get: 630 calls, max 174 s. For a full run id this reads only one file (findGlobalById), so the time was spent waiting behind searches.- Worker data writes (
POST /data/*): p50 1,264 s.
- Consequence: a release
buildstep failed withThe operation was aborted due to timeout. Its finalwriteResourcetook 65.2 s on serve and returned 200, but the worker had given up at 60 s, so a write that succeeded was reported as failed.
Steps to reproduce
- A repo with about 10k run records across a few workflows.
swamp workflow run search --since 1htakes seconds, and grows linearly with total history.- Through serve: call it in a loop from a few clients while a remote worker writes data (
context.writeResource). WatchPOST /data/*latency climb.
Expected
- Search and listing come from the index, and the time, status and workflow filters and the limit are applied before any run file is read.
- Paged results, so a
--limit 20search reads about 20 records. - Cost grows with the result size, not with total history.
Related: #3051 (same fix for run doctor), #2865 (datastore rework), and the tiered-storage feature request filed alongside this.
Shipped
Click a lifecycle step above to view its details.
swamp_lord commented 10/13/2026, 10:12:30 PM
The tiered-storage feature request mentioned above is #3408.
Sign in to post a ripple.