From fe65713ecad2fb46cf66adb3e2e58307ee0432d8 Mon Sep 17 00:00:00 2001 From: James Coleman Date: Sat, 7 Mar 2020 17:09:39 -0500 Subject: [PATCH 3/5] Rework EXPLAIN for incremental sort --- src/backend/commands/explain.c | 253 +++++++++--------- src/backend/executor/nodeIncrementalSort.c | 121 ++++++--- src/include/nodes/execnodes.h | 29 +- .../regress/expected/incremental_sort.out | 160 +++++++++++ src/test/regress/sql/incremental_sort.sql | 10 + 5 files changed, 402 insertions(+), 171 deletions(-) diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 14aedec919..8262c54e6a 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -2701,80 +2701,114 @@ show_sort_info(SortState *sortstate, ExplainState *es) } } -/* - * If it's EXPLAIN ANALYZE, show tuplesort stats for a incremental sort node - */ + static void -show_incremental_sort_info(IncrementalSortState *incrsortstate, - ExplainState *es) +show_incremental_sort_group_info(IncrementalSortGroupInfo *groupInfo, + const char *groupLabel, ExplainState *es) { - if (es->analyze && incrsortstate->sort_Done && - incrsortstate->fullsort_state != NULL) + const char *sortMethodName; + const char *spaceTypeName; + ListCell *methodCell; + int methodCount = list_length(groupInfo->sortMethods); + + if (es->format == EXPLAIN_FORMAT_TEXT) { - /* TODO: is it valid to get space used etc. only once given we re-use the sort? */ - /* TODO: maybe show average, min, max sort group size? */ - - Tuplesortstate *fullsort_state = incrsortstate->fullsort_state; - TuplesortInstrumentation fullsort_stats; - const char *fullsort_sortMethod; - const char *fullsort_spaceType; - Tuplesortstate *prefixsort_state = incrsortstate->prefixsort_state; - TuplesortInstrumentation prefixsort_stats; - const char *prefixsort_sortMethod; - const char *prefixsort_spaceType; - - tuplesort_get_stats(fullsort_state, &fullsort_stats); - fullsort_sortMethod = tuplesort_method_name(fullsort_stats.sortMethod); - fullsort_spaceType = tuplesort_space_type_name(fullsort_stats.spaceType); - if (prefixsort_state != NULL) + appendStringInfoSpaces(es->str, es->indent * 2); + appendStringInfo(es->str, "%s Groups: %ld (Methods: ", groupLabel, + groupInfo->groupCount); + foreach(methodCell, groupInfo->sortMethods) { - tuplesort_get_stats(prefixsort_state, &prefixsort_stats); - prefixsort_sortMethod = tuplesort_method_name(prefixsort_stats.sortMethod); - prefixsort_spaceType = tuplesort_space_type_name(prefixsort_stats.spaceType); + sortMethodName = tuplesort_method_name(methodCell->int_value); + appendStringInfo(es->str, "%s", sortMethodName); + if (foreach_current_index(methodCell) < methodCount - 1) + appendStringInfo(es->str, ", "); } + appendStringInfo(es->str, ")"); - if (es->format == EXPLAIN_FORMAT_TEXT) + if (groupInfo->maxMemorySpaceUsed > 0) { - appendStringInfoSpaces(es->str, es->indent * 2); - appendStringInfo(es->str, "Sort Method: Full: %s %s: %ldkB", - fullsort_sortMethod, fullsort_spaceType, - fullsort_stats.spaceUsed); - if (prefixsort_state != NULL) - appendStringInfo(es->str, ", Prefix-only: %s %s: %ldkB\n", - prefixsort_sortMethod, prefixsort_spaceType, - prefixsort_stats.spaceUsed); - else - appendStringInfo(es->str, "\n"); - appendStringInfoSpaces(es->str, es->indent * 2); - appendStringInfo(es->str, "Sort Groups: Full: %ld", - incrsortstate->fullsort_group_count); - if (prefixsort_state != NULL) - appendStringInfo(es->str, ", Prefix-only: %ld\n", - incrsortstate->prefixsort_group_count); - else - appendStringInfo(es->str, "\n"); + long avgSpace = groupInfo->totalMemorySpaceUsed / groupInfo->groupCount; + spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_MEMORY); + appendStringInfo(es->str, " %s: %ldkB (avg), %ldkB (max)", + spaceTypeName, avgSpace, + groupInfo->maxMemorySpaceUsed); } - else + + if (groupInfo->maxDiskSpaceUsed > 0) { - /* TODO */ - ExplainPropertyText("Full Sort Method", fullsort_sortMethod, es); - ExplainPropertyInteger("Full Sort Space Used", "kB", - fullsort_stats.spaceUsed, es); - ExplainPropertyText("Full Sort Space Type", fullsort_spaceType, es); - ExplainPropertyInteger("Full Sort Groups", NULL, - incrsortstate->fullsort_group_count, es); - - if (prefixsort_state != NULL) - { - ExplainPropertyText("Prefix Sort Method", prefixsort_sortMethod, es); - ExplainPropertyInteger("Prefix Sort Space Used", "kB", - prefixsort_stats.spaceUsed, es); - ExplainPropertyText("Prefix Sort Space Type", prefixsort_spaceType, es); - ExplainPropertyInteger("Prefix Sort Groups", NULL, - incrsortstate->prefixsort_group_count, es); - } + long avgSpace = groupInfo->totalDiskSpaceUsed / groupInfo->groupCount; + spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_DISK); + /* Add a semicolon separator only if memory stats were printed. */ + if (groupInfo->maxMemorySpaceUsed > 0) + appendStringInfo(es->str, ";"); + appendStringInfo(es->str, " %s: %ldkB (avg), %ldkB (max)", + spaceTypeName, avgSpace, + groupInfo->maxDiskSpaceUsed); + } + + appendStringInfo(es->str, "\n"); + } + else + { + List *methodNames = NIL; + StringInfoData groupName; + + initStringInfo(&groupName); + appendStringInfo(&groupName, "%s Groups", groupLabel); + ExplainOpenGroup("Incremental Sort Groups", groupName.data, true, es); + ExplainPropertyInteger("Group Count", NULL, groupInfo->groupCount, es); + + foreach(methodCell, groupInfo->sortMethods) + { + sortMethodName = tuplesort_method_name(methodCell->int_value); + methodNames = lappend(methodNames, sortMethodName); + } + ExplainPropertyList("Sort Methods Used", methodNames, es); + + if (groupInfo->maxMemorySpaceUsed > 0) + { + long avgSpace = groupInfo->totalMemorySpaceUsed / groupInfo->groupCount; + + ExplainPropertyInteger("Average Sort Space Used", "kB", avgSpace, es); + ExplainPropertyInteger("Maximum Sort Space Used", "kB", + groupInfo->maxMemorySpaceUsed, es); + spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_MEMORY); + ExplainPropertyText("Sort Space Type", spaceTypeName, es); + } + if (groupInfo->maxDiskSpaceUsed > 0) + { + long avgSpace = groupInfo->totalDiskSpaceUsed / groupInfo->groupCount; + + ExplainPropertyInteger("Average Sort Space Used", "kB", avgSpace, es); + ExplainPropertyInteger("Maximum Sort Space Used", "kB", + groupInfo->maxDiskSpaceUsed, es); + spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_MEMORY); + ExplainPropertyText("Sort Space Type", spaceTypeName, es); } + + ExplainCloseGroup("Incremental Sort Groups", "XXX Groups", true, es); } +} + +/* + * If it's EXPLAIN ANALYZE, show tuplesort stats for a incremental sort node + */ +static void +show_incremental_sort_info(IncrementalSortState *incrsortstate, + ExplainState *es) +{ + IncrementalSortGroupInfo *fullsortGroupInfo; + IncrementalSortGroupInfo *prefixsortGroupInfo; + + if (!(es->analyze && incrsortstate->sort_Done)) + return; + + fullsortGroupInfo = &incrsortstate->incsort_info.fullsortGroupInfo; + if (fullsortGroupInfo->groupCount > 0) + show_incremental_sort_group_info(fullsortGroupInfo, "Full-sort", es); + prefixsortGroupInfo = &incrsortstate->incsort_info.prefixsortGroupInfo; + if (prefixsortGroupInfo->groupCount > 0) + show_incremental_sort_group_info(prefixsortGroupInfo, "Presorted", es); if (incrsortstate->shared_info != NULL) { @@ -2785,79 +2819,36 @@ show_incremental_sort_info(IncrementalSortState *incrsortstate, { IncrementalSortInfo *incsort_info = &incrsortstate->shared_info->sinfo[n]; - TuplesortInstrumentation *fullsort_instrument; - const char *fullsort_sortMethod; - const char *fullsort_spaceType; - long fullsort_spaceUsed; - int64 fullsort_group_count; - TuplesortInstrumentation *prefixsort_instrument; - const char *prefixsort_sortMethod; - const char *prefixsort_spaceType; - long prefixsort_spaceUsed; - int64 prefixsort_group_count; - - fullsort_instrument = &incsort_info->fullsort_instrument; - fullsort_group_count = incsort_info->fullsort_group_count; - - prefixsort_instrument = &incsort_info->prefixsort_instrument; - prefixsort_group_count = incsort_info->prefixsort_group_count; - - if (fullsort_instrument->sortMethod == SORT_TYPE_STILL_IN_PROGRESS) - continue; /* ignore any unfilled slots */ - - fullsort_sortMethod = tuplesort_method_name( - fullsort_instrument->sortMethod); - fullsort_spaceType = tuplesort_space_type_name( - fullsort_instrument->spaceType); - fullsort_spaceUsed = fullsort_instrument->spaceUsed; + /* + * XXX: The previous version of the patch chcked: + * fullsort_instrument->sortMethod == SORT_TYPE_STILL_IN_PROGRESS + * and continued if the condition was true (with the comment "ignore + * any unfilled slots"). + * I'm not convinced that makes sense since the same sort instrument + * can have been used multiple times, so the last time it being used + * being still in progress, doesn't seem to be relevant. + * Instead I'm now checking to see if the group count for each group + * info is 0. If both are 0, then we exclude the worker since it + * didn't contribute anything meaningful. + */ + fullsortGroupInfo = &incsort_info->fullsortGroupInfo; + prefixsortGroupInfo = &incsort_info->prefixsortGroupInfo; + if (fullsortGroupInfo->groupCount == 0 && + prefixsortGroupInfo->groupCount == 0) + continue; - if (prefixsort_instrument) + if (!opened_group) { - prefixsort_sortMethod = tuplesort_method_name( - prefixsort_instrument->sortMethod); - prefixsort_spaceType = tuplesort_space_type_name( - prefixsort_instrument->spaceType); - prefixsort_spaceUsed = prefixsort_instrument->spaceUsed; + ExplainOpenGroup("Workers", "Workers", false, es); + opened_group = true; } - if (es->format == EXPLAIN_FORMAT_TEXT) - { - appendStringInfoSpaces(es->str, es->indent * 2); - appendStringInfo(es->str, - "Worker %d: Full Sort Method: %s %s: %ldkB Groups: %ld", - n, fullsort_sortMethod, fullsort_spaceType, - fullsort_spaceUsed, fullsort_group_count); - if (prefixsort_instrument) - appendStringInfo(es->str, - ", Prefix Sort Method: %s %s: %ldkB Groups: %ld\n", - prefixsort_sortMethod, prefixsort_spaceType, - prefixsort_spaceUsed, prefixsort_group_count); - else - appendStringInfo(es->str, "\n"); - } - else - { - if (!opened_group) - { - ExplainOpenGroup("Workers", "Workers", false, es); - opened_group = true; - } - ExplainOpenGroup("Worker", NULL, true, es); - ExplainPropertyInteger("Worker Number", NULL, n, es); - ExplainPropertyText("Full Sort Method", fullsort_sortMethod, es); - ExplainPropertyInteger("Full Sort Space Used", "kB", fullsort_spaceUsed, es); - ExplainPropertyText("Full Sort Space Type", fullsort_spaceType, es); - ExplainPropertyInteger("Full Sort Groups", NULL, fullsort_group_count, es); - if (prefixsort_instrument) - { - ExplainPropertyText("Prefix Sort Method", prefixsort_sortMethod, es); - ExplainPropertyInteger("Prefix Sort Space Used", "kB", prefixsort_spaceUsed, es); - ExplainPropertyText("Prefix Sort Space Type", prefixsort_spaceType, es); - ExplainPropertyInteger("Prefix Sort Groups", NULL, prefixsort_group_count, es); - } - ExplainCloseGroup("Worker", NULL, true, es); - } + if (fullsortGroupInfo->groupCount > 0) + show_incremental_sort_group_info(fullsortGroupInfo, "Full-sort", es); + if (prefixsortGroupInfo->groupCount > 0) + show_incremental_sort_group_info(prefixsortGroupInfo, "Presorted", es); } + if (opened_group) ExplainCloseGroup("Workers", "Workers", false, es); } diff --git a/src/backend/executor/nodeIncrementalSort.c b/src/backend/executor/nodeIncrementalSort.c index c3b903e568..e6f749a798 100644 --- a/src/backend/executor/nodeIncrementalSort.c +++ b/src/backend/executor/nodeIncrementalSort.c @@ -68,6 +68,47 @@ #include "utils/lsyscache.h" #include "utils/tuplesort.h" +static void +instrumentSortedGroup(PlanState *pstate, IncrementalSortGroupInfo *groupInfo, + Tuplesortstate *sortState) +{ + IncrementalSortState *node = castNode(IncrementalSortState, pstate); + TuplesortInstrumentation sort_instr; + + groupInfo->groupCount++; + + tuplesort_get_stats(sortState, &sort_instr); + switch (sort_instr.spaceType) + { + case SORT_SPACE_TYPE_DISK: + groupInfo->totalDiskSpaceUsed += sort_instr.spaceUsed; + if (sort_instr.spaceUsed > groupInfo->maxDiskSpaceUsed) + groupInfo->maxDiskSpaceUsed = sort_instr.spaceUsed; + + break; + case SORT_SPACE_TYPE_MEMORY: + groupInfo->totalMemorySpaceUsed += sort_instr.spaceUsed; + if (sort_instr.spaceUsed > groupInfo->maxMemorySpaceUsed) + groupInfo->maxMemorySpaceUsed = sort_instr.spaceUsed; + + break; + } + + if (!list_member_int(groupInfo->sortMethods, sort_instr.sortMethod)) + groupInfo->sortMethods = lappend_int(groupInfo->sortMethods, + sort_instr.sortMethod); + + /* Record shared stats if we're a parallel worker. */ + if (node->shared_info && node->am_worker) + { + Assert(IsParallelWorker()); + Assert(ParallelWorkerNumber <= node->shared_info->num_workers); + + memcpy(&node->shared_info->sinfo[ParallelWorkerNumber], + &node->incsort_info, sizeof(IncrementalSortInfo)); + } +} + /* * Prepare information for presorted_keys comparison. */ @@ -199,8 +240,9 @@ isCurrentGroup(IncrementalSortState *node, TupleTableSlot *pivot, TupleTableSlot * one different prefix key group before the large prefix key group. */ static void -switchToPresortedPrefixMode(IncrementalSortState *node) +switchToPresortedPrefixMode(PlanState *pstate) { + IncrementalSortState *node = castNode(IncrementalSortState, pstate); ScanDirection dir; int64 nTuples = 0; bool lastTuple = false; @@ -355,7 +397,11 @@ switchToPresortedPrefixMode(IncrementalSortState *node) */ SO1_printf("Sorting presorted prefix tuplesort with %ld tuples\n", nTuples); tuplesort_performsort(node->prefixsort_state); - node->prefixsort_group_count++; + + if (pstate->instrument != NULL) + instrumentSortedGroup(pstate, + &node->incsort_info.prefixsortGroupInfo, + node->prefixsort_state); if (node->bounded) { @@ -479,7 +525,7 @@ ExecIncrementalSort(PlanState *pstate) */ SO1_printf("Re-calling switchToPresortedPrefixMode() because n_fullsort_remaining is > 0 (%ld)\n", node->n_fullsort_remaining); - switchToPresortedPrefixMode(node); + switchToPresortedPrefixMode(pstate); } else { @@ -602,7 +648,11 @@ ExecIncrementalSort(PlanState *pstate) SO1_printf("Sorting fullsort with %ld tuples\n", nTuples); tuplesort_performsort(fullsort_state); - node->fullsort_group_count++; + + if (pstate->instrument != NULL) + instrumentSortedGroup(pstate, + &node->incsort_info.fullsortGroupInfo, + fullsort_state); SO_printf("Setting execution_status to INCSORT_READFULLSORT (final tuple) \n"); node->execution_status = INCSORT_READFULLSORT; @@ -673,7 +723,12 @@ ExecIncrementalSort(PlanState *pstate) */ SO1_printf("Sorting fullsort tuplesort with %ld tuples\n", nTuples); tuplesort_performsort(fullsort_state); - node->fullsort_group_count++; + + if (pstate->instrument != NULL) + instrumentSortedGroup(pstate, + &node->incsort_info.fullsortGroupInfo, + fullsort_state); + SO_printf("Setting execution_status to INCSORT_READFULLSORT (found end of group)\n"); node->execution_status = INCSORT_READFULLSORT; break; @@ -705,7 +760,10 @@ ExecIncrementalSort(PlanState *pstate) */ SO1_printf("Sorting fullsort tuplesort with %ld tuples\n", nTuples); tuplesort_performsort(fullsort_state); - node->fullsort_group_count++; + if (pstate->instrument != NULL) + instrumentSortedGroup(pstate, + &node->incsort_info.fullsortGroupInfo, + fullsort_state); /* * If the full sort tuplesort happened to switch into top-n heapsort mode @@ -735,7 +793,7 @@ ExecIncrementalSort(PlanState *pstate) node->n_fullsort_remaining = nTuples; /* Transition the tuples to the presorted prefix tuplesort. */ - switchToPresortedPrefixMode(node); + switchToPresortedPrefixMode(pstate); /* * Since we know we had tuples to move to the presorted prefix @@ -801,7 +859,12 @@ ExecIncrementalSort(PlanState *pstate) /* Perform the sort and return the tuples to the inner plan nodes. */ SO1_printf("Sorting presorted prefix tuplesort with >= %ld tuples\n", nTuples); tuplesort_performsort(node->prefixsort_state); - node->prefixsort_group_count++; + + if (pstate->instrument != NULL) + instrumentSortedGroup(pstate, + &node->incsort_info.prefixsortGroupInfo, + node->prefixsort_state); + SO_printf("Setting execution_status to INCSORT_READPREFIXSORT (found end of group)\n"); node->execution_status = INCSORT_READPREFIXSORT; @@ -828,26 +891,6 @@ ExecIncrementalSort(PlanState *pstate) */ node->sort_Done = true; - /* Record shared stats if we're a parallel worker. */ - if (node->shared_info && node->am_worker) - { - IncrementalSortInfo *incsort_info = - &node->shared_info->sinfo[ParallelWorkerNumber]; - - Assert(IsParallelWorker()); - Assert(ParallelWorkerNumber <= node->shared_info->num_workers); - - tuplesort_get_stats(fullsort_state, &incsort_info->fullsort_instrument); - incsort_info->fullsort_group_count = node->fullsort_group_count; - - if (node->prefixsort_state) - { - tuplesort_get_stats(node->prefixsort_state, - &incsort_info->prefixsort_instrument); - incsort_info->prefixsort_group_count = node->prefixsort_group_count; - } - } - /* * Get the first or next tuple from tuplesort. Returns NULL if no more * tuples. @@ -900,10 +943,28 @@ ExecInitIncrementalSort(IncrementalSort *node, EState *estate, int eflags) incrsortstate->transfer_tuple = NULL; incrsortstate->n_fullsort_remaining = 0; incrsortstate->bound_Done = 0; - incrsortstate->fullsort_group_count = 0; - incrsortstate->prefixsort_group_count = 0; incrsortstate->presorted_keys = NULL; + if (incrsortstate->ss.ps.instrument != NULL) + { + IncrementalSortGroupInfo *fullsortGroupInfo = + &incrsortstate->incsort_info.fullsortGroupInfo; + IncrementalSortGroupInfo *prefixsortGroupInfo = + &incrsortstate->incsort_info.prefixsortGroupInfo; + fullsortGroupInfo->groupCount = 0; + fullsortGroupInfo->maxDiskSpaceUsed = 0; + fullsortGroupInfo->totalDiskSpaceUsed = 0; + fullsortGroupInfo->maxMemorySpaceUsed = 0; + fullsortGroupInfo->totalMemorySpaceUsed = 0; + fullsortGroupInfo->sortMethods = NIL; + prefixsortGroupInfo->groupCount = 0; + prefixsortGroupInfo->maxDiskSpaceUsed = 0; + prefixsortGroupInfo->totalDiskSpaceUsed = 0; + prefixsortGroupInfo->maxMemorySpaceUsed = 0; + prefixsortGroupInfo->totalMemorySpaceUsed = 0; + prefixsortGroupInfo->sortMethods = NIL; + } + /* * Miscellaneous initialization * diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index f905e384a2..0934482123 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -2022,18 +2022,26 @@ typedef struct SortState SharedSortInfo *shared_info; /* one entry per worker */ } SortState; -/* ---------------- - * Shared memory container for per-worker incremental sort information - * ---------------- - */ +typedef struct IncrementalSortGroupInfo +{ + int64 groupCount; + long maxDiskSpaceUsed; + long totalDiskSpaceUsed; + long maxMemorySpaceUsed; + long totalMemorySpaceUsed; + List *sortMethods; +} IncrementalSortGroupInfo; + typedef struct IncrementalSortInfo { - TuplesortInstrumentation fullsort_instrument; - int64 fullsort_group_count; - TuplesortInstrumentation prefixsort_instrument; - int64 prefixsort_group_count; + IncrementalSortGroupInfo fullsortGroupInfo; + IncrementalSortGroupInfo prefixsortGroupInfo; } IncrementalSortInfo; +/* ---------------- + * Shared memory container for per-worker incremental sort information + * ---------------- + */ typedef struct SharedIncrementalSortInfo { int num_workers; @@ -2067,8 +2075,9 @@ typedef struct IncrementalSortState Tuplesortstate *prefixsort_state; /* private state of tuplesort.c */ /* the keys by which the input path is already sorted */ PresortedKeyData *presorted_keys; - int64 fullsort_group_count; /* number of groups with equal presorted keys */ - int64 prefixsort_group_count; /* number of groups with equal presorted keys */ + + IncrementalSortInfo incsort_info; + /* slot for pivot tuple defining values of presorted keys within group */ TupleTableSlot *group_pivot; TupleTableSlot *transfer_tuple; diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out index 3a58efdf91..7892b111d7 100644 --- a/src/test/regress/expected/incremental_sort.out +++ b/src/test/regress/expected/incremental_sort.out @@ -436,6 +436,82 @@ select * from (select * from t order by a) s order by a, b limit 55; 2 | 55 (55 rows) +-- Test EXPLAIN ANALYZE (text output) with only a fullsort group. +explain (analyze, costs off, summary off, timing off) +select * from (select * from t order by a) s order by a, b limit 55; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Limit (actual rows=55 loops=1) + -> Incremental Sort (actual rows=55 loops=1) + Sort Key: t.a, t.b + Presorted Key: t.a + Full-sort Groups: 2 (Methods: quicksort, top-N heapsort) Memory: 27kB (avg), 27kB (max) + -> Sort (actual rows=100 loops=1) + Sort Key: t.a + Sort Method: quicksort Memory: 30kB + -> Seq Scan on t (actual rows=100 loops=1) +(9 rows) + +explain (analyze, costs off, summary off, timing off, format json) +select * from (select * from t order by a) s order by a, b limit 55; + QUERY PLAN +------------------------------------------------------------------- + [ + + { + + "Plan": { + + "Node Type": "Limit", + + "Parallel Aware": false, + + "Actual Rows": 55, + + "Actual Loops": 1, + + "Plans": [ + + { + + "Node Type": "Incremental Sort", + + "Parent Relationship": "Outer", + + "Parallel Aware": false, + + "Actual Rows": 55, + + "Actual Loops": 1, + + "Sort Key": ["t.a", "t.b"], + + "Presorted Key": ["t.a"], + + "Full-sort Groups": { + + "Group Count": 2, + + "Sort Methods Used": ["quicksort", "top-N heapsort"],+ + "Average Sort Space Used": 27, + + "Maximum Sort Space Used": 27, + + "Sort Space Type": "Memory" + + }, + + "Plans": [ + + { + + "Node Type": "Sort", + + "Parent Relationship": "Outer", + + "Parallel Aware": false, + + "Actual Rows": 100, + + "Actual Loops": 1, + + "Sort Key": ["t.a"], + + "Sort Method": "quicksort", + + "Sort Space Used": 30, + + "Sort Space Type": "Memory", + + "Plans": [ + + { + + "Node Type": "Seq Scan", + + "Parent Relationship": "Outer", + + "Parallel Aware": false, + + "Relation Name": "t", + + "Alias": "t", + + "Actual Rows": 100, + + "Actual Loops": 1 + + } + + ] + + } + + ] + + } + + ] + + }, + + "Triggers": [ + + ] + + } + + ] +(1 row) + delete from t; -- An initial small group followed by a large group. insert into t(a, b) select (case when i < 5 then i else 9 end), i from generate_series(1, 100) n(i); @@ -526,6 +602,90 @@ select * from (select * from t order by a) s order by a, b limit 70; 9 | 70 (70 rows) +-- Test EXPLAIN ANALYZE (text output) with both fullsort and presorted groups. +explain (analyze, costs off, summary off, timing off) +select * from (select * from t order by a) s order by a, b limit 70; + QUERY PLAN +--------------------------------------------------------------------------------- + Limit (actual rows=70 loops=1) + -> Incremental Sort (actual rows=70 loops=1) + Sort Key: t.a, t.b + Presorted Key: t.a + Full-sort Groups: 1 (Methods: quicksort) Memory: 28kB (avg), 28kB (max) + Presorted Groups: 5 (Methods: quicksort) Memory: 26kB (avg), 30kB (max) + -> Sort (actual rows=100 loops=1) + Sort Key: t.a + Sort Method: quicksort Memory: 30kB + -> Seq Scan on t (actual rows=100 loops=1) +(10 rows) + +explain (analyze, costs off, summary off, timing off, format json) +select * from (select * from t order by a) s order by a, b limit 70; + QUERY PLAN +--------------------------------------------------- + [ + + { + + "Plan": { + + "Node Type": "Limit", + + "Parallel Aware": false, + + "Actual Rows": 70, + + "Actual Loops": 1, + + "Plans": [ + + { + + "Node Type": "Incremental Sort", + + "Parent Relationship": "Outer", + + "Parallel Aware": false, + + "Actual Rows": 70, + + "Actual Loops": 1, + + "Sort Key": ["t.a", "t.b"], + + "Presorted Key": ["t.a"], + + "Full-sort Groups": { + + "Group Count": 1, + + "Sort Methods Used": ["quicksort"], + + "Average Sort Space Used": 28, + + "Maximum Sort Space Used": 28, + + "Sort Space Type": "Memory" + + }, + + "Presorted Groups": { + + "Group Count": 5, + + "Sort Methods Used": ["quicksort"], + + "Average Sort Space Used": 26, + + "Maximum Sort Space Used": 30, + + "Sort Space Type": "Memory" + + }, + + "Plans": [ + + { + + "Node Type": "Sort", + + "Parent Relationship": "Outer", + + "Parallel Aware": false, + + "Actual Rows": 100, + + "Actual Loops": 1, + + "Sort Key": ["t.a"], + + "Sort Method": "quicksort", + + "Sort Space Used": 30, + + "Sort Space Type": "Memory", + + "Plans": [ + + { + + "Node Type": "Seq Scan", + + "Parent Relationship": "Outer",+ + "Parallel Aware": false, + + "Relation Name": "t", + + "Alias": "t", + + "Actual Rows": 100, + + "Actual Loops": 1 + + } + + ] + + } + + ] + + } + + ] + + }, + + "Triggers": [ + + ] + + } + + ] +(1 row) + delete from t; -- Small groups of 10 tuples each tested around each mode transition point. insert into t(a, b) select i / 10, i from generate_series(1, 70) n(i); diff --git a/src/test/regress/sql/incremental_sort.sql b/src/test/regress/sql/incremental_sort.sql index b9df37412f..9320a10b91 100644 --- a/src/test/regress/sql/incremental_sort.sql +++ b/src/test/regress/sql/incremental_sort.sql @@ -39,12 +39,22 @@ delete from t; insert into t(a, b) select (case when i < 50 then 1 else 2 end), i from generate_series(1, 100) n(i); explain (costs off) select * from (select * from t order by a) s order by a, b limit 55; select * from (select * from t order by a) s order by a, b limit 55; +-- Test EXPLAIN ANALYZE (text output) with only a fullsort group. +explain (analyze, costs off, summary off, timing off) +select * from (select * from t order by a) s order by a, b limit 55; +explain (analyze, costs off, summary off, timing off, format json) +select * from (select * from t order by a) s order by a, b limit 55; delete from t; -- An initial small group followed by a large group. insert into t(a, b) select (case when i < 5 then i else 9 end), i from generate_series(1, 100) n(i); explain (costs off) select * from (select * from t order by a) s order by a, b limit 70; select * from (select * from t order by a) s order by a, b limit 70; +-- Test EXPLAIN ANALYZE (text output) with both fullsort and presorted groups. +explain (analyze, costs off, summary off, timing off) +select * from (select * from t order by a) s order by a, b limit 70; +explain (analyze, costs off, summary off, timing off, format json) +select * from (select * from t order by a) s order by a, b limit 70; delete from t; -- Small groups of 10 tuples each tested around each mode transition point. -- 2.21.1