mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-10-15 22:55:42 -04:00
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:
4 files changed
+57
No files matched your search
@@ -13,6 +13,7 @@ import {
|
||||
getTitleStatusValue,
|
||||
updateImportJobProgress,
|
||||
} from "@sofa/db/queries/imports";
|
||||
import { refreshPlannerStats } from "@sofa/db/queries/maintenance";
|
||||
import { findEpisodeBySeasonAndNumber, findSeasonByTitleAndNumber } from "@sofa/db/queries/title";
|
||||
import { createLogger } from "@sofa/logger";
|
||||
|
||||
@@ -525,6 +526,12 @@ export async function processImportJob(jobId: string): Promise<void> {
|
||||
currentMessage: "Import complete",
|
||||
});
|
||||
|
||||
try {
|
||||
refreshPlannerStats();
|
||||
} catch (err) {
|
||||
log.warn("Failed to refresh planner statistics after import:", err);
|
||||
}
|
||||
|
||||
log.info(
|
||||
`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 * as maintenance from "@sofa/db/queries/maintenance";
|
||||
import {
|
||||
importJobs,
|
||||
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 ──────────────────────────────────────────────────
|
||||
|
||||
describe("processImportJob — episodes", () => {
|
||||
|
||||
@@ -61,6 +61,9 @@ function getClient() {
|
||||
globalForDb.client.run("PRAGMA cache_size = -64000");
|
||||
globalForDb.client.run("PRAGMA temp_store = MEMORY");
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
Reference in new issue
Block a user