From ad42f04efb4cd5c4ba013909cd21594f6e360dfc Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 6 Aug 2026 17:12:28 +0000 Subject: [PATCH] perf(db): index cost_events issue sums and session token lookups The productivity-review sweep sums cost_cents per candidate issue every 30 seconds, and cost_events has no index on issue_id, so each sum reads every cost event the company recorded (~21k rows, ~168ms cold; 51B tuples read via 1.8M seq scans since Jul 23). Session auth resolves the bearer token on every request against a session table with no token index (1.6M seq scans). Add cost_events(company_id, issue_id) and a unique index on session(token), with an embedded-postgres test covering index presence, planner usage, and migration idempotency. Co-Authored-By: Claude Fable 5 --- ...ost-events-session-index-migration.test.ts | 64 +++++++++++++++++++ ...ost_events_issue_session_token_indexes.sql | 2 + packages/db/src/migrations/meta/_journal.json | 9 ++- packages/db/src/schema/auth.ts | 28 ++++---- packages/db/src/schema/cost_events.ts | 4 ++ 5 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 packages/db/src/cost-events-session-index-migration.test.ts create mode 100644 packages/db/src/migrations/0228_cost_events_issue_session_token_indexes.sql diff --git a/packages/db/src/cost-events-session-index-migration.test.ts b/packages/db/src/cost-events-session-index-migration.test.ts new file mode 100644 index 00000000000..d1fabdaada5 --- /dev/null +++ b/packages/db/src/cost-events-session-index-migration.test.ts @@ -0,0 +1,64 @@ +import { readFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it, afterEach } from "vitest"; +import postgres from "postgres"; +import { + getEmbeddedPostgresTestSupport, + startEmbeddedPostgresTestDatabase, +} from "./test-embedded-postgres.js"; + +const cleanups: Array<() => Promise> = []; +const support = await getEmbeddedPostgresTestSupport(); +const d = support.supported ? describe : describe.skip; + +afterEach(async () => { + while (cleanups.length > 0) await cleanups.pop()?.(); +}); + +d("cost_events issue and session token index migration", () => { + it("applies full migration chain and uses the new indexes", async () => { + const dbh = await startEmbeddedPostgresTestDatabase("pap16575-r3-idx-"); + cleanups.push(() => dbh.cleanup()); + const sql = postgres(dbh.connectionString, { max: 1 }); + cleanups.push(async () => { await sql.end(); }); + + const idx = await sql`SELECT indexname FROM pg_indexes WHERE tablename IN ('cost_events','session')`; + const names = idx.map((r) => r.indexname as string); + expect(names).toContain("cost_events_company_issue_idx"); + expect(names).toContain("session_token_uq"); + + await sql.unsafe("SET enable_seqscan = off"); + + // The productivity-review sweep sums cost per candidate issue on every + // 30s tick. Without this index each sum reads every cost event the + // company ever recorded. + const costPlan = await sql.unsafe( + "EXPLAIN SELECT coalesce(sum(cost_cents), 0)::int FROM cost_events WHERE company_id = '00000000-0000-0000-0000-000000000001' AND issue_id = '00000000-0000-0000-0000-000000000002'", + ); + const costText = costPlan.map((r) => Object.values(r)[0]).join("\n"); + expect(costText).toContain("cost_events_company_issue_idx"); + + // Session auth resolves the bearer token on every request; the lookup + // must be an index scan, and tokens must be unique. + const sessionPlan = await sql.unsafe( + "EXPLAIN SELECT id FROM session WHERE token = 'x'", + ); + const sessionText = sessionPlan.map((r) => Object.values(r)[0]).join("\n"); + expect(sessionText).toContain("session_token_uq"); + + // Idempotency: re-running the migration statements against an already + // migrated database must be a no-op, not an error. + const migrationSql = await readFile( + fileURLToPath(new URL("./migrations/0228_cost_events_issue_session_token_indexes.sql", import.meta.url)), + "utf8", + ); + const statements = migrationSql + .split("--> statement-breakpoint") + .map((s) => s.trim()) + .filter((s) => s.length > 0); + expect(statements.length).toBeGreaterThan(0); + for (const statement of statements) { + await sql.unsafe(statement); + } + }, 240_000); +}); diff --git a/packages/db/src/migrations/0228_cost_events_issue_session_token_indexes.sql b/packages/db/src/migrations/0228_cost_events_issue_session_token_indexes.sql new file mode 100644 index 00000000000..09ef0d128eb --- /dev/null +++ b/packages/db/src/migrations/0228_cost_events_issue_session_token_indexes.sql @@ -0,0 +1,2 @@ +CREATE INDEX IF NOT EXISTS "cost_events_company_issue_idx" ON "cost_events" USING btree ("company_id", "issue_id");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "session_token_uq" ON "session" USING btree ("token"); diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 585f274aab1..0438d7ab5d8 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -1583,6 +1583,13 @@ "when": 1787668790923, "tag": "0227_modern_pandemic", "breakpoints": true + }, + { + "idx": 228, + "version": "7", + "when": 1787839200000, + "tag": "0228_cost_events_issue_session_token_indexes", + "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/packages/db/src/schema/auth.ts b/packages/db/src/schema/auth.ts index b6ffe08e5ac..772a5414247 100644 --- a/packages/db/src/schema/auth.ts +++ b/packages/db/src/schema/auth.ts @@ -1,4 +1,4 @@ -import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core"; +import { pgTable, text, timestamp, boolean, uniqueIndex } from "drizzle-orm/pg-core"; export const authUsers = pgTable("user", { id: text("id").primaryKey(), @@ -10,16 +10,22 @@ export const authUsers = pgTable("user", { updatedAt: timestamp("updated_at", { withTimezone: true }).notNull(), }); -export const authSessions = pgTable("session", { - id: text("id").primaryKey(), - expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(), - token: text("token").notNull(), - createdAt: timestamp("created_at", { withTimezone: true }).notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true }).notNull(), - ipAddress: text("ip_address"), - userAgent: text("user_agent"), - userId: text("user_id").notNull().references(() => authUsers.id, { onDelete: "cascade" }), -}); +export const authSessions = pgTable( + "session", + { + id: text("id").primaryKey(), + expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(), + token: text("token").notNull(), + createdAt: timestamp("created_at", { withTimezone: true }).notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull(), + ipAddress: text("ip_address"), + userAgent: text("user_agent"), + userId: text("user_id").notNull().references(() => authUsers.id, { onDelete: "cascade" }), + }, + (table) => ({ + tokenUq: uniqueIndex("session_token_uq").on(table.token), + }), +); export const authAccounts = pgTable("account", { id: text("id").primaryKey(), diff --git a/packages/db/src/schema/cost_events.ts b/packages/db/src/schema/cost_events.ts index 9ca547651f5..fc103e4bf25 100644 --- a/packages/db/src/schema/cost_events.ts +++ b/packages/db/src/schema/cost_events.ts @@ -50,5 +50,9 @@ export const costEvents = pgTable( table.companyId, table.heartbeatRunId, ), + companyIssueIdx: index("cost_events_company_issue_idx").on( + table.companyId, + table.issueId, + ), }), );