Skip to main content

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_wait event 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 queriesjob_events(kind, job_id, id) eliminates the temp b-tree spill and 62k row lookups per candidate query; the now-redundant single-column idx_job_events_kind is 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

  • setTaskState no longer crashes on branch-unique constraint during advancement, and doctor --json provides 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).