v1-0001-Add-temp-table-monitoring-columns-to-pg_stat_data.patch
application/octet-stream
Filename: v1-0001-Add-temp-table-monitoring-columns-to-pg_stat_data.patch
Type: application/octet-stream
Part: 0
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 v1-0001
Subject: Add temp table monitoring columns to pg_stat_database
| File | + | − |
|---|---|---|
| doc/src/sgml/monitoring.sgml | 49 | 0 |
| src/backend/catalog/heap.c | 6 | 0 |
| src/backend/catalog/system_views.sql | 5 | 0 |
| src/backend/storage/buffer/bufmgr.c | 10 | 0 |
| src/backend/storage/buffer/localbuf.c | 7 | 0 |
| src/backend/utils/activity/pgstat_database.c | 80 | 0 |
| src/backend/utils/adt/pgstatfuncs.c | 15 | 0 |
| src/include/catalog/catversion.h | 1 | 1 |
| src/include/catalog/pg_proc.dat | 25 | 0 |
| src/include/pgstat.h | 10 | 0 |
| src/test/regress/expected/rules.out | 5 | 0 |
From c314143f4eb8a4daf08be01bba3e313b4fcf138d Mon Sep 17 00:00:00 2001
From: Mohamed Ali <75553212+mmohali@users.noreply.github.com>
Date: Thu, 7 May 2026 12:33:37 -0700
Subject: [PATCH v1] Add temp table monitoring columns to pg_stat_database
Add visibility into temporary table usage and I/O at the database
level. Currently pg_stat_database tracks temp_files and temp_bytes
for temporary files created during query execution (sorts, hashes),
but provides no metrics for temporary tables created with CREATE
TEMP TABLE.
pg_stat_statements already tracks local_blks_* metrics per-query,
but there is no database-level aggregation. This patch fills that
gap by adding the following columns to pg_stat_database:
temp_tables - number of temporary tables created
local_blks_hit - local buffer cache hits
local_blks_read - local blocks read from disk
local_blks_dirtied - local blocks dirtied
local_blks_written - local blocks written to disk
Implementation:
- Track temp table creation in heap_create_with_catalog()
- Track I/O operations in bufmgr.c (reads, hits) and localbuf.c
(writes, dirtied)
- Add counting functions in pgstat_database.c with proper guards
for bootstrap and invalid database OID
- Add accessor functions using PG_STAT_GET_DBENTRY_INT64 macro
- Register 5 new system functions
- Update pg_stat_database view and documentation
This enables DBAs to monitor temp table disk I/O, calculate cache
hit ratios, and identify undersized temp_buffers at the database
level.
---
doc/src/sgml/monitoring.sgml | 49 ++++++++++++
src/backend/catalog/heap.c | 6 ++
src/backend/catalog/system_views.sql | 5 ++
src/backend/storage/buffer/bufmgr.c | 10 +++
src/backend/storage/buffer/localbuf.c | 7 ++
src/backend/utils/activity/pgstat_database.c | 80 ++++++++++++++++++++
src/backend/utils/adt/pgstatfuncs.c | 15 ++++
src/include/catalog/catversion.h | 2 +-
src/include/catalog/pg_proc.dat | 25 ++++++
src/include/pgstat.h | 10 +++
src/test/regress/expected/rules.out | 5 ++
11 files changed, 213 insertions(+), 1 deletion(-)
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index cc014564c97..f4258ea487f 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -3766,6 +3766,55 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>temp_tables</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of temporary tables created in this database.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>local_blks_hit</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of times temporary table blocks were found already in the
+ local buffer cache, so that a read was not necessary (this only
+ includes hits in the local buffer cache, whose size is controlled
+ by <xref linkend="guc-temp-buffers"/>, not the operating system's
+ file system cache).
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>local_blks_read</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of temporary table blocks read from disk in this database.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>local_blks_dirtied</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of temporary table blocks dirtied in this database.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>local_blks_written</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of temporary table blocks written to disk in this database.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>deadlocks</structfield> <type>bigint</type>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 5748aa9a1a9..39221dcd388 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1520,6 +1520,12 @@ heap_create_with_catalog(const char *relname,
if (oncommit != ONCOMMIT_NOOP)
register_on_commit_action(relid, oncommit);
+ /*
+ * Track temporary table statistics.
+ */
+ if (relpersistence == RELPERSISTENCE_TEMP && relkind == RELKIND_RELATION)
+ pgstat_count_temp_table(MyDatabaseId);
+
/*
* ok, the relation has been cataloged, so close our relations and return
* the OID of the newly created relation.
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 339c016e510..20371822bf9 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1133,6 +1133,11 @@ CREATE VIEW pg_stat_database AS
pg_stat_get_db_conflict_all(D.oid) AS conflicts,
pg_stat_get_db_temp_files(D.oid) AS temp_files,
pg_stat_get_db_temp_bytes(D.oid) AS temp_bytes,
+ pg_stat_get_db_temp_tables(D.oid) AS temp_tables,
+ pg_stat_get_db_local_blks_hit(D.oid) AS local_blks_hit,
+ pg_stat_get_db_local_blks_read(D.oid) AS local_blks_read,
+ pg_stat_get_db_local_blks_dirtied(D.oid) AS local_blks_dirtied,
+ pg_stat_get_db_local_blks_written(D.oid) AS local_blks_written,
pg_stat_get_db_deadlocks(D.oid) AS deadlocks,
pg_stat_get_db_checksum_failures(D.oid) AS checksum_failures,
pg_stat_get_db_checksum_last_failure(D.oid) AS checksum_last_failure,
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5f3d083e938..78c1db9c318 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -828,6 +828,7 @@ ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum, BlockNumber blockN
PinLocalBuffer(bufHdr, true);
pgBufferUsage.local_blks_hit++;
+ pgstat_count_local_blk_hit(MyDatabaseId);
return true;
}
@@ -1249,7 +1250,10 @@ PinBufferForBlock(Relation rel,
{
bufHdr = LocalBufferAlloc(smgr, forkNum, blockNum, foundPtr);
if (*foundPtr)
+ {
pgBufferUsage.local_blks_hit++;
+ pgstat_count_local_blk_hit(MyDatabaseId);
+ }
}
else
{
@@ -1990,7 +1994,10 @@ AsyncReadBuffers(ReadBuffersOperation *operation, int *nblocks_progress)
true);
if (persistence == RELPERSISTENCE_TEMP)
+ {
pgBufferUsage.local_blks_hit += 1;
+ pgstat_count_local_blk_hit(MyDatabaseId);
+ }
else
pgBufferUsage.shared_blks_hit += 1;
@@ -2060,7 +2067,10 @@ AsyncReadBuffers(ReadBuffersOperation *operation, int *nblocks_progress)
io_start, 1, io_buffers_len * BLCKSZ);
if (persistence == RELPERSISTENCE_TEMP)
+ {
pgBufferUsage.local_blks_read += io_buffers_len;
+ pgstat_count_local_blk_read(MyDatabaseId, io_buffers_len);
+ }
else
pgBufferUsage.shared_blks_read += io_buffers_len;
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 04a540379a2..643936e3cf4 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -218,6 +218,9 @@ FlushLocalBuffer(BufferDesc *bufHdr, SMgrRelation reln)
TerminateLocalBufferIO(bufHdr, true, 0, false);
pgBufferUsage.local_blks_written++;
+
+ /* Track at database level */
+ pgstat_count_local_blk_written(MyDatabaseId, 1);
}
static Buffer
@@ -479,6 +482,7 @@ ExtendBufferedRelLocal(BufferManagerRelation bmr,
*extended_by = extend_by;
pgBufferUsage.local_blks_written += extend_by;
+ pgstat_count_local_blk_written(MyDatabaseId, extend_by);
return first_block;
}
@@ -509,7 +513,10 @@ MarkLocalBufferDirty(Buffer buffer)
buf_state = pg_atomic_read_u64(&bufHdr->state);
if (!(buf_state & BM_DIRTY))
+ {
pgBufferUsage.local_blks_dirtied++;
+ pgstat_count_local_blk_dirtied(MyDatabaseId);
+ }
buf_state |= BM_DIRTY;
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index 933dcb5cae5..c499459f3b7 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -143,6 +143,81 @@ pgstat_report_deadlock(void)
dbent->deadlocks++;
}
+/*
+ * Report creation of a temporary table.
+ */
+void
+pgstat_count_temp_table(Oid dboid)
+{
+ PgStat_StatDBEntry *dbent;
+
+ if (!pgstat_track_counts)
+ return;
+
+ dbent = pgstat_prep_database_pending(dboid);
+ dbent->temp_tables++;
+}
+
+/*
+ * Report local buffer (temp table) write operations.
+ */
+void
+pgstat_count_local_blk_written(Oid dboid, int64 count)
+{
+ PgStat_StatDBEntry *dbent;
+
+ if (!pgstat_track_counts || !OidIsValid(dboid) || !IsUnderPostmaster)
+ return;
+
+ dbent = pgstat_prep_database_pending(dboid);
+ dbent->local_blks_written += count;
+}
+
+/*
+ * Report local buffer (temp table) read operations.
+ */
+void
+pgstat_count_local_blk_read(Oid dboid, int64 count)
+{
+ PgStat_StatDBEntry *dbent;
+
+ if (!pgstat_track_counts || !OidIsValid(dboid) || !IsUnderPostmaster)
+ return;
+
+ dbent = pgstat_prep_database_pending(dboid);
+ dbent->local_blks_read += count;
+}
+
+/*
+ * Report local buffer (temp table) cache hit.
+ */
+void
+pgstat_count_local_blk_hit(Oid dboid)
+{
+ PgStat_StatDBEntry *dbent;
+
+ if (!pgstat_track_counts || !OidIsValid(dboid) || !IsUnderPostmaster)
+ return;
+
+ dbent = pgstat_prep_database_pending(dboid);
+ dbent->local_blks_hit++;
+}
+
+/*
+ * Report local buffer (temp table) dirtied.
+ */
+void
+pgstat_count_local_blk_dirtied(Oid dboid)
+{
+ PgStat_StatDBEntry *dbent;
+
+ if (!pgstat_track_counts || !OidIsValid(dboid) || !IsUnderPostmaster)
+ return;
+
+ dbent = pgstat_prep_database_pending(dboid);
+ dbent->local_blks_dirtied++;
+}
+
/*
* Allow this backend to later report checksum failures for dboid, even if in
* a critical section at the time of the report.
@@ -472,6 +547,11 @@ pgstat_database_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
PGSTAT_ACCUM_DBCOUNT(temp_bytes);
PGSTAT_ACCUM_DBCOUNT(temp_files);
+ PGSTAT_ACCUM_DBCOUNT(temp_tables);
+ PGSTAT_ACCUM_DBCOUNT(local_blks_hit);
+ PGSTAT_ACCUM_DBCOUNT(local_blks_read);
+ PGSTAT_ACCUM_DBCOUNT(local_blks_dirtied);
+ PGSTAT_ACCUM_DBCOUNT(local_blks_written);
PGSTAT_ACCUM_DBCOUNT(deadlocks);
/* checksum failures are reported immediately */
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 5ac022274a7..a35be46e217 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1110,6 +1110,21 @@ PG_STAT_GET_DBENTRY_INT64(temp_bytes)
/* pg_stat_get_db_temp_files */
PG_STAT_GET_DBENTRY_INT64(temp_files)
+/* pg_stat_get_db_temp_tables */
+PG_STAT_GET_DBENTRY_INT64(temp_tables)
+
+/* pg_stat_get_db_local_blks_hit */
+PG_STAT_GET_DBENTRY_INT64(local_blks_hit)
+
+/* pg_stat_get_db_local_blks_read */
+PG_STAT_GET_DBENTRY_INT64(local_blks_read)
+
+/* pg_stat_get_db_local_blks_dirtied */
+PG_STAT_GET_DBENTRY_INT64(local_blks_dirtied)
+
+/* pg_stat_get_db_local_blks_written */
+PG_STAT_GET_DBENTRY_INT64(local_blks_written)
+
/* pg_stat_get_db_tuples_deleted */
PG_STAT_GET_DBENTRY_INT64(tuples_deleted)
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 90f46b03502..59bebd8b079 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -57,6 +57,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202603101
+#define CATALOG_VERSION_NO 202603106
#endif
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 361e2cfffeb..e609d90d99b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5898,6 +5898,31 @@
proname => 'pg_stat_get_db_temp_bytes', provolatile => 's',
proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
prosrc => 'pg_stat_get_db_temp_bytes' },
+{ oid => '9950',
+ descr => 'statistics: number of temporary tables created',
+ proname => 'pg_stat_get_db_temp_tables', provolatile => 's',
+ proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+ prosrc => 'pg_stat_get_db_temp_tables' },
+{ oid => '9951',
+ descr => 'statistics: temp table blocks written to disk',
+ proname => 'pg_stat_get_db_local_blks_written', provolatile => 's',
+ proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+ prosrc => 'pg_stat_get_db_local_blks_written' },
+{ oid => '9952',
+ descr => 'statistics: temp table blocks read from disk',
+ proname => 'pg_stat_get_db_local_blks_read', provolatile => 's',
+ proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+ prosrc => 'pg_stat_get_db_local_blks_read' },
+{ oid => '9953',
+ descr => 'statistics: temp table cache hits',
+ proname => 'pg_stat_get_db_local_blks_hit', provolatile => 's',
+ proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+ prosrc => 'pg_stat_get_db_local_blks_hit' },
+{ oid => '9954',
+ descr => 'statistics: temp table blocks dirtied',
+ proname => 'pg_stat_get_db_local_blks_dirtied', provolatile => 's',
+ proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
+ prosrc => 'pg_stat_get_db_local_blks_dirtied' },
{ oid => '2844', descr => 'statistics: block read time, in milliseconds',
proname => 'pg_stat_get_db_blk_read_time', provolatile => 's',
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 216b93492ba..a09b127a9bd 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -365,6 +365,11 @@ typedef struct PgStat_StatDBEntry
PgStat_Counter conflict_startup_deadlock;
PgStat_Counter temp_files;
PgStat_Counter temp_bytes;
+ PgStat_Counter temp_tables; /* # of temp tables created */
+ PgStat_Counter local_blks_hit; /* temp table cache hits */
+ PgStat_Counter local_blks_read; /* temp table blocks read */
+ PgStat_Counter local_blks_dirtied; /* temp table blocks dirtied */
+ PgStat_Counter local_blks_written; /* temp table blocks written */
PgStat_Counter deadlocks;
PgStat_Counter checksum_failures;
TimestampTz last_checksum_failure;
@@ -625,6 +630,11 @@ extern void pgstat_drop_database(Oid databaseid);
extern void pgstat_report_autovac(Oid dboid);
extern void pgstat_report_recovery_conflict(int reason);
extern void pgstat_report_deadlock(void);
+extern void pgstat_count_temp_table(Oid dboid);
+extern void pgstat_count_local_blk_hit(Oid dboid);
+extern void pgstat_count_local_blk_read(Oid dboid, int64 count);
+extern void pgstat_count_local_blk_dirtied(Oid dboid);
+extern void pgstat_count_local_blk_written(Oid dboid, int64 count);
extern void pgstat_prepare_report_checksum_failure(Oid dboid);
extern void pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount);
extern void pgstat_report_connect(Oid dboid);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index f373ad704b6..d1b78a199ec 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1893,6 +1893,11 @@ pg_stat_database| SELECT oid AS datid,
pg_stat_get_db_conflict_all(oid) AS conflicts,
pg_stat_get_db_temp_files(oid) AS temp_files,
pg_stat_get_db_temp_bytes(oid) AS temp_bytes,
+ pg_stat_get_db_temp_tables(oid) AS temp_tables,
+ pg_stat_get_db_local_blks_hit(oid) AS local_blks_hit,
+ pg_stat_get_db_local_blks_read(oid) AS local_blks_read,
+ pg_stat_get_db_local_blks_dirtied(oid) AS local_blks_dirtied,
+ pg_stat_get_db_local_blks_written(oid) AS local_blks_written,
pg_stat_get_db_deadlocks(oid) AS deadlocks,
pg_stat_get_db_checksum_failures(oid) AS checksum_failures,
pg_stat_get_db_checksum_last_failure(oid) AS checksum_last_failure,
--
2.50.1 (Apple Git-155)