From 07e4a253ce79f4f54150b2c4d46d3e76a660b8a9 Mon Sep 17 00:00:00 2001 From: Cristina Poncela Cubeiro <140309543+cristinaponcela@users.noreply.github.com> Date: Mon, 17 Aug 2026 08:53:09 +0200 Subject: [PATCH] feat(sqlite): cache insert statements for trigger performance --- .../sqlite-node/src/sqlite/session/entries.ts | 40 ++++++++++++++----- .../src/sqlite/session/usage-ledger.ts | 38 +++++++++++++----- .../sqlite-node/src/sqlite/storage.ts | 12 ++++-- 3 files changed, 65 insertions(+), 25 deletions(-) diff --git a/packages/session-backends/sqlite-node/src/sqlite/session/entries.ts b/packages/session-backends/sqlite-node/src/sqlite/session/entries.ts index 7c3d3558e..1e1480445 100644 --- a/packages/session-backends/sqlite-node/src/sqlite/session/entries.ts +++ b/packages/session-backends/sqlite-node/src/sqlite/session/entries.ts @@ -8,7 +8,7 @@ import type { MessageEntry, } from "@earendil-works/pi-agent-core"; import { joinSqlFragments, type SqlQuery, sql } from "../sql.ts"; -import type { SqliteDatabase } from "../types.ts"; +import type { SqliteDatabase, SqliteStatement } from "../types.ts"; export interface EntryRow { id: string; @@ -66,17 +66,35 @@ function parsePayload(row: EntryRow): StoredEntryPayload; } +const INSERT_ENTRY_SQL = `INSERT INTO entries (id, parent_id, seq, type, custom_type, timestamp, payload) + VALUES (?, ?, ?, ?, ?, ?, ?)`; + +function entryRowParams(entry: Entry): unknown[] { + return [ + entry.id, + entry.parentId, + entry.seq, + entry.type, + entry.type === "custom" ? entry.customType : null, + entry.timestamp, + JSON.stringify(entryPayload(entry)), + ]; +} + +export class EntryRowWriter { + private readonly insertStatement: SqliteStatement; + + constructor(db: SqliteDatabase) { + this.insertStatement = db.prepare(INSERT_ENTRY_SQL); + } + + insert(entry: Entry): void { + this.insertStatement.run(...entryRowParams(entry)); + } +} + export function insertEntryRow(db: SqliteDatabase, entry: Entry): void { - sql`INSERT INTO entries (id, parent_id, seq, type, custom_type, timestamp, payload) - VALUES ( - ${entry.id}, - ${entry.parentId}, - ${entry.seq}, - ${entry.type}, - ${entry.type === "custom" ? entry.customType : null}, - ${entry.timestamp}, - ${JSON.stringify(entryPayload(entry))} - )`.run(db); + db.prepare(INSERT_ENTRY_SQL).run(...entryRowParams(entry)); } export function decodeEntryRow(row: EntryRow): Entry { diff --git a/packages/session-backends/sqlite-node/src/sqlite/session/usage-ledger.ts b/packages/session-backends/sqlite-node/src/sqlite/session/usage-ledger.ts index 31009e4cf..4a5efa3c1 100644 --- a/packages/session-backends/sqlite-node/src/sqlite/session/usage-ledger.ts +++ b/packages/session-backends/sqlite-node/src/sqlite/session/usage-ledger.ts @@ -1,6 +1,6 @@ import type { UsageRow, UsageScan } from "@earendil-works/pi-agent-core"; import { joinSqlFragments, type SqlQuery, sql } from "../sql.ts"; -import type { SqliteDatabase } from "../types.ts"; +import type { SqliteDatabase, SqliteStatement } from "../types.ts"; export interface UsageLedgerRow { id: string; @@ -11,16 +11,34 @@ export interface UsageLedgerRow { details: string | null; } +const INSERT_USAGE_LEDGER_SQL = `INSERT INTO usage_ledger (id, seq, entry_id, adjustment, usage, details) + VALUES (?, ?, ?, ?, ?, ?)`; + +function usageLedgerRowParams(row: UsageRow): unknown[] { + return [ + row.id, + row.seq, + row.entryId ?? null, + row.adjustment ? 1 : 0, + JSON.stringify(row.usage), + row.details === undefined ? null : JSON.stringify(row.details), + ]; +} + +export class UsageLedgerRowWriter { + private readonly insertStatement: SqliteStatement; + + constructor(db: SqliteDatabase) { + this.insertStatement = db.prepare(INSERT_USAGE_LEDGER_SQL); + } + + insert(row: UsageRow): void { + this.insertStatement.run(...usageLedgerRowParams(row)); + } +} + export function insertUsageLedgerRow(db: SqliteDatabase, row: UsageRow): void { - sql`INSERT INTO usage_ledger (id, seq, entry_id, adjustment, usage, details) - VALUES ( - ${row.id}, - ${row.seq}, - ${row.entryId ?? null}, - ${row.adjustment ? 1 : 0}, - ${JSON.stringify(row.usage)}, - ${row.details === undefined ? null : JSON.stringify(row.details)} - )`.run(db); + db.prepare(INSERT_USAGE_LEDGER_SQL).run(...usageLedgerRowParams(row)); } export function decodeUsageLedgerRow(row: UsageLedgerRow): UsageRow { diff --git a/packages/session-backends/sqlite-node/src/sqlite/storage.ts b/packages/session-backends/sqlite-node/src/sqlite/storage.ts index 003939fbf..83345a57a 100644 --- a/packages/session-backends/sqlite-node/src/sqlite/storage.ts +++ b/packages/session-backends/sqlite-node/src/sqlite/storage.ts @@ -14,11 +14,11 @@ import type { } from "@earendil-works/pi-agent-core"; import { prepareStorageCommit } from "@earendil-works/pi-agent-core"; import { appendEntryToBranchIndex, scanBranchEntries, scanBranchEntryStructures } from "./session/branch-entries.ts"; -import { decodeEntryRow, insertEntryRow, readEntryRows, scanEntryRows } from "./session/entries.ts"; +import { decodeEntryRow, EntryRowWriter, readEntryRows, scanEntryRows } from "./session/entries.ts"; import { deleteRegisterRow, listRegisterRows, readRegisterRow, setRegisterRow } from "./session/registers.ts"; import { advanceNextSeq, readNextSeq } from "./session/session-sequences.ts"; import { addUsageToSessionStats, incrementMessageCount, readSessionStats } from "./session/session-stats.ts"; -import { decodeUsageLedgerRow, insertUsageLedgerRow, scanUsageLedgerRows } from "./session/usage-ledger.ts"; +import { decodeUsageLedgerRow, scanUsageLedgerRows, UsageLedgerRowWriter } from "./session/usage-ledger.ts"; import type { SqliteDatabase } from "./types.ts"; export interface SqliteStorageOptions { @@ -28,6 +28,8 @@ export interface SqliteStorageOptions { export class SqliteStorage implements Storage { private readonly db: SqliteDatabase; private readonly now: () => number; + private readonly entryWriter: EntryRowWriter; + private readonly usageWriter: UsageLedgerRowWriter; private commitQueue: Promise = Promise.resolve(); private state: "open" | "closing" | "closed" = "open"; private closePromise: Promise | undefined; @@ -35,6 +37,8 @@ export class SqliteStorage implements Storage { constructor(db: SqliteDatabase, options: SqliteStorageOptions = {}) { this.db = db; this.now = options.now ?? Date.now; + this.entryWriter = new EntryRowWriter(db); + this.usageWriter = new UsageLedgerRowWriter(db); } commit(transaction: Transaction): Promise { @@ -107,14 +111,14 @@ export class SqliteStorage implements Storage { switch (write.kind) { case "entry": { const { kind: _kind, ...entry } = write; - insertEntryRow(this.db, entry); + this.entryWriter.insert(entry); appendEntryToBranchIndex(this.db, entry); if (entry.type === "message") incrementMessageCount(this.db); break; } case "usage": { const { kind: _kind, ...row } = write; - insertUsageLedgerRow(this.db, row); + this.usageWriter.insert(row); addUsageToSessionStats(this.db, row.usage); break; }