# 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 `_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_idx` - `graphql_operation_scope_id_idx`, `graphql_source_scope_id_idx` - `mcp_binding_scope_id_idx`, `mcp_oauth_session_scope_id_idx`, `mcp_source_scope_id_idx` - `openapi_oauth_session_scope_id_idx`, `openapi_operation_scope_id_idx`, `openapi_source_scope_id_idx` - `secret_scope_id_idx` - `source_scope_id_idx` - `tool_scope_id_idx` - `workos_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: 1. **Generator** (`packages/core/cli/src/generators/drizzle.ts`) — skip emitting `index(...)` for `field.index` when the field is the first column of a composite PK. 2. **Schemas** — drop `index: true` from the `scope_id` field 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: ```sql 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_idx` - `source_plugin_id_idx` - `tool_plugin_id_idx` - `secret_provider_idx` If kept, leave a note on the call site so future maintainers know why. ## Fine as-is - `*_source_id_idx` on child tables (`definition`, `mcp_binding`, `graphql_operation`, `openapi_operation`, `tool`) — exercised by `findMany(where: source_id = X)` via the scoped adapter. - Composite `(scope_id, source_id)` would be marginally better than the current `source_id` single-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.