fix(server): narrow metric_series_rollups covering index to scalar INCLUDE (JEF-591) - #152
Merged
thejefflarson merged 1 commit intoJul 28, 2026
Conversation
…CLUDE (JEF-591) Narrows metric_series_rollups_name_bucket_covering_idx's INCLUDE list to scalar-only columns (service, kind, unit, is_monotonic, count, sum, avg, max), dropping the heavy attrs JSONB and bucket_bounds/bucket_counts arrays -- only the rarer facet/histogram read paths need those; the hot query_metric_series path only ever selects sum/count. Narrower INCLUDE means a smaller index tuple, cutting per-ingest-upsert write amplification on the Pi. The JEF-580 online-DDL lane only matched a desired index by name + pg_index.indisvalid, so editing DESIRED_INDEXES' create_sql alone would have been a silent no-op against the existing wide, valid, same-named prod index -- it would never narrow. Added definition-drift detection: OnlineIndex gains an include_cols field, and reconcile_index now also compares a *valid* index's live INCLUDE columns (via pg_index/ pg_attribute -- indkey past indnkeyatts -- not pg_get_indexdef's text, which isn't a stable string to compare against) against the desired set, dropping + rebuilding on a mismatch. Absent/invalid handling is unchanged. Tests (against a real Postgres): a synthetic pre-created wide index with a narrow desired def is dropped + rebuilt to the narrow INCLUDE set, and a second run against the now-matching definition is a no-op; the existing seeded-index test now exercises the real migration-0017-built wide index narrowing via the lane, then confirms idempotency. Locally verified with a seeded ~2.1M-row table: query_metric_series's query plans as an Index Only Scan (Heap Fetches: 0) with the narrow index (matching the wide index's plan -- no regression on the hot path), and metric_facet/metric_histogram-equivalent queries return correct results via a plain Index Scan (heap fetches for attrs/ bucket_bounds/bucket_counts, as expected). Real-prod (10.4M row) EXPLAIN and index-size measurement are out of scope here -- only reachable against the production database. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
thejefflarson
deleted the
thejefflarson/jef-591-narrow-the-metric_series_rollups-covering-index-to-scalar
branch
July 28, 2026 05:22
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes JEF-591.
Summary
Narrows
metric_series_rollups_name_bucket_covering_idx'sINCLUDElist toscalar-only columns (
service, kind, unit, is_monotonic, count, sum, avg, max),dropping the heavy
attrsJSONB andbucket_bounds/bucket_countsarrays --only the rarer facet/histogram read paths need those; the hot
query_metric_seriespath only ever selectssum/count. This killsper-ingest-upsert fat-tuple write amplification on the Pi.
The real work (per the ticket) was making the JEF-580 online-DDL lane detect
the definition change at all. The lane originally matched a desired index by
name +
pg_index.indisvalidonly, so just editingDESIRED_INDEXES'create_sqlwould have been a silent no-op against the existing wide, valid,same-named prod index -- it would never narrow.
Drift-detection approach
OnlineIndexgains aninclude_cols: &'static [&'static str]field.reconcile_index'sValidbranch now also callsinclude_columns_drifted,which compares the index's live
INCLUDEcolumns againstinclude_cols.pg_index/pg_attribute--unnest(indkey)pastindnkeyatts, the count of true key columns),not
pg_get_indexdef's text, which isn't a stable string to diffagainst our own
create_sql(omitsCONCURRENTLY, may schema-qualifynames, doesn't guarantee
INCLUDEcolumn order).Vec<String>equality) -- only thecolumn set matters.
DROP INDEX CONCURRENTLY+ rebuild (same path as the existingINVALID-index handling, factored into a shared
drop_index_concurrentlyhelper). Absent/invalid handling is unchanged.
DESIRED_INDEXESUpdated to the scalar-only
INCLUDEdef. On deploy, the lane will detect thewide-vs-narrow drift against the index migration 0017 built by hand in prod
and rebuild it narrow, off the boot path (that's the whole point of the
JEF-580 lane).
Testing
drifted_include_columns_are_dropped_and_rebuilt(new): pre-creates a wideindex on a throwaway table, sets desired = narrow, runs
reconcile_index--asserts it drops + rebuilds to the narrow
INCLUDEset (new oid); runs again-- asserts no-op (same oid).
seeded_covering_index_narrows_then_is_a_noop(renamed fromseeded_covering_index_run_is_a_noop): forces the realmetric_series_rollupsindex into migration 0017's exact wide state, thenruns the real lane (
run(&url)) -- asserts the first run narrows it (newoid, narrow
INCLUDEset), and the second run is a no-op.online_ddltests updated only for the newinclude_colsfield (no behavior change to absent/invalid handling).Local
EXPLAINverification (seeded ~2.1M-rowmetric_series_rollups,~6% matching one metric name, to force a realistic plan choice instead of a
trivial seq scan):
query_metric_series-equivalent SQL:Index Only Scan ... Heap Fetches: 0with both the wide and the narrow index -- confirms no regression on the
hot path.
metric_facet/metric_histogram-equivalent SQL against the narrow index:plain
Index Scan(heap fetch forattrs/bucket_bounds/bucket_counts,as expected/acceptable per the ticket) -- correctness spot-checked and
correct (matched the seeded avg/last/histogram-bucket-sum values).
What I could not measure: the real 10.4M-row prod
EXPLAINand prodindex-size delta -- only reachable against the production database, per the
ticket's own note. Local index-size numbers (269MB wide vs 228MB narrow on the
synthetic table) are not representative of prod: my synthetic rows left
attrs/bucket_bounds/bucket_countsmostly NULL/tiny, understating the realper-tuple savings prod's populated JSONB attrs + histogram arrays would show.
Checks
cargo fmt-- cleancargo check-- cleancargo clippy --all-targets -- -D warnings-- cleancargo test --locked-- 68 lib tests + 66 integration tests, all green (runtwice consecutively against the same persistent local dev DB to confirm
idempotency, not just from a pristine DB)
/soundcheck:pr-review-- 0 Critical/High findings (all new SQL is eitherparameterized or the same pre-existing
AssertSqlSafe-on-hardcoded-constantpattern the JEF-580 drop path already used)
/simplify-- single pass (no Agent-tool fan-out in this context); extracteda shared
desired_include_colshelper to dedupe a map+sort pattern thatappeared in both production code and a test
🤖 Generated with Claude Code