Skip to content

Commit c04b6f9

Browse files
author
Commitfest Bot
committed
[CF 5093] Incremental Sort Cost Estimation Instability
This branch was automatically generated by a robot using patches from an email thread registered at: https://commitfest.postgresql.org/patch/5093 The branch will be overwritten each time a new patch version is posted to the thread, and also periodically to check for bitrot caused by changes on the master branch. Patch(es): https://www.postgresql.org/message-id/[email protected] Author(s): Andrei Lepikhov
2 parents f056f75 + 096a063 commit c04b6f9

File tree

6 files changed

+180
-32
lines changed

6 files changed

+180
-32
lines changed

src/backend/optimizer/path/costsize.c

+9-31
Original file line numberDiff line numberDiff line change
@@ -2008,13 +2008,12 @@ cost_incremental_sort(Path *path,
20082008
run_cost,
20092009
input_run_cost = input_total_cost - input_startup_cost;
20102010
double group_tuples,
2011-
input_groups;
2011+
input_groups,
2012+
estimated_groups;
20122013
Cost group_startup_cost,
20132014
group_run_cost,
20142015
group_input_run_cost;
20152016
List *presortedExprs = NIL;
2016-
ListCell *l;
2017-
bool unknown_varno = false;
20182017

20192018
Assert(presorted_keys > 0 && presorted_keys < list_length(pathkeys));
20202019

@@ -2025,9 +2024,6 @@ cost_incremental_sort(Path *path,
20252024
if (input_tuples < 2.0)
20262025
input_tuples = 2.0;
20272026

2028-
/* Default estimate of number of groups, capped to one group per row. */
2029-
input_groups = Min(input_tuples, DEFAULT_NUM_DISTINCT);
2030-
20312027
/*
20322028
* Extract presorted keys as list of expressions.
20332029
*
@@ -2050,33 +2046,15 @@ cost_incremental_sort(Path *path,
20502046
* anyway - from that standpoint the DEFAULT_NUM_DISTINCT is defensive
20512047
* while maintaining lower startup cost.
20522048
*/
2053-
foreach(l, pathkeys)
2054-
{
2055-
PathKey *key = (PathKey *) lfirst(l);
2056-
EquivalenceMember *member = (EquivalenceMember *)
2057-
linitial(key->pk_eclass->ec_members);
2058-
2059-
/*
2060-
* Check if the expression contains Var with "varno 0" so that we
2061-
* don't call estimate_num_groups in that case.
2062-
*/
2063-
if (bms_is_member(0, pull_varnos(root, (Node *) member->em_expr)))
2064-
{
2065-
unknown_varno = true;
2066-
break;
2067-
}
2049+
presortedExprs = list_copy_head(pathkeys, presorted_keys);
20682050

2069-
/* expression not containing any Vars with "varno 0" */
2070-
presortedExprs = lappend(presortedExprs, member->em_expr);
2071-
2072-
if (foreach_current_index(l) + 1 >= presorted_keys)
2073-
break;
2074-
}
2075-
2076-
/* Estimate the number of groups with equal presorted keys. */
2077-
if (!unknown_varno)
2078-
input_groups = estimate_num_groups(root, presortedExprs, input_tuples,
2051+
estimated_groups = estimate_num_groups(root, presortedExprs, input_tuples,
20792052
NULL, NULL);
2053+
if (estimated_groups > 0.0)
2054+
input_groups = estimated_groups;
2055+
else
2056+
/* Default estimate of number of groups, capped to one group per row. */
2057+
input_groups = Min(input_tuples, DEFAULT_NUM_DISTINCT);
20802058

20812059
group_tuples = input_tuples / input_groups;
20822060
group_input_run_cost = input_run_cost / input_groups;

src/backend/optimizer/path/equivclass.c

+1
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
525525
em->em_datatype = datatype;
526526
em->em_jdomain = jdomain;
527527
em->em_parent = parent;
528+
em->em_ndistinct = -1.0;
528529

529530
if (bms_is_empty(relids))
530531
{

src/backend/utils/adt/selfuncs.c

+86-1
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ static bool get_actual_variable_endpoint(Relation heapRel,
214214
MemoryContext outercontext,
215215
Datum *endpointDatum);
216216
static RelOptInfo *find_join_input_rel(PlannerInfo *root, Relids relids);
217+
static EquivalenceMember *identify_sort_ecmember(PlannerInfo *root,
218+
EquivalenceClass *ec);
217219

218220

219221
/*
@@ -3468,12 +3470,34 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
34683470
i = 0;
34693471
foreach(l, groupExprs)
34703472
{
3471-
Node *groupexpr = (Node *) lfirst(l);
3473+
Node *node = (Node *) lfirst(l);
3474+
Node *groupexpr;
34723475
double this_srf_multiplier;
34733476
VariableStatData vardata;
34743477
List *varshere;
34753478
ListCell *l2;
34763479

3480+
/* Find an expression beforehand */
3481+
if (IsA(node, PathKey))
3482+
{
3483+
PathKey *key = (PathKey *) node;
3484+
EquivalenceMember *em = identify_sort_ecmember(root, key->pk_eclass);
3485+
3486+
/*
3487+
* Check if the expression contains Var with "varno 0" so that we
3488+
* don't call estimate_num_groups in that case.
3489+
*/
3490+
if (bms_is_member(0, pull_varnos(root, (Node *) em->em_expr)))
3491+
{
3492+
/* Return 'unknown' value */
3493+
return 0.0;
3494+
}
3495+
3496+
groupexpr = (Node *) em->em_expr;
3497+
}
3498+
else
3499+
groupexpr = node;
3500+
34773501
/* is expression in this grouping set? */
34783502
if (pgset && !list_member_int(*pgset, i++))
34793503
continue;
@@ -8404,3 +8428,64 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
84048428

84058429
*indexPages = index->pages;
84068430
}
8431+
8432+
/*
8433+
* Find suitable member of the equivalence class.
8434+
* Passing through the list of EC members find the member with minimum of
8435+
* distinct values. Cache estimated number of distincts in the em_ndistinct
8436+
* field of each member.
8437+
*/
8438+
static EquivalenceMember *
8439+
identify_sort_ecmember(PlannerInfo *root, EquivalenceClass *ec)
8440+
{
8441+
EquivalenceMember *candidate = linitial(ec->ec_members);
8442+
8443+
if (root == NULL)
8444+
/* Fast path */
8445+
return candidate;
8446+
8447+
foreach_node(EquivalenceMember, em, ec->ec_members)
8448+
{
8449+
VariableStatData vardata;
8450+
8451+
if (em->em_ndistinct == 0.)
8452+
/* Nothing helpful */
8453+
continue;
8454+
8455+
if (em->em_is_child || em->em_is_const || bms_is_empty(em->em_relids) ||
8456+
bms_is_member(0, em->em_relids))
8457+
{
8458+
em->em_ndistinct = 0.;
8459+
continue;
8460+
}
8461+
8462+
if (em->em_ndistinct < 0.)
8463+
{
8464+
bool isdefault = true;
8465+
double ndist = 0.;
8466+
8467+
/* Let's check candidate's ndistinct value */
8468+
examine_variable(root, (Node *) em->em_expr, 0, &vardata);
8469+
if (HeapTupleIsValid(vardata.statsTuple))
8470+
ndist = get_variable_numdistinct(&vardata, &isdefault);
8471+
ReleaseVariableStats(vardata);
8472+
8473+
if (isdefault)
8474+
{
8475+
em->em_ndistinct = 0.;
8476+
continue;
8477+
}
8478+
8479+
em->em_ndistinct = ndist;
8480+
}
8481+
8482+
Assert(em->em_ndistinct > 0.);
8483+
8484+
if (candidate->em_ndistinct == 0. ||
8485+
em->em_ndistinct < candidate->em_ndistinct)
8486+
candidate = em;
8487+
}
8488+
8489+
Assert(candidate != NULL);
8490+
return candidate;
8491+
}

src/include/nodes/pathnodes.h

+2
Original file line numberDiff line numberDiff line change
@@ -1477,6 +1477,8 @@ typedef struct EquivalenceMember
14771477
JoinDomain *em_jdomain; /* join domain containing the source clause */
14781478
/* if em_is_child is true, this links to corresponding EM for top parent */
14791479
struct EquivalenceMember *em_parent pg_node_attr(read_write_ignore);
1480+
1481+
double em_ndistinct; /* cached value of ndistinct: 0- default value or 'unknown'; -1 - not defined yet */
14801482
} EquivalenceMember;
14811483

14821484
/*

src/test/regress/expected/incremental_sort.out

+51
Original file line numberDiff line numberDiff line change
@@ -1722,3 +1722,54 @@ order by t1.four, t1.two limit 1;
17221722
-> Seq Scan on tenk1 t2
17231723
(12 rows)
17241724

1725+
-- Check:
1726+
-- commuting of sides in an expression doesn't influence cost estimation
1727+
CREATE TABLE sort_ndist_t1 (x numeric, y numeric);
1728+
CREATE TABLE sort_ndist_t2 (x numeric, y numeric);
1729+
INSERT INTO sort_ndist_t1 (x,y)
1730+
SELECT gs%10, gs%10 FROM generate_series(1,1E4) AS gs;
1731+
INSERT INTO sort_ndist_t2 (x,y)
1732+
SELECT gs, gs FROM generate_series(1,1E4) AS gs;
1733+
CREATE INDEX t1_idx ON sort_ndist_t1 (x);
1734+
CREATE INDEX t2_idx ON sort_ndist_t2 (x);
1735+
VACUUM ANALYZE sort_ndist_t1, sort_ndist_t2;
1736+
SET enable_hashjoin = 'off';
1737+
-- Having lots of duplicates after the join it is more effective to use plain
1738+
-- Sort instead of incremental sort: with small number of groups we do the same
1739+
-- stuff like Sort but with extra penalty.
1740+
EXPLAIN (COSTS OFF)
1741+
SELECT t1.x, t1.y FROM sort_ndist_t1 t1, sort_ndist_t2 t2
1742+
WHERE t1.x=t2.x
1743+
ORDER BY t1.x,t1.y;
1744+
QUERY PLAN
1745+
--------------------------------------------------------------------
1746+
Sort
1747+
Sort Key: t1.x, t1.y
1748+
-> Nested Loop
1749+
-> Seq Scan on sort_ndist_t1 t1
1750+
-> Memoize
1751+
Cache Key: t1.x
1752+
Cache Mode: logical
1753+
-> Index Only Scan using t2_idx on sort_ndist_t2 t2
1754+
Index Cond: (x = t1.x)
1755+
(9 rows)
1756+
1757+
EXPLAIN (COSTS OFF) -- the plan must be the same as above
1758+
SELECT t1.x, t1.y FROM sort_ndist_t1 t1, sort_ndist_t2 t2
1759+
WHERE t2.x=t1.x
1760+
ORDER BY t1.x,t1.y;
1761+
QUERY PLAN
1762+
--------------------------------------------------------------------
1763+
Sort
1764+
Sort Key: t1.x, t1.y
1765+
-> Nested Loop
1766+
-> Seq Scan on sort_ndist_t1 t1
1767+
-> Memoize
1768+
Cache Key: t1.x
1769+
Cache Mode: logical
1770+
-> Index Only Scan using t2_idx on sort_ndist_t2 t2
1771+
Index Cond: (x = t1.x)
1772+
(9 rows)
1773+
1774+
RESET enable_hashjoin;
1775+
DROP TABLE sort_ndist_t1, sort_ndist_t2;

src/test/regress/sql/incremental_sort.sql

+31
Original file line numberDiff line numberDiff line change
@@ -298,3 +298,34 @@ explain (costs off)
298298
select * from
299299
(select * from tenk1 order by four) t1 join tenk1 t2 on t1.four = t2.four and t1.two = t2.two
300300
order by t1.four, t1.two limit 1;
301+
302+
-- Check:
303+
-- commuting of sides in an expression doesn't influence cost estimation
304+
CREATE TABLE sort_ndist_t1 (x numeric, y numeric);
305+
CREATE TABLE sort_ndist_t2 (x numeric, y numeric);
306+
307+
INSERT INTO sort_ndist_t1 (x,y)
308+
SELECT gs%10, gs%10 FROM generate_series(1,1E4) AS gs;
309+
INSERT INTO sort_ndist_t2 (x,y)
310+
SELECT gs, gs FROM generate_series(1,1E4) AS gs;
311+
CREATE INDEX t1_idx ON sort_ndist_t1 (x);
312+
CREATE INDEX t2_idx ON sort_ndist_t2 (x);
313+
VACUUM ANALYZE sort_ndist_t1, sort_ndist_t2;
314+
315+
SET enable_hashjoin = 'off';
316+
317+
-- Having lots of duplicates after the join it is more effective to use plain
318+
-- Sort instead of incremental sort: with small number of groups we do the same
319+
-- stuff like Sort but with extra penalty.
320+
EXPLAIN (COSTS OFF)
321+
SELECT t1.x, t1.y FROM sort_ndist_t1 t1, sort_ndist_t2 t2
322+
WHERE t1.x=t2.x
323+
ORDER BY t1.x,t1.y;
324+
325+
EXPLAIN (COSTS OFF) -- the plan must be the same as above
326+
SELECT t1.x, t1.y FROM sort_ndist_t1 t1, sort_ndist_t2 t2
327+
WHERE t2.x=t1.x
328+
ORDER BY t1.x,t1.y;
329+
330+
RESET enable_hashjoin;
331+
DROP TABLE sort_ndist_t1, sort_ndist_t2;

0 commit comments

Comments
 (0)