Skip to content

sync-indexes: create expression statistics alongside identifier indexes - #95

Merged
koenvo merged 1 commit into
mainfrom
feat/identifier-expression-statistics
Aug 12, 2026
Merged

sync-indexes: create expression statistics alongside identifier indexes#95
koenvo merged 1 commit into
mainfrom
feat/identifier-expression-statistics

Conversation

@koenvo

@koenvo koenvo commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

An identifier expression index lets Postgres find rows by a JSONB key, but the planner has no cardinality statistics for the expression, so it defaults to a poor n_distinct and mis-estimates selectivity. For a batch identifier lookup (WHERE identifier->>'k' = ANY / JOIN VALUES) it then prefers a full-partition hash join over a per-key index nested loop, so a large table stays slow despite the index (observed: a keyword lookup estimated 151 rows/lookup vs 1 actual, and picked a full scan of a 181k-row partition, ~2s/chunk).

create_identifier_indexes now also creates table-level expression statistics (CREATE STATISTICS ... ON (), PostgreSQL 14+) on the same expression(s), and runs ANALYZE once so they take effect immediately. With real n_distinct the planner chooses the index nested loop on its own -- no planner hints, and safe when no index exists (the hash join stays the correct default). Adds a Postgres-14+ test asserting the statistics are created.

An identifier expression index lets Postgres find rows by a JSONB key, but the
planner has no cardinality statistics for the expression, so it defaults to a
poor n_distinct and mis-estimates selectivity. For a batch identifier lookup
(WHERE identifier->>'k' = ANY / JOIN VALUES) it then prefers a full-partition
hash join over a per-key index nested loop, so a large table stays slow despite
the index (observed: a keyword lookup estimated 151 rows/lookup vs 1 actual, and
picked a full scan of a 181k-row partition, ~2s/chunk).

create_identifier_indexes now also creates table-level expression statistics
(CREATE STATISTICS ... ON (<expr>), PostgreSQL 14+) on the same expression(s),
and runs ANALYZE once so they take effect immediately. With real n_distinct the
planner chooses the index nested loop on its own -- no planner hints, and safe
when no index exists (the hash join stays the correct default). Adds a
Postgres-14+ test asserting the statistics are created.

Claude-Session: https://claude.ai/code/session_01B5EfLJqoafjW1FhvkxGSmg
@koenvo
koenvo merged commit 7c3c032 into main Aug 12, 2026
13 checks passed
@koenvo
koenvo deleted the feat/identifier-expression-statistics branch August 12, 2026 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant