mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-10-15 23:55:43 -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,
|
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", () => {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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