Skip to content

Commit f199303

Browse files
committed
Avoid making a separate pass over the query to check for partializability.
It's rather silly to make a separate pass over the tlist + HAVING qual, and a separate set of visits to the syscache, when get_agg_clause_costs already has all the required information in hand. This nets out as less code as well as fewer cycles.
1 parent 19e972d commit f199303

File tree

4 files changed

+54
-118
lines changed

4 files changed

+54
-118
lines changed

src/backend/optimizer/plan/planner.c

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,10 @@ static double get_number_of_groups(PlannerInfo *root,
110110
List *rollup_groupclauses);
111111
static void set_grouped_rel_consider_parallel(PlannerInfo *root,
112112
RelOptInfo *grouped_rel,
113-
PathTarget *target);
114-
static Size estimate_hashagg_tablesize(Path *path, AggClauseCosts *agg_costs,
113+
PathTarget *target,
114+
const AggClauseCosts *agg_costs);
115+
static Size estimate_hashagg_tablesize(Path *path,
116+
const AggClauseCosts *agg_costs,
115117
double dNumGroups);
116118
static RelOptInfo *create_grouping_paths(PlannerInfo *root,
117119
RelOptInfo *input_rel,
@@ -3207,7 +3209,8 @@ get_number_of_groups(PlannerInfo *root,
32073209
*/
32083210
static void
32093211
set_grouped_rel_consider_parallel(PlannerInfo *root, RelOptInfo *grouped_rel,
3210-
PathTarget *target)
3212+
PathTarget *target,
3213+
const AggClauseCosts *agg_costs)
32113214
{
32123215
Query *parse = root->parse;
32133216

@@ -3240,15 +3243,14 @@ set_grouped_rel_consider_parallel(PlannerInfo *root, RelOptInfo *grouped_rel,
32403243
return;
32413244

32423245
/*
3243-
* All that's left to check now is to make sure all aggregate functions
3244-
* support partial mode. If there's no aggregates then we can skip
3245-
* checking that.
3246+
* If we have any non-partial-capable aggregates, or if any of them can't
3247+
* be serialized, we can't go parallel.
32463248
*/
3247-
if (!parse->hasAggs)
3248-
grouped_rel->consider_parallel = true;
3249-
else if (aggregates_allow_partial((Node *) target->exprs) == PAT_ANY &&
3250-
aggregates_allow_partial(root->parse->havingQual) == PAT_ANY)
3251-
grouped_rel->consider_parallel = true;
3249+
if (agg_costs->hasNonPartial || agg_costs->hasNonSerial)
3250+
return;
3251+
3252+
/* OK, consider parallelization */
3253+
grouped_rel->consider_parallel = true;
32523254
}
32533255

32543256
/*
@@ -3257,7 +3259,7 @@ set_grouped_rel_consider_parallel(PlannerInfo *root, RelOptInfo *grouped_rel,
32573259
* require based on the agg_costs, path width and dNumGroups.
32583260
*/
32593261
static Size
3260-
estimate_hashagg_tablesize(Path *path, AggClauseCosts *agg_costs,
3262+
estimate_hashagg_tablesize(Path *path, const AggClauseCosts *agg_costs,
32613263
double dNumGroups)
32623264
{
32633265
Size hashentrysize;
@@ -3411,7 +3413,8 @@ create_grouping_paths(PlannerInfo *root,
34113413
* going to be safe to do so.
34123414
*/
34133415
if (input_rel->partial_pathlist != NIL)
3414-
set_grouped_rel_consider_parallel(root, grouped_rel, target);
3416+
set_grouped_rel_consider_parallel(root, grouped_rel,
3417+
target, &agg_costs);
34153418

34163419
/*
34173420
* Determine whether it's possible to perform sort-based implementations

src/backend/optimizer/util/clauses.c

Lines changed: 34 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,6 @@
5252
#include "utils/syscache.h"
5353
#include "utils/typcache.h"
5454

55-
typedef struct
56-
{
57-
PartialAggType allowedtype;
58-
} partial_agg_context;
5955

6056
typedef struct
6157
{
@@ -98,8 +94,6 @@ typedef struct
9894
bool allow_restricted;
9995
} has_parallel_hazard_arg;
10096

101-
static bool aggregates_allow_partial_walker(Node *node,
102-
partial_agg_context *context);
10397
static bool contain_agg_clause_walker(Node *node, void *context);
10498
static bool get_agg_clause_costs_walker(Node *node,
10599
get_agg_clause_costs_context *context);
@@ -403,81 +397,6 @@ make_ands_implicit(Expr *clause)
403397
* Aggregate-function clause manipulation
404398
*****************************************************************************/
405399

406-
/*
407-
* aggregates_allow_partial
408-
* Recursively search for Aggref clauses and determine the maximum
409-
* level of partial aggregation which can be supported.
410-
*/
411-
PartialAggType
412-
aggregates_allow_partial(Node *clause)
413-
{
414-
partial_agg_context context;
415-
416-
/* initially any type is okay, until we find Aggrefs which say otherwise */
417-
context.allowedtype = PAT_ANY;
418-
419-
if (!aggregates_allow_partial_walker(clause, &context))
420-
return context.allowedtype;
421-
return context.allowedtype;
422-
}
423-
424-
static bool
425-
aggregates_allow_partial_walker(Node *node, partial_agg_context *context)
426-
{
427-
if (node == NULL)
428-
return false;
429-
if (IsA(node, Aggref))
430-
{
431-
Aggref *aggref = (Aggref *) node;
432-
HeapTuple aggTuple;
433-
Form_pg_aggregate aggform;
434-
435-
Assert(aggref->agglevelsup == 0);
436-
437-
/*
438-
* We can't perform partial aggregation with Aggrefs containing a
439-
* DISTINCT or ORDER BY clause.
440-
*/
441-
if (aggref->aggdistinct || aggref->aggorder)
442-
{
443-
context->allowedtype = PAT_DISABLED;
444-
return true; /* abort search */
445-
}
446-
aggTuple = SearchSysCache1(AGGFNOID,
447-
ObjectIdGetDatum(aggref->aggfnoid));
448-
if (!HeapTupleIsValid(aggTuple))
449-
elog(ERROR, "cache lookup failed for aggregate %u",
450-
aggref->aggfnoid);
451-
aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
452-
453-
/*
454-
* If there is no combine function, then partial aggregation is not
455-
* possible.
456-
*/
457-
if (!OidIsValid(aggform->aggcombinefn))
458-
{
459-
ReleaseSysCache(aggTuple);
460-
context->allowedtype = PAT_DISABLED;
461-
return true; /* abort search */
462-
}
463-
464-
/*
465-
* If we find any aggs with an internal transtype then we must check
466-
* whether these have serialization/deserialization functions;
467-
* otherwise, we set the maximum allowed type to PAT_INTERNAL_ONLY.
468-
*/
469-
if (aggform->aggtranstype == INTERNALOID &&
470-
(!OidIsValid(aggform->aggserialfn) ||
471-
!OidIsValid(aggform->aggdeserialfn)))
472-
context->allowedtype = PAT_INTERNAL_ONLY;
473-
474-
ReleaseSysCache(aggTuple);
475-
return false; /* continue searching */
476-
}
477-
return expression_tree_walker(node, aggregates_allow_partial_walker,
478-
(void *) context);
479-
}
480-
481400
/*
482401
* contain_agg_clause
483402
* Recursively search for Aggref/GroupingFunc nodes within a clause.
@@ -529,8 +448,9 @@ contain_agg_clause_walker(Node *node, void *context)
529448
*
530449
* We count the nodes, estimate their execution costs, and estimate the total
531450
* space needed for their transition state values if all are evaluated in
532-
* parallel (as would be done in a HashAgg plan). See AggClauseCosts for
533-
* the exact set of statistics collected.
451+
* parallel (as would be done in a HashAgg plan). Also, we check whether
452+
* partial aggregation is feasible. See AggClauseCosts for the exact set
453+
* of statistics collected.
534454
*
535455
* In addition, we mark Aggref nodes with the correct aggtranstype, so
536456
* that that doesn't need to be done repeatedly. (That makes this function's
@@ -616,10 +536,40 @@ get_agg_clause_costs_walker(Node *node, get_agg_clause_costs_context *context)
616536
aggref->aggtranstype = aggtranstype;
617537
}
618538

619-
/* count it; note ordered-set aggs always have nonempty aggorder */
539+
/*
540+
* Count it, and check for cases requiring ordered input. Note that
541+
* ordered-set aggs always have nonempty aggorder. Any ordered-input
542+
* case also defeats partial aggregation.
543+
*/
620544
costs->numAggs++;
621545
if (aggref->aggorder != NIL || aggref->aggdistinct != NIL)
546+
{
622547
costs->numOrderedAggs++;
548+
costs->hasNonPartial = true;
549+
}
550+
551+
/*
552+
* Check whether partial aggregation is feasible, unless we already
553+
* found out that we can't do it.
554+
*/
555+
if (!costs->hasNonPartial)
556+
{
557+
/*
558+
* If there is no combine function, then partial aggregation is
559+
* not possible.
560+
*/
561+
if (!OidIsValid(aggcombinefn))
562+
costs->hasNonPartial = true;
563+
564+
/*
565+
* If we have any aggs with transtype INTERNAL then we must check
566+
* whether they have serialization/deserialization functions; if
567+
* not, we can't serialize partial-aggregation results.
568+
*/
569+
else if (aggtranstype == INTERNALOID &&
570+
(!OidIsValid(aggserialfn) || !OidIsValid(aggdeserialfn)))
571+
costs->hasNonSerial = true;
572+
}
623573

624574
/*
625575
* Add the appropriate component function execution costs to

src/include/nodes/relation.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,15 @@ typedef struct QualCost
5050
* Costing aggregate function execution requires these statistics about
5151
* the aggregates to be executed by a given Agg node. Note that the costs
5252
* include the execution costs of the aggregates' argument expressions as
53-
* well as the aggregate functions themselves.
53+
* well as the aggregate functions themselves. Also, the fields must be
54+
* defined so that initializing the struct to zeroes with memset is correct.
5455
*/
5556
typedef struct AggClauseCosts
5657
{
5758
int numAggs; /* total number of aggregate functions */
5859
int numOrderedAggs; /* number w/ DISTINCT/ORDER BY/WITHIN GROUP */
60+
bool hasNonPartial; /* does any agg not support partial mode? */
61+
bool hasNonSerial; /* is any partial agg non-serializable? */
5962
QualCost transCost; /* total per-input-row execution costs */
6063
Cost finalCost; /* total per-aggregated-row costs */
6164
Size transitionSpace; /* space for pass-by-ref transition data */

src/include/optimizer/clauses.h

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,6 @@ typedef struct
2727
List **windowFuncs; /* lists of WindowFuncs for each winref */
2828
} WindowFuncLists;
2929

30-
/*
31-
* PartialAggType
32-
* PartialAggType stores whether partial aggregation is allowed and
33-
* which context it is allowed in. We require three states here as there are
34-
* two different contexts in which partial aggregation is safe. For aggregates
35-
* which have an 'stype' of INTERNAL, it is okay to pass a pointer to the
36-
* aggregate state within a single process, since the datum is just a
37-
* pointer. In cases where the aggregate state must be passed between
38-
* different processes, for example during parallel aggregation, passing
39-
* pointers directly is not going to work.
40-
*/
41-
typedef enum
42-
{
43-
PAT_ANY = 0, /* Any type of partial aggregation is okay. */
44-
PAT_INTERNAL_ONLY, /* Some aggregates support only internal mode. */
45-
PAT_DISABLED /* Some aggregates don't support partial mode
46-
* at all */
47-
} PartialAggType;
48-
4930
extern Expr *make_opclause(Oid opno, Oid opresulttype, bool opretset,
5031
Expr *leftop, Expr *rightop,
5132
Oid opcollid, Oid inputcollid);
@@ -65,7 +46,6 @@ extern Node *make_and_qual(Node *qual1, Node *qual2);
6546
extern Expr *make_ands_explicit(List *andclauses);
6647
extern List *make_ands_implicit(Expr *clause);
6748

68-
extern PartialAggType aggregates_allow_partial(Node *clause);
6949
extern bool contain_agg_clause(Node *clause);
7050
extern void get_agg_clause_costs(PlannerInfo *root, Node *clause,
7151
AggSplit aggsplit, AggClauseCosts *costs);

0 commit comments

Comments
 (0)