Skip to content

fix: watch loop holds one persistent DB connection instead of open/close per batch - #43

Draft
pradeepmouli wants to merge 1 commit into
intuit:mainfrom
pradeepmouli:fix/watch-persistent-db-connection
Draft

fix: watch loop holds one persistent DB connection instead of open/close per batch#43
pradeepmouli wants to merge 1 commit into
intuit:mainfrom
pradeepmouli:fix/watch-persistent-db-connection

Conversation

@pradeepmouli

@pradeepmouli pradeepmouli commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Status: draft, reworking. As currently written, this fix would make #46 worse — holding one connection open for the watcher's whole session (this PR's fix for the checkpoint-storm bloat below) means readers get permanently locked out instead of hitting only brief per-batch windows. #43 and #46 are two sides of the same tradeoff and need a combined solution before this merges. See #46 for details.

Relates to #46

Summary

Root-caused a real-world incident where infigraph watch grew a project's .infigraph/graph file to 54GB over a long editing session (logical content was ~58k symbols; a single open/close cycle on the bloated file showed ~3GB of write amplification, confirmed via an APFS CoW clone before cleanup).

Root cause: infigraph never issues an explicit CHECKPOINT, and lbug/Kuzu's SystemConfig::default() has forceCheckpointOnClose = true — every Database close forces a full checkpoint. The watch loop's open_transient opened a brand-new DB connection on every batch flush (roughly once per second during active editing) and on every file-removal event, so a multi-hour session turned into thousands of forced checkpoint cycles. The underlying storage engine's page allocator requires one contiguous free range per allocation and can't shrink an interior free region — so under sustained small-write churn at scale, this pattern is a plausible driver of unbounded file growth (independent of actual logical data growth).

Fix

watch_project_with_periodic now holds one DB connection open for the whole watch session on non-Windows platforms (watch_db/poison_watch_db), reopening only if an operation on it errors — e.g. if the on-disk database gets replaced out from under it by a concurrent infigraph index --full elsewhere. Windows keeps the original per-batch open/close behavior, since Windows mandatory file locking (which prevents a second concurrent connection while another handle on the same file is open) was open_transient's sole original rationale — that comment is preserved and updated in the diff.

Also hardened groups_watch_perf.rs's group_index assertion: it previously assumed the explicit group_index call always performs the (re)indexing work, but auto-watch (triggered by earlier calls in the same test) can legitimately have already indexed both repos in the background by the time it runs, in which case index_group correctly reports 0 repos needing work rather than re-listing them by name. Both outcomes mean the data is indexed (already confirmed via group_query earlier in the test) — the assertion now accepts either. This is a genuine, if narrow, pre-existing race between the test's assumptions and background auto-watch timing; this PR's connection-reuse change appears to make the background indexing complete measurably faster/more reliably, which increases how often it wins the race (observed empirically: before this test fix, this specific branch failed with the "already indexed" signature in 3 of 6 groups_watch_perf runs, vs. 0 of 4 on an unmodified main baseline with a matched build).

Verification

  • Full infigraph-core, infigraph-cli, and infigraph-mcp test suites green (the only unrelated failure, compression_eval's search-savings assertions, is a pre-existing environment quirk when tests run from a directory nested under an actively-watched parent repo — unrelated to this diff, not reproducible in a normal checkout).
  • fmt/clippy clean.
  • Empirically verified via a live smoke test: started a real infigraph watch session and confirmed via lsof that the graph file's file descriptor (same inode) stays open across two separate batches rather than closing and reopening between them.

Test plan

  • cargo test -p infigraph-core — all pass
  • cargo test -p infigraph-cli — all pass
  • cargo test -p infigraph-mcp (excluding the pre-existing unrelated compression_eval environment quirk noted above) — all pass
  • cargo fmt --all -- --check / cargo clippy --all-targets -- -D warnings — clean
  • Manual smoke test confirming the DB connection stays open across watch batches via lsof

…ose per batch

Root cause of the sittir 54GB graph-bloat incident: infigraph never issues
an explicit CHECKPOINT, and lbug's forceCheckpointOnClose=true means every
Database close forces a full checkpoint. The watch loop's open_transient
opened a fresh connection on every batch flush (~1/sec during active
editing) and on every file-removal event, so a multi-hour editing session
turned into thousands of forced checkpoint cycles. Empirically confirmed
~3GB of write amplification from a single open/close cycle at 54GB scale.

Fix: watch_project_with_periodic now holds one connection open for the
whole watch session on non-Windows platforms (watch_db/poison_watch_db),
reopening only if an operation on it errors -- e.g. if the underlying
database file gets replaced out from under it by a concurrent `infigraph
index --full` elsewhere. The original per-batch open/close behavior is
kept Windows-only, since Windows mandatory file locking (which prevents a
second concurrent connection while another handle is open) was
open_transient's sole original rationale.

Verified empirically: started a real watch session and confirmed via lsof
that the graph file's fd (same inode) stays open across two separate
batches rather than closing and reopening between them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant