mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 17:45:53 +01:00
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
1de105c475
commit
ad42f04efb
5 files changed
+95
-12
No files matched your search
@@ -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<void>> = [];
|
||||
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);
|
||||
});
|
||||
@@ -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");
|
||||
+8
-1
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
@@ -50,5 +50,9 @@ export const costEvents = pgTable(
|
||||
table.companyId,
|
||||
table.heartbeatRunId,
|
||||
),
|
||||
companyIssueIdx: index("cost_events_company_issue_idx").on(
|
||||
table.companyId,
|
||||
table.issueId,
|
||||
),
|
||||
}),
|
||||
);
|
||||
Reference in new issue
Block a user