perf(db): collect planner statistics on connect and after imports

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jakeandClaude Opus 5.5 committed 2026-10-04 23:18:25 -04:00
1 parent 6620f0b76f
commit 82f0bb11e2
4 files changed
+57

No files matched your search

+7
View File
@@ -13,6 +13,7 @@ import {
getTitleStatusValue, getTitleStatusValue,
updateImportJobProgress, updateImportJobProgress,
} from "@sofa/db/queries/imports"; } from "@sofa/db/queries/imports";
import { refreshPlannerStats } from "@sofa/db/queries/maintenance";
import { findEpisodeBySeasonAndNumber, findSeasonByTitleAndNumber } from "@sofa/db/queries/title"; import { findEpisodeBySeasonAndNumber, findSeasonByTitleAndNumber } from "@sofa/db/queries/title";
import { createLogger } from "@sofa/logger"; import { createLogger } from "@sofa/logger";
@@ -525,6 +526,12 @@ export async function processImportJob(jobId: string): Promise<void> {
currentMessage: "Import complete", currentMessage: "Import complete",
}); });
try {
refreshPlannerStats();
} catch (err) {
log.warn("Failed to refresh planner statistics after import:", err);
}
log.info( log.info(
`Import job ${jobId} complete: ${result.imported} imported, ${result.skipped} skipped, ${result.failed} failed`, `Import job ${jobId} complete: ${result.imported} imported, ${result.skipped} skipped, ${result.failed} failed`,
); );
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
import { beforeEach, describe, expect, test, vi } from "vitest"; import { beforeEach, describe, expect, test, vi } from "vitest";
import * as maintenance from "@sofa/db/queries/maintenance";
import { import {
importJobs, importJobs,
titles, titles,
@@ -162,6 +163,44 @@ describe("processImportJob — movies", () => {
}); });
}); });
// ── Planner Statistics ──────────────────────────────────────────────
describe("processImportJob — planner statistics", () => {
function createMovieJob() {
const userId = insertUser();
insertMovieTitle("movie-1", 550, "Fight Club");
const payload: NormalizedImport = {
source: "trakt",
movies: [{ tmdbId: 550, title: "Fight Club", watchedAt: "2024-06-15T20:00:00Z" }],
episodes: [],
watchlist: [],
ratings: [],
};
return createJob(userId, payload);
}
test("refreshes planner statistics once after a successful import", async () => {
const spy = vi.spyOn(maintenance, "refreshPlannerStats");
const jobId = createMovieJob();
await processImportJob(jobId);
expect(spy).toHaveBeenCalledTimes(1);
expect(readImportJob(jobId).status).toBe("success");
});
test("a failing statistics refresh does not fail the import", async () => {
vi.spyOn(maintenance, "refreshPlannerStats").mockImplementation(() => {
throw new Error("boom");
});
const jobId = createMovieJob();
await processImportJob(jobId);
expect(readImportJob(jobId).status).toBe("success");
});
});
// ── Episode Import ────────────────────────────────────────────────── // ── Episode Import ──────────────────────────────────────────────────
describe("processImportJob — episodes", () => { describe("processImportJob — episodes", () => {
+3
View File
@@ -61,6 +61,9 @@ function getClient() {
globalForDb.client.run("PRAGMA cache_size = -64000"); globalForDb.client.run("PRAGMA cache_size = -64000");
globalForDb.client.run("PRAGMA temp_store = MEMORY"); globalForDb.client.run("PRAGMA temp_store = MEMORY");
globalForDb.client.run("PRAGMA mmap_size = 268435456"); globalForDb.client.run("PRAGMA mmap_size = 268435456");
// Gather planner statistics now instead of waiting for the weekly optimize job
// (SQLite's recommendation for long-lived connections).
globalForDb.client.run("PRAGMA optimize=0x10002");
} }
return globalForDb.client; return globalForDb.client;
} }
+8
View File
@@ -0,0 +1,8 @@
import { sql } from "drizzle-orm";
import { db } from "../client";
/** Refresh SQLite query-planner statistics after a bulk write (e.g. an import). */
export function refreshPlannerStats(): void {
db.run(sql`PRAGMA optimize=0x10002`);
}