Обсуждение: Add memory/disk usage for WindowAgg nodes in EXPLAIN

Поиск
Список
Период
Сортировка

Add memory/disk usage for WindowAgg nodes in EXPLAIN

От
Tatsuo Ishii
Дата:
1eff8279d4 added memory/disk usage for materialize nodes in EXPLAIN
ANALYZE.
In the commit message:
> There are a few other executor node types that use tuplestores, so we
> could also consider adding these details to the EXPLAIN ANALYZE for
> those nodes too.

So I wanted to Add memory/disk usage for WindowAgg. Patch attached.

Since WindowAgg node could create multiple tuplestore for each Window
partition, we need to track each tuplestore storage usage so that the
maximum storage usage is determined. For this purpose I added new
fields to the WindowAggState.
--
Tatsuo Ishii
SRA OSS LLC
English: http://www.sraoss.co.jp/index_en/
Japanese:http://www.sraoss.co.jp
From 00c7546659e305be45dbeb13a14bcde5066be76f Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Sat, 6 Jul 2024 19:48:30 +0900
Subject: [PATCH v1] Add memory/disk usage for WindowAgg nodes in EXPLAIN.

---
 src/backend/commands/explain.c       | 37 ++++++++++++++++++++++++++++
 src/backend/executor/nodeWindowAgg.c | 19 ++++++++++++++
 src/include/nodes/execnodes.h        |  2 ++
 3 files changed, 58 insertions(+)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1e80fd8b68..177687ea80 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -126,6 +126,7 @@ 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_windowagg_info(WindowAggState *winstate, ExplainState *es);
 static void show_memoize_info(MemoizeState *mstate, List *ancestors,
                               ExplainState *es);
 static void show_hashagg_info(AggState *aggstate, ExplainState *es);
@@ -2215,6 +2216,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
                                            planstate, es);
             show_upper_qual(((WindowAgg *) plan)->runConditionOrig,
                             "Run Condition", planstate, ancestors, es);
+            show_windowagg_info(castNode(WindowAggState, planstate), es);
             break;
         case T_Group:
             show_group_keys(castNode(GroupState, planstate), ancestors, es);
@@ -3362,6 +3364,41 @@ show_material_info(MaterialState *mstate, ExplainState *es)
     }
 }
 
+/*
+ * Show information on WindowAgg node, storage method and maximum memory/disk
+ * space used.
+ */
+static void
+show_windowagg_info(WindowAggState *winstate, ExplainState *es)
+{
+    const char *storageType;
+    int64        spaceUsedKB;
+
+    /*
+     * Nothing to show if ANALYZE option wasn't used or if execution didn't
+     * get as far as creating the tuplestore.
+     */
+    if (!es->analyze || winstate->storageType == NULL)
+        return;
+
+    storageType = winstate->storageType;
+    spaceUsedKB = BYTES_TO_KILOBYTES(winstate->storageSize);
+
+    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.
  */
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..bcfe144511 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -1360,7 +1360,23 @@ release_partition(WindowAggState *winstate)
     }
 
     if (winstate->buffer)
+    {
+        int64    spaceUsed = tuplestore_space_used(winstate->buffer);
+
+        /*
+         * We want to track the max mem/disk usage so that we can use the info
+         * in EXPLAIN (ANALYZE).
+         */
+        if (spaceUsed > winstate->storageSize)
+        {
+            if (winstate->storageType != NULL)
+                pfree(winstate->storageType);
+            winstate->storageType =
+                pstrdup(tuplestore_storage_type_name(winstate->buffer));
+            winstate->storageSize = spaceUsed;
+        }
         tuplestore_end(winstate->buffer);
+    }
     winstate->buffer = NULL;
     winstate->partition_spooled = false;
 }
@@ -2671,6 +2687,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
     winstate->partition_spooled = false;
     winstate->more_partitions = false;
 
+    winstate->storageType = NULL;
+    winstate->storageSize = 0;
+
     return winstate;
 }
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b62c96f206..7a3dfa2bc3 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2567,6 +2567,8 @@ typedef struct WindowAggState
     ExprState  *partEqfunction; /* equality funcs for partition columns */
     ExprState  *ordEqfunction;    /* equality funcs for ordering columns */
     Tuplestorestate *buffer;    /* stores rows of current partition */
+    int64        storageSize;    /* max storage size in buffer */
+    char        *storageType;    /* the storage type above */
     int            current_ptr;    /* read pointer # for current row */
     int            framehead_ptr;    /* read pointer # for frame head, if used */
     int            frametail_ptr;    /* read pointer # for frame tail, if used */
-- 
2.25.1


Re: Add memory/disk usage for WindowAgg nodes in EXPLAIN

От
David Rowley
Дата:
On Sat, 6 Jul 2024 at 23:23, Tatsuo Ishii <ishii@postgresql.org> wrote:
> So I wanted to Add memory/disk usage for WindowAgg. Patch attached.

Thanks for working on that.

> Since WindowAgg node could create multiple tuplestore for each Window
> partition, we need to track each tuplestore storage usage so that the
> maximum storage usage is determined. For this purpose I added new
> fields to the WindowAggState.

I'd recently been looking at the code that recreates the tuplestore
for each partition and thought we could do a bit better. In [1], I
proposed a patch to make this better.

If you based your patch on [1], maybe a better way of doing this is
having tuplestore.c track the maximum space used on disk in an extra
field which is updated with tuplestore_clear().  It's probably ok to
update a new field called maxDiskSpace in tuplestore_clear() if
state->status != TSS_INMEM. If the tuplestore went to disk then an
extra call to BufFileSize() isn't going to be noticeable, even in
cases where we only just went over work_mem. You could then adjust
tuplestore_space_used() to look at maxDiskSpace and return that value
if it's larger than BufFileSize(state->myfile) and state->maxSpace.
You could check if maxDiskSpace == 0 to determine if the tuplestore
has ever gone to disk. tuplestore_storage_type_name() would also need
to check maxDiskSpace and return "Disk" if that's non-zero.

David

[1] https://www.postgresql.org/message-id/CAHoyFK9n-QCXKTUWT_xxtXninSMEv+gbJN66-y6prM3f4WkEHw@mail.gmail.com



Re: Add memory/disk usage for WindowAgg nodes in EXPLAIN

От
Tatsuo Ishii
Дата:
> On Sat, 6 Jul 2024 at 23:23, Tatsuo Ishii <ishii@postgresql.org> wrote:
>> So I wanted to Add memory/disk usage for WindowAgg. Patch attached.
> 
> Thanks for working on that.

Thank you for the infrastructure you created in tuplestore.c and explain.c.

BTW, it seems these executor nodes (other than Materialize and Window
Aggregate node) use tuplestore for their own purpose.

CTE Scan
Recursive Union
Table Function Scan

I have already implemented that for CTE Scan. Do you think other two
nodes are worth to add the information? I think for consistency sake,
it will better to add the info Recursive Union and Table Function
Scan.

>> Since WindowAgg node could create multiple tuplestore for each Window
>> partition, we need to track each tuplestore storage usage so that the
>> maximum storage usage is determined. For this purpose I added new
>> fields to the WindowAggState.
> 
> I'd recently been looking at the code that recreates the tuplestore
> for each partition and thought we could do a bit better. In [1], I
> proposed a patch to make this better.
> 
> If you based your patch on [1], maybe a better way of doing this is
> having tuplestore.c track the maximum space used on disk in an extra
> field which is updated with tuplestore_clear().  It's probably ok to
> update a new field called maxDiskSpace in tuplestore_clear() if
> state->status != TSS_INMEM. If the tuplestore went to disk then an
> extra call to BufFileSize() isn't going to be noticeable, even in
> cases where we only just went over work_mem. You could then adjust
> tuplestore_space_used() to look at maxDiskSpace and return that value
> if it's larger than BufFileSize(state->myfile) and state->maxSpace.
> You could check if maxDiskSpace == 0 to determine if the tuplestore
> has ever gone to disk. tuplestore_storage_type_name() would also need
> to check maxDiskSpace and return "Disk" if that's non-zero.

Thank you for the suggestion. Yes, I noticed [1] and once it is
committed, I will start to study tuplestore.c in this direction.

Best reagards,
--
Tatsuo Ishii
SRA OSS LLC
English: http://www.sraoss.co.jp/index_en/
Japanese:http://www.sraoss.co.jp



Re: Add memory/disk usage for WindowAgg nodes in EXPLAIN

От
David Rowley
Дата:
On Tue, 9 Jul 2024 at 14:44, Tatsuo Ishii <ishii@postgresql.org> wrote:
> BTW, it seems these executor nodes (other than Materialize and Window
> Aggregate node) use tuplestore for their own purpose.
>
> CTE Scan
> Recursive Union
> Table Function Scan
>
> I have already implemented that for CTE Scan. Do you think other two
> nodes are worth to add the information?

Yes, I think so. I'd keep each as a separate patch so they can be
considered independently. Doing all of them should hopefully ensure we
strike the right balance of what code to put in explain.c and what
code to put in tuplestore.c.  I think the WindowAgg's tuplestore usage
pattern might show that the API I picked isn't well suited when a
tuplestore is cleared and refilled over and over.

David



Re: Add memory/disk usage for WindowAgg nodes in EXPLAIN

От
Ashutosh Bapat
Дата:
On Tue, Jul 9, 2024 at 8:20 AM David Rowley <dgrowleyml@gmail.com> wrote:
>
> On Tue, 9 Jul 2024 at 14:44, Tatsuo Ishii <ishii@postgresql.org> wrote:
> > BTW, it seems these executor nodes (other than Materialize and Window
> > Aggregate node) use tuplestore for their own purpose.
> >
> > CTE Scan
> > Recursive Union
> > Table Function Scan
> >
> > I have already implemented that for CTE Scan. Do you think other two
> > nodes are worth to add the information?
>
> Yes, I think so. I'd keep each as a separate patch so they can be
> considered independently. Doing all of them should hopefully ensure we
> strike the right balance of what code to put in explain.c and what
> code to put in tuplestore.c.
+1

+ 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);
+ }

It will be good to move this code to a function which will be called
by show_*_info functions(). We might even convert it into a tuplestore
specific implementation hook after David's work.

--
Best Wishes,
Ashutosh Bapat



Re: Add memory/disk usage for WindowAgg nodes in EXPLAIN

От
Tatsuo Ishii
Дата:
>> Yes, I think so. I'd keep each as a separate patch so they can be
>> considered independently. Doing all of them should hopefully ensure we
>> strike the right balance of what code to put in explain.c and what
>> code to put in tuplestore.c.
> +1
> 
> + 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);
> + }
> 
> It will be good to move this code to a function which will be called
> by show_*_info functions().

I have already implemented that in this direction in my working in
progress patch:

/*
 * Show information regarding storage method and maximum memory/disk space
 * used.
 */
static void
show_storage_info(Tuplestorestate *tupstore, ExplainState *es)

Which can be shared by Material and CTE scan node. I am going to post
it after I take care Recursive Union and Table Function Scan node.

> We might even convert it into a tuplestore
> specific implementation hook after David's work.

Best reagards,
--
Tatsuo Ishii
SRA OSS LLC
English: http://www.sraoss.co.jp/index_en/
Japanese:http://www.sraoss.co.jp



Re: Add memory/disk usage for WindowAgg nodes in EXPLAIN

От
Tatsuo Ishii
Дата:
>>> Yes, I think so. I'd keep each as a separate patch so they can be
>>> considered independently. Doing all of them should hopefully ensure we
>>> strike the right balance of what code to put in explain.c and what
>>> code to put in tuplestore.c.
>> +1
>> 
>> + 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);
>> + }
>> 
>> It will be good to move this code to a function which will be called
>> by show_*_info functions().
> 
> I have already implemented that in this direction in my working in
> progress patch:

Attached are the v2 patches. As suggested by David, I split them
into multiple patches so that each patch implements the feature for
each node. You need to apply the patches in the order of patch number
(if you want to apply all of them, "git apply v2-*.patch" should
work).

v2-0001-Refactor-show_material_info.patch:
This refactors show_material_info(). The guts are moved to new
show_storage_info() so that it can be shared by not only Materialized
node.

v2-0002-Add-memory-disk-usage-for-CTE-Scan-nodes-in-EXPLA.patch:
This adds memory/disk usage for CTE Scan nodes in EXPLAIN (ANALYZE) command.

v2-0003-Add-memory-disk-usage-for-Table-Function-Scan-nod.patch:
This adds memory/disk usage for Table Function Scan nodes in EXPLAIN (ANALYZE) command.

v2-0004-Add-memory-disk-usage-for-Recursive-Union-nodes-i.patch:
This adds memory/disk usage for Recursive Union nodes in EXPLAIN
(ANALYZE) command. Also show_storage_info() is changed so that it
accepts int64 storage_used, char *storage_type arguments. They are
used if the target node uses multiple tuplestores, in case a simple
call to tuplestore_space_used() does not work. Such executor nodes
need to collect storage_used while running the node. This type of node
includes Recursive Union and Window Aggregate.

v2-0005-Add-memory-disk-usage-for-Window-Aggregate-nodes-.patch: This
adds memory/disk usage for Window Aggregate nodes in EXPLAIN (ANALYZE)
command. Note that if David's proposal
https://www.postgresql.org/message-id/CAHoyFK9n-QCXKTUWT_xxtXninSMEv%2BgbJN66-y6prM3f4WkEHw%40mail.gmail.com
is committed, this will need to be adjusted.

For a demonstration, how storage/memory usage is shown in EXPLAIN
(notice "Storage: Memory Maximum Storage: 120kB" etc.). The script
used is attached (test.sql.txt). The SQLs are shamelessly copied from
David's example and the regression test (some of them were modified by
me).

EXPLAIN (ANALYZE, COSTS OFF)
SELECT count(t1.b) FROM (VALUES(1),(2)) t2(x) LEFT JOIN (SELECT * FROM t1 WHERE a <= 100) t1 ON TRUE;
                                   QUERY PLAN                                    
---------------------------------------------------------------------------------
 Aggregate (actual time=0.345..0.346 rows=1 loops=1)
   ->  Nested Loop Left Join (actual time=0.015..0.330 rows=200 loops=1)
         ->  Values Scan on "*VALUES*" (actual time=0.001..0.003 rows=2 loops=1)
         ->  Materialize (actual time=0.006..0.152 rows=100 loops=2)
               Storage: Memory  Maximum Storage: 120kB
               ->  Seq Scan on t1 (actual time=0.007..0.213 rows=100 loops=1)
                     Filter: (a <= 100)
                     Rows Removed by Filter: 900
 Planning Time: 0.202 ms
 Execution Time: 0.377 ms
(10 rows)

-- CTE Scan node
EXPLAIN (ANALYZE, COSTS OFF)
WITH RECURSIVE t(n) AS (
    VALUES (1)
UNION ALL
    SELECT n+1 FROM t WHERE n < 100
)
SELECT sum(n) OVER() FROM t;
                                    QUERY PLAN                                     
-----------------------------------------------------------------------------------
 WindowAgg (actual time=0.151..0.169 rows=100 loops=1)
   Storage: Memory  Maximum Storage: 20kB
   CTE t
     ->  Recursive Union (actual time=0.001..0.105 rows=100 loops=1)
           Storage: Memory  Maximum Storage: 17kB
           ->  Result (actual time=0.001..0.001 rows=1 loops=1)
           ->  WorkTable Scan on t t_1 (actual time=0.000..0.000 rows=1 loops=100)
                 Filter: (n < 100)
                 Rows Removed by Filter: 0
   ->  CTE Scan on t (actual time=0.002..0.127 rows=100 loops=1)
         Storage: Memory  Maximum Storage: 20kB
 Planning Time: 0.053 ms
 Execution Time: 0.192 ms
(13 rows)

-- Table Function Scan node
CREATE OR REPLACE VIEW public.jsonb_table_view6 AS
 SELECT js2,
    jsb2w,
    jsb2q,
    ia,
    ta,
    jba
   FROM JSON_TABLE(
            'null'::jsonb, '$[*]' AS json_table_path_0
            PASSING
                1 + 2 AS a,
                '"foo"'::json AS "b c"
            COLUMNS (
                js2 json PATH '$' WITHOUT WRAPPER KEEP QUOTES,
                jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER KEEP QUOTES,
                jsb2q jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES,
                ia integer[] PATH '$' WITHOUT WRAPPER KEEP QUOTES,
                ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES,
                jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES
            )
        );
CREATE VIEW
EXPLAIN (ANALYZE, COSTS OFF) SELECT * FROM jsonb_table_view6;
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Table Function Scan on "json_table" (actual time=0.024..0.025 rows=1 loops=1)
   Storage: Memory  Maximum Storage: 17kB
 Planning Time: 0.100 ms
 Execution Time: 0.054 ms
(4 rows)

From 1d2f67dff48601f92b5378838c0e908d200d4493 Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Wed, 10 Jul 2024 14:02:13 +0900
Subject: [PATCH v2 1/5] Refactor show_material_info().

