Index Cleanup
Audit of apps/cloud/drizzle/0000_lame_rage.sql against the query
patterns in packages/core/sdk/src/executor.ts. Summary: mostly
over-indexed, one important miss.
Redundant: single-column scope_id indexes (× 15)
Every scoped table has a composite PK (scope_id, id) AND a separate
<table>_scope_id_idx. Postgres composite btrees support leftmost-
prefix queries — WHERE scope_id = ? uses the PK directly. The
single-column indexes are dead weight: extra storage, extra write
amplification on every insert, never chosen by the planner when the
composite PK is available.
Tables affected (all drop candidates):
definition_scope_id_idxgraphql_operation_scope_id_idx,graphql_source_scope_id_idxmcp_binding_scope_id_idx,mcp_oauth_session_scope_id_idx,mcp_source_scope_id_idxopenapi_oauth_session_scope_id_idx,openapi_operation_scope_id_idx,openapi_source_scope_id_idxsecret_scope_id_idxsource_scope_id_idxtool_scope_id_idxworkos_vault_metadata_scope_id_idx
Root cause: core-schema.ts and plugin schemas mark scope_id: { index: true }. The drizzle generator should probably skip emitting
a single-column index on a column that's already the leftmost column
of a composite primary key. Fix in two places:
- Generator (
packages/core/cli/src/generators/drizzle.ts) — skip emittingindex(...)forfield.indexwhen the field is the first column of a composite PK. - Schemas — drop
index: truefrom thescope_idfield declarations (fallback if we want to be explicit).
Cheaper to fix at the generator level — behaves correctly regardless of what plugin authors write.
Missing: memberships.organization_id
memberships has PK (account_id, organization_id). Queries:
WHERE account_id = ?— "orgs for this user" — uses PK leftmost ✓WHERE organization_id = ?— "members of this org" — table scan
The second query is at least as common as the first. Fix:
CREATE INDEX "memberships_organization_id_idx"
ON "memberships" USING btree ("organization_id");
Or flip the PK column order, but that's a bigger migration.
Probably dead weight — worth auditing queries
Nothing in the executor source currently queries by these fields alone. Keep or drop based on where these are actually exercised (admin UIs? analytics?):
definition_plugin_id_idxsource_plugin_id_idxtool_plugin_id_idxsecret_provider_idx
If kept, leave a note on the call site so future maintainers know why.
Fine as-is
*_source_id_idxon child tables (definition,mcp_binding,graphql_operation,openapi_operation,tool) — exercised byfindMany(where: source_id = X)via the scoped adapter.- Composite
(scope_id, source_id)would be marginally better than the currentsource_idsingle-column index (lets the planner use one index for both filters), but not worth the churn unless we're redoing the DDL anyway.
Rollout
Not blocking PR #262. Do this as a follow-up: one small PR that
(a) teaches the drizzle generator the "skip index on composite-PK-
leading column" rule, (b) regenerates the schemas, (c) adds the
memberships_organization_id_idx, (d) produces a single cleanup
migration. Zero code changes to the runtime.