Gitmoot v0.8.3
The performance release: the daemon goes from a permanently pegged CPU core to near-idle between sweeps. Two rounds of live profiling (#598, #619) found and eliminated every hot path in the worker tick, the dispatcher, and the poll loop — each fix verified by before/after measurement on a production 18-repo daemon. Everything is behavior-preserving; no defaults change.
What's new since v0.8.2
Daemon CPU: from pegged core to near-idle (#598, #619)
Round one (#598 → #615) eliminated the three biggest sinks:
- Bounded worker-tick retry scans — the 1s tick no longer full-scans the jobs table per repo (36 full scans + ~1.3 GB of payload JSON parsed per sweep to find ~3 pending jobs). Indexed candidate queries return just the pending IDs; the existing predicates re-verify each candidate, so behavior is identical.
- No more dispatcher busy-spin — a queued job whose runtime session is busy
is no longer re-dispatched in a tight loop (measured 36 attempts/s writing a
runtime_lock_waitevent per attempt — 56% of the whole job_events table). Busy jobs and their same-session siblings wait for the next tick, and the wait event is recorded once per episode (with a 15-minute liveness re-emit), not per attempt. - Escalation auto-finalize is candidate-driven — no more full job-table walk from every repo's poll; an indexed query finds open escalations, and zero candidates costs zero work.
Round two (#619 → #620) removed the read amplification the first round exposed:
- Per-tick candidate queries run once, not once per repo — the global candidate sets are computed a single time per worker tick and shared across all repos (previously 18× the identical query per tick ≈ 71% of all daemon reads). Query errors are deliberately not memoized, so a transient DB fault retries immediately and keeps per-repo fault isolation.
- Covering index for candidate queries —
job_events(kind, job_id, id)eliminates the temp b-tree spill and 62k row lookups per candidate query; the now-redundant single-columnidx_job_events_kindis dropped, saving one b-tree update on every event insert. - The PR poll path stops reading every payload — routing/reconcile/ supersede now fetch only review jobs (previously a full 40 MB jobs+payloads scan up to twice per open PR per sweep), and the review-job list is fetched once per poll, not per PR.
- Index hygiene — queued-job scans and running-job recovery now hit partial indexes instead of full-table scans with temp sorts (plan-asserted in tests against the production SQL).
Measured on the reference deployment (18 repos, --watch-issues): 73–110% of
a core pegged → bursty ~40% after round one → round two targets the residual
tick reads (~426 MiB/tick of repeated candidate scans reduced ~18×, plus the
poll-path payload reads eliminated).
Workflow: merge-gate no-CI race closed (#596, #614)
A PR whose checks had not yet been created could slip past the native merge gate in the window before CI registered. The gate now applies a grace window and is workflow-aware, so "no checks yet" is treated as "not green yet" instead of "nothing to wait for".
Fixes & polish
setTaskStateno longer crashes on branch-unique constraint during advancement, anddoctor --jsonprovides machine-readable diagnostics (#580).- Dashboard (galaxy view): custom hit-testing, node selectability, brighter active nodes, visible packets (#610, #613, #616, #618).
Upgrading
Drop-in. The two index changes ship as ordinary append-only schema migrations
applied automatically on first start; the first startup after upgrade may take
a few extra seconds while the covering index builds on a large job_events
table. Operators with a long-lived database can reclaim additional headroom by
pruning historical advance_retry / runtime_lock_wait events (95% of the
events table on the reference deployment was dead rows from pre-0.8.3
pathologies).