Skip to content

fix(sqlite): eliminate unchanged reference-fact rewrites - #773

Merged
zzet merged 2 commits into
mainfrom
fix/issue-767-write-amplification
Sep 7, 2026
Merged

fix(sqlite): eliminate unchanged reference-fact rewrites#773
zzet merged 2 commits into
mainfrom
fix/issue-767-write-amplification

Conversation

@zzet

@zzet zzet commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Summary

Refs #767.

  • Replace incremental reference-fact delete/refill with scoped deletion of obsolete facts and a conditional UPSERT of changed payloads.
  • Start the projection from the requested file frontier, collapse duplicate fact identities deterministically, and preserve generation isolation and transaction semantics.
  • Add regression tests, embedded SQLite query-plan checks, a reproducible write benchmark, and an opt-in isolated worktree lifecycle/idle-I/O harness.
  • Document the evidence, invariant, measurements, and limitations in the performance specification.

No schema migration, new index, public API change, checkpoint tuning, store deletion, or live-daemon restart.

Measured result

1,000 facts + 10,000 unrelated nodes; Apple M1 Pro, Go 1.27.0, modernc SQLite v1.58.0; baseline 0f462eff. Medians of three 20-iteration runs.

Refresh Row mutations, before → after WAL bytes/op, before → after Time, before → after
Unchanged 2,000 → 0 255,442 → 0 38.018 → 29.519 ms
One changed target 2,000 → 1 267,802 → 20,602 38.032 → 30.229 ms

The changed-target case includes the underlying node update in WAL/time, but counts only fact refreshes in the row metric. This is a 92.3% WAL reduction for that case, not a whole-daemon/SSD-write claim.

Validation

Passed locally on the final production code:

  • Full normal storage suite (55.606s) and final full indexer suite (410.317s).
  • Full storage race suite (1,073.317s); targeted indexer race (10.838s), reference-fact race (30.007s), and harness-helper race (1.627s).
  • Build, gofmt/whitespace checks, and scoped linter: zero issues.
  • Baseline/fixed isolated lifecycle smoke (382.805s).
  • Final safeguarded lifecycle smoke (98.719s), including selected-primary facts and preflight deadline refusal before creating a fixture/child.
  • Continuous 30-minute warm soak: 1,804.759s, 1,212,416 disk-accounted bytes and 43,778,776 logical bytes. Generation allocation stayed at 2; 101 nodes, 200 edges, and 2 primary-scoped facts stayed unchanged. The captured child/socket/PID file were cleaned up.

The lifecycle leaves the primary dirty after creating, editing, querying, and removing an automatic linked worktree, then warm-restarts only the isolated child. The long run began before the two final harness safeguards; an explicit 45-minute deadline and independent before/after primary-scope checks supplied those protections. The finalized harness was separately smoke-tested against the same daemon binary.

The daemon harness is opt-in; ordinary CI runs its helper tests without launching a daemon. Reproduction commands and binary fingerprint are in the specification.

Scope and remaining uncertainty

This fixes a proven, profiled incremental write-amplification mechanism. It does not establish that it explains every observation in #767: both baseline and fixed tiny-fixture runs had low, nonzero background I/O and did not reproduce the production-scale incident.

The measured SQL frontier is 1,000 facts. Larger-frontier/transient-sort I/O and incident-scale post-deployment profiling remain follow-ups. Zero fact-row/WAL writes is not a guarantee of zero temporary-file or other daemon writes. Existing enlarged stores are not automatically compacted.

Commits

  • 627432c5: storage fix, correctness regressions, and benchmark.
  • c1e0d8eb: isolated validation harness and specification.

@zzet
zzet merged commit 990cb35 into main Sep 7, 2026
11 checks passed
@zzet
zzet deleted the fix/issue-767-write-amplification branch September 7, 2026 20:31
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