---
 src/backend/commands/explain.c | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 118db12903..43ef33295e 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_storage_info(Tuplestorestate *tupstore, ExplainState *es);
 static void show_material_info(MaterialState *mstate, ExplainState *es);
 static void show_memoize_info(MemoizeState *mstate, List *ancestors,
                               ExplainState *es);
@@ -3326,13 +3327,11 @@ show_hash_info(HashState *hashstate, ExplainState *es)
 }
 
 /*
- * Show information on material node, storage method and maximum memory/disk
- * space used.
+ * Show information on storage method and maximum memory/disk space used.
  */
 static void
-show_material_info(MaterialState *mstate, ExplainState *es)
+show_storage_info(Tuplestorestate *tupstore, ExplainState *es)
 {
-    Tuplestorestate *tupstore = mstate->tuplestorestate;
     const char *storageType;
     int64        spaceUsedKB;
 
@@ -3361,6 +3360,18 @@ show_material_info(MaterialState *mstate, 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 = mstate->tuplestorestate;
+
+    show_storage_info(tupstore, es);
+}
+
 /*
  * Show information on memoize hits/misses/evictions and memory usage.
  */
-- 
2.25.1

From e4eb2288c90e92a42ba85c672d477b1a93ebf6f8 Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Wed, 10 Jul 2024 14:09:46 +0900
Subject: [PATCH v2 2/5] Add memory/disk usage for CTE Scan nodes in EXPLAIN

---
 src/backend/commands/explain.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 43ef33295e..661e9101cb 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -127,6 +127,7 @@ static void show_incremental_sort_info(IncrementalSortState *incrsortstate,
 static void show_hash_info(HashState *hashstate, ExplainState *es);
 static void show_storage_info(Tuplestorestate *tupstore, ExplainState *es);
 static void show_material_info(MaterialState *mstate, ExplainState *es);
+static void show_ctescan_info(CteScanState *ctescanstate, ExplainState *es);
 static void show_memoize_info(MemoizeState *mstate, List *ancestors,
                               ExplainState *es);
 static void show_hashagg_info(AggState *aggstate, ExplainState *es);
@@ -2028,6 +2029,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
             if (plan->qual)
                 show_instrumentation_count("Rows Removed by Filter", 1,
                                            planstate, es);
+            if (IsA(plan, CteScan))
+                show_ctescan_info(castNode(CteScanState, planstate), es);
             break;
         case T_Gather:
             {
@@ -3372,6 +3375,18 @@ show_material_info(MaterialState *mstate, ExplainState *es)
     show_storage_info(tupstore, es);
 }
 
+/*
+ * Show information on CTE Scan node, storage method and maximum memory/disk
+ * space used.
+ */
+static void
+show_ctescan_info(CteScanState *ctescanstate, ExplainState *es)
+{
+    Tuplestorestate *tupstore = ctescanstate->leader->cte_table;
+
+    show_storage_info(tupstore, es);
+}
+
 /*
  * Show information on memoize hits/misses/evictions and memory usage.
  */
-- 
2.25.1

From a7bfaf711b5a1ecb4fe58d3b46db1911383dbfb8 Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Wed, 10 Jul 2024 14:32:59 +0900
Subject: [PATCH v2 3/5] Add memory/disk usage for Table Function Scan nodes in
 EXPLAIN

---
 src/backend/commands/explain.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 661e9101cb..1a4e83ad38 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -128,6 +128,7 @@ static void show_hash_info(HashState *hashstate, ExplainState *es);
 static void show_storage_info(Tuplestorestate *tupstore, ExplainState *es);
 static void show_material_info(MaterialState *mstate, ExplainState *es);
 static void show_ctescan_info(CteScanState *ctescanstate, ExplainState *es);
+static void show_table_func_can_info(TableFuncScanState *tscanstate, ExplainState *es);
 static void show_memoize_info(MemoizeState *mstate, List *ancestors,
                               ExplainState *es);
 static void show_hashagg_info(AggState *aggstate, ExplainState *es);
@@ -2112,6 +2113,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
             if (plan->qual)
                 show_instrumentation_count("Rows Removed by Filter", 1,
                                            planstate, es);
+            show_table_func_can_info(castNode(TableFuncScanState, planstate), es);
             break;
         case T_TidScan:
             {
@@ -3387,6 +3389,18 @@ show_ctescan_info(CteScanState *ctescanstate, ExplainState *es)
     show_storage_info(tupstore, es);
 }
 
+/*
+ * Show information on Table Function Scan node, storage method and maximum
+ * memory/disk space used.
+ */
+static void
+show_table_func_can_info(TableFuncScanState *tscanstate, ExplainState *es)
+{
+    Tuplestorestate *tupstore = tscanstate->tupstore;
+
+    show_storage_info(tupstore, es);
+}
+
 /*
  * Show information on memoize hits/misses/evictions and memory usage.
  */
-- 
2.25.1

From b03264d55d0c23885fe9ce4f9d93a6ca65d45261 Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Wed, 10 Jul 2024 16:06:59 +0900
Subject: [PATCH v2 4/5] Add memory/disk usage for Recursive Union nodes in
 EXPLAIN

---
 src/backend/commands/explain.c            | 47 +++++++++++++++++------
 src/backend/executor/nodeRecursiveunion.c | 30 +++++++++++++++
 src/include/nodes/execnodes.h             |  2 +
 3 files changed, 68 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1a4e83ad38..054d909093 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -125,10 +125,12 @@ 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_storage_info(Tuplestorestate *tupstore, ExplainState *es);
+static void show_storage_info(Tuplestorestate *tupstore, int64 storage_used, char *storage_type,
+                              ExplainState *es);
 static void show_material_info(MaterialState *mstate, ExplainState *es);
 static void show_ctescan_info(CteScanState *ctescanstate, ExplainState *es);
 static void show_table_func_can_info(TableFuncScanState *tscanstate, ExplainState *es);
+static void show_recursive_union_info(RecursiveUnionState *rstate, ExplainState *es);
 static void show_memoize_info(MemoizeState *mstate, List *ancestors,
                               ExplainState *es);
 static void show_hashagg_info(AggState *aggstate, ExplainState *es);
@@ -2264,6 +2266,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
             show_memoize_info(castNode(MemoizeState, planstate), ancestors,
                               es);
             break;
+        case T_RecursiveUnion:
+            show_recursive_union_info(castNode(RecursiveUnionState, planstate), es);
+            break;
         default:
             break;
     }
@@ -3332,23 +3337,32 @@ show_hash_info(HashState *hashstate, ExplainState *es)
 }
 
 /*
- * Show information on storage method and maximum memory/disk space used.
+ * Show information on storage method and maximum memory/disk space used.  If
+ * tupstore is NULL, then storage_used and storage_type are used instead.
  */
 static void
