Skip to content

Commit 4bc62b8

Browse files
committed
Display Memoize planner estimates in EXPLAIN
There've been a few complaints that it can be overly difficult to figure out why the planner picked a Memoize plan. To help address that, here we adjust the EXPLAIN output to display the following additional details: 1) The estimated number of cache entries that can be stored at once 2) The estimated number of unique lookup keys that we expect to see 3) The number of lookups we expect 4) The estimated hit ratio Technically #4 can be calculated using #1, #2 and #3, but it's not a particularly obvious calculation, so we opt to display it explicitly. The original patch by Lukas Fittl only displayed the hit ratio, but there was a fear that might lead to more questions about how that was calculated. The idea with displaying all 4 is to be transparent which may allow queries to be tuned more easily. For example, if #2 isn't correct then maybe extended statistics or a manual n_distinct estimate can be used to help fix poor plan choices. Author: Ilia Evdokimov <ilya.evdokimov@tantorlabs.com> Author: Lukas Fittl <lukas@fittl.com> Reviewed-by: David Rowley <dgrowleyml@gmail.com> Reviewed-by: Andrei Lepikhov <lepihov@gmail.com> Reviewed-by: Robert Haas <robertmhaas@gmail.com> Discussion: https://postgr.es/m/CAP53Pky29GWAVVk3oBgKBDqhND0BRBN6yTPeguV_qSivFL5N_g%40mail.gmail.com
1 parent 71c0921 commit 4bc62b8

File tree

7 files changed

+66
-15
lines changed

7 files changed

+66
-15
lines changed

src/backend/commands/explain.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3582,6 +3582,7 @@ static void
35823582
show_memoize_info(MemoizeState *mstate, List *ancestors, ExplainState *es)
35833583
{
35843584
Plan *plan = ((PlanState *) mstate)->plan;
3585+
Memoize *mplan = (Memoize *) plan;
35853586
ListCell *lc;
35863587
List *context;
35873588
StringInfoData keystr;
@@ -3602,7 +3603,7 @@ show_memoize_info(MemoizeState *mstate, List *ancestors, ExplainState *es)
36023603
plan,
36033604
ancestors);
36043605

