v5-0005-show-prefetch-stats-for-TidRangeScan.patch
text/x-patch
Filename: v5-0005-show-prefetch-stats-for-TidRangeScan.patch
Type: text/x-patch
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v5-0005
Subject: show prefetch stats for TidRangeScan
| File | + | − |
|---|---|---|
| src/backend/commands/explain.c | 31 | 0 |
| src/backend/executor/execParallel.c | 3 | 0 |
| src/backend/executor/nodeTidrangescan.c | 110 | 8 |
| src/include/executor/instrument_node.h | 11 | 0 |
| src/include/executor/nodeTidrangescan.h | 1 | 0 |
| src/include/nodes/execnodes.h | 1 | 0 |
From 3f53baf68d9dabf7728829055c6d207b2eadc0f1 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Wed, 18 Mar 2026 20:55:00 +0100
Subject: [PATCH v5 5/6] show prefetch stats for TidRangeScan
- enable show_scan_io_usage for TidRangeScan
- add infrastructure to allocate/collect instrumentation from parallel
workers
---
src/backend/commands/explain.c | 31 +++++++
src/backend/executor/execParallel.c | 3 +
src/backend/executor/nodeTidrangescan.c | 118 ++++++++++++++++++++++--
src/include/executor/instrument_node.h | 11 +++
src/include/executor/nodeTidrangescan.h | 1 +
src/include/nodes/execnodes.h | 1 +
6 files changed, 157 insertions(+), 8 deletions(-)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index fe137978e0c..5933993533e 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2152,6 +2152,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
+ show_scan_io_usage((ScanState *) planstate, es);
}
break;
case T_ForeignScan:
@@ -4117,6 +4118,26 @@ show_scan_io_usage(ScanState *planstate, ExplainState *es)
}
}
+ break;
+ }
+ case T_TidRangeScan:
+ {
+ SharedTidRangeScanInstrumentation *sinstrument
+ = ((TidRangeScanState *) planstate)->trss_sinstrument;
+
+ /* collect prefetch statistics from the read stream */
+ stats = planstate->ss_currentScanDesc->rs_instrument->io;
+
+ /* get the sum of the counters set within each and every process */
+ if (sinstrument)
+ {
+ for (int i = 0; i < sinstrument->num_workers; ++i)
+ {
+ TidRangeScanInstrumentation *winstrument = &sinstrument->sinstrument[i];
+ ACCUMULATE_IO_STATS(&stats, &winstrument->stats.io);
+ }
+ }
+
break;
}
default:
@@ -4163,6 +4184,16 @@ show_io_usage(PlanState *planstate, ExplainState *es, int worker)
stats = &instrument->stats.io;
+ break;
+ }
+ case T_TidRangeScan:
+ {
+ TidRangeScanState *state = ((TidRangeScanState *) planstate);
+ SharedTidRangeScanInstrumentation *sinstrument = state->trss_sinstrument;
+ TidRangeScanInstrumentation *instrument = &sinstrument->sinstrument[worker];
+
+ stats = &instrument->stats.io;
+
break;
}
default:
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index ffc708ed6be..62b26bfd18a 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -1122,6 +1122,9 @@ ExecParallelRetrieveInstrumentation(PlanState *planstate,
case T_SeqScanState:
ExecSeqScanRetrieveInstrumentation((SeqScanState *) planstate);
break;
+ case T_TidRangeScanState:
+ ExecTidRangeScanRetrieveInstrumentation((TidRangeScanState *) planstate);
+ break;
default:
break;
}
diff --git a/src/backend/executor/nodeTidrangescan.c b/src/backend/executor/nodeTidrangescan.c
index bacd7aa5bc4..d0bf98654c8 100644
--- a/src/backend/executor/nodeTidrangescan.c
+++ b/src/backend/executor/nodeTidrangescan.c
@@ -243,7 +243,7 @@ TidRangeNext(TidRangeScanState *node)
if (scandesc == NULL)
{
scandesc = table_beginscan_tidrange(node->ss.ss_currentRelation,
- 0,
+ (estate->es_instrument) ? SO_SCAN_INSTRUMENT : 0,
estate->es_snapshot,
&node->trss_mintid,
&node->trss_maxtid);
@@ -341,6 +341,29 @@ ExecEndTidRangeScan(TidRangeScanState *node)
{
TableScanDesc scan = node->ss.ss_currentScanDesc;
+ /*
+ * When ending a parallel worker, copy the statistics gathered by the
+ * worker back into shared memory so that it can be picked up by the main
+ * process to report in EXPLAIN ANALYZE.
+ */
+ if (node->trss_sinstrument != NULL && IsParallelWorker())
+ {
+ TidRangeScanInstrumentation *si;
+
+ Assert(ParallelWorkerNumber < node->trss_sinstrument->num_workers);
+ si = &node->trss_sinstrument->sinstrument[ParallelWorkerNumber];
+
+ /*
+ * Here we accumulate the stats rather than performing memcpy on
+ * node->stats into si. When a Gather/GatherMerge node finishes it
+ * will perform planner shutdown on the workers. On rescan it will
+ * spin up new workers which will have a new state and zeroed stats.
+ */
+
+ /* collect prefetch info for this process from the read_stream */
+ ACCUMULATE_IO_STATS(&si->stats.io, &node->ss.ss_currentScanDesc->rs_instrument->io);
+ }
+
if (scan != NULL)
table_endscan(scan);
}
@@ -434,11 +457,23 @@ void
ExecTidRangeScanEstimate(TidRangeScanState *node, ParallelContext *pcxt)
{
EState *estate = node->ss.ps.state;
+ Size size;
+
+ size = table_parallelscan_estimate(node->ss.ss_currentRelation,
+ estate->es_snapshot);
+ node->trss_pscanlen = size;
+
+ /* make sure the instrumentation is properly aligned */
+ size = MAXALIGN(size);
- node->trss_pscanlen =
- table_parallelscan_estimate(node->ss.ss_currentRelation,
- estate->es_snapshot);
- shm_toc_estimate_chunk(&pcxt->estimator, node->trss_pscanlen);
+ /* account for instrumentation, if required */
+ if (node->ss.ps.instrument && pcxt->nworkers > 0)
+ {
+ size = add_size(size, offsetof(SharedTidRangeScanInstrumentation, sinstrument));
+ size = add_size(size, mul_size(pcxt->nworkers, sizeof(TidRangeScanInstrumentation)));
+ }
+
+ shm_toc_estimate_chunk(&pcxt->estimator, size);
shm_toc_estimate_keys(&pcxt->estimator, 1);
}
@@ -453,15 +488,43 @@ ExecTidRangeScanInitializeDSM(TidRangeScanState *node, ParallelContext *pcxt)
{
EState *estate = node->ss.ps.state;
ParallelTableScanDesc pscan;
+ SharedTidRangeScanInstrumentation *sinstrument = NULL;
+ Size size;
+ char *ptr;
- pscan = shm_toc_allocate(pcxt->toc, node->trss_pscanlen);
+ size = MAXALIGN(node->trss_pscanlen);
+ if (node->ss.ps.instrument && pcxt->nworkers > 0)
+ {
+ size = add_size(size, offsetof(SharedTidRangeScanInstrumentation, sinstrument));
+ size = add_size(size, mul_size(pcxt->nworkers, sizeof(TidRangeScanInstrumentation)));
+ }
+
+ pscan = shm_toc_allocate(pcxt->toc, size);
table_parallelscan_initialize(node->ss.ss_currentRelation,
pscan,
estate->es_snapshot);
shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pscan);
node->ss.ss_currentScanDesc =
table_beginscan_parallel_tidrange(node->ss.ss_currentRelation,
- 0, pscan);
+ (estate->es_instrument) ? SO_SCAN_INSTRUMENT : 0,
+ pscan);
+
+ /* initialize the shared instrumentation (with correct alignment) */
+ ptr = (char *) pscan;
+ ptr += MAXALIGN(node->trss_pscanlen);
+ if (node->ss.ps.instrument && pcxt->nworkers > 0)
+ sinstrument = (SharedTidRangeScanInstrumentation *) ptr;
+
+ if (sinstrument)
+ {
+ sinstrument->num_workers = pcxt->nworkers;
+
+ /* ensure any unfilled slots will contain zeroes */
+ memset(sinstrument->sinstrument, 0,
+ pcxt->nworkers * sizeof(TidRangeScanInstrumentation));
+ }
+
+ node->trss_sinstrument = sinstrument;
}
/* ----------------------------------------------------------------
@@ -490,10 +553,49 @@ void
ExecTidRangeScanInitializeWorker(TidRangeScanState *node,
ParallelWorkerContext *pwcxt)
{
+ EState *estate = node->ss.ps.state;
ParallelTableScanDesc pscan;
+ char *ptr;
+ Size size;
pscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
node->ss.ss_currentScanDesc =
table_beginscan_parallel_tidrange(node->ss.ss_currentRelation,
- 0, pscan);
+ (estate->es_instrument) ? SO_SCAN_INSTRUMENT : 0,
+ pscan);
+
+ /*
+ * Workers don't get the trss_pscanlen value in scan descriptor, so use
+ * the TAM callback again. The result has to match the earlier result in
+ * ExecTidRangeScanEstimate.
+ */
+ size = table_parallelscan_estimate(node->ss.ss_currentRelation,
+ estate->es_snapshot);
+ ptr = (char *) pscan;
+ ptr += MAXALIGN(size);
+
+ if (node->ss.ps.instrument)
+ node->trss_sinstrument = (SharedTidRangeScanInstrumentation *) ptr;
+}
+
+/* ----------------------------------------------------------------
+ * ExecTidRangeScanRetrieveInstrumentation
+ *
+ * Transfer scan statistics from DSM to private memory.
+ * ----------------------------------------------------------------
+ */
+void
+ExecTidRangeScanRetrieveInstrumentation(TidRangeScanState *node)
+{
+ SharedTidRangeScanInstrumentation *sinstrument = node->trss_sinstrument;
+ Size size;
+
+ if (sinstrument == NULL)
+ return;
+
+ size = offsetof(SharedTidRangeScanInstrumentation, sinstrument)
+ + sinstrument->num_workers * sizeof(TidRangeScanInstrumentation);
+
+ node->trss_sinstrument = palloc(size);
+ memcpy(node->trss_sinstrument, sinstrument, size);
}
diff --git a/src/include/executor/instrument_node.h b/src/include/executor/instrument_node.h
index f27cef56d07..5e7237a241b 100644
--- a/src/include/executor/instrument_node.h
+++ b/src/include/executor/instrument_node.h
@@ -104,6 +104,17 @@ typedef struct SharedSeqScanInstrumentation
SeqScanInstrumentation sinstrument[FLEXIBLE_ARRAY_MEMBER];
} SharedSeqScanInstrumentation;
+typedef struct TidRangeScanInstrumentation
+{
+ TableScanInstrumentation stats;
+} TidRangeScanInstrumentation;
+
+typedef struct SharedTidRangeScanInstrumentation
+{
+ int num_workers;
+ TidRangeScanInstrumentation sinstrument[FLEXIBLE_ARRAY_MEMBER];
+} SharedTidRangeScanInstrumentation;
+
typedef struct IndexScanInstrumentation
{
/* Index search count (incremented with pgstat_count_index_scan call) */
diff --git a/src/include/executor/nodeTidrangescan.h b/src/include/executor/nodeTidrangescan.h
index 8752d1ea8c4..4f5dd74e0fc 100644
--- a/src/include/executor/nodeTidrangescan.h
+++ b/src/include/executor/nodeTidrangescan.h
@@ -27,5 +27,6 @@ extern void ExecTidRangeScanEstimate(TidRangeScanState *node, ParallelContext *p
extern void ExecTidRangeScanInitializeDSM(TidRangeScanState *node, ParallelContext *pcxt);
extern void ExecTidRangeScanReInitializeDSM(TidRangeScanState *node, ParallelContext *pcxt);
extern void ExecTidRangeScanInitializeWorker(TidRangeScanState *node, ParallelWorkerContext *pwcxt);
+extern void ExecTidRangeScanRetrieveInstrumentation(TidRangeScanState *node);
#endif /* NODETIDRANGESCAN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 90da865518c..903f8c1160c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1896,6 +1896,7 @@ typedef struct TidRangeScanState
ItemPointerData trss_maxtid;
bool trss_inScan;
Size trss_pscanlen;
+ SharedTidRangeScanInstrumentation *trss_sinstrument;
} TidRangeScanState;
/* ----------------
--
2.53.0