From 5d9712f90473d49181e888b2e1b01c46cb6c0907 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Mon, 27 Jul 2026 15:24:24 +0200 Subject: [PATCH v9 15/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 e532e4cfd0a..2dbbfc3a34f 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -2350,6 +2350,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. @@ -2388,6 +2410,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 6a496e4df6c..f6ade21b13e 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -4947,6 +4947,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 18b725aee61..5fe975f1c48 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)