-show_storage_info(Tuplestorestate *tupstore, ExplainState *es)
+show_storage_info(Tuplestorestate *tupstore, int64 storage_used, char *storage_type,
+                  ExplainState *es)
 {
     const char *storageType;
     int64        spaceUsedKB;
 
     /*
-     * Nothing to show if ANALYZE option wasn't used or if execution didn't
-     * get as far as creating the tuplestore.
+     * Nothing to show if ANALYZE option wasn't used.
      */
-    if (!es->analyze || tupstore == NULL)
+    if (!es->analyze)
         return;
 
-    storageType = tuplestore_storage_type_name(tupstore);
-    spaceUsedKB = BYTES_TO_KILOBYTES(tuplestore_space_used(tupstore));
+    if (tupstore != NULL)
+    {
+        storageType = tuplestore_storage_type_name(tupstore);
+        spaceUsedKB = BYTES_TO_KILOBYTES(tuplestore_space_used(tupstore));
+    }
+    else
+    {
+        storageType = storage_type;
+        spaceUsedKB = BYTES_TO_KILOBYTES(storage_used);
+    }
 
     if (es->format != EXPLAIN_FORMAT_TEXT)
     {
@@ -3374,7 +3388,7 @@ show_material_info(MaterialState *mstate, ExplainState *es)
 {
     Tuplestorestate *tupstore = mstate->tuplestorestate;
 
-    show_storage_info(tupstore, es);
+    show_storage_info(tupstore, 0, NULL, es);
 }
 
 /*
@@ -3386,7 +3400,7 @@ show_ctescan_info(CteScanState *ctescanstate, ExplainState *es)
 {
     Tuplestorestate *tupstore = ctescanstate->leader->cte_table;
 
-    show_storage_info(tupstore, es);
+    show_storage_info(tupstore, 0, NULL, es);
 }
 
 /*
@@ -3398,7 +3412,18 @@ show_table_func_can_info(TableFuncScanState *tscanstate, ExplainState *es)
 {
     Tuplestorestate *tupstore = tscanstate->tupstore;
 
-    show_storage_info(tupstore, es);
+    show_storage_info(tupstore, 0, NULL, es);
+}
+
+/*
+ * Show information on Recursive Union node, storage method and maximum
+ * memory/disk space used.
+ */
+
+static void
+show_recursive_union_info(RecursiveUnionState *rstate, ExplainState *es)
+{
+    show_storage_info(NULL, rstate->storageSize, rstate->storageType, es);
 }
 
 /*
diff --git a/src/backend/executor/nodeRecursiveunion.c b/src/backend/executor/nodeRecursiveunion.c
index c7f8a19fa4..8667b7ca93 100644
--- a/src/backend/executor/nodeRecursiveunion.c
+++ b/src/backend/executor/nodeRecursiveunion.c
@@ -52,6 +52,33 @@ build_hash_table(RecursiveUnionState *rustate)
                                                 false);
 }
 
+/*
+ * Track the maximum memory/disk usage in working_table and
+ * intermediate_table.  Supposed to be called just before tuplestore_end.
+ */
+static void
+track_storage_usage(RecursiveUnionState *state, Tuplestorestate *tup)
+{
+    int64    spaceUsed;
+
+    if (tup == NULL)
+        return;
+
+    spaceUsed = tuplestore_space_used(tup);
+
+    /*
+     * We want to track the maximum mem/disk usage so that we can use the info
+     * in EXPLAIN (ANALYZE).
+     */
+    if (spaceUsed > state->storageSize)
+    {
+        if (state->storageType != NULL)
+            pfree(state->storageType);
+        state->storageType =
+            pstrdup(tuplestore_storage_type_name(tup));
+        state->storageSize = spaceUsed;
+    }
+}
 
 /* ----------------------------------------------------------------
  *        ExecRecursiveUnion(node)
@@ -120,6 +147,7 @@ ExecRecursiveUnion(PlanState *pstate)
                 break;
 
             /* done with old working table ... */
+            track_storage_usage(node, node->working_table);
             tuplestore_end(node->working_table);
 
             /* intermediate table becomes working table */
@@ -191,6 +219,8 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags)
     rustate->intermediate_empty = true;
     rustate->working_table = tuplestore_begin_heap(false, false, work_mem);
     rustate->intermediate_table = tuplestore_begin_heap(false, false, work_mem);
+    rustate->storageSize = 0;
+    rustate->storageType = NULL;
 
     /*
      * If hashing, we need a per-tuple memory context for comparisons, and a
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cac684d9b3..bc2c0baed6 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1509,6 +1509,8 @@ typedef struct RecursiveUnionState
     bool        intermediate_empty;
     Tuplestorestate *working_table;
     Tuplestorestate *intermediate_table;
+    int64        storageSize;    /* max storage size Tuplestore */
+    char        *storageType;    /* the storage type above */
     /* Remaining fields are unused in UNION ALL case */
     Oid           *eqfuncoids;        /* per-grouping-field equality fns */
     FmgrInfo   *hashfunctions;    /* per-grouping-field hash fns */
-- 
2.25.1

From b61edc12210eb71c8a7a987171e8bbf39299989c Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Wed, 10 Jul 2024 16:22:41 +0900
Subject: [PATCH v2 5/5] Add memory/disk usage for Window Aggregate nodes in
 EXPLAIN

---
 src/backend/commands/explain.c       | 12 ++++++++++++
 src/backend/executor/nodeWindowAgg.c | 19 +++++++++++++++++++
 src/include/nodes/execnodes.h        |  2 ++
 3 files changed, 33 insertions(+)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 054d909093..5f32c967e1 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -131,6 +131,7 @@ static void show_material_info(MaterialState *mstate, ExplainState *es);
 static void show_ctescan_info(CteScanState *ctescanstate, ExplainState *es);
 static void show_table_func_can_info(TableFuncScanState *tscanstate, ExplainState *es);
 static void show_recursive_union_info(RecursiveUnionState *rstate, ExplainState *es);
+static void show_windowagg_info(WindowAggState *winstate, ExplainState *es);
 static void show_memoize_info(MemoizeState *mstate, List *ancestors,
                               ExplainState *es);
 static void show_hashagg_info(AggState *aggstate, ExplainState *es);
@@ -2222,6 +2223,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
                                            planstate, es);
             show_upper_qual(((WindowAgg *) plan)->runConditionOrig,
                             "Run Condition", planstate, ancestors, es);
