From 94dfc6c61e28e8ddef74fd39a05bf7b0fedb5f9d Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Mon, 27 Jul 2026 15:24:24 +0200 Subject: [PATCH v10 13/21] Sort filters pushed-down to a scan by selectivity Initially this was an attempt to stabilize plans in regression tests, but it's not sufficient - the join order may change independently of this, as the filter pushdown eliminates impact on intermediate join result sizes. Still, it's desirable to evaluate the most selective filters first. Right now it's based only on selectivity, but it should consider the cost of probing the filter too (number of clauses, filter size, ...). The order in which filters end up pushed to the scan is somewhat volatile, because the filter pushdown limits the importance of join order - the cardinality gets reduced by the filter, not by the join which happens sometime later. That means the order of filter-producing joins has mostly the same cost. XXX Maybe we should still influence the cost based on the expected selectivity of the filter it generates, by the "risk" of the filter being less effective, etc. --- .../pg_plan_advice/expected/join_order.out | 4 +-- .../expected/pg_stash_advice.out | 10 +++---- src/backend/executor/nodeHashjoin.c | 29 +++++++++++++++++++ src/backend/optimizer/plan/createplan.c | 1 + src/include/nodes/plannodes.h | 1 + 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/contrib/pg_plan_advice/expected/join_order.out b/contrib/pg_plan_advice/expected/join_order.out index ab4125b4243..ded25c4010b 100644 --- a/contrib/pg_plan_advice/expected/join_order.out +++ b/contrib/pg_plan_advice/expected/join_order.out @@ -34,8 +34,8 @@ SELECT * FROM jo_fact f -> Hash Join Hash Cond: (f.dim1_id = d1.id) -> Seq Scan on jo_fact f - Bloom Filter 1: keys=(dim1_id) Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) -> Hash Bloom Filter 1 -> Seq Scan on jo_dim1 d1 @@ -67,8 +67,8 @@ SELECT * FROM jo_fact f -> Hash Join Hash Cond: (f.dim1_id = d1.id) -> Seq Scan on jo_fact f - Bloom Filter 1: keys=(dim1_id) Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) -> Hash Bloom Filter 1 -> Seq Scan on jo_dim1 d1 diff --git a/contrib/pg_stash_advice/expected/pg_stash_advice.out b/contrib/pg_stash_advice/expected/pg_stash_advice.out index e14e5af4677..6372bf36c2c 100644 --- a/contrib/pg_stash_advice/expected/pg_stash_advice.out +++ b/contrib/pg_stash_advice/expected/pg_stash_advice.out @@ -64,8 +64,8 @@ SELECT * FROM aa_fact f LEFT JOIN aa_dim1 d1 ON f.dim1_id = d1.id -> Hash Join Hash Cond: (f.dim1_id = d1.id) -> Seq Scan on aa_fact f - Bloom Filter 1: keys=(dim1_id) Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) -> Hash Bloom Filter 1 -> Seq Scan on aa_dim1 d1 @@ -95,8 +95,8 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f -> Hash Join Hash Cond: (f.dim1_id = d1.id) -> Seq Scan on aa_fact f - Bloom Filter 1: keys=(dim1_id) Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) -> Hash Bloom Filter 1 -> Index Scan using aa_dim1_pkey on aa_dim1 d1 @@ -128,8 +128,8 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f -> Hash Join Hash Cond: (f.dim1_id = d1.id) -> Seq Scan on aa_fact f - Bloom Filter 1: keys=(dim1_id) Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) -> Hash Bloom Filter 1 -> Seq Scan on aa_dim1 d1 @@ -221,8 +221,8 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f -> Hash Join Hash Cond: (f.dim1_id = d1.id) -> Seq Scan on aa_fact f - Bloom Filter 1: keys=(dim1_id) Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) -> Hash Bloom Filter 1 -> Seq Scan on aa_dim1 d1 @@ -288,8 +288,8 @@ EXPLAIN (COSTS OFF) SELECT * FROM aa_fact f -> Hash Join Hash Cond: (f.dim1_id = d1.id) -> Seq Scan on aa_fact f - Bloom Filter 1: keys=(dim1_id) Bloom Filter 2: keys=(dim2_id) + Bloom Filter 1: keys=(dim1_id) -> Hash Bloom Filter 1 -> Seq Scan on aa_dim1 d1 diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c index 7beb7ebd8da..4f58abf6459 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -2366,6 +2366,28 @@ ExecBloomFilters(List *filters, ExprContext *econtext) return true; } +static int +list_filter_selectivity_cmp(const ListCell *p1, const ListCell *p2) +{ + struct BloomFilter *bf1 = lfirst(p1); + struct BloomFilter *bf2 = lfirst(p2); + + /* compare by selectivity first (prefer more selective) */ + if (bf1->selectivity < bf2->selectivity) + return -1; + else if (bf1->selectivity > bf2->selectivity) + return 1; + + /* then by the producer ID, as a tie break */ + if (bf1->producer_id < bf2->producer_id) + return -1; + else if (bf1->producer_id > bf2->producer_id) + return 1; + + /* should be the same filter */ + return 0; +} + /* * ExecInitBloomFilters * Initialize state for pushed-down bloom filters. @@ -2404,6 +2426,13 @@ ExecInitBloomFilters(PlanState *planstate, TupleTableSlot *output_slot) if (plan->bloom_filters == NIL) return; + /* + * XXX Sort filters from the most to least selective ones. Ideally, + * we'd consider how expensive it's to evaluate the filter, i.e. how + * many keys/clauses it has, how large it is, etc. Left for future. + */ + list_sort(plan->bloom_filters, list_filter_selectivity_cmp); + oldctx = MemoryContextSwitchTo(estate->es_query_cxt); foreach(lc, plan->bloom_filters) diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 21967adaa94..5599751f73d 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -4975,6 +4975,7 @@ try_push_bloom_filter(PlannerInfo *root, HashJoin *hj, Plan *outer_plan, bf->hashops = list_copy(hj->hashoperators); bf->hashcollations = list_copy(hj->hashcollations); bf->producer_id = hj->bloom_filter_id; + bf->selectivity = f->selectivity; recipient->bloom_filters = lappend(recipient->bloom_filters, bf); diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 1f4cd049d59..84e67a43384 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -286,6 +286,7 @@ typedef struct BloomFilter List *hashops; List *hashcollations; int producer_id; + Selectivity selectivity; /* estimates selectivity */ } BloomFilter; /* ---------------- -- 2.50.1 (Apple Git-155)