diff options
-rw-r--r-- | src/backend/commands/explain.c | 37 | ||||
-rw-r--r-- | src/backend/utils/sort/tuplestore.c | 53 | ||||
-rw-r--r-- | src/include/utils/tuplestore.h | 4 | ||||
-rw-r--r-- | src/test/regress/expected/partition_prune.out | 37 | ||||
-rw-r--r-- | src/test/regress/sql/partition_prune.sql | 29 |
5 files changed, 148 insertions, 12 deletions
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 30de9de9d4f..1b5ab503898 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -125,6 +125,7 @@ static void show_sort_info(SortState *sortstate, ExplainState *es); static void show_incremental_sort_info(IncrementalSortState *incrsortstate, ExplainState *es); static void show_hash_info(HashState *hashstate, ExplainState *es); +static void show_material_info(MaterialState *mstate, ExplainState *es); static void show_memoize_info(MemoizeState *mstate, List *ancestors, ExplainState *es); static void show_hashagg_info(AggState *aggstate, ExplainState *es); @@ -2251,6 +2252,9 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_Hash: show_hash_info(castNode(HashState, planstate), es); break; + case T_Material: + show_material_info(castNode(MaterialState, planstate), es); + break; case T_Memoize: show_memoize_info(castNode(MemoizeState, planstate), ancestors, es); @@ -3323,6 +3327,39 @@ show_hash_info(HashState *hashstate, ExplainState *es) } /* + * Show information on material node, storage method and maximum memory/disk + * space used. + */ +static void +show_material_info(MaterialState *mstate, ExplainState *es) +{ + Tuplestorestate *tupstore; + const char *storageType; + int64 spaceUsedKB; + + if (!es->analyze) + return; + + tupstore = mstate->tuplestorestate; + storageType = tuplestore_storage_type_name(tupstore); + spaceUsedKB = BYTES_TO_KILOBYTES(tuplestore_space_used(tupstore)); + + if (es->format != EXPLAIN_FORMAT_TEXT) + { + ExplainPropertyText("Storage", storageType, es); + ExplainPropertyInteger("Maximum Storage", "kB", spaceUsedKB, es); + } + else + { + ExplainIndentText(es); + appendStringInfo(es->str, + "Storage: %s Maximum Storage: " INT64_FORMAT "kB\n", + storageType, + spaceUsedKB); + } +} + +/* * Show information on memoize hits/misses/evictions and memory usage. */ static void diff --git a/src/backend/utils/sort/tuplestore.c b/src/backend/utils/sort/tuplestore.c index 947a868e569..24bb49ca874 100644 --- a/src/backend/utils/sort/tuplestore.c +++ b/src/backend/utils/sort/tuplestore.c @@ -109,6 +109,7 @@ struct Tuplestorestate bool truncated; /* tuplestore_trim has removed tuples? */ int64 availMem; /* remaining memory available, in bytes */ int64 allowedMem; /* total memory allowed, in bytes */ + int64 maxSpace; /* maximum space used in memory */ int64 tuples; /* number of tuples added */ BufFile *myfile; /* underlying file, or NULL if none */ MemoryContext context; /* memory context for holding tuples */ @@ -238,6 +239,7 @@ static Tuplestorestate *tuplestore_begin_common(int eflags, int maxKBytes); static void tuplestore_puttuple_common(Tuplestorestate *state, void *tuple); static void dumptuples(Tuplestorestate *state); +static void tuplestore_updatemax(Tuplestorestate *state); static unsigned int getlen(Tuplestorestate *state, bool eofOK); static void *copytup_heap(Tuplestorestate *state, void *tup); static void writetup_heap(Tuplestorestate *state, void *tup); @@ -262,6 +264,7 @@ tuplestore_begin_common(int eflags, bool interXact, int maxKBytes) state->truncated = false; state->allowedMem = maxKBytes * 1024L; state->availMem = state->allowedMem; + state->maxSpace = 0; state->myfile = NULL; state->context = CurrentMemoryContext; state->resowner = CurrentResourceOwner; @@ -420,6 +423,9 @@ tuplestore_clear(Tuplestorestate *state) int i; TSReadPointer *readptr; + /* update the maxSpace before doing any USEMEM/FREEMEM adjustments */ + tuplestore_updatemax(state); + if (state->myfile) BufFileClose(state->myfile); state->myfile = NULL; @@ -1402,6 +1408,9 @@ tuplestore_trim(Tuplestorestate *state) Assert(nremove >= state->memtupdeleted); Assert(nremove <= state->memtupcount); + /* before freeing any memory, update maxSpace */ + tuplestore_updatemax(state); + /* Release no-longer-needed tuples */ for (i = state->memtupdeleted; i < nremove; i++) { @@ -1445,6 +1454,49 @@ tuplestore_trim(Tuplestorestate *state) } /* + * tuplestore_updatemax + * Update maxSpace field + */ +static void +tuplestore_updatemax(Tuplestorestate *state) +{ + if (state->status == TSS_INMEM) + state->maxSpace = Max(state->maxSpace, + state->allowedMem - state->availMem); +} + +/* + * tuplestore_storage_type_name + * Return a string description of the storage method used to store the + * tuples. + */ +const char * +tuplestore_storage_type_name(Tuplestorestate *state) +{ + if (state->status == TSS_INMEM) + return "Memory"; + else + return "Disk"; +} + +/* + * tuplestore_space_used + * Return the maximum space used in memory unless the tuplestore has spilled + * to disk, in which case, return the disk space used. + */ +int64 +tuplestore_space_used(Tuplestorestate *state) +{ + /* First, update the maxSpace field */ + tuplestore_updatemax(state); + + if (state->status == TSS_INMEM) + return state->maxSpace; + else + return BufFileSize(state->myfile); +} + +/* * tuplestore_in_memory * * Returns true if the tuplestore has not spilled to disk. @@ -1513,6 +1565,7 @@ writetup_heap(Tuplestorestate *state, void *tup) if (state->backward) /* need trailing length word? */ BufFileWrite(state->myfile, &tuplen, sizeof(tuplen)); + /* no need to call tuplestore_updatemax() when not in TSS_INMEM */ FREEMEM(state, GetMemoryChunkSpace(tuple)); heap_free_minimal_tuple(tuple); } diff --git a/src/include/utils/tuplestore.h b/src/include/utils/tuplestore.h index 419613c17ba..3d8a90caaf9 100644 --- a/src/include/utils/tuplestore.h +++ b/src/include/utils/tuplestore.h @@ -65,6 +65,10 @@ extern void tuplestore_copy_read_pointer(Tuplestorestate *state, extern void tuplestore_trim(Tuplestorestate *state); +extern const char *tuplestore_storage_type_name(Tuplestorestate *state); + +extern int64 tuplestore_space_used(Tuplestorestate *state); + extern bool tuplestore_in_memory(Tuplestorestate *state); extern bool tuplestore_gettupleslot(Tuplestorestate *state, bool forward, diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 7ca98397aec..7a03b4e3607 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -1,6 +1,24 @@ -- -- Test partitioning planner code -- +-- Helper function which can be used for masking out portions of EXPLAIN +-- ANALYZE which could contain information that's not consistent on all +-- platforms. +create function explain_analyze(query text) returns setof text +language plpgsql as +$$ +declare + ln text; +begin + for ln in + execute format('explain (analyze, costs off, summary off, timing off) %s', + query) + loop + ln := regexp_replace(ln, 'Maximum Storage: \d+', 'Maximum Storage: N'); + return next ln; + end loop; +end; +$$; -- Force generic plans to be used for all prepared statements in this file. set plan_cache_mode = force_generic_plan; create table lp (a char) partition by list (a); @@ -2826,9 +2844,9 @@ deallocate ab_q5; deallocate ab_q6; -- UPDATE on a partition subtree has been seen to have problems. insert into ab values (1,2); -explain (analyze, costs off, summary off, timing off) -update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; - QUERY PLAN +select explain_analyze(' +update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;'); + explain_analyze ------------------------------------------------------------------------------------------- Update on ab_a1 (actual rows=0 loops=1) Update on ab_a1_b1 ab_a1_1 @@ -2851,6 +2869,7 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) -> Materialize (actual rows=1 loops=1) + Storage: Memory Maximum Storage: NkB -> Append (actual rows=1 loops=1) -> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1) Recheck Cond: (a = 1) @@ -2866,7 +2885,7 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) -(36 rows) +(37 rows) table ab; a | b @@ -2877,9 +2896,9 @@ table ab; -- Test UPDATE where source relation has run-time pruning enabled truncate ab; insert into ab values (1, 1), (1, 2), (1, 3), (2, 1); -explain (analyze, costs off, summary off, timing off) -update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1); - QUERY PLAN +select explain_analyze(' +update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1);'); + explain_analyze ------------------------------------------------------------------------------ Update on ab_a1 (actual rows=0 loops=1) Update on ab_a1_b1 ab_a1_1 @@ -2893,6 +2912,7 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1); -> Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1) -> Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1) -> Materialize (actual rows=1 loops=3) + Storage: Memory Maximum Storage: NkB -> Append (actual rows=1 loops=1) -> Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1) Filter: (b = (InitPlan 1).col1) @@ -2900,7 +2920,7 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1); Filter: (b = (InitPlan 1).col1) -> Seq Scan on ab_a2_b3 ab_a2_3 (never executed) Filter: (b = (InitPlan 1).col1) -(19 rows) +(20 rows) select tableoid::regclass, * from ab; tableoid | a | b @@ -4419,3 +4439,4 @@ explain (costs off) select * from hp_contradict_test where a === 1 and b === 1 a drop table hp_contradict_test; drop operator class part_test_int4_ops2 using hash; drop operator ===(int4, int4); +drop function explain_analyze(text); diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index a09b27d820c..442428d937c 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -2,6 +2,25 @@ -- Test partitioning planner code -- +-- Helper function which can be used for masking out portions of EXPLAIN +-- ANALYZE which could contain information that's not consistent on all +-- platforms. +create function explain_analyze(query text) returns setof text +language plpgsql as +$$ +declare + ln text; +begin + for ln in + execute format('explain (analyze, costs off, summary off, timing off) %s', + query) + loop + ln := regexp_replace(ln, 'Maximum Storage: \d+', 'Maximum Storage: N'); + return next ln; + end loop; +end; +$$; + -- Force generic plans to be used for all prepared statements in this file. set plan_cache_mode = force_generic_plan; @@ -676,15 +695,15 @@ deallocate ab_q6; -- UPDATE on a partition subtree has been seen to have problems. insert into ab values (1,2); -explain (analyze, costs off, summary off, timing off) -update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; +select explain_analyze(' +update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;'); table ab; -- Test UPDATE where source relation has run-time pruning enabled truncate ab; insert into ab values (1, 1), (1, 2), (1, 3), (2, 1); -explain (analyze, costs off, summary off, timing off) -update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1); +select explain_analyze(' +update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1);'); select tableoid::regclass, * from ab; drop table ab, lprt_a; @@ -1318,3 +1337,5 @@ explain (costs off) select * from hp_contradict_test where a === 1 and b === 1 a drop table hp_contradict_test; drop operator class part_test_int4_ops2 using hash; drop operator ===(int4, int4); + +drop function explain_analyze(text); |