+            show_windowagg_info(castNode(WindowAggState, planstate), es);
             break;
         case T_Group:
             show_group_keys(castNode(GroupState, planstate), ancestors, es);
@@ -3426,6 +3428,16 @@ show_recursive_union_info(RecursiveUnionState *rstate, ExplainState *es)
     show_storage_info(NULL, rstate->storageSize, rstate->storageType, es);
 }
 
+/*
+ * Show information on WindowAgg node, storage method and maximum memory/disk
+ * space used.
+ */
+static void
+show_windowagg_info(WindowAggState *winstate, ExplainState *es)
+{
+    show_storage_info(NULL, winstate->storageSize, winstate->storageType, es);
+}
+
 /*
  * Show information on memoize hits/misses/evictions and memory usage.
  */
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..bcfe144511 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -1360,7 +1360,23 @@ release_partition(WindowAggState *winstate)
     }
 
     if (winstate->buffer)
+    {
+        int64    spaceUsed = tuplestore_space_used(winstate->buffer);
+
+        /*
+         * We want to track the max mem/disk usage so that we can use the info
+         * in EXPLAIN (ANALYZE).
+         */
+        if (spaceUsed > winstate->storageSize)
+        {
+            if (winstate->storageType != NULL)
+                pfree(winstate->storageType);
+            winstate->storageType =
+                pstrdup(tuplestore_storage_type_name(winstate->buffer));
+            winstate->storageSize = spaceUsed;
+        }
         tuplestore_end(winstate->buffer);
+    }
     winstate->buffer = NULL;
     winstate->partition_spooled = false;
 }
@@ -2671,6 +2687,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
     winstate->partition_spooled = false;
     winstate->more_partitions = false;
 
+    winstate->storageType = NULL;
+    winstate->storageSize = 0;
+
     return winstate;
 }
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index bc2c0baed6..82a597aae9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2596,6 +2596,8 @@ typedef struct WindowAggState
     ExprState  *partEqfunction; /* equality funcs for partition columns */
     ExprState  *ordEqfunction;    /* equality funcs for ordering columns */
     Tuplestorestate *buffer;    /* stores rows of current partition */
+    int64        storageSize;    /* max storage size in buffer */
+    char        *storageType;    /* the storage type above */
     int            current_ptr;    /* read pointer # for current row */
     int            framehead_ptr;    /* read pointer # for frame head, if used */
     int            frametail_ptr;    /* read pointer # for frame tail, if used */
-- 
2.25.1

-- Mateialize node
DROP TABLE t1;
CREATE TABLE t1 (a INT, b TEXT);
INSERT INTO t1 SELECT x,repeat('a',1024) from generate_series(1,1000)x;
CREATE INDEX ON t1(a);
EXPLAIN (ANALYZE, COSTS OFF)
SELECT count(t1.b) FROM (VALUES(1),(2)) t2(x) LEFT JOIN (SELECT * FROM t1 WHERE a <= 100) t1 ON TRUE;

-- CTE Scan node
EXPLAIN (ANALYZE, COSTS OFF)
WITH RECURSIVE t(n) AS (
    VALUES (1)
UNION ALL
    SELECT n+1 FROM t WHERE n < 100
)
SELECT sum(n) OVER() FROM t;

-- Table Function Scan node
CREATE OR REPLACE VIEW public.jsonb_table_view6 AS
 SELECT js2,
    jsb2w,
    jsb2q,
    ia,
    ta,
    jba
   FROM JSON_TABLE(
            'null'::jsonb, '$[*]' AS json_table_path_0
            PASSING
                1 + 2 AS a,
                '"foo"'::json AS "b c"
            COLUMNS (
                js2 json PATH '$' WITHOUT WRAPPER KEEP QUOTES,
                jsb2w jsonb PATH '$' WITH UNCONDITIONAL WRAPPER KEEP QUOTES,
                jsb2q jsonb PATH '$' WITHOUT WRAPPER OMIT QUOTES,
                ia integer[] PATH '$' WITHOUT WRAPPER KEEP QUOTES,
                ta text[] PATH '$' WITHOUT WRAPPER KEEP QUOTES,
                jba jsonb[] PATH '$' WITHOUT WRAPPER KEEP QUOTES
            )
        );

EXPLAIN (ANALYZE, COSTS OFF) SELECT * FROM jsonb_table_view6;