Port performance improvements from PR #15
- SQLite performance pragmas: synchronous=NORMAL, 64MB cache, memory temp store, 256MB mmap (safe with WAL mode) - Batch insert for unresolved refs: single transaction instead of N individual inserts per file - Symbol caching (warmCaches): pre-load all nodes into memory maps before resolution, eliminating repeated SQLite queries per ref - Async file I/O: fs.stat/readFile in indexFile() are now non-blocking - Denormalize filePath/language onto UnresolvedReference: avoids N node lookups during resolution, with schema migration v2 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
e07250ed60
commit
d80900f653
@@ -41,6 +41,11 @@ export class DatabaseConnection {
|
||||
// Wait up to 2 minutes if database is locked by another process
|
||||
// (indexing operations can hold locks for extended periods)
|
||||
db.pragma('busy_timeout = 120000');
|
||||
// Performance tuning
|
||||
db.pragma('synchronous = NORMAL'); // Safe with WAL mode
|
||||
db.pragma('cache_size = -64000'); // 64 MB page cache
|
||||
db.pragma('temp_store = MEMORY'); // Temp tables in memory
|
||||
db.pragma('mmap_size = 268435456'); // 256 MB memory-mapped I/O
|
||||
|
||||
// Run schema initialization
|
||||
const schemaPath = path.join(__dirname, 'schema.sql');
|
||||
@@ -66,6 +71,11 @@ export class DatabaseConnection {
|
||||
// Wait up to 2 minutes if database is locked by another process
|
||||
// (indexing operations can hold locks for extended periods)
|
||||
db.pragma('busy_timeout = 120000');
|
||||
// Performance tuning
|
||||
db.pragma('synchronous = NORMAL');
|
||||
db.pragma('cache_size = -64000');
|
||||
db.pragma('temp_store = MEMORY');
|
||||
db.pragma('mmap_size = 268435456');
|
||||
|
||||
// Check and run migrations if needed
|
||||
const conn = new DatabaseConnection(db, dbPath);
|
||||
|
||||
+11
-12
@@ -9,7 +9,7 @@ import Database from 'better-sqlite3';
|
||||
/**
|
||||
* Current schema version
|
||||
*/
|
||||
export const CURRENT_SCHEMA_VERSION = 1;
|
||||
export const CURRENT_SCHEMA_VERSION = 2;
|
||||
|
||||
/**
|
||||
* Migration definition
|
||||
@@ -27,17 +27,16 @@ interface Migration {
|
||||
* Future migrations go here.
|
||||
*/
|
||||
const migrations: Migration[] = [
|
||||
// Example migration for version 2 (when needed):
|
||||
// {
|
||||
// version: 2,
|
||||
// description: 'Add support for module resolution',
|
||||
// up: (db) => {
|
||||
// db.exec(`
|
||||
// ALTER TABLE nodes ADD COLUMN module_path TEXT;
|
||||
// CREATE INDEX idx_nodes_module_path ON nodes(module_path);
|
||||
// `);
|
||||
// },
|
||||
// },
|
||||
{
|
||||
version: 2,
|
||||
description: 'Add filePath and language to unresolved_refs for performance',
|
||||
up: (db) => {
|
||||
db.exec(`
|
||||
ALTER TABLE unresolved_refs ADD COLUMN file_path TEXT;
|
||||
ALTER TABLE unresolved_refs ADD COLUMN language TEXT;
|
||||
`);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
+47
-2
@@ -73,6 +73,8 @@ interface UnresolvedRefRow {
|
||||
reference_kind: string;
|
||||
line: number;
|
||||
col: number;
|
||||
file_path: string | null;
|
||||
language: string | null;
|
||||
candidates: string | null;
|
||||
}
|
||||
|
||||
@@ -422,6 +424,14 @@ export class QueryBuilder {
|
||||
return rows.map(rowToNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all nodes in the database
|
||||
*/
|
||||
getAllNodes(): Node[] {
|
||||
const rows = this.db.prepare('SELECT * FROM nodes').all() as NodeRow[];
|
||||
return rows.map(rowToNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search nodes by name using FTS with fallback to LIKE for better matching
|
||||
*
|
||||
@@ -778,8 +788,8 @@ export class QueryBuilder {
|
||||
insertUnresolvedRef(ref: UnresolvedReference): void {
|
||||
if (!this.stmts.insertUnresolved) {
|
||||
this.stmts.insertUnresolved = this.db.prepare(`
|
||||
INSERT INTO unresolved_refs (from_node_id, reference_name, reference_kind, line, col, candidates)
|
||||
VALUES (@fromNodeId, @referenceName, @referenceKind, @line, @col, @candidates)
|
||||
INSERT INTO unresolved_refs (from_node_id, reference_name, reference_kind, line, col, file_path, language, candidates)
|
||||
VALUES (@fromNodeId, @referenceName, @referenceKind, @line, @col, @filePath, @language, @candidates)
|
||||
`);
|
||||
}
|
||||
|
||||
@@ -789,10 +799,41 @@ export class QueryBuilder {
|
||||
referenceKind: ref.referenceKind,
|
||||
line: ref.line,
|
||||
col: ref.column,
|
||||
filePath: ref.filePath ?? null,
|
||||
language: ref.language ?? null,
|
||||
candidates: ref.candidates ? JSON.stringify(ref.candidates) : null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert multiple unresolved references in a single transaction
|
||||
*/
|
||||
insertUnresolvedRefsBatch(refs: UnresolvedReference[]): void {
|
||||
if (refs.length === 0) return;
|
||||
|
||||
if (!this.stmts.insertUnresolved) {
|
||||
this.stmts.insertUnresolved = this.db.prepare(`
|
||||
INSERT INTO unresolved_refs (from_node_id, reference_name, reference_kind, line, col, file_path, language, candidates)
|
||||
VALUES (@fromNodeId, @referenceName, @referenceKind, @line, @col, @filePath, @language, @candidates)
|
||||
`);
|
||||
}
|
||||
|
||||
this.db.transaction(() => {
|
||||
for (const ref of refs) {
|
||||
this.stmts.insertUnresolved!.run({
|
||||
fromNodeId: ref.fromNodeId,
|
||||
referenceName: ref.referenceName,
|
||||
referenceKind: ref.referenceKind,
|
||||
line: ref.line,
|
||||
col: ref.column,
|
||||
filePath: ref.filePath ?? null,
|
||||
language: ref.language ?? null,
|
||||
candidates: ref.candidates ? JSON.stringify(ref.candidates) : null,
|
||||
});
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete unresolved references from a node
|
||||
*/
|
||||
@@ -821,6 +862,8 @@ export class QueryBuilder {
|
||||
referenceKind: row.reference_kind as EdgeKind,
|
||||
line: row.line,
|
||||
column: row.col,
|
||||
filePath: row.file_path ?? undefined,
|
||||
language: (row.language as Language) ?? undefined,
|
||||
candidates: row.candidates ? safeJsonParse<string[]>(row.candidates, []) : undefined,
|
||||
}));
|
||||
}
|
||||
@@ -836,6 +879,8 @@ export class QueryBuilder {
|
||||
referenceKind: row.reference_kind as EdgeKind,
|
||||
line: row.line,
|
||||
column: row.col,
|
||||
filePath: row.file_path ?? undefined,
|
||||
language: (row.language as Language) ?? undefined,
|
||||
candidates: row.candidates ? safeJsonParse<string[]>(row.candidates, []) : undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ CREATE TABLE IF NOT EXISTS schema_versions (
|
||||
-- Insert initial version
|
||||
INSERT INTO schema_versions (version, applied_at, description)
|
||||
VALUES (1, strftime('%s', 'now') * 1000, 'Initial schema');
|
||||
INSERT INTO schema_versions (version, applied_at, description)
|
||||
VALUES (2, strftime('%s', 'now') * 1000, 'Add filePath and language to unresolved_refs');
|
||||
|
||||
-- =============================================================================
|
||||
-- Core Tables
|
||||
@@ -73,6 +75,8 @@ CREATE TABLE IF NOT EXISTS unresolved_refs (
|
||||
reference_kind TEXT NOT NULL,
|
||||
line INTEGER NOT NULL,
|
||||
col INTEGER NOT NULL,
|
||||
file_path TEXT,
|
||||
language TEXT,
|
||||
candidates TEXT, -- JSON array
|
||||
FOREIGN KEY (from_node_id) REFERENCES nodes(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user