Steer /agg off the sort-based GroupAggregate plan

pf_logid arrives pre-sorted off its index, so the planner picks an
Incremental Sort GroupAggregate on the strength of that "free" sort —
but comparing 30+ grouping columns per row makes it 5-7x slower in
practice than a HashAggregate. Confirmed via EXPLAIN ANALYZE on
fc_osm_skinny_34: 34-50s sorted vs ~7-10s hashed, independent of
work_mem or stats (ANALYZE didn't change the plan choice).

Scoped to this transaction only, since disabling incremental sort
globally could hurt some other query's plan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-09-22 22:32:24 -04:00
parent 8bc7d65fce
commit 748e301464

View File

@ -517,6 +517,8 @@ module.exports = function(pool) {
// result loads as one small native Perspective table indexed on pf_gkey and
// the WASM view still does all rollup/expand/collapse locally.
router.get('/versions/:id/agg', async (req, res) => {
let client;
let committed = false;
try {
const ctx = await getContext(parseInt(req.params.id), 'get_agg');
// Before the GROUP BY, not after: the territory column need not be
@ -526,7 +528,23 @@ module.exports = function(pool) {
territory_clause:
buildTerritoryClause(sessionTerritory(req), ctx.territoryCol, 't') || 'TRUE',
});
const { rows } = await runSQL(sql);
// The GROUP BY spans every in_grain column plus pf_logid and the
// pf.log/pf.version columns joined in for segment/bucket — 30+ keys
// on a wide source. The planner sees pf_logid arrive pre-sorted off
// its index and picks a sort-based GroupAggregate on the strength of
// that "free" sort, but comparing that many columns per row makes it
// 5-7x slower in practice than a HashAggregate — confirmed via
// EXPLAIN ANALYZE on fc_osm_skinny_34 (34-50s sorted vs ~7s hashed,
// independent of work_mem or stats). Scoped to this transaction only:
// disabling incremental sort globally could hurt some other query's plan.
client = await pool.connect();
await client.query('BEGIN');
await client.query('SET LOCAL enable_sort = off');
await client.query('SET LOCAL enable_incremental_sort = off');
const { rows } = await runSQL(sql, client);
await client.query('COMMIT');
committed = true;
res.setHeader('Content-Type', 'application/vnd.apache.arrow.stream');
res.setHeader('X-Row-Count', String(rows.length));
@ -545,6 +563,11 @@ module.exports = function(pool) {
console.error(err);
if (!res.headersSent) res.status(err.status || 500).json({ error: err.message });
else res.destroy();
} finally {
if (client) {
if (!committed) try { await client.query('ROLLBACK'); } catch {}
client.release();
}
}
});