feat(sqlite): cache insert statements for trigger performance

This commit is contained in:
Cristina Poncela Cubeiro 2026-08-17 08:53:09 +02:00
parent 34bad4471a
commit 07e4a253ce
3 changed files with 65 additions and 25 deletions

View file

@ -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<TEntry extends Entry>(row: EntryRow): StoredEntryPayload<T
return JSON.parse(row.payload) as StoredEntryPayload<TEntry>;
}
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 {

View file

@ -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 {

View file

@ -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<void> = Promise.resolve();
private state: "open" | "closing" | "closed" = "open";
private closePromise: Promise<void> | 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<CommitResult> {
@ -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;
}