Skip to content

Convert mz_index_columns to a builtin materialized view - #39427

Merged
SangJunBak merged 8 commits into
jun/mz-column-migrationfrom
jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views
Oct 28, 2026
Merged

SangJunBak merged 8 commits into
jun/mz-column-migrationfrom
jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views

Conversation

@SangJunBak

@SangJunBak SangJunBak commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

From the durable index key information we recorded in #39818, we derive the index key information from it and convert mz_index_columns to a builtin mv.

See commit messages for more details

Motivation

Part of sql-494

Verification

  • Diffed this relation using the prod analytics workload replay against main and this branch.
  • Created an SLT for index columns that assert expected behavior as well as behavior for indexes where the keys are varying expressions

@linear-code

linear-code Bot commented Sep 30, 2026

Copy link
Copy Markdown

SQL-494

@SangJunBak
SangJunBak force-pushed the jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views branch from a659af0 to 27c8872 Compare October 6, 2026 19:49
@SangJunBak
SangJunBak changed the base branch from main to jun/sql-702/mv-refresh-strat October 6, 2026 19:56
@SangJunBak
SangJunBak added this pull request to stack #38849 October 6, 2026 19:56
@SangJunBak SangJunBak changed the title sql: extract plan_refresh_time from plan_create_materialized_view Refactor mz_columns and mz_index_columns Oct 7, 2026
@SangJunBak
SangJunBak force-pushed the jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views branch from 27c8872 to 7857df2 Compare October 9, 2026 15:29
@SangJunBak
SangJunBak force-pushed the jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views branch from 7857df2 to 3a32217 Compare October 9, 2026 15:34
@SangJunBak
SangJunBak force-pushed the jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views branch from 3a32217 to 27a429d Compare October 13, 2026 17:38
@SangJunBak
SangJunBak force-pushed the jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views branch from 27a429d to 3c03c63 Compare October 14, 2026 08:57
Base automatically changed from jun/sql-702/mv-refresh-strat to main October 14, 2026 10:25
@SangJunBak SangJunBak added the ci-nightly PR CI control: also trigger Nightly label Oct 14, 2026
@SangJunBak
SangJunBak force-pushed the jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views branch 4 times, most recently from 2194479 to ea159b9 Compare October 14, 2026 14:40
@SangJunBak
SangJunBak removed this pull request from stack #38849 October 14, 2026 14:41
@SangJunBak
SangJunBak changed the base branch from main to jun/durable-column October 14, 2026 14:41
@SangJunBak
SangJunBak added this pull request to stack #39819 October 14, 2026 14:41
@SangJunBak SangJunBak changed the title Refactor mz_columns and mz_index_columns Convert mz_columns and mz_index_columns to builtin mvs Oct 14, 2026
@SangJunBak
SangJunBak removed this pull request from stack #39819 October 15, 2026 19:41
@SangJunBak
SangJunBak changed the base branch from jun/durable-column to jun/name-from-oid October 15, 2026 19:42
@SangJunBak
SangJunBak added this pull request to stack #39878 October 15, 2026 19:42
@SangJunBak
SangJunBak force-pushed the jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views branch 2 times, most recently from 38b4832 to e10f1a1 Compare October 15, 2026 22:01
@SangJunBak
SangJunBak removed this pull request from stack #40002 October 20, 2026 20:51
@SangJunBak
SangJunBak added this pull request to stack #40040 October 20, 2026 20:51
@SangJunBak
SangJunBak force-pushed the jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views branch 2 times, most recently from df74254 to 89691b6 Compare October 21, 2026 02:05
@SangJunBak
SangJunBak marked this pull request as ready for review October 21, 2026 02:07
@SangJunBak
SangJunBak requested review from a team as code owners October 21, 2026 02:07
@SangJunBak
SangJunBak force-pushed the jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views branch from 89691b6 to b033e06 Compare October 23, 2026 11:34
@SangJunBak
SangJunBak force-pushed the jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views branch from b033e06 to ed0d814 Compare October 23, 2026 11:43
@def-

def- commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

QA LLM Review

1. MEDIUM -- mz_index_columns arranges every key row with its index's whole IndexColumns record, so its memory grows with the square of the key count

src/catalog/src/builtin/mz_catalog.rs:1030

user_index_columns unnests k.data->'value'->'columns' before joining to the Item row, and it computes index_id from k.data after the join. The optimizer therefore arranges (data, value, ordinality) for every key, and each of those rows carries the full O(k) record. That is about 34·k² bytes held resident on mz_catalog_server for as long as the index exists, and rebuilt on every hydration. The old builtin table cost O(k).

Details

