perf(server): stop loading every job definition to describe schedules - #19
Merged
Conversation
Grouping by schedule read `job_versions.definition` for every current job in the estate, then used one small array from each. That definition contains every step's full T-SQL body: on a 50-instance estate with 50 jobs apiece, tens of megabytes crossed the wire and were parsed in Node on every request, to read `schedules`. Extracts `definition -> 'schedules'` in Postgres instead. Same result, a fraction of the payload, and the step bodies never leave the database. Also surfaces the row cap. It was silently truncating at 5,000 jobs, which presented a partial estate as though it were the whole one — the failure mode being a group that looks healthy because the failing instances fell off the end. The cap is now 20,000, and the API reports `truncated` so the dashboard can say the grouping is incomplete and to narrow the filter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AyYg2j8FVkLjiaVcj5HCkj
semics-tech
added a commit
that referenced
this pull request
Jul 30, 2026
#19 changed groupJobs from returning an array to returning { groups, truncated } so the endpoint can say when it capped the result. This branch's test still asserted length on the return value. Git merged the two cleanly and TypeScript accepted it, because the assertion was toHaveLength on an untyped await — so the only thing that caught it was running the test. The one other caller, api/app.ts, already destructures the new shape. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AyYg2j8FVkLjiaVcj5HCkj
semics-tech
added a commit
that referenced
this pull request
Jul 30, 2026
## What's wrong Removing an instance configuration deletes the config and stops the worker monitoring it — the worker correctly logs `Instance removed in the dashboard; disconnecting`. But the mirrored `instances` row was never touched. It kept appearing in: - the estate grid and the sidebar tree - overview totals (`instances`, `jobs`, worker `instanceCount`) - cross-estate job grouping frozen at whatever it last reported, forever. Nothing in the UI could remove it. Found while cleaning up after testing the onboarding flow — the test instance had to be deleted from Postgres by hand. ## Why not just delete the row `instances` cascades to `jobs`, `job_versions`, `job_history`, `job_activity` and `agent_log_entries`. Deleting would destroy the run history and version timeline, which are most of the reason to run this product. That is far too much data loss to sit behind a one-click **Remove** with no warning. ## Fix New `instances.detached_at`. On config removal the instance is marked rather than deleted: - excluded from `getEstateOverview`, `getRunningJobs`, `getRecentFailures`, `getWorkerHealth`, the overview totals and `groupJobs` - history stays queryable - configuring the instance again clears the mark, so it comes back **with its history** instead of starting over Migration `0003_fair_whizzer.sql`, nullable column, no backfill needed. ## Tests Six cases in a new file. The important one is that *all four* estate-wide views agree — a detached instance leaking into one view but not another is worse than it showing in all of them, because the numbers stop reconciling. Also covers re-attachment preserving history, and not disturbing sibling instances on the same worker. 86 server tests pass. ## Note on merge order Trivially conflicts with #19, which changes `groupJobs` to return `{ groups, truncated }`. The assertion in `instance-detach.test.ts` uses this branch's array signature; whichever merges second needs `.groups` added or removed on two lines. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01AyYg2j8FVkLjiaVcj5HCkj --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's wrong
Grouping jobs by schedule needs each job's
schedulesarray. It was getting there by selecting the whole definition:A
JobDefinition.v1contains every step's full T-SQL body. On the reference estate — 50 instances, ~50 jobs each — that is tens of megabytes pulled across the wire and parsed in Node on every request to/api/jobs/groups?by=schedule, to read one small array from each row.Fix
Extract it in Postgres:
Same result, a fraction of the payload, and step bodies — which routinely contain connection strings — never leave the database for this query at all.
Also: the silent truncation
groupJobscapped at 5,000 rows and said nothing. The failure mode is quiet and bad: a group renders as "all healthy" because the failing instances fell off the end of the limit.truncated, and the Jobs page shows a notice saying the grouping is incomplete and to narrow the filter.A bounded result is fine. A bounded result presented as complete is not.
Verification
80 server tests pass; lint, typecheck and dashboard build clean.
🤖 Generated with Claude Code
https://claude.ai/code/session_01AyYg2j8FVkLjiaVcj5HCkj