-- 048b — supporting index for the lineage family lookup. -- -- WHY. The family lookup was `read_natural_key IN (<100 keys>)`. Measured -- 2026-08-28: `read_natural_key` has NO pg_stats row at all (the table's last -- autoanalyze predates the column ever being populated), so the planner used a -- default per-value selectivity, estimated 172,409 rows for 100 keys, and chose -- a sequential scan of 344,818 rows -- 8.5s, then 57014. At 50 keys the same -- shape planned differently and returned in ~357ms. That cliff is a statistics -- artifact, not a data-volume one, which is why the repair does not depend on -- the estimate improving. -- -- The lookup is now scoped by (sport, game_date) -- both are COMPONENTS OF THE -- NATURAL KEY ITSELF, so the bound is lossless by construction -- and reads only -- COMPLETED lineage actions. This index serves exactly that shape: -- -- Index Scan using model_snapshots_lineage_family_idx (cost=0.28..1.92) -- -- Its size grows with completed lineage actions, and the query's date bound -- means a lookup scans ONE slate's worth however long the chronology gets. -- -- FORWARD-ONLY and NON-DESTRUCTIVE: CONCURRENTLY (no table lock, no rewrite), -- IF NOT EXISTS (safe to re-run), and no existing index is dropped. -- Built on production 2026-08-28: 128 kB over 1,024 covered rows, indisvalid. create index concurrently if not exists model_snapshots_lineage_family_idx on public.model_snapshots (game_date, sport, read_natural_key) where lineage_action is not null;