Skip to content

PR: SQLite Query Caching Optimizations#1241

Open
archpulse wants to merge 1 commit into
colbymchenry:mainfrom
archpulse:main
Open

PR: SQLite Query Caching Optimizations#1241
archpulse wants to merge 1 commit into
colbymchenry:mainfrom
archpulse:main

Conversation

@archpulse

@archpulse archpulse commented Jul 10, 2026

Copy link
Copy Markdown

PR: SQLite Query Caching Optimizations

Description

This pull request optimizes database query performance in CodeGraph by caching static prepared statements inside the QueryBuilder class.
By caching static SQL statements inside this.stmts instead of compiling them dynamically on every database method invocation, we reduce CPU consumption and query preparation overhead.

Key Changes

  • Modified src/db/queries.ts to add type definitions for new cached statements.
  • Updated QueryBuilder methods to prepare the statement on the first call and reuse it afterwards:
    • isNameSegmentVocabEmpty
    • getDistinctNodeNames
    • getNamesForSegment
    • getAllNodes
    • getDistinctFileLanguages
    • getDependentFilePaths
    • getDependencyFilePaths
    • getCrossFileIncomingEdgesWithTarget
    • getLastIndexedAt
    • getNodeAndEdgeCount
    • getStats (and internal helpers)
    • getMetadata, setMetadata, getAllMetadata

Performance Benchmarks

Timing comparison before and after the optimization for 5,000 iterations:

  • getDependentFilePaths: 290.55 ms ➔ 177.69 ms (~1.63x speedup)
  • getStats: 60.43 ms ➔ 47.23 ms (~1.28x speedup)
  • getMetadata: 46.15 ms ➔ 21.00 ms (~2.20x speedup)
  • getNamesForSegment: 745.96 ms ➔ 696.91 ms (~1.07x speedup)

Detailed Changes (Diff Summary)

1. src/db/queries.ts Prepared Statements Typings

Added the following properties to the stmts object type signature inside QueryBuilder:

    isNameSegmentVocabEmpty?: SqliteStatement;
    getDistinctNodeNames?: SqliteStatement;
    getNamesForSegment?: SqliteStatement;
    getAllNodes?: SqliteStatement;
    getDistinctFileLanguages?: SqliteStatement;
    getDependentFilePaths?: SqliteStatement;
    getDependencyFilePaths?: SqliteStatement;
    getCrossFileIncomingEdgesWithTarget?: SqliteStatement;
    getLastIndexedAt?: SqliteStatement;
    getNodeAndEdgeCount?: SqliteStatement;
    getStatsCounts?: SqliteStatement;
    getStatsNodesByKind?: SqliteStatement;
    getStatsEdgesByKind?: SqliteStatement;
    getStatsFilesByLanguage?: SqliteStatement;
    getMetadata?: SqliteStatement;
    setMetadata?: SqliteStatement;
    getAllMetadata?: SqliteStatement;

2. Method Optimizations in QueryBuilder

Updated methods to use lazy compilation and caching of their static SQL query prepared statements. Below is an example pattern implemented in getMetadata and setMetadata:

  getMetadata(key: string): string | null {
    if (!this.stmts.getMetadata) {
      this.stmts.getMetadata = this.db.prepare('SELECT value FROM project_metadata WHERE key = ?');
    }
    const row = this.stmts.getMetadata.get(key) as { value: string } | undefined;
    return row?.value ?? null;
  }
  setMetadata(key: string, value: string): void {
    if (!this.stmts.setMetadata) {
      this.stmts.setMetadata = this.db.prepare(
        'INSERT INTO project_metadata (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at'
      );
    }
    this.stmts.setMetadata.run(key, value, Date.now());
  }

All other listed query methods were modified to adhere to this pattern.

Verification

All 20 database correctness/performance tests pass successfully:

npx vitest run __tests__/db-perf.test.ts

Test Environment

  • OS: Windows 11
  • CPU: Ryzen 7 7735H
  • Node.js: v24.4.1
  • Benchmark iterations: 5,000
    Note: Benchmarks were run on a battery-powered system under a non-performance power profile, so results represent a conservative measurement.

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