The tell in the new catalog_server_explain.slt golden is →Arrange (((#0{data} -> "key") -> "id")) directly above →Table Function jsonb_array_elements[with_ordinality].

Measured with CREATE INDEX i ON t (a, a, …):

  • Arrangement size of the mz_index_columns dataflow: 8.6 MB at 500 keys, 34 MB at 1000, and 136 MB at 2000. At the base, a 2000-key index grows all catalog-server arrangements combined by 0.37 MB.
  • 30 indexes of 2000 keys: mz_index_columns is the largest arrangement on the catalog server at 3.9 GB for 60k rows. For comparison, mz_show_indexes_ind is 4.4 MB for 188k rows. Replica RSS peaked at 4.1 GB and settled at 2.45 GB. At the base the same workload settles at 859 MB.
  • Privileges: CREATE on a cluster and a schema are enough, so any role that can create indexes can force this. A default index on a wide relation pays the same cost without any intent.

Fix: project the id and the columns array before unnesting, and join to the item before the LATERAL, as mz_columns does. This shape plans with the join on one row per index and the FlatMap after it, with no arrangement downstream:

FROM
    (
        SELECT
            data->'key'->'id' AS id,
            mz_internal.parse_catalog_id(data->'key'->'id') AS index_id,
            data->'value'->'columns' AS columns
        FROM mz_internal.mz_catalog_raw
        WHERE data->>'kind' = 'IndexColumns'
    ) AS k
    JOIN (
        SELECT
            data->'key'->'gid' AS id,
            mz_internal.parse_catalog_create_sql(data->'value'->'definition'->'V1'->>'create_sql')->'key_parts' AS key_parts
        FROM mz_internal.mz_catalog_raw
        WHERE data->>'kind' = 'Item'
    ) AS i ON i.id = k.id
    CROSS JOIN LATERAL jsonb_array_elements(k.columns) WITH ORDINALITY AS ik(value, ordinality)

This also stops parse_catalog_create_sql from running once per expression key, and the Item arrangement keeps only key_parts instead of every item's full jsonb. The cost is one parse per item, the same as mz_columns already pays.

@SangJunBak
SangJunBak force-pushed the jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views branch 2 times, most recently from 35f2e5a to 5035985 Compare October 26, 2026 21:40
@SangJunBak
SangJunBak force-pushed the jun/sql-494-convert-mz_columns-and-mz_index_columns-to-catalog-views branch from 5035985 to 9cc5283 Compare October 26, 2026 21:56
@SangJunBak
SangJunBak requested a review from ggevay October 27, 2026 17:07

@ggevay ggevay left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, minor things.

Comment on lines +1035 to +1037
JOIN mz_internal.mz_catalog_raw AS i ON
i.data->>'kind' = 'Item' AND
i.data->'key'->'gid' = k.data->'key'->'id'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This join arranges the full raw Item row of every item, create_sql included, keyed by gid (the Arrange (((#0{data} -> "key") -> "gid")) over Project: #0 in catalog_server_explain.slt). On a synthetic catalog with 14k items, that's about one copy of all create_sql text on mz_catalog_server (the mz_index_columns dataflow holds 53 MB there).

Only index items can match. Prefiltering them arranges only their rows. On that catalog, run as a user MV next to an exact copy of user_index_columns, the rows are identical and the size is 55 → 2.2 MB:

Suggested change
JOIN mz_internal.mz_catalog_raw AS i ON
i.data->>'kind' = 'Item' AND
i.data->'key'->'gid' = k.data->'key'->'id'
JOIN mz_internal.mz_catalog_raw AS i ON
i.data->>'kind' = 'Item' AND
i.data->'value'->'definition'->'V1'->>'create_sql' LIKE 'CREATE INDEX %' AND
i.data->'key'->'gid' = k.data->'key'->'id'

Every persisted index's create_sql starts with CREATE INDEX ", CREATE DEFAULT INDEX included, because planning fills in the name and key parts and drops IF NOT EXISTS.

parse_catalog_create_sql reports an index's key expressions in index order,
so that mz_index_columns can read them.
The generated view lists the key columns of every builtin index, so that
catalog views can reference them in SQL.

To get information about a builtin index column, we parse the DDL of the builtin index to get information about the underlying relation, get the underlying relation's desc, then get the `on_position` and nullability from that. We also panic in this function if users ever create an expression for an index key.
- Deletes the builtin table packing code
- Breaks GID_MAPPING_CTES into two CTEs such that we can reuse the builtin index CTE for mz_index_columns
- Creates a definition for mz_index_columns using mz_catalog_raw and mz_builtin_index_columns
- Creates a table to mv migration
Create an SLT to ensure:
- The columns of the builtin report what we expect them to
- We test for index keys that were created with expressions
The `IndexColumns` row of an index holds its key list, so the view
unnests it with `WITH ORDINALITY`, whose one-based numbering is the
reported index_position.
- Filters index_keys for just indexes so that the join arranges only their rows. It reduces the size from  55MB to 2.2 MB on a synthetic catalog with 14k items.
- Also records the mz_index_columns conversion in the builtin version pin check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-nightly PR CI control: also trigger Nightly

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants