fix(db): add missing indexes and drop redundant ones (#319)
Postgres advisor flagged seq-scans and redundant indexes across both backends (D1 + Postgres): - add projects(organization_id) — org-scoped project listings seq-scanned - add account(account_id, provider_id) — better-auth sign-in lookup - add verification(expires_at) — expired-token cleanup range scan - drop saved_keyword_tag_assignments_keyword_idx — covered by unique (saved_keyword_id, tag_id) prefix - drop rank_snapshots_run_idx — covered by unique (run_id, tracking_keyword_id, device) prefix Mirrored in both schema dialects + parity-test required-index guard.
This commit is contained in:
parent
e5e5029e6b
commit
116719a019
3
drizzle-pg/0003_sturdy_may_parker.sql
Normal file
3
drizzle-pg/0003_sturdy_may_parker.sql
Normal file
@ -0,0 +1,3 @@
|
||||
CREATE INDEX "projects_organization_id_idx" ON "projects" USING btree ("organization_id");--> statement-breakpoint
|
||||
CREATE INDEX "account_accountId_providerId_idx" ON "account" USING btree ("account_id","provider_id");--> statement-breakpoint
|
||||
CREATE INDEX "verification_expiresAt_idx" ON "verification" USING btree ("expires_at");
|
||||
2
drizzle-pg/0004_dashing_betty_ross.sql
Normal file
2
drizzle-pg/0004_dashing_betty_ross.sql
Normal file
@ -0,0 +1,2 @@
|
||||
DROP INDEX "rank_snapshots_run_idx";--> statement-breakpoint
|
||||
DROP INDEX "saved_keyword_tag_assignments_keyword_idx";
|
||||
2920
drizzle-pg/meta/0003_snapshot.json
Normal file
2920
drizzle-pg/meta/0003_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2890
drizzle-pg/meta/0004_snapshot.json
Normal file
2890
drizzle-pg/meta/0004_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,20 @@
|
||||
"when": 1781902417326,
|
||||
"tag": "0002_clean_moira_mactaggert",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1782833725049,
|
||||
"tag": "0003_sturdy_may_parker",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "7",
|
||||
"when": 1782851683039,
|
||||
"tag": "0004_dashing_betty_ross",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
3
drizzle/0026_wide_red_skull.sql
Normal file
3
drizzle/0026_wide_red_skull.sql
Normal file
@ -0,0 +1,3 @@
|
||||
CREATE INDEX `projects_organization_id_idx` ON `projects` (`organization_id`);--> statement-breakpoint
|
||||
CREATE INDEX `account_accountId_providerId_idx` ON `account` (`account_id`,`provider_id`);--> statement-breakpoint
|
||||
CREATE INDEX `verification_expiresAt_idx` ON `verification` (`expires_at`);
|
||||
2
drizzle/0027_reflective_molten_man.sql
Normal file
2
drizzle/0027_reflective_molten_man.sql
Normal file
@ -0,0 +1,2 @@
|
||||
DROP INDEX `rank_snapshots_run_idx`;--> statement-breakpoint
|
||||
DROP INDEX `saved_keyword_tag_assignments_keyword_idx`;
|
||||
2656
drizzle/meta/0026_snapshot.json
Normal file
2656
drizzle/meta/0026_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2642
drizzle/meta/0027_snapshot.json
Normal file
2642
drizzle/meta/0027_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -183,6 +183,20 @@
|
||||
"when": 1781892941214,
|
||||
"tag": "0025_loving_mojo",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 26,
|
||||
"version": "6",
|
||||
"when": 1782833723883,
|
||||
"tag": "0026_wide_red_skull",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 27,
|
||||
"version": "6",
|
||||
"when": 1782851682143,
|
||||
"tag": "0027_reflective_molten_man",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -71,6 +71,11 @@ export const projects = sqliteTable(
|
||||
.where(
|
||||
sql`${table.name} = 'Default' AND ${table.domain} IS NULL AND ${table.archivedAt} IS NULL`,
|
||||
),
|
||||
// Every project listing filters by organization; the partial-unique index
|
||||
// above only covers the Default-project row, so without this the org-scoped
|
||||
// list queries seq-scan. Per-org row counts are small, so the archived/
|
||||
// created_at ordering sorts cheaply on top of this single-column lookup.
|
||||
index("projects_organization_id_idx").on(table.organizationId),
|
||||
],
|
||||
);
|
||||
|
||||
@ -149,7 +154,8 @@ export const savedKeywordTagAssignments = sqliteTable(
|
||||
table.savedKeywordId,
|
||||
table.tagId,
|
||||
),
|
||||
index("saved_keyword_tag_assignments_keyword_idx").on(table.savedKeywordId),
|
||||
// No standalone index on savedKeywordId — the unique index above has it as
|
||||
// its leftmost column, so it already serves savedKeywordId lookups.
|
||||
index("saved_keyword_tag_assignments_tag_idx").on(table.tagId),
|
||||
],
|
||||
);
|
||||
@ -323,7 +329,8 @@ export const rankSnapshots = sqliteTable(
|
||||
.default(sql`(current_timestamp)`),
|
||||
},
|
||||
(table) => [
|
||||
index("rank_snapshots_run_idx").on(table.runId),
|
||||
// No standalone index on runId — the unique index below has it as its
|
||||
// leftmost column, so it already serves runId lookups.
|
||||
index("rank_snapshots_keyword_device_idx").on(
|
||||
table.trackingKeywordId,
|
||||
table.device,
|
||||
|
||||
@ -74,7 +74,15 @@ export const account = sqliteTable(
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [index("account_userId_idx").on(table.userId)],
|
||||
(table) => [
|
||||
index("account_userId_idx").on(table.userId),
|
||||
// better-auth looks up accounts by (accountId, providerId) on every
|
||||
// credential/OAuth sign-in; without this it seq-scans the account table.
|
||||
index("account_accountId_providerId_idx").on(
|
||||
table.accountId,
|
||||
table.providerId,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
export const verification = sqliteTable(
|
||||
@ -92,7 +100,12 @@ export const verification = sqliteTable(
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [index("verification_identifier_idx").on(table.identifier)],
|
||||
(table) => [
|
||||
index("verification_identifier_idx").on(table.identifier),
|
||||
// better-auth's periodic cleanup deletes rows via `expires_at < now()`,
|
||||
// a range scan that seq-scans the whole table without this index.
|
||||
index("verification_expiresAt_idx").on(table.expiresAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const organization = sqliteTable(
|
||||
|
||||
@ -82,6 +82,11 @@ export const projects = pgTable(
|
||||
.where(
|
||||
sql`${table.name} = 'Default' AND ${table.domain} IS NULL AND ${table.archivedAt} IS NULL`,
|
||||
),
|
||||
// Every project listing filters by organization; the partial-unique index
|
||||
// above only covers the Default-project row, so without this the org-scoped
|
||||
// list queries seq-scan. Per-org row counts are small, so the archived/
|
||||
// created_at ordering sorts cheaply on top of this single-column lookup.
|
||||
index("projects_organization_id_idx").on(table.organizationId),
|
||||
],
|
||||
);
|
||||
|
||||
@ -154,7 +159,8 @@ export const savedKeywordTagAssignments = pgTable(
|
||||
table.savedKeywordId,
|
||||
table.tagId,
|
||||
),
|
||||
index("saved_keyword_tag_assignments_keyword_idx").on(table.savedKeywordId),
|
||||
// No standalone index on savedKeywordId — the unique index above has it as
|
||||
// its leftmost column, so it already serves savedKeywordId lookups.
|
||||
index("saved_keyword_tag_assignments_tag_idx").on(table.tagId),
|
||||
],
|
||||
);
|
||||
@ -316,7 +322,8 @@ export const rankSnapshots = pgTable(
|
||||
checkedAt: timestampColumn("checked_at").notNull().default(isoNow),
|
||||
},
|
||||
(table) => [
|
||||
index("rank_snapshots_run_idx").on(table.runId),
|
||||
// No standalone index on runId — the unique index below has it as its
|
||||
// leftmost column, so it already serves runId lookups.
|
||||
index("rank_snapshots_keyword_device_idx").on(
|
||||
table.trackingKeywordId,
|
||||
table.device,
|
||||
|
||||
@ -66,7 +66,15 @@ export const account = pgTable(
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [index("account_userId_idx").on(table.userId)],
|
||||
(table) => [
|
||||
index("account_userId_idx").on(table.userId),
|
||||
// better-auth looks up accounts by (accountId, providerId) on every
|
||||
// credential/OAuth sign-in; without this it seq-scans the account table.
|
||||
index("account_accountId_providerId_idx").on(
|
||||
table.accountId,
|
||||
table.providerId,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
export const verification = pgTable(
|
||||
@ -82,7 +90,12 @@ export const verification = pgTable(
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
},
|
||||
(table) => [index("verification_identifier_idx").on(table.identifier)],
|
||||
(table) => [
|
||||
index("verification_identifier_idx").on(table.identifier),
|
||||
// better-auth's periodic cleanup deletes rows via `expires_at < now()`,
|
||||
// a range scan that seq-scans the whole table without this index.
|
||||
index("verification_expiresAt_idx").on(table.expiresAt),
|
||||
],
|
||||
);
|
||||
|
||||
export const organization = pgTable(
|
||||
|
||||
@ -225,7 +225,9 @@ const REQUIRED_BETTER_AUTH_INDEXES: {
|
||||
}[] = [
|
||||
{ table: "session", columns: ["user_id"], unique: false },
|
||||
{ table: "account", columns: ["user_id"], unique: false },
|
||||
{ table: "account", columns: ["account_id", "provider_id"], unique: false },
|
||||
{ table: "verification", columns: ["identifier"], unique: false },
|
||||
{ table: "verification", columns: ["expires_at"], unique: false },
|
||||
{ table: "organization", columns: ["slug"], unique: true },
|
||||
{ table: "member", columns: ["organization_id"], unique: false },
|
||||
{ table: "member", columns: ["user_id"], unique: false },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user