perf(resolution): defer checkpoints and double-buffer persist during resolution, byte-identical graphs (#1320)

Fresh init on a 4,402-file Java repo (dubbo): 18.2s -> 13.5s (-26%), with
the resolution phase going 12.6s -> 7.9s (-38%). Graphs verified
byte-identical on both the pool path (dubbo) and the sequential path
(excalidraw).

Two changes:

1. The fastInit+pool path restored WAL for the resolver workers but left
   wal_autocheckpoint at its default, so the persist loop inline-checkpointed
   hot pages all phase long (#1231's pathology inside resolution — measured
   at 58% of resolution wall). Checkpointing is now deferred behind the
   bounded valve and folded once at maintenance, mirroring the deferWal path.

2. The resolution loop is double-buffered: batch k+1 is prefetched (OFFSET
   past batch k's still-pending rows, under an explicit ORDER BY rowid) and
   fanned out across the pool while batch k's ref cleanup runs on the main
   thread. Batch settle-waits dropped 2572ms -> 117ms.

Correctness invariant found by the byte-identical gate and now documented in
the loop: batch k+1's resolution READS batch k's edges (resolveMethodOnType
walks supertype chains over extends/implements edges that resolution itself
inserts), so edges must persist BEFORE the next batch fans out; only the ref
cleanup overlaps.

Also extends the CODEGRAPH_SYNTH_TIMINGS instrumentation with phase labels
(grammar-init, parse-loop, fts-rebuild, resolver-reinit, resolution,
callback-synthesis) and pool timings (worker open/resolve, per-batch mode,
persist), so the next profile is one env var away.

Suite green (2444). Sync path timings unchanged. Pool floor re-validated:
forced-on at 40k refs is still net-slower, so the 150k threshold stands.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-16 17:26:29 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent 1de7e8f8b5
commit a2f3c31a97
6 changed files with 152 additions and 40 deletions
+5
View File
@@ -43,19 +43,24 @@ port.on('message', (msg: InMessage) => {
try {
switch (msg.type) {
case 'open': {
const tOpen = Date.now();
const created = createDatabase(msg.dbPath, { readOnly: true });
db = created.db;
db.pragma('busy_timeout = 5000');
db.pragma('cache_size = -32000');
const tDb = Date.now();
const queries = new QueryBuilder(db);
resolver = new ReferenceResolver(msg.projectRoot, queries);
resolver.initialize();
if (process.env.CODEGRAPH_SYNTH_TIMINGS) console.error(`[pool-timing] worker open: db=${tDb - tOpen}ms init=${Date.now() - tDb}ms`);
port.postMessage({ type: 'ready' });
break;
}
case 'resolve': {
if (!resolver) throw new Error('resolver-worker: resolve before open');
const tRes = Date.now();
const out = resolver.resolveListForAdmission(msg.refs);
if (process.env.CODEGRAPH_SYNTH_TIMINGS) console.error(`[pool-timing] worker resolve: ${msg.refs.length} refs in ${Date.now() - tRes}ms`);
port.postMessage({ type: 'result', id: msg.id, ...out });
break;
}