Skip to content

Commit a32d3d3

Browse files
authored
perf(incremental): batch WAL checkpoints and fix native CFG bulk insert (#917)
* perf(incremental): batch WAL checkpoints and fix native CFG bulk insert Replace per-feature wal_checkpoint(TRUNCATE) calls (~68ms each × 3-4 features) with a single upfront FULL checkpoint before analyses and one TRUNCATE after all analyses complete. suspendJsDb/resumeJsDb callbacks become no-ops, eliminating ~200-270ms of WAL checkpoint overhead on incremental rebuilds with the native engine. Also fix bulkInsertCfg silently failing due to napi-rs null→u32 conversion error: pass undefined (maps to Option::None) instead of null for optional CfgBlockRow fields (startLine, endLine, label). * fix: correct PASSIVE→FULL in WAL checkpoint comments (#917)
1 parent f0ce430 commit a32d3d3

2 files changed

Lines changed: 29 additions & 35 deletions

File tree

src/domain/graph/builder/pipeline.ts

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -491,20 +491,13 @@ async function runPostNativeAnalysis(
491491
}
492492
}
493493

494-
// Wire up WAL checkpoint callbacks for the analysis engine
494+
// Flush JS WAL pages once so Rust can see them, then no-op callbacks.
495+
// Previously each feature called wal_checkpoint(TRUNCATE) individually
496+
// (~68ms each × 3-4 features). One FULL checkpoint suffices.
495497
if (ctx.nativeDb && ctx.engineOpts) {
496-
ctx.engineOpts.suspendJsDb = () => {
497-
ctx.db.pragma('wal_checkpoint(TRUNCATE)');
498-
};
499-
ctx.engineOpts.resumeJsDb = () => {
500-
try {
501-
ctx.nativeDb?.exec('PRAGMA wal_checkpoint(TRUNCATE)');
502-
} catch (e) {
503-
debug(
504-
`resumeJsDb: WAL checkpoint failed (nativeDb may already be closed): ${toErrorMessage(e)}`,
505-
);
506-
}
507-
};
498+
ctx.db.pragma('wal_checkpoint(FULL)');
499+
ctx.engineOpts.suspendJsDb = () => {};
500+
ctx.engineOpts.resumeJsDb = () => {};
508501
}
509502

510503
try {
@@ -532,7 +525,9 @@ async function runPostNativeAnalysis(
532525
warn(`Analysis phases failed after native build: ${toErrorMessage(err)}`);
533526
}
534527

535-
// Close nativeDb after analyses
528+
// Close nativeDb after analyses — TRUNCATE checkpoint flushes all Rust
529+
// WAL writes so JS and external readers can see them. Runs once after
530+
// all analysis features complete (not per-feature).
536531
if (ctx.nativeDb) {
537532
try {
538533
ctx.nativeDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
@@ -753,25 +748,20 @@ async function runPipelineStages(ctx: PipelineContext): Promise<void> {
753748
await buildEdges(ctx);
754749
await buildStructure(ctx);
755750

756-
// Reopen nativeDb for feature modules (ast, cfg, complexity, dataflow)
757-
// which use suspendJsDb/resumeJsDb WAL checkpoint before native writes.
751+
// Reopen nativeDb for feature modules (ast, cfg, complexity, dataflow).
758752
// Skip for small incremental builds — same rationale as insertNodes above.
753+
//
754+
// Perf: do ONE upfront FULL checkpoint to flush JS WAL pages so Rust
755+
// can see the latest rows, then make suspendJsDb/resumeJsDb no-ops.
756+
// Previously each feature called wal_checkpoint(TRUNCATE) individually
757+
// (~68ms each × 3-4 features = ~200-270ms overhead on incremental builds).
759758
if (ctx.nativeAvailable && !smallIncremental) {
760759
reopenNativeDb(ctx, 'analyses');
761760
if (ctx.nativeDb && ctx.engineOpts) {
761+
ctx.db.pragma('wal_checkpoint(FULL)');
762762
ctx.engineOpts.nativeDb = ctx.nativeDb;
763-
ctx.engineOpts.suspendJsDb = () => {
764-
ctx.db.pragma('wal_checkpoint(TRUNCATE)');
765-
};
766-
ctx.engineOpts.resumeJsDb = () => {
767-
try {
768-
ctx.nativeDb?.exec('PRAGMA wal_checkpoint(TRUNCATE)');
769-
} catch (e) {
770-
debug(
771-
`resumeJsDb: WAL checkpoint failed (nativeDb may already be closed): ${toErrorMessage(e)}`,
772-
);
773-
}
774-
};
763+
ctx.engineOpts.suspendJsDb = () => {};
764+
ctx.engineOpts.resumeJsDb = () => {};
775765
}
776766
if (!ctx.nativeDb && ctx.engineOpts) {
777767
ctx.engineOpts.nativeDb = undefined;
@@ -782,11 +772,15 @@ async function runPipelineStages(ctx: PipelineContext): Promise<void> {
782772

783773
await runAnalyses(ctx);
784774

785-
// Keep nativeDb open through finalize so persistBuildMetadata, advisory
786-
// checks, and count queries use the native path. closeDbPair inside
787-
// finalize handles both connections. Refresh the JS db so it has a
788-
// valid page cache in case finalize falls back to JS paths (#751).
775+
// Flush Rust WAL writes (AST, complexity, CFG, dataflow) so the JS
776+
// connection and any post-build readers can see them. One TRUNCATE
777+
// here replaces the N per-feature resumeJsDb checkpoints (#checkpoint-opt).
789778
if (ctx.nativeDb) {
779+
try {
780+
ctx.nativeDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
781+
} catch (e) {
782+
debug(`post-analyses WAL checkpoint failed: ${toErrorMessage(e)}`);
783+
}
790784
refreshJsDb(ctx);
791785
}
792786

src/features/cfg.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,9 +404,9 @@ export async function buildCFGData(
404404
blocks: cfg.blocks.map((b) => ({
405405
index: b.index,
406406
blockType: b.type,
407-
startLine: b.startLine ?? null,
408-
endLine: b.endLine ?? null,
409-
label: b.label ?? null,
407+
startLine: b.startLine ?? undefined,
408+
endLine: b.endLine ?? undefined,
409+
label: b.label ?? undefined,
410410
})),
411411
edges: (cfg.edges || []).map((e) => ({
412412
sourceIndex: e.sourceIndex,

0 commit comments

Comments
 (0)