Skip to content

Commit d251928

Browse files
committed
Improve performance of EXPLAIN with large range tables.
As of 9.3, ruleutils.c goes to some lengths to ensure that table and column aliases used in its output are unique. Of course this takes more time than was required before, which in itself isn't fatal. However, EXPLAIN was set up so that recalculation of the unique aliases was repeated for each subexpression printed in a plan. That results in O(N^2) time and memory consumption for large plan trees, which did not happen in older branches. Fortunately, the expensive work is the same across a whole plan tree, so there is no need to repeat it; we can do most of the initialization just once per query and re-use it for each subexpression. This buys back most (not all) of the performance loss since 9.2. We need an extra ExplainState field to hold the precalculated deparse context. That's no problem in HEAD, but in the back branches, expanding sizeof(ExplainState) seems risky because third-party extensions might have local variables of that struct type. So, in 9.4 and 9.3, introduce an auxiliary struct to keep sizeof(ExplainState) the same. We should refactor the APIs to avoid such local variables in future, but that's material for a separate HEAD-only commit. Per gripe from Alexey Bashtanov. Back-patch to 9.3 where the issue was introduced.
1 parent 7b65f19 commit d251928

File tree

4 files changed

+77
-37
lines changed

4 files changed

+77
-37
lines changed

src/backend/commands/explain.c

+17-12
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
#include "utils/xml.h"
3434

3535

36+
/* Crude hack to avoid changing sizeof(ExplainState) in released branches */
37+
#define grouping_stack extra->groupingstack
38+
#define deparse_cxt extra->deparsecxt
39+
3640
/* Hook for plugins to get control in ExplainOneQuery() */
3741
ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL;
3842

@@ -262,6 +266,8 @@ ExplainInitState(ExplainState *es)
262266
es->costs = true;
263267
/* Prepare output buffer. */
264268
es->str = makeStringInfo();
269+
/* Kluge to avoid changing sizeof(ExplainState) in released branches. */
270+
es->extra = (ExplainStateExtra *) palloc0(sizeof(ExplainStateExtra));
265271
}
266272

267273
/*
@@ -562,6 +568,8 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
562568
es->rtable = queryDesc->plannedstmt->rtable;
563569
ExplainPreScanNode(queryDesc->planstate, &rels_used);
564570
es->rtable_names = select_rtable_names_for_explain(es->rtable, rels_used);
571+
es->deparse_cxt = deparse_context_for_plan_rtable(es->rtable,
572+
es->rtable_names);
565573
ExplainNode(queryDesc->planstate, NIL, NULL, NULL, es);
566574
}
567575

@@ -1653,10 +1661,9 @@ show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es)
16531661
return;
16541662

16551663
/* Set up deparsing context */
1656-
context = deparse_context_for_planstate((Node *) planstate,
1657-
ancestors,
1658-
es->rtable,
1659-
es->rtable_names);
1664+
context = set_deparse_context_planstate(es->deparse_cxt,
1665+
(Node *) planstate,
1666+
ancestors);
16601667
useprefix = list_length(es->rtable) > 1;
16611668

