Skip to content

Commit 30b950d

Browse files
perf(tables): tenant-scoped containment index (migration 0232)
The plain GIN on user_table_rows.data matched @> candidates across every tenant sharing the relation — a hot value in someone else's table inflated everyone's equality filters (1.07M candidates fetched for a 33k-row match, lossy bitmap, 1.1s). Replace it with btree_gin (table_id, data jsonb_path_ops): the tenant intersection happens inside the index and paths are single hashed entries. Rare-equality probe 326ms -> 17ms with zero wasted candidates; unique-constraint checks and upsert conflict lookups ride the same index. The new index is smaller than the one it replaces (529MB vs 694MB on the 12M-row dev relation). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5464ce7 commit 30b950d

4 files changed

Lines changed: 16788 additions & 1 deletion

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Tenant-scoped containment index: a plain GIN on data matches @> candidates across every
2+
-- tenant sharing this relation (measured 1.07M candidates fetched for a 33k-row match). btree_gin
3+
-- lets table_id lead the GIN so the intersection happens inside the index, and jsonb_path_ops
4+
-- indexes whole key->value paths (single lookup, smaller index). Every @> query carries table_id,
5+
-- so the old cross-tenant index is strictly redundant.
6+
CREATE EXTENSION IF NOT EXISTS btree_gin;--> statement-breakpoint
7+
DROP INDEX "user_table_rows_data_gin_idx";--> statement-breakpoint
8+
CREATE INDEX "user_table_rows_tenant_data_gin_idx" ON "user_table_rows" USING gin ("table_id","data" jsonb_path_ops);

0 commit comments

Comments
 (0)