Quellcode durchsuchen

perf(db): drop redundant idx_edges_source / idx_edges_target (#142)

Both narrow indexes are fully covered by the existing (source, kind)
and (target, kind) composites via SQLite's left-prefix scan, so
they're dead weight on every write. Empirical measurements (from the
spike script in PR #122 on a 50K-node / 250K-edge synthetic DB):

  - DB size: 34.7 MB → 27.0 MB (-22.2%)
  - Bulk insert (250K edges): 590ms → 431ms (1.37× faster)
  - source/target lookup latency: no regression

Adds migration v4 to drop both on existing databases; fresh-DB schema
no longer creates them.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Colby Mchenry vor 1 Monat
Ursprung
Commit
a460b856c2
4 geänderte Dateien mit 20 neuen und 6 gelöschten Zeilen
  1. 1 1
      __tests__/foundation.test.ts
  2. 1 1
      __tests__/pr19-improvements.test.ts
  3. 12 1
      src/db/migrations.ts
  4. 6 3
      src/db/schema.sql

+ 1 - 1
__tests__/foundation.test.ts

@@ -305,7 +305,7 @@ describe('Database Connection', () => {
 
     const version = db.getSchemaVersion();
     expect(version).not.toBeNull();
-    expect(version?.version).toBe(3);
+    expect(version?.version).toBe(4);
 
     db.close();
   });

+ 1 - 1
__tests__/pr19-improvements.test.ts

@@ -299,7 +299,7 @@ describe('Best-Candidate Resolution', () => {
 describe('Schema v2 Migration', () => {
   it.skipIf(!HAS_SQLITE)('should have correct current schema version', async () => {
     const { CURRENT_SCHEMA_VERSION } = await import('../src/db/migrations');
-    expect(CURRENT_SCHEMA_VERSION).toBe(3);
+    expect(CURRENT_SCHEMA_VERSION).toBe(4);
   });
 
   it.skipIf(!HAS_SQLITE)('should have migration for version 2', async () => {

+ 12 - 1
src/db/migrations.ts

@@ -9,7 +9,7 @@ import { SqliteDatabase } from './sqlite-adapter';
 /**
  * Current schema version
  */
-export const CURRENT_SCHEMA_VERSION = 3;
+export const CURRENT_SCHEMA_VERSION = 4;
 
 /**
  * Migration definition
@@ -54,6 +54,17 @@ const migrations: Migration[] = [
       `);
     },
   },
+  {
+    version: 4,
+    description:
+      'Drop redundant idx_edges_source / idx_edges_target (covered by source_kind / target_kind composites)',
+    up: (db) => {
+      db.exec(`
+        DROP INDEX IF EXISTS idx_edges_source;
+        DROP INDEX IF EXISTS idx_edges_target;
+      `);
+    },
+  },
 ];
 
 /**

+ 6 - 3
src/db/schema.sql

@@ -122,9 +122,12 @@ CREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE ON nodes BEGIN
     VALUES (NEW.rowid, NEW.id, NEW.name, NEW.qualified_name, NEW.docstring, NEW.signature);
 END;
 
--- Edge indexes
-CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source);
-CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target);
+-- Edge indexes.
+-- idx_edges_source / idx_edges_target are intentionally omitted —
+-- the (source, kind) and (target, kind) composites below cover the
+-- corresponding source-only / target-only lookups via SQLite's
+-- left-prefix scan, so the narrow indexes are dead weight on writes.
+-- Migration v4 drops them on existing databases.
 CREATE INDEX IF NOT EXISTS idx_edges_kind ON edges(kind);
 CREATE INDEX IF NOT EXISTS idx_edges_source_kind ON edges(source, kind);
 CREATE INDEX IF NOT EXISTS idx_edges_target_kind ON edges(target, kind);