16621669
/* Deparse each result column (we now include resjunk ones) */
@@ -1685,10 +1692,9 @@ show_expression(Node *node, const char *qlabel,
16851692
char *exprstr;
16861693

16871694
/* Set up deparsing context */
1688-
context = deparse_context_for_planstate((Node *) planstate,
1689-
ancestors,
1690-
es->rtable,
1691-
es->rtable_names);
1695+
context = set_deparse_context_planstate(es->deparse_cxt,
1696+
(Node *) planstate,
1697+
ancestors);
16921698

16931699
/* Deparse the expression */
16941700
exprstr = deparse_expression(node, context, useprefix, false);
@@ -1830,10 +1836,9 @@ show_sort_group_keys(PlanState *planstate, const char *qlabel,
18301836
return;
18311837

18321838
/* Set up deparsing context */
1833-
context = deparse_context_for_planstate((Node *) planstate,
1834-
ancestors,
1835-
es->rtable,
1836-
es->rtable_names);
1839+
context = set_deparse_context_planstate(es->deparse_cxt,
1840+
(Node *) planstate,
1841+
ancestors);
18371842
useprefix = (list_length(es->rtable) > 1 || es->verbose);
18381843

18391844
for (keyno = 0; keyno < nkeys; keyno++)

src/backend/utils/adt/ruleutils.c

+49-22
Original file line numberDiff line numberDiff line change
@@ -2519,7 +2519,43 @@ deparse_context_for(const char *aliasname, Oid relid)
25192519
}
25202520

25212521
/*
2522-
* deparse_context_for_planstate - Build deparse context for a plan
2522+
* deparse_context_for_plan_rtable - Build deparse context for a plan's rtable
2523+
*
2524+
* When deparsing an expression in a Plan tree, we use the plan's rangetable
2525+
* to resolve names of simple Vars. The initialization of column names for
2526+
* this is rather expensive if the rangetable is large, and it'll be the same
2527+
* for every expression in the Plan tree; so we do it just once and re-use
2528+
* the result of this function for each expression. (Note that the result
2529+
* is not usable until set_deparse_context_planstate() is applied to it.)
2530+
*
2531+
* In addition to the plan's rangetable list, pass the per-RTE alias names
2532+
* assigned by a previous call to select_rtable_names_for_explain.
2533+
*/
2534+
List *
2535+
deparse_context_for_plan_rtable(List *rtable, List *rtable_names)
2536+
{
2537+
deparse_namespace *dpns;
2538+
2539+
dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace));
2540+
2541+
/* Initialize fields that stay the same across the whole plan tree */
2542+
dpns->rtable = rtable;
2543+
dpns->rtable_names = rtable_names;
2544+
dpns->ctes = NIL;
2545+
2546+
/*
2547+
* Set up column name aliases. We will get rather bogus results for join
2548+
* RTEs, but that doesn't matter because plan trees don't contain any join
2549+
* alias Vars.
2550+
*/
2551+
set_simple_column_names(dpns);
2552+
2553+
/* Return a one-deep namespace stack */
2554+
return list_make1(dpns);
2555+
}
2556+
2557+
/*
2558+
* set_deparse_context_planstate - Specify Plan node containing expression
25232559
*
25242560
* When deparsing an expression in a Plan tree, we might have to resolve
25252561
* OUTER_VAR, INNER_VAR, or INDEX_VAR references. To do this, the caller must
@@ -2538,37 +2574,28 @@ deparse_context_for(const char *aliasname, Oid relid)
25382574
* most-closely-nested first. This is needed to resolve PARAM_EXEC Params.
25392575
* Note we assume that all the PlanStates share the same rtable.
25402576
*
2541-
* The plan's rangetable list must also be passed, along with the per-RTE
2542-
* alias names assigned by a previous call to select_rtable_names_for_explain.
2543-
* (We use the rangetable to resolve simple Vars, but the plan inputs are
2544-
* necessary for Vars with special varnos.)
2577+
* Once this function has been called, deparse_expression() can be called on
2578+
* subsidiary expression(s) of the specified PlanState node. To deparse
2579+
* expressions of a different Plan node in the same Plan tree, re-call this
2580+
* function to identify the new parent Plan node.
2581+
*
2582+
* The result is the same List passed in; this is a notational convenience.
25452583
*/
25462584
List *
2547-
deparse_context_for_planstate(Node *planstate, List *ancestors,
2548-
List *rtable, List *rtable_names)
2585+
set_deparse_context_planstate(List *dpcontext,
2586+
Node *planstate, List *ancestors)
25492587
{
25502588
deparse_namespace *dpns;
25512589

2552-
dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace));
2553-
2554-
/* Initialize fields that stay the same across the whole plan tree */
2555-
dpns->rtable = rtable;
2556-
dpns->rtable_names = rtable_names;
2557-
dpns->ctes = NIL;
2558-
2559-
/*
2560-
* Set up column name aliases. We will get rather bogus results for join
2561-
* RTEs, but that doesn't matter because plan trees don't contain any join
2562-
* alias Vars.
2563-
*/
2564-
set_simple_column_names(dpns);
2590+
/* Should always have one-entry namespace list for Plan deparsing */
2591+
Assert(list_length(dpcontext) == 1);
2592+
dpns = (deparse_namespace *) linitial(dpcontext);
25652593

25662594
/* Set our attention on the specific plan node passed in */
25672595
set_deparse_planstate(dpns, (PlanState *) planstate);
25682596
dpns->ancestors = ancestors;
25692597

2570-
/* Return a one-deep namespace stack */
2571-
return list_make1(dpns);
2598+
return dpcontext;
25722599
}
25732600

25742601
/*

src/include/commands/explain.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ typedef enum ExplainFormat
2424
EXPLAIN_FORMAT_YAML
2525
} ExplainFormat;
2626

27+
/* Crude hack to avoid changing sizeof(ExplainState) in released branches */
28+
typedef struct ExplainStateExtra
29+
{
30+
List *groupingstack; /* format-specific grouping state */
31+
List *deparsecxt; /* context list for deparsing expressions */
32+
} ExplainStateExtra;
33+
2734
typedef struct ExplainState
2835
{
2936
StringInfo str; /* output buffer */
@@ -40,7 +47,7 @@ typedef struct ExplainState
4047
List *rtable; /* range table */
4148
List *rtable_names; /* alias names for RTEs */
4249
int indent; /* current indentation level */
43-
List *grouping_stack; /* format-specific grouping state */
50+
ExplainStateExtra *extra; /* pointer to additional data */
4451
} ExplainState;
4552

4653
/* Hook for plugins to get control in ExplainOneQuery() */

src/include/utils/builtins.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -694,8 +694,9 @@ extern Datum pg_get_function_arg_default(PG_FUNCTION_ARGS);
694694
extern char *deparse_expression(Node *expr, List *dpcontext,
695695
bool forceprefix, bool showimplicit);
696696
extern List *deparse_context_for(const char *aliasname, Oid relid);
697-
extern List *deparse_context_for_planstate(Node *planstate, List *ancestors,
698-
List *rtable, List *rtable_names);
697+
extern List *deparse_context_for_plan_rtable(List *rtable, List *rtable_names);
698+
extern List *set_deparse_context_planstate(List *dpcontext,
699+
Node *planstate, List *ancestors);
699700
extern List *select_rtable_names_for_explain(List *rtable,
700701
Bitmapset *rels_used);
701702
extern const char *quote_identifier(const char *ident);

0 commit comments

Comments
 (0)