3605-
foreach(lc, ((Memoize *) plan)->param_exprs)
3606+
foreach(lc, mplan->param_exprs)
36063607
{
36073608
Node *expr = (Node *) lfirst(lc);
36083609

@@ -3618,6 +3619,24 @@ show_memoize_info(MemoizeState *mstate, List *ancestors, ExplainState *es)
36183619

36193620
pfree(keystr.data);
36203621

3622+
if (es->costs)
3623+
{
3624+
if (es->format == EXPLAIN_FORMAT_TEXT)
3625+
{
3626+
ExplainIndentText(es);
3627+
appendStringInfo(es->str, "Estimates: capacity=%u distinct keys=%.0f lookups=%.0f hit percent=%.2f%%\n",
3628+
mplan->est_entries, mplan->est_unique_keys,
3629+
mplan->est_calls, mplan->est_hit_ratio * 100.0);
3630+
}
3631+
else
3632+
{
3633+
ExplainPropertyUInteger("Estimated Capacity", NULL, mplan->est_entries, es);
3634+
ExplainPropertyFloat("Estimated Distinct Lookup Keys", NULL, mplan->est_unique_keys, 0, es);
3635+
ExplainPropertyFloat("Estimated Lookups", NULL, mplan->est_calls, 0, es);
3636+
ExplainPropertyFloat("Estimated Hit Percent", NULL, mplan->est_hit_ratio * 100.0, 2, es);
3637+
}
3638+
}
3639+
36213640
if (!es->analyze)
36223641
return;
36233642

src/backend/optimizer/path/costsize.c

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2572,13 +2572,13 @@ cost_memoize_rescan(PlannerInfo *root, MemoizePath *mpath,
25722572
Cost input_startup_cost = mpath->subpath->startup_cost;
25732573
Cost input_total_cost = mpath->subpath->total_cost;
25742574
double tuples = mpath->subpath->rows;
2575-
double calls = mpath->calls;
2575+
Cardinality est_calls = mpath->est_calls;
25762576
int width = mpath->subpath->pathtarget->width;
25772577

25782578
double hash_mem_bytes;
25792579
double est_entry_bytes;
2580-
double est_cache_entries;
2581-
double ndistinct;
2580+
Cardinality est_cache_entries;
2581+
Cardinality ndistinct;
25822582
double evict_ratio;
25832583
double hit_ratio;
25842584
Cost startup_cost;
@@ -2604,7 +2604,7 @@ cost_memoize_rescan(PlannerInfo *root, MemoizePath *mpath,
26042604
est_cache_entries = floor(hash_mem_bytes / est_entry_bytes);
26052605

26062606
/* estimate on the distinct number of parameter values */
2607-
ndistinct = estimate_num_groups(root, mpath->param_exprs, calls, NULL,
2607+
ndistinct = estimate_num_groups(root, mpath->param_exprs, est_calls, NULL,
26082608
&estinfo);
26092609

26102610
/*
@@ -2616,7 +2616,10 @@ cost_memoize_rescan(PlannerInfo *root, MemoizePath *mpath,
26162616
* certainly mean a MemoizePath will never survive add_path().
26172617
*/
26182618
if ((estinfo.flags & SELFLAG_USED_DEFAULT) != 0)
2619-
ndistinct = calls;
2619+
ndistinct = est_calls;
2620+
2621+
/* Remember the ndistinct estimate for EXPLAIN */
2622+
mpath->est_unique_keys = ndistinct;
26202623

26212624
/*
26222625
* Since we've already estimated the maximum number of entries we can
@@ -2644,9 +2647,12 @@ cost_memoize_rescan(PlannerInfo *root, MemoizePath *mpath,
26442647
* must look at how many scans are estimated in total for this node and
26452648
* how many of those scans we expect to get a cache hit.
26462649
*/
2647-
hit_ratio = ((calls - ndistinct) / calls) *
2650+
hit_ratio = ((est_calls - ndistinct) / est_calls) *
26482651
(est_cache_entries / Max(ndistinct, est_cache_entries));
26492652

2653+
/* Remember the hit ratio estimate for EXPLAIN */
2654+
mpath->est_hit_ratio = hit_ratio;
2655+
26502656
Assert(hit_ratio >= 0 && hit_ratio <= 1.0);
26512657

26522658
/*

src/backend/optimizer/plan/createplan.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,10 @@ static Material *make_material(Plan *lefttree);
284284
static Memoize *make_memoize(Plan *lefttree, Oid *hashoperators,
285285
Oid *collations, List *param_exprs,
286286
bool singlerow, bool binary_mode,
287-
uint32 est_entries, Bitmapset *keyparamids);
287+
uint32 est_entries, Bitmapset *keyparamids,
288+
Cardinality est_calls,
289+
Cardinality est_unique_keys,
290+
double est_hit_ratio);
288291
static WindowAgg *make_windowagg(List *tlist, WindowClause *wc,
289292
int partNumCols, AttrNumber *partColIdx, Oid *partOperators, Oid *partCollations,
290293
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, Oid *ordCollations,
@@ -1753,7 +1756,8 @@ create_memoize_plan(PlannerInfo *root, MemoizePath *best_path, int flags)
17531756

17541757
plan = make_memoize(subplan, operators, collations, param_exprs,
17551758
best_path->singlerow, best_path->binary_mode,
1756-
best_path->est_entries, keyparamids);
1759+
best_path->est_entries, keyparamids, best_path->est_calls,
1760+
best_path->est_unique_keys, best_path->est_hit_ratio);
17571761

17581762
copy_generic_path_info(&plan->plan, (Path *) best_path);
17591763

@@ -6749,7 +6753,9 @@ materialize_finished_plan(Plan *subplan)
67496753
static Memoize *
67506754
make_memoize(Plan *lefttree, Oid *hashoperators, Oid *collations,
67516755
List *param_exprs, bool singlerow, bool binary_mode,
6752-
uint32 est_entries, Bitmapset *keyparamids)
6756+
uint32 est_entries, Bitmapset *keyparamids,
6757+
Cardinality est_calls, Cardinality est_unique_keys,
6758+
double est_hit_ratio)
67536759
{
67546760
Memoize *node = makeNode(Memoize);
67556761
Plan *plan = &node->plan;
@@ -6767,6 +6773,9 @@ make_memoize(Plan *lefttree, Oid *hashoperators, Oid *collations,
67676773
node->binary_mode = binary_mode;
67686774
node->est_entries = est_entries;
67696775
node->keyparamids = keyparamids;
6776+
node->est_calls = est_calls;
6777+
node->est_unique_keys = est_unique_keys;
6778+
node->est_hit_ratio = est_hit_ratio;
67706779

67716780
return node;
67726781
}

src/backend/optimizer/util/pathnode.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,7 +1689,7 @@ create_material_path(RelOptInfo *rel, Path *subpath)
16891689
MemoizePath *
16901690
create_memoize_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
16911691
List *param_exprs, List *hash_operators,
1692-
bool singlerow, bool binary_mode, double calls)
1692+
bool singlerow, bool binary_mode, Cardinality est_calls)
16931693
{
16941694
MemoizePath *pathnode = makeNode(MemoizePath);
16951695

@@ -1710,7 +1710,6 @@ create_memoize_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
17101710
pathnode->param_exprs = param_exprs;
17111711
pathnode->singlerow = singlerow;
17121712
pathnode->binary_mode = binary_mode;
1713-
pathnode->calls = clamp_row_est(calls);
17141713

17151714
/*
17161715
* For now we set est_entries to 0. cost_memoize_rescan() does all the
@@ -1720,6 +1719,12 @@ create_memoize_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
17201719
*/
17211720
pathnode->est_entries = 0;
17221721

1722+
pathnode->est_calls = clamp_row_est(est_calls);
1723+
1724+
/* These will also be set later in cost_memoize_rescan() */
1725+
pathnode->est_unique_keys = 0.0;
1726+
pathnode->est_hit_ratio = 0.0;
1727+
17231728
/* we should not generate this path type when enable_memoize=false */
17241729
Assert(enable_memoize);
17251730
pathnode->path.disabled_nodes = subpath->disabled_nodes;
@@ -4259,7 +4264,7 @@ reparameterize_path(PlannerInfo *root, Path *path,
42594264
mpath->hash_operators,
42604265
mpath->singlerow,
42614266
mpath->binary_mode,
4262-
mpath->calls);
4267+
mpath->est_calls);
42634268
}
42644269
default:
42654270
break;

src/include/nodes/pathnodes.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2133,10 +2133,12 @@ typedef struct MemoizePath
21332133
* complete after caching the first record. */
21342134
bool binary_mode; /* true when cache key should be compared bit
21352135
* by bit, false when using hash equality ops */
2136-
Cardinality calls; /* expected number of rescans */
21372136
uint32 est_entries; /* The maximum number of entries that the
21382137
* planner expects will fit in the cache, or 0
21392138
* if unknown */
2139+
Cardinality est_calls; /* expected number of rescans */
2140+
Cardinality est_unique_keys; /* estimated unique keys, for EXPLAIN */
2141+
double est_hit_ratio; /* estimated cache hit ratio, for EXPLAIN */
21402142
} MemoizePath;
21412143

21422144
/*

src/include/nodes/plannodes.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,16 @@ typedef struct Memoize
10731073

10741074
/* paramids from param_exprs */
10751075
Bitmapset *keyparamids;
1076+
1077+
/* Estimated number of rescans, for EXPLAIN */
1078+
Cardinality est_calls;
1079+
1080+
/* Estimated number of distinct lookup keys, for EXPLAIN */
1081+
Cardinality est_unique_keys;
1082+
1083+
/* Estimated cache hit ratio, for EXPLAIN */
1084+
double est_hit_ratio;
1085+
10761086
} Memoize;
10771087

10781088
/* ----------------

src/include/optimizer/pathnode.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ extern MemoizePath *create_memoize_path(PlannerInfo *root,
9090
List *hash_operators,
9191
bool singlerow,
9292
bool binary_mode,
93-
double calls);
93+
Cardinality est_calls);
9494
extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
9595
Path *subpath, SpecialJoinInfo *sjinfo);
9696
extern GatherPath *create_gather_path(PlannerInfo *root,

0 commit comments

Comments
 (0)