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.
Summary
pgcolumnar_index_build_range_scanopens its reader with no projection: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 carriesii_IndexAttrNumbersand the predicate and expression trees. Everything needed tobuild the projection is already in the argument list.
Measured
300,000 rows, 20 columns (one
intkey and 19textof 80 bytes), index on the keyalone:
CREATE INDEX ON w (k)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_scanis one of the pathsthat 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
Bitmapsetfromindex_info->ii_IndexAttrNumbers, pluspull_varattnosoverii_Expressionsandii_Predicatefor expression and partial indexes, and pass it toPgColumnarBeginRead. The existingpgcolumnar_projected_columnsincolumnar_customscan.cis the same computation over a different source and is worthreusing rather than rewriting.
Care needed on two points:
predicate is evaluated against unset slot values;
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, anexpression 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
ANALYZEshows a larger gap on the same table (622 ms against heap's 92 ms) and isnot 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.