Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions src/columnar_vector.c
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,25 @@ columnar_fill_native_metadata_agg(ColumnarAggScanState *state)
uint64 storageId;
List *groups;
ListCell *lc;
bool needZones = false;
int na;

/*
* count(*) comes from the row group's own stored row count. Every other
* aggregate here reads a zone map, and reading them means one catalog lookup
* per row group returning an entry for every column, whether the query names
* that column or not. For a count(*) on its own that is all waste, and it is
* the whole cost of the query: at 6,000,000 rows and the default limits it
* measured 5.2 ms over 40 row groups, and 1.4 ms over 10 (issue #133).
*/
for (na = 0; na < state->naggs; na++)
{
if (state->specs[na].kind != COLUMNAR_AGG_COUNT_STAR)
{
needZones = true;
break;
}
}

ColumnarFlushWriteStateForRelation(state->relid);
rel = table_open(state->relid, AccessShareLock);
Expand All @@ -1053,26 +1072,31 @@ columnar_fill_native_metadata_agg(ColumnarAggScanState *state)
foreach(lc, groups)
{
NativeRowGroupMetadata *rg = (NativeRowGroupMetadata *) lfirst(lc);
List *zones = ColumnarReadZoneMapList(storageId, rg->groupNumber,
snap);
NativeZoneMapMetadata **byCol =
palloc0(sizeof(NativeZoneMapMetadata *) * tupdesc->natts);
ListCell *zc;
NativeZoneMapMetadata **byCol = NULL;
int a;

foreach(zc, zones)
if (needZones)
{
NativeZoneMapMetadata *z = (NativeZoneMapMetadata *) lfirst(zc);
List *zones = ColumnarReadZoneMapList(storageId,
rg->groupNumber, snap);
ListCell *zc;

if (z->columnIndex >= 0 && z->columnIndex < tupdesc->natts)
byCol[z->columnIndex] = z;
byCol = palloc0(sizeof(NativeZoneMapMetadata *) * tupdesc->natts);
foreach(zc, zones)
{
NativeZoneMapMetadata *z = (NativeZoneMapMetadata *) lfirst(zc);

if (z->columnIndex >= 0 && z->columnIndex < tupdesc->natts)
byCol[z->columnIndex] = z;
}
}

for (a = 0; a < state->naggs; a++)
{
ColumnarAggSpec *spec = &state->specs[a];
NativeZoneMapMetadata *z =
(spec->attidx >= 0 && spec->attidx < tupdesc->natts)
(byCol != NULL && spec->attidx >= 0 &&
spec->attidx < tupdesc->natts)
? byCol[spec->attidx] : NULL;

switch (spec->kind)
Expand Down
20 changes: 20 additions & 0 deletions test/native_agg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,26 @@ par_multi="$(plan_par 'SELECT count(*), sum(id), min(id), max(id) FROM n')"
check "multi-agg beats a parallel plan" "$(has_aggnode "$par_multi")" "yes"
check "multi-agg plan has no Gather" "$(has_gather "$par_multi")" "no"

# count(*) is answered from each row group's stored row count, so it has no
# reason to read zone maps at all. Reading them costs one catalog lookup per row
# group returning an entry for every column, and on a 6,000,000-row table that
# was the entire cost of the query (issue #133). Count the scans of
# pgcolumnar.zone_map across the statement: each q is its own backend, and a
# backend flushes its statistics when it exits, so the counter is settled by the
# time the next one reads it.
zm_scans() {
q "SELECT coalesce(idx_scan, 0) + coalesce(seq_scan, 0)
FROM pg_stat_all_tables WHERE relid = 'pgcolumnar.zone_map'::regclass;"
}
zm_before="$(zm_scans)"; q 'SELECT count(*) FROM n;' >/dev/null; zm_after="$(zm_scans)"
check "count(*) reads no zone maps" "$((zm_after - zm_before))" "0"

# The same measurement the other way round, so the check above cannot pass just
# because the counter never moves.
zm_before="$(zm_scans)"; q 'SELECT count(*), sum(id) FROM n;' >/dev/null; zm_after="$(zm_scans)"
check "an aggregate that needs zone maps reads them" \
"$([ "$((zm_after - zm_before))" -gt 0 ] && echo yes || echo no)" "yes"

# A filtered aggregate falls back (no zone-map answer) but is still correct.
check "filtered aggregate parity" \
"$(q 'SELECT count(*), sum(id) FROM n WHERE id > 5000;')" \
Expand Down