Skip to content

CREATE INDEX decodes every column, though index_build_range_scan is told which ones it needs #413

Description

@jdatcmd

Summary

pgcolumnar_index_build_range_scan opens its reader with no projection:

readState = PgColumnarBeginRead(table_rel, snapshot, NULL, NULL, 0, NULL);

so building an index on one column of a wide table decodes every column. It does
not have to: the callback receives struct IndexInfo *index_info, which carries
ii_IndexAttrNumbers and the predicate and expression trees. Everything needed to
build the projection is already in the argument list.

Measured

300,000 rows, 20 columns (one int key and 19 text of 80 bytes), index on the key
alone:

columnar heap
CREATE INDEX ON w (k) 517 ms 442 ms

Columnar is slower than heap at building a single-column index on a 20-column
table
, which is the shape columnar storage should win by a wide margin. It reads
1/20th of the data and takes 17 percent longer.

For contrast, on the same table through the custom scan, which does get a projection:
SELECT max(k) is 10 ms with projection and 21 ms without.

Why it is worth its own issue

This is the table-AM interface having nowhere to put a projection, which we normally
work around with the custom scan node. index_build_range_scan is one of the paths
that cannot use that workaround, and unlike most of them it does not need to,
because the information is in its own parameters.

Suggested fix

Build a Bitmapset from index_info->ii_IndexAttrNumbers, plus pull_varattnos over
ii_Expressions and ii_Predicate for expression and partial indexes, and pass it to
PgColumnarBeginRead. The existing pgcolumnar_projected_columns in
columnar_customscan.c is the same computation over a different source and is worth
reusing rather than rewriting.

Care needed on two points:

  • a partial index needs the predicate's columns as well as the key's, or the
    predicate is evaluated against unset slot values;
  • an expression index needs every column its expressions reference.

Both are the same class of bug as the system-column and whole-row rejections already
handled in columnar_customscan.c, so the test should cover a plain index, an
expression index and a partial index, and compare the built index's contents against
the heap equivalent rather than only its row count.

Not in scope here

ANALYZE shows a larger gap on the same table (622 ms against heap's 92 ms) and is
not fixable the same way: the analyze callbacks are not told which columns ANALYZE
was asked for, so the AM cannot project. That is a genuine interface limit rather than
an oversight, and it needs its own issue if we want to pursue it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions