Add wal_fpi_bytes_[un]compressed to pg_stat_wal
Hi hackers,
I am proposing a patch that adds wal_fpi_bytes_[un]compressed columns
to pg_stat_wal. These columns help us calculate WAL FPI (full page
image) compression rates, confirm the usefulness of wal_compression
and determine which compression algorithms are most effective.
Currently, we must use cumbersome methods to compute the WAL compression rate:
1. Run the same benchmark twice (once with wal_compression enabled
and one disabled) and compare the wal_bytes values in pg_stat_wal.
However, this value reflects the total WAL reduction, not just the
reduction from full page images. (pg_waldump --stats can provide
similar data but it also requires direct access to WAL files and must
be run on the server.)
2. Run pg_waldump --fullpage and compare the reported compressed size
against the calculated uncompressed size (e.g. 8192 bytes -
hole_length). This computation is inconvenient and also requires WAL
access on the server.
With these patches applied, we can easily compute the FPI compression
rate with the following SQL:
=# SELECT wal_fpi_bytes_compressed / wal_fpi_bytes_uncompressed * 100
AS wal_compression_rate FROM pg_stat_wal;
wal_compression_rate
-------------------------
34.07161865906799706100
(1 row)
The 0001 patch adds these columns to pg_stat_wal. The 0002 and 0003
patches add this information to EXPLAIN (WAL) and pg_stat_statements,
respectively. I don't think these additions (0002 and 0003) are
mandatory, so I suggest we focus the discussion on the 0001 patch
first.
Thoughts?
--
Best regards,
Shinya Kato
NTT OSS Center
Attachments:
v1-0001-Add-wal_fpi_bytes_-un-compressed-to-pg_stat_wal.patchapplication/octet-stream; name=v1-0001-Add-wal_fpi_bytes_-un-compressed-to-pg_stat_wal.patchDownload
From f9153f1e7cf0a1fbe9b09b60562873508bff61a2 Mon Sep 17 00:00:00 2001
From: Shinya Kato <shinya11.kato@gmail.com>
Date: Sat, 18 Oct 2025 16:27:32 +0900
Subject: [PATCH v1 1/3] Add wal_fpi_bytes_[un]compressed to pg_stat_wal
---
doc/src/sgml/monitoring.sgml | 18 +++++++++++++++++
src/backend/access/transam/xlog.c | 4 ++++
src/backend/access/transam/xloginsert.c | 22 ++++++++++++++++++---
src/backend/catalog/system_views.sql | 4 +++-
src/backend/utils/activity/pgstat_backend.c | 2 ++
src/backend/utils/activity/pgstat_wal.c | 2 ++
src/backend/utils/adt/pgstatfuncs.c | 20 ++++++++++++++++++-
src/include/access/xlog.h | 2 ++
src/include/catalog/pg_proc.dat | 12 +++++------
src/include/pgstat.h | 2 ++
src/test/regress/expected/rules.out | 6 ++++--
11 files changed, 81 insertions(+), 13 deletions(-)
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index d5f0fb7ba7c..4cbdf0889d7 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -3340,6 +3340,24 @@ description | Waiting for a newly initialized WAL file to reach durable storage
Time at which these statistics were last reset
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>wal_fpi_bytes_uncompressed</structfield> <type>numeric</type>
+ </para>
+ <para>
+ Total amount of WAL full page images in bytes before compression
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>wal_fpi_bytes_compressed</structfield> <type>numeric</type>
+ </para>
+ <para>
+ Total amount of WAL full page images in bytes after compression
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index eceab341255..8b22b9a1d46 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -749,6 +749,8 @@ XLogInsertRecord(XLogRecData *rdata,
XLogRecPtr fpw_lsn,
uint8 flags,
int num_fpi,
+ uint64 fpi_bytes_uncompressed,
+ uint64 fpi_bytes_compressed,
bool topxid_included)
{
XLogCtlInsert *Insert = &XLogCtl->Insert;
@@ -1081,6 +1083,8 @@ XLogInsertRecord(XLogRecData *rdata,
pgWalUsage.wal_bytes += rechdr->xl_tot_len;
pgWalUsage.wal_records++;
pgWalUsage.wal_fpi += num_fpi;
+ pgWalUsage.wal_fpi_bytes_uncompressed += fpi_bytes_uncompressed;
+ pgWalUsage.wal_fpi_bytes_compressed += fpi_bytes_compressed;
/* Required for the flush of pending stats WAL data */
pgstat_report_fixed = true;
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index 496e0fa4ac6..47e9025993d 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -137,7 +137,9 @@ static MemoryContext xloginsert_cxt;
static XLogRecData *XLogRecordAssemble(RmgrId rmid, uint8 info,
XLogRecPtr RedoRecPtr, bool doPageWrites,
XLogRecPtr *fpw_lsn, int *num_fpi,
- bool *topxid_included);
+ bool *topxid_included,
+ uint64 *fpi_bytes_uncompressed,
+ uint64 *fpi_bytes_compressed);
static bool XLogCompressBackupBlock(const PageData *page, uint16 hole_offset,
uint16 hole_length, void *dest, uint16 *dlen);
@@ -510,6 +512,8 @@ XLogInsert(RmgrId rmid, uint8 info)
XLogRecPtr fpw_lsn;
XLogRecData *rdt;
int num_fpi = 0;
+ uint64 fpi_bytes_uncompressed = 0;
+ uint64 fpi_bytes_compressed = 0;
/*
* Get values needed to decide whether to do full-page writes. Since
@@ -519,9 +523,13 @@ XLogInsert(RmgrId rmid, uint8 info)
GetFullPageWriteInfo(&RedoRecPtr, &doPageWrites);
rdt = XLogRecordAssemble(rmid, info, RedoRecPtr, doPageWrites,
- &fpw_lsn, &num_fpi, &topxid_included);
+ &fpw_lsn, &num_fpi, &topxid_included,
+ &fpi_bytes_uncompressed,
+ &fpi_bytes_compressed);
EndPos = XLogInsertRecord(rdt, fpw_lsn, curinsert_flags, num_fpi,
+ fpi_bytes_uncompressed,
+ fpi_bytes_compressed,
topxid_included);
} while (EndPos == InvalidXLogRecPtr);
@@ -560,7 +568,9 @@ XLogSimpleInsertInt64(RmgrId rmid, uint8 info, int64 value)
static XLogRecData *
XLogRecordAssemble(RmgrId rmid, uint8 info,
XLogRecPtr RedoRecPtr, bool doPageWrites,
- XLogRecPtr *fpw_lsn, int *num_fpi, bool *topxid_included)
+ XLogRecPtr *fpw_lsn, int *num_fpi, bool *topxid_included,
+ uint64 *fpi_bytes_uncompressed,
+ uint64 *fpi_bytes_compressed)
{
XLogRecData *rdt;
uint64 total_len = 0;
@@ -571,6 +581,9 @@ XLogRecordAssemble(RmgrId rmid, uint8 info,
XLogRecord *rechdr;
char *scratch = hdr_scratch;
+ *fpi_bytes_uncompressed = 0;
+ *fpi_bytes_compressed = 0;
+
/*
* Note: this function can be called multiple times for the same record.
* All the modifications we do to the rdata chains below must handle that.
@@ -796,6 +809,9 @@ XLogRecordAssemble(RmgrId rmid, uint8 info,
}
total_len += bimg.length;
+
+ *fpi_bytes_uncompressed += BLCKSZ - cbimg.hole_length;
+ *fpi_bytes_compressed += bimg.length;
}
if (needs_data)
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 823776c1498..9cbf3723e73 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1222,7 +1222,9 @@ CREATE VIEW pg_stat_wal AS
w.wal_fpi,
w.wal_bytes,
w.wal_buffers_full,
- w.stats_reset
+ w.stats_reset,
+ w.wal_fpi_bytes_uncompressed,
+ w.wal_fpi_bytes_compressed
FROM pg_stat_get_wal() w;
CREATE VIEW pg_stat_progress_analyze AS
diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c
index a864ae8e6a6..b31c7205e9d 100644
--- a/src/backend/utils/activity/pgstat_backend.c
+++ b/src/backend/utils/activity/pgstat_backend.c
@@ -252,6 +252,8 @@ pgstat_flush_backend_entry_wal(PgStat_EntryRef *entry_ref)
WALSTAT_ACC(wal_records, wal_usage_diff);
WALSTAT_ACC(wal_fpi, wal_usage_diff);
WALSTAT_ACC(wal_bytes, wal_usage_diff);
+ WALSTAT_ACC(wal_fpi_bytes_uncompressed, wal_usage_diff);
+ WALSTAT_ACC(wal_fpi_bytes_compressed, wal_usage_diff);
#undef WALSTAT_ACC
/*
diff --git a/src/backend/utils/activity/pgstat_wal.c b/src/backend/utils/activity/pgstat_wal.c
index 0d04480d2f6..c1fd0758c19 100644
--- a/src/backend/utils/activity/pgstat_wal.c
+++ b/src/backend/utils/activity/pgstat_wal.c
@@ -121,6 +121,8 @@ pgstat_wal_flush_cb(bool nowait)
WALSTAT_ACC(wal_records, wal_usage_diff);
WALSTAT_ACC(wal_fpi, wal_usage_diff);
WALSTAT_ACC(wal_bytes, wal_usage_diff);
+ WALSTAT_ACC(wal_fpi_bytes_uncompressed, wal_usage_diff);
+ WALSTAT_ACC(wal_fpi_bytes_compressed, wal_usage_diff);
WALSTAT_ACC(wal_buffers_full, wal_usage_diff);
#undef WALSTAT_ACC
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1fe33df2756..69acfb863f8 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1637,7 +1637,7 @@ static Datum
pg_stat_wal_build_tuple(PgStat_WalCounters wal_counters,
TimestampTz stat_reset_timestamp)
{
-#define PG_STAT_WAL_COLS 5
+#define PG_STAT_WAL_COLS 7
TupleDesc tupdesc;
Datum values[PG_STAT_WAL_COLS] = {0};
bool nulls[PG_STAT_WAL_COLS] = {0};
@@ -1655,6 +1655,10 @@ pg_stat_wal_build_tuple(PgStat_WalCounters wal_counters,
INT8OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 5, "stats_reset",
TIMESTAMPTZOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 6, "wal_fpi_bytes_uncompressed",
+ NUMERICOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 7, "wal_fpi_bytes_compressed",
+ NUMERICOID, -1, 0);
BlessTupleDesc(tupdesc);
@@ -1676,6 +1680,20 @@ pg_stat_wal_build_tuple(PgStat_WalCounters wal_counters,
else
nulls[4] = true;
+ snprintf(buf, sizeof buf, UINT64_FORMAT,
+ wal_counters.wal_fpi_bytes_uncompressed);
+ values[5] = DirectFunctionCall3(numeric_in,
+ CStringGetDatum(buf),
+ ObjectIdGetDatum(0),
+ Int32GetDatum(-1));
+
+ snprintf(buf, sizeof buf, UINT64_FORMAT,
+ wal_counters.wal_fpi_bytes_compressed);
+ values[6] = DirectFunctionCall3(numeric_in,
+ CStringGetDatum(buf),
+ ObjectIdGetDatum(0),
+ Int32GetDatum(-1));
+
/* Returns the record as Datum */
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
}
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index d12798be3d8..1505b6842a4 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -202,6 +202,8 @@ extern XLogRecPtr XLogInsertRecord(struct XLogRecData *rdata,
XLogRecPtr fpw_lsn,
uint8 flags,
int num_fpi,
+ uint64 fpi_bytes_uncompressed,
+ uint64 fpi_bytes_compressed,
bool topxid_included);
extern void XLogFlush(XLogRecPtr record);
extern bool XLogBackgroundFlush(void);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index eecb43ec6f0..f6115f3aa4e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6029,16 +6029,16 @@
{ oid => '1136', descr => 'statistics: information about WAL activity',
proname => 'pg_stat_get_wal', proisstrict => 'f', provolatile => 's',
proparallel => 'r', prorettype => 'record', proargtypes => '',
- proallargtypes => '{int8,int8,numeric,int8,timestamptz}',
- proargmodes => '{o,o,o,o,o}',
- proargnames => '{wal_records,wal_fpi,wal_bytes,wal_buffers_full,stats_reset}',
+ proallargtypes => '{int8,int8,numeric,int8,timestamptz,numeric,numeric}',
+ proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{wal_records,wal_fpi,wal_bytes,wal_buffers_full,stats_reset,wal_fpi_bytes_uncompressed,wal_fpi_bytes_compressed}',
prosrc => 'pg_stat_get_wal' },
{ oid => '6313', descr => 'statistics: backend WAL activity',
proname => 'pg_stat_get_backend_wal', provolatile => 'v', proparallel => 'r',
prorettype => 'record', proargtypes => 'int4',
- proallargtypes => '{int4,int8,int8,numeric,int8,timestamptz}',
- proargmodes => '{i,o,o,o,o,o}',
- proargnames => '{backend_pid,wal_records,wal_fpi,wal_bytes,wal_buffers_full,stats_reset}',
+ proallargtypes => '{int4,int8,int8,numeric,int8,timestamptz,numeric,numeric}',
+ proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{backend_pid,wal_records,wal_fpi,wal_bytes,wal_buffers_full,stats_reset,wal_fpi_bytes_uncompressed,wal_fpi_bytes_compressed}',
prosrc => 'pg_stat_get_backend_wal' },
{ oid => '6248', descr => 'statistics: information about WAL prefetching',
proname => 'pg_stat_get_recovery_prefetch', prorows => '1', proretset => 't',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index bc8077cbae6..c6349f3a43e 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -473,6 +473,8 @@ typedef struct PgStat_WalCounters
PgStat_Counter wal_records;
PgStat_Counter wal_fpi;
uint64 wal_bytes;
+ uint64 wal_fpi_bytes_uncompressed;
+ uint64 wal_fpi_bytes_compressed;
PgStat_Counter wal_buffers_full;
} PgStat_WalCounters;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 16753b2e4c0..3690272435e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2307,8 +2307,10 @@ pg_stat_wal| SELECT wal_records,
wal_fpi,
wal_bytes,
wal_buffers_full,
- stats_reset
- FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, stats_reset);
+ stats_reset,
+ wal_fpi_bytes_uncompressed,
+ wal_fpi_bytes_compressed
+ FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, stats_reset, wal_fpi_bytes_uncompressed, wal_fpi_bytes_compressed);
pg_stat_wal_receiver| SELECT pid,
status,
receive_start_lsn,
--
2.47.3
v1-0002-Add-wal_fpi_bytes_-un-compressed-to-EXPLAIN-WAL.patchapplication/octet-stream; name=v1-0002-Add-wal_fpi_bytes_-un-compressed-to-EXPLAIN-WAL.patchDownload
From 1a1f7d972ef7be24e5adf76c2ae201d6f4b6e396 Mon Sep 17 00:00:00 2001
From: Shinya Kato <shinya11.kato@gmail.com>
Date: Sat, 18 Oct 2025 16:28:05 +0900
Subject: [PATCH v1 2/3] Add wal_fpi_bytes_[un]compressed to EXPLAIN (WAL)
---
doc/src/sgml/ref/explain.sgml | 4 +++-
src/backend/commands/explain.c | 14 +++++++++++++-
src/backend/executor/instrument.c | 6 ++++++
src/include/executor/instrument.h | 2 ++
4 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml
index 6dda680aa0d..7a4eeb06b3e 100644
--- a/doc/src/sgml/ref/explain.sgml
+++ b/doc/src/sgml/ref/explain.sgml
@@ -241,7 +241,9 @@ ROLLBACK;
<para>
Include information on WAL record generation. Specifically, include the
number of records, number of full page images (fpi), the amount of WAL
- generated in bytes and the number of times the WAL buffers became full.
+ generated in bytes, the number of times the WAL buffers became full, the
+ amount of WAL fpi generated in bytes after compression, and the amount of
+ WAL fpi generated in bytes before compression.
In text format, only non-zero values are printed.
This parameter may only be used when <literal>ANALYZE</literal> is also
enabled. It defaults to <literal>FALSE</literal>.
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index e6edae0845c..4dcfaf08cca 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -4283,7 +4283,9 @@ show_wal_usage(ExplainState *es, const WalUsage *usage)
{
/* Show only positive counter values. */
if ((usage->wal_records > 0) || (usage->wal_fpi > 0) ||
- (usage->wal_bytes > 0) || (usage->wal_buffers_full > 0))
+ (usage->wal_bytes > 0) || (usage->wal_buffers_full > 0) ||
+ (usage->wal_fpi_bytes_uncompressed > 0) ||
+ (usage->wal_fpi_bytes_compressed > 0))
{
ExplainIndentText(es);
appendStringInfoString(es->str, "WAL:");
@@ -4300,6 +4302,12 @@ show_wal_usage(ExplainState *es, const WalUsage *usage)
if (usage->wal_buffers_full > 0)
appendStringInfo(es->str, " buffers full=%" PRId64,
usage->wal_buffers_full);
+ if (usage->wal_fpi_bytes_uncompressed > 0)
+ appendStringInfo(es->str, " fpi bytes uncompressed=%" PRIu64,
+ usage->wal_fpi_bytes_uncompressed);
+ if (usage->wal_fpi_bytes_compressed > 0)
+ appendStringInfo(es->str, " fpi bytes compressed=%" PRIu64,
+ usage->wal_fpi_bytes_compressed);
appendStringInfoChar(es->str, '\n');
}
}
@@ -4313,6 +4321,10 @@ show_wal_usage(ExplainState *es, const WalUsage *usage)
usage->wal_bytes, es);
ExplainPropertyInteger("WAL Buffers Full", NULL,
usage->wal_buffers_full, es);
+ ExplainPropertyUInteger("WAL FPI Bytes Uncompressed", NULL,
+ usage->wal_fpi_bytes_uncompressed, es);
+ ExplainPropertyUInteger("WAL FPI Bytes Compressed", NULL,
+ usage->wal_fpi_bytes_compressed, es);
}
}
diff --git a/src/backend/executor/instrument.c b/src/backend/executor/instrument.c
index 56e635f4700..1d11a001d22 100644
--- a/src/backend/executor/instrument.c
+++ b/src/backend/executor/instrument.c
@@ -281,6 +281,8 @@ WalUsageAdd(WalUsage *dst, WalUsage *add)
dst->wal_records += add->wal_records;
dst->wal_fpi += add->wal_fpi;
dst->wal_buffers_full += add->wal_buffers_full;
+ dst->wal_fpi_bytes_uncompressed += add->wal_fpi_bytes_uncompressed;
+ dst->wal_fpi_bytes_compressed += add->wal_fpi_bytes_compressed;
}
void
@@ -290,4 +292,8 @@ WalUsageAccumDiff(WalUsage *dst, const WalUsage *add, const WalUsage *sub)
dst->wal_records += add->wal_records - sub->wal_records;
dst->wal_fpi += add->wal_fpi - sub->wal_fpi;
dst->wal_buffers_full += add->wal_buffers_full - sub->wal_buffers_full;
+ dst->wal_fpi_bytes_uncompressed +=
+ add->wal_fpi_bytes_uncompressed - sub->wal_fpi_bytes_uncompressed;
+ dst->wal_fpi_bytes_compressed +=
+ add->wal_fpi_bytes_compressed - sub->wal_fpi_bytes_compressed;
}
diff --git a/src/include/executor/instrument.h b/src/include/executor/instrument.h
index 03653ab6c6c..2cc9c1e2b88 100644
--- a/src/include/executor/instrument.h
+++ b/src/include/executor/instrument.h
@@ -54,6 +54,8 @@ typedef struct WalUsage
int64 wal_fpi; /* # of WAL full page images produced */
uint64 wal_bytes; /* size of WAL records produced */
int64 wal_buffers_full; /* # of times the WAL buffers became full */
+ uint64 wal_fpi_bytes_uncompressed; /* size of FPIs before compression */
+ uint64 wal_fpi_bytes_compressed; /* size of FPIs after compression */
} WalUsage;
/* Flag bits included in InstrAlloc's instrument_options bitmask */
--
2.47.3
v1-0003-pg_stat_statements-Add-wal_fpi_bytes_-un-compress.patchapplication/octet-stream; name=v1-0003-pg_stat_statements-Add-wal_fpi_bytes_-un-compress.patchDownload
From 1576634d66956d232a7b980353409d81521d24c3 Mon Sep 17 00:00:00 2001
From: Shinya Kato <shinya11.kato@gmail.com>
Date: Sun, 19 Oct 2025 15:34:25 +0900
Subject: [PATCH v1 3/3] pg_stat_statements: Add wal_fpi_bytes_[un]compressed
---
contrib/pg_stat_statements/Makefile | 1 +
.../expected/oldextversions.out | 69 ++++++++++++++++
contrib/pg_stat_statements/meson.build | 1 +
.../pg_stat_statements--1.13--1.14.sql | 80 +++++++++++++++++++
.../pg_stat_statements/pg_stat_statements.c | 52 +++++++++++-
.../pg_stat_statements.control | 2 +-
.../pg_stat_statements/sql/oldextversions.sql | 5 ++
doc/src/sgml/pgstatstatements.sgml | 20 +++++
8 files changed, 227 insertions(+), 3 deletions(-)
create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.13--1.14.sql
diff --git a/contrib/pg_stat_statements/Makefile b/contrib/pg_stat_statements/Makefile
index fe0478ac552..ee41ee0311e 100644
--- a/contrib/pg_stat_statements/Makefile
+++ b/contrib/pg_stat_statements/Makefile
@@ -7,6 +7,7 @@ OBJS = \
EXTENSION = pg_stat_statements
DATA = pg_stat_statements--1.4.sql \
+ pg_stat_statements--1.13--1.14.sql \
pg_stat_statements--1.12--1.13.sql \
pg_stat_statements--1.11--1.12.sql pg_stat_statements--1.10--1.11.sql \
pg_stat_statements--1.9--1.10.sql pg_stat_statements--1.8--1.9.sql \
diff --git a/contrib/pg_stat_statements/expected/oldextversions.out b/contrib/pg_stat_statements/expected/oldextversions.out
index 726383a99d7..257c474d42d 100644
--- a/contrib/pg_stat_statements/expected/oldextversions.out
+++ b/contrib/pg_stat_statements/expected/oldextversions.out
@@ -474,4 +474,73 @@ SELECT count(*) > 0 AS has_data FROM pg_stat_statements;
t
(1 row)
+-- New functions and views for pg_stat_statements in 1.14
+AlTER EXTENSION pg_stat_statements UPDATE TO '1.14';
+\d pg_stat_statements
+ View "public.pg_stat_statements"
+ Column | Type | Collation | Nullable | Default
+----------------------------+--------------------------+-----------+----------+---------
+ userid | oid | | |
+ dbid | oid | | |
+ toplevel | boolean | | |
+ queryid | bigint | | |
+ query | text | | |
+ plans | bigint | | |
+ total_plan_time | double precision | | |
+ min_plan_time | double precision | | |
+ max_plan_time | double precision | | |
+ mean_plan_time | double precision | | |
+ stddev_plan_time | double precision | | |
+ calls | bigint | | |
+ total_exec_time | double precision | | |
+ min_exec_time | double precision | | |
+ max_exec_time | double precision | | |
+ mean_exec_time | double precision | | |
+ stddev_exec_time | double precision | | |
+ rows | bigint | | |
+ shared_blks_hit | bigint | | |
+ shared_blks_read | bigint | | |
+ shared_blks_dirtied | bigint | | |
+ shared_blks_written | bigint | | |
+ local_blks_hit | bigint | | |
+ local_blks_read | bigint | | |
+ local_blks_dirtied | bigint | | |
+ local_blks_written | bigint | | |
+ temp_blks_read | bigint | | |
+ temp_blks_written | bigint | | |
+ shared_blk_read_time | double precision | | |
+ shared_blk_write_time | double precision | | |
+ local_blk_read_time | double precision | | |
+ local_blk_write_time | double precision | | |
+ temp_blk_read_time | double precision | | |
+ temp_blk_write_time | double precision | | |
+ wal_records | bigint | | |
+ wal_fpi | bigint | | |
+ wal_bytes | numeric | | |
+ wal_buffers_full | bigint | | |
+ wal_fpi_bytes_uncompressed | numeric | | |
+ wal_fpi_bytes_compressed | numeric | | |
+ jit_functions | bigint | | |
+ jit_generation_time | double precision | | |
+ jit_inlining_count | bigint | | |
+ jit_inlining_time | double precision | | |
+ jit_optimization_count | bigint | | |
+ jit_optimization_time | double precision | | |
+ jit_emission_count | bigint | | |
+ jit_emission_time | double precision | | |
+ jit_deform_count | bigint | | |
+ jit_deform_time | double precision | | |
+ parallel_workers_to_launch | bigint | | |
+ parallel_workers_launched | bigint | | |
+ generic_plan_calls | bigint | | |
+ custom_plan_calls | bigint | | |
+ stats_since | timestamp with time zone | | |
+ minmax_stats_since | timestamp with time zone | | |
+
+SELECT count(*) > 0 AS has_data FROM pg_stat_statements;
+ has_data
+----------
+ t
+(1 row)
+
DROP EXTENSION pg_stat_statements;
diff --git a/contrib/pg_stat_statements/meson.build b/contrib/pg_stat_statements/meson.build
index 7b8bfbb1de7..378d363eef8 100644
--- a/contrib/pg_stat_statements/meson.build
+++ b/contrib/pg_stat_statements/meson.build
@@ -21,6 +21,7 @@ contrib_targets += pg_stat_statements
install_data(
'pg_stat_statements.control',
'pg_stat_statements--1.4.sql',
+ 'pg_stat_statements--1.13--1.14.sql',
'pg_stat_statements--1.12--1.13.sql',
'pg_stat_statements--1.11--1.12.sql',
'pg_stat_statements--1.10--1.11.sql',
diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.13--1.14.sql b/contrib/pg_stat_statements/pg_stat_statements--1.13--1.14.sql
new file mode 100644
index 00000000000..c76a66fe4e2
--- /dev/null
+++ b/contrib/pg_stat_statements/pg_stat_statements--1.13--1.14.sql
@@ -0,0 +1,80 @@
+/* contrib/pg_stat_statements/pg_stat_statements--1.13--1.14.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.14'" to load this file. \quit
+
+/* First we have to remove them from the extension */
+ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements;
+ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean);
+
+/* Then we can drop them */
+DROP VIEW pg_stat_statements;
+DROP FUNCTION pg_stat_statements(boolean);
+
+/* Now redefine */
+CREATE FUNCTION pg_stat_statements(IN showtext boolean,
+ OUT userid oid,
+ OUT dbid oid,
+ OUT toplevel bool,
+ OUT queryid bigint,
+ OUT query text,
+ OUT plans int8,
+ OUT total_plan_time float8,
+ OUT min_plan_time float8,
+ OUT max_plan_time float8,
+ OUT mean_plan_time float8,
+ OUT stddev_plan_time float8,
+ OUT calls int8,
+ OUT total_exec_time float8,
+ OUT min_exec_time float8,
+ OUT max_exec_time float8,
+ OUT mean_exec_time float8,
+ OUT stddev_exec_time float8,
+ OUT rows int8,
+ OUT shared_blks_hit int8,
+ OUT shared_blks_read int8,
+ OUT shared_blks_dirtied int8,
+ OUT shared_blks_written int8,
+ OUT local_blks_hit int8,
+ OUT local_blks_read int8,
+ OUT local_blks_dirtied int8,
+ OUT local_blks_written int8,
+ OUT temp_blks_read int8,
+ OUT temp_blks_written int8,
+ OUT shared_blk_read_time float8,
+ OUT shared_blk_write_time float8,
+ OUT local_blk_read_time float8,
+ OUT local_blk_write_time float8,
+ OUT temp_blk_read_time float8,
+ OUT temp_blk_write_time float8,
+ OUT wal_records int8,
+ OUT wal_fpi int8,
+ OUT wal_bytes numeric,
+ OUT wal_buffers_full int8,
+ OUT wal_fpi_bytes_uncompressed numeric,
+ OUT wal_fpi_bytes_compressed numeric,
+ OUT jit_functions int8,
+ OUT jit_generation_time float8,
+ OUT jit_inlining_count int8,
+ OUT jit_inlining_time float8,
+ OUT jit_optimization_count int8,
+ OUT jit_optimization_time float8,
+ OUT jit_emission_count int8,
+ OUT jit_emission_time float8,
+ OUT jit_deform_count int8,
+ OUT jit_deform_time float8,
+ OUT parallel_workers_to_launch int8,
+ OUT parallel_workers_launched int8,
+ OUT generic_plan_calls int8,
+ OUT custom_plan_calls int8,
+ OUT stats_since timestamp with time zone,
+ OUT minmax_stats_since timestamp with time zone
+)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'pg_stat_statements_1_14'
+LANGUAGE C STRICT VOLATILE PARALLEL SAFE;
+
+CREATE VIEW pg_stat_statements AS
+ SELECT * FROM pg_stat_statements(true);
+
+GRANT SELECT ON pg_stat_statements TO PUBLIC;
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index f2187167c5c..0f658f2122e 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -86,7 +86,7 @@ PG_MODULE_MAGIC_EXT(
#define PGSS_TEXT_FILE PG_STAT_TMP_DIR "/pgss_query_texts.stat"
/* Magic number identifying the stats file format */
-static const uint32 PGSS_FILE_HEADER = 0x20250731;
+static const uint32 PGSS_FILE_HEADER = 0x20251020;
/* PostgreSQL major version number, changes in which invalidate all entries */
static const uint32 PGSS_PG_MAJOR_VERSION = PG_VERSION_NUM / 100;
@@ -116,6 +116,7 @@ typedef enum pgssVersion
PGSS_V1_11,
PGSS_V1_12,
PGSS_V1_13,
+ PGSS_V1_14,
} pgssVersion;
typedef enum pgssStoreKind
@@ -191,6 +192,12 @@ typedef struct Counters
int64 wal_records; /* # of WAL records generated */
int64 wal_fpi; /* # of WAL full page images generated */
uint64 wal_bytes; /* total amount of WAL generated in bytes */
+ uint64 wal_fpi_bytes_uncompressed; /* total amount of WAL full page
+ * images generated in bytes before
+ * compression */
+ uint64 wal_fpi_bytes_compressed; /* total amount of WAL full page
+ * images generated in bytes after
+ * compression */
int64 wal_buffers_full; /* # of times the WAL buffers became full */
int64 jit_functions; /* total number of JIT functions emitted */
double jit_generation_time; /* total time to generate jit code */
@@ -327,6 +334,7 @@ PG_FUNCTION_INFO_V1(pg_stat_statements_1_10);
PG_FUNCTION_INFO_V1(pg_stat_statements_1_11);
PG_FUNCTION_INFO_V1(pg_stat_statements_1_12);
PG_FUNCTION_INFO_V1(pg_stat_statements_1_13);
+PG_FUNCTION_INFO_V1(pg_stat_statements_1_14);
PG_FUNCTION_INFO_V1(pg_stat_statements);
PG_FUNCTION_INFO_V1(pg_stat_statements_info);
@@ -1481,6 +1489,8 @@ pgss_store(const char *query, int64 queryId,
entry->counters.wal_fpi += walusage->wal_fpi;
entry->counters.wal_bytes += walusage->wal_bytes;
entry->counters.wal_buffers_full += walusage->wal_buffers_full;
+ entry->counters.wal_fpi_bytes_uncompressed += walusage->wal_fpi_bytes_uncompressed;
+ entry->counters.wal_fpi_bytes_compressed += walusage->wal_fpi_bytes_compressed;
if (jitusage)
{
entry->counters.jit_functions += jitusage->created_functions;
@@ -1581,7 +1591,8 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
#define PG_STAT_STATEMENTS_COLS_V1_11 49
#define PG_STAT_STATEMENTS_COLS_V1_12 52
#define PG_STAT_STATEMENTS_COLS_V1_13 54
-#define PG_STAT_STATEMENTS_COLS 54 /* maximum of above */
+#define PG_STAT_STATEMENTS_COLS_V1_14 56
+#define PG_STAT_STATEMENTS_COLS 56 /* maximum of above */
/*
* Retrieve statement statistics.
@@ -1593,6 +1604,16 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
* expected API version is identified by embedding it in the C name of the
* function. Unfortunately we weren't bright enough to do that for 1.1.
*/
+Datum
+pg_stat_statements_1_14(PG_FUNCTION_ARGS)
+{
+ bool showtext = PG_GETARG_BOOL(0);
+
+ pg_stat_statements_internal(fcinfo, PGSS_V1_14, showtext);
+
+ return (Datum) 0;
+}
+
Datum
pg_stat_statements_1_13(PG_FUNCTION_ARGS)
{
@@ -1765,6 +1786,10 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
if (api_version != PGSS_V1_13)
elog(ERROR, "incorrect number of output arguments");
break;
+ case PG_STAT_STATEMENTS_COLS_V1_14:
+ if (api_version != PGSS_V1_14)
+ elog(ERROR, "incorrect number of output arguments");
+ break;
default:
elog(ERROR, "incorrect number of output arguments");
}
@@ -1996,6 +2021,28 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
{
values[i++] = Int64GetDatumFast(tmp.wal_buffers_full);
}
+ if (api_version >= PGSS_V1_14)
+ {
+ char buf[256];
+ Datum wal_fpi_bytes_uncompressed;
+ Datum wal_fpi_bytes_compressed;
+
+ snprintf(buf, sizeof buf, UINT64_FORMAT,
+ tmp.wal_fpi_bytes_uncompressed);
+ wal_fpi_bytes_uncompressed = DirectFunctionCall3(numeric_in,
+ CStringGetDatum(buf),
+ ObjectIdGetDatum(0),
+ Int32GetDatum(-1));
+ values[i++] = wal_fpi_bytes_uncompressed;
+
+ snprintf(buf, sizeof buf, UINT64_FORMAT,
+ tmp.wal_fpi_bytes_compressed);
+ wal_fpi_bytes_compressed = DirectFunctionCall3(numeric_in,
+ CStringGetDatum(buf),
+ ObjectIdGetDatum(0),
+ Int32GetDatum(-1));
+ values[i++] = wal_fpi_bytes_compressed;
+ }
if (api_version >= PGSS_V1_10)
{
values[i++] = Int64GetDatumFast(tmp.jit_functions);
@@ -2038,6 +2085,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
api_version == PGSS_V1_11 ? PG_STAT_STATEMENTS_COLS_V1_11 :
api_version == PGSS_V1_12 ? PG_STAT_STATEMENTS_COLS_V1_12 :
api_version == PGSS_V1_13 ? PG_STAT_STATEMENTS_COLS_V1_13 :
+ api_version == PGSS_V1_14 ? PG_STAT_STATEMENTS_COLS_V1_14 :
-1 /* fail if you forget to update this assert */ ));
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
diff --git a/contrib/pg_stat_statements/pg_stat_statements.control b/contrib/pg_stat_statements/pg_stat_statements.control
index 2eee0ceffa8..61ae41efc14 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.control
+++ b/contrib/pg_stat_statements/pg_stat_statements.control
@@ -1,5 +1,5 @@
# pg_stat_statements extension
comment = 'track planning and execution statistics of all SQL statements executed'
-default_version = '1.13'
+default_version = '1.14'
module_pathname = '$libdir/pg_stat_statements'
relocatable = true
diff --git a/contrib/pg_stat_statements/sql/oldextversions.sql b/contrib/pg_stat_statements/sql/oldextversions.sql
index e416efe9ffb..a43fc740906 100644
--- a/contrib/pg_stat_statements/sql/oldextversions.sql
+++ b/contrib/pg_stat_statements/sql/oldextversions.sql
@@ -68,4 +68,9 @@ AlTER EXTENSION pg_stat_statements UPDATE TO '1.13';
\d pg_stat_statements
SELECT count(*) > 0 AS has_data FROM pg_stat_statements;
+-- New functions and views for pg_stat_statements in 1.14
+AlTER EXTENSION pg_stat_statements UPDATE TO '1.14';
+\d pg_stat_statements
+SELECT count(*) > 0 AS has_data FROM pg_stat_statements;
+
DROP EXTENSION pg_stat_statements;
diff --git a/doc/src/sgml/pgstatstatements.sgml b/doc/src/sgml/pgstatstatements.sgml
index d753de5836e..a968b428d7a 100644
--- a/doc/src/sgml/pgstatstatements.sgml
+++ b/doc/src/sgml/pgstatstatements.sgml
@@ -445,6 +445,26 @@
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>wal_fpi_bytes_uncompressed</structfield> <type>numeric</type>
+ </para>
+ <para>
+ Total amount of WAL full page images by the statement in bytes before
+ compression
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>wal_fpi_bytes_compressed</structfield> <type>numeric</type>
+ </para>
+ <para>
+ Total amount of WAL full page images by the statement in bytes after
+ compression
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>jit_functions</structfield> <type>bigint</type>
--
2.47.3
On Wed, Oct 22, 2025 at 05:04:58PM +0900, Shinya Kato wrote:
The 0001 patch adds these columns to pg_stat_wal. The 0002 and 0003
patches add this information to EXPLAIN (WAL) and pg_stat_statements,
respectively. I don't think these additions (0002 and 0003) are
mandatory, so I suggest we focus the discussion on the 0001 patch
first.
We already know the number of FPIs generated. Hence my take would be
to use only one counter, not two: an aggregated FPI length like in
xlogstats.h as exposed in pg_walinspect. That should be enough to
offer trends regarding the effects of compression, even if some pages
have holes that are discarded.
Thoughts?
I would suggest to leave PGSS out of it for now. We really need to do
something about the number of fields computed, with more GUCs to
disable groups of them, at least, like JIT or the planning parts. No
objections for the EXPLAIN and pg_stat_wal parts.
The patch can be simpler. There is no need to pass the calculated
number(s) across multiple functions in the stack, you can just
aggregate the numbers in pgWalUsage directly in XLogRecordAssemble().
The only extra thing to do is that one needs to set
pgstat_report_fixed to true to force the report to pgstats.
--
Michael
On Wed, Oct 22, 2025 at 5:45 PM Michael Paquier <michael@paquier.xyz> wrote:
We already know the number of FPIs generated. Hence my take would be
to use only one counter, not two: an aggregated FPI length like in
xlogstats.h as exposed in pg_walinspect. That should be enough to
offer trends regarding the effects of compression, even if some pages
have holes that are discarded.
Yeah, I would like to know the trends of FPI compression rates, not
the exact FPI compression rates. So, I agree with Michael, and have
updated the patches.
I would suggest to leave PGSS out of it for now. We really need to do
something about the number of fields computed, with more GUCs to
disable groups of them, at least, like JIT or the planning parts. No
objections for the EXPLAIN and pg_stat_wal parts.
Okay, since I'm not strongly attached to this idea, I've removed the
0003 patch for now.
The patch can be simpler. There is no need to pass the calculated
number(s) across multiple functions in the stack, you can just
aggregate the numbers in pgWalUsage directly in XLogRecordAssemble().
The only extra thing to do is that one needs to set
pgstat_report_fixed to true to force the report to pgstats.
Thank you for your review. I've implemented this suggestion in the v2 patches.
--
Best regards,
Shinya Kato
NTT OSS Center
Attachments:
v2-0001-Add-wal_fpi_bytes-to-pg_stat_wal.patchapplication/octet-stream; name=v2-0001-Add-wal_fpi_bytes-to-pg_stat_wal.patchDownload
From ff10d7684ead9efd0b7f6ff03b68d598917c7c5c Mon Sep 17 00:00:00 2001
From: Shinya Kato <shinya11.kato@gmail.com>
Date: Sat, 18 Oct 2025 16:27:32 +0900
Subject: [PATCH v2 1/2] Add wal_fpi_bytes to pg_stat_wal
Track the total size of full-page images in WAL statistics so that the
effect of wal_compression can be monitored. XLogRecordAssemble now
increments a new wal_fpi_bytes counter in WalUsage, and the backend and
shared WAL stats paths flush that value alongside the existing counters.
Expose the counter through pg_stat_get_wal(), pg_stat_get_backend_wal(),
pg_stat_wal.
Author: Shinya Kato <shinya11.kato@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Discusssion: https://postgr.es/m/CAOzEurQtZEAfg6P0kU3Wa-f9BWQOi0RzJEMPN56wNTOmJLmfaQ@mail.gmail.com
---
doc/src/sgml/monitoring.sgml | 11 +++++++++++
src/backend/access/transam/xloginsert.c | 6 ++++++
src/backend/catalog/system_views.sql | 3 ++-
src/backend/utils/activity/pgstat_backend.c | 1 +
src/backend/utils/activity/pgstat_wal.c | 1 +
src/backend/utils/adt/pgstatfuncs.c | 10 +++++++++-
src/include/catalog/pg_proc.dat | 12 ++++++------
src/include/executor/instrument.h | 1 +
src/include/pgstat.h | 1 +
src/test/regress/expected/rules.out | 5 +++--
10 files changed, 41 insertions(+), 10 deletions(-)
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index d5f0fb7ba7c..0db378ee99e 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -3340,6 +3340,17 @@ description | Waiting for a newly initialized WAL file to reach durable storage
Time at which these statistics were last reset
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>wal_fpi_bytes</structfield> <type>numeric</type>
+ </para>
+ <para>
+ Total amount of WAL full page images in bytes; if
+ <varname>wal_compression</varname> is enabled, this reflects the
+ compressed size
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index 496e0fa4ac6..b3abf386f80 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -33,12 +33,14 @@
#include "access/xloginsert.h"
#include "catalog/pg_control.h"
#include "common/pg_lzcompress.h"
+#include "executor/instrument.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "replication/origin.h"
#include "storage/bufmgr.h"
#include "storage/proc.h"
#include "utils/memutils.h"
+#include "utils/pgstat_internal.h"
/*
* Guess the maximum buffer size required to store a compressed version of
@@ -796,6 +798,10 @@ XLogRecordAssemble(RmgrId rmid, uint8 info,
}
total_len += bimg.length;
+
+ /* Track the WAL full page images in bytes */
+ pgWalUsage.wal_fpi_bytes += bimg.length;
+ pgstat_report_fixed = true;
}
if (needs_data)
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 823776c1498..d7f149f9328 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1222,7 +1222,8 @@ CREATE VIEW pg_stat_wal AS
w.wal_fpi,
w.wal_bytes,
w.wal_buffers_full,
- w.stats_reset
+ w.stats_reset,
+ w.wal_fpi_bytes
FROM pg_stat_get_wal() w;
CREATE VIEW pg_stat_progress_analyze AS
diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c
index a864ae8e6a6..199ba2cc17a 100644
--- a/src/backend/utils/activity/pgstat_backend.c
+++ b/src/backend/utils/activity/pgstat_backend.c
@@ -252,6 +252,7 @@ pgstat_flush_backend_entry_wal(PgStat_EntryRef *entry_ref)
WALSTAT_ACC(wal_records, wal_usage_diff);
WALSTAT_ACC(wal_fpi, wal_usage_diff);
WALSTAT_ACC(wal_bytes, wal_usage_diff);
+ WALSTAT_ACC(wal_fpi_bytes, wal_usage_diff);
#undef WALSTAT_ACC
/*
diff --git a/src/backend/utils/activity/pgstat_wal.c b/src/backend/utils/activity/pgstat_wal.c
index 0d04480d2f6..4d796c5e790 100644
--- a/src/backend/utils/activity/pgstat_wal.c
+++ b/src/backend/utils/activity/pgstat_wal.c
@@ -122,6 +122,7 @@ pgstat_wal_flush_cb(bool nowait)
WALSTAT_ACC(wal_fpi, wal_usage_diff);
WALSTAT_ACC(wal_bytes, wal_usage_diff);
WALSTAT_ACC(wal_buffers_full, wal_usage_diff);
+ WALSTAT_ACC(wal_fpi_bytes, wal_usage_diff);
#undef WALSTAT_ACC
LWLockRelease(&stats_shmem->lock);
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 1fe33df2756..21efc010334 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1637,7 +1637,7 @@ static Datum
pg_stat_wal_build_tuple(PgStat_WalCounters wal_counters,
TimestampTz stat_reset_timestamp)
{
-#define PG_STAT_WAL_COLS 5
+#define PG_STAT_WAL_COLS 6
TupleDesc tupdesc;
Datum values[PG_STAT_WAL_COLS] = {0};
bool nulls[PG_STAT_WAL_COLS] = {0};
@@ -1655,6 +1655,8 @@ pg_stat_wal_build_tuple(PgStat_WalCounters wal_counters,
INT8OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 5, "stats_reset",
TIMESTAMPTZOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 6, "wal_fpi_bytes",
+ NUMERICOID, -1, 0);
BlessTupleDesc(tupdesc);
@@ -1676,6 +1678,12 @@ pg_stat_wal_build_tuple(PgStat_WalCounters wal_counters,
else
nulls[4] = true;
+ snprintf(buf, sizeof buf, UINT64_FORMAT, wal_counters.wal_fpi_bytes);
+ values[5] = DirectFunctionCall3(numeric_in,
+ CStringGetDatum(buf),
+ ObjectIdGetDatum(0),
+ Int32GetDatum(-1));
+
/* Returns the record as Datum */
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index eecb43ec6f0..952a51969b3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6029,16 +6029,16 @@
{ oid => '1136', descr => 'statistics: information about WAL activity',
proname => 'pg_stat_get_wal', proisstrict => 'f', provolatile => 's',
proparallel => 'r', prorettype => 'record', proargtypes => '',
- proallargtypes => '{int8,int8,numeric,int8,timestamptz}',
- proargmodes => '{o,o,o,o,o}',
- proargnames => '{wal_records,wal_fpi,wal_bytes,wal_buffers_full,stats_reset}',
+ proallargtypes => '{int8,int8,numeric,int8,timestamptz,numeric}',
+ proargmodes => '{o,o,o,o,o,o}',
+ proargnames => '{wal_records,wal_fpi,wal_bytes,wal_buffers_full,stats_reset,wal_fpi_bytes}',
prosrc => 'pg_stat_get_wal' },
{ oid => '6313', descr => 'statistics: backend WAL activity',
proname => 'pg_stat_get_backend_wal', provolatile => 'v', proparallel => 'r',
prorettype => 'record', proargtypes => 'int4',
- proallargtypes => '{int4,int8,int8,numeric,int8,timestamptz}',
- proargmodes => '{i,o,o,o,o,o}',
- proargnames => '{backend_pid,wal_records,wal_fpi,wal_bytes,wal_buffers_full,stats_reset}',
+ proallargtypes => '{int4,int8,int8,numeric,int8,timestamptz,numeric}',
+ proargmodes => '{i,o,o,o,o,o,o}',
+ proargnames => '{backend_pid,wal_records,wal_fpi,wal_bytes,wal_buffers_full,stats_reset,wal_fpi_bytes}',
prosrc => 'pg_stat_get_backend_wal' },
{ oid => '6248', descr => 'statistics: information about WAL prefetching',
proname => 'pg_stat_get_recovery_prefetch', prorows => '1', proretset => 't',
diff --git a/src/include/executor/instrument.h b/src/include/executor/instrument.h
index 03653ab6c6c..7eb02833fad 100644
--- a/src/include/executor/instrument.h
+++ b/src/include/executor/instrument.h
@@ -54,6 +54,7 @@ typedef struct WalUsage
int64 wal_fpi; /* # of WAL full page images produced */
uint64 wal_bytes; /* size of WAL records produced */
int64 wal_buffers_full; /* # of times the WAL buffers became full */
+ uint64 wal_fpi_bytes; /* size of FPIs after compression */
} WalUsage;
/* Flag bits included in InstrAlloc's instrument_options bitmask */
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index bc8077cbae6..9c2d44bab89 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -473,6 +473,7 @@ typedef struct PgStat_WalCounters
PgStat_Counter wal_records;
PgStat_Counter wal_fpi;
uint64 wal_bytes;
+ uint64 wal_fpi_bytes;
PgStat_Counter wal_buffers_full;
} PgStat_WalCounters;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 16753b2e4c0..6c3d78e96aa 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2307,8 +2307,9 @@ pg_stat_wal| SELECT wal_records,
wal_fpi,
wal_bytes,
wal_buffers_full,
- stats_reset
- FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, stats_reset);
+ stats_reset,
+ wal_fpi_bytes
+ FROM pg_stat_get_wal() w(wal_records, wal_fpi, wal_bytes, wal_buffers_full, stats_reset, wal_fpi_bytes);
pg_stat_wal_receiver| SELECT pid,
status,
receive_start_lsn,
--
2.47.3
v2-0002-Expose-WAL-FPI-byte-totals-in-EXPLAIN.patchapplication/octet-stream; name=v2-0002-Expose-WAL-FPI-byte-totals-in-EXPLAIN.patchDownload
From 47a131b524fea7213ff51c21efb5665308e3664c Mon Sep 17 00:00:00 2001
From: Shinya Kato <shinya11.kato@gmail.com>
Date: Sat, 18 Oct 2025 16:28:05 +0900
Subject: [PATCH v2 2/2] Expose WAL FPI byte totals in EXPLAIN
Show the new wal_fpi_bytes counter when EXPLAIN (ANALYZE, WAL) reports
per-plan WAL usage, emitting it alongside the existing counters in both
text and JSON/YAML output. Extend WalUsage aggregation helpers so the
value accumulates correctly.
Author: Shinya Kato <shinya11.kato@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Discusssion: https://postgr.es/m/CAOzEurQtZEAfg6P0kU3Wa-f9BWQOi0RzJEMPN56wNTOmJLmfaQ@mail.gmail.com
---
doc/src/sgml/ref/explain.sgml | 4 +++-
src/backend/commands/explain.c | 8 +++++++-
src/backend/executor/instrument.c | 2 ++
3 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml
index 6dda680aa0d..ead4017d172 100644
--- a/doc/src/sgml/ref/explain.sgml
+++ b/doc/src/sgml/ref/explain.sgml
@@ -241,7 +241,9 @@ ROLLBACK;
<para>
Include information on WAL record generation. Specifically, include the
number of records, number of full page images (fpi), the amount of WAL
- generated in bytes and the number of times the WAL buffers became full.
+ generated in bytes, the number of times the WAL buffers became full, the
+ amount of WAL fpi generated in bytes (if <varname>wal_compression</varname>
+ is enabled, this reflects the compressed size).
In text format, only non-zero values are printed.
This parameter may only be used when <literal>ANALYZE</literal> is also
enabled. It defaults to <literal>FALSE</literal>.
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index e6edae0845c..68c3d20353c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -4283,7 +4283,8 @@ show_wal_usage(ExplainState *es, const WalUsage *usage)
{
/* Show only positive counter values. */
if ((usage->wal_records > 0) || (usage->wal_fpi > 0) ||
- (usage->wal_bytes > 0) || (usage->wal_buffers_full > 0))
+ (usage->wal_bytes > 0) || (usage->wal_buffers_full > 0) ||
+ (usage->wal_fpi_bytes > 0))
{
ExplainIndentText(es);
appendStringInfoString(es->str, "WAL:");
@@ -4300,6 +4301,9 @@ show_wal_usage(ExplainState *es, const WalUsage *usage)
if (usage->wal_buffers_full > 0)
appendStringInfo(es->str, " buffers full=%" PRId64,
usage->wal_buffers_full);
+ if (usage->wal_fpi_bytes > 0)
+ appendStringInfo(es->str, " fpi bytes=%" PRIu64,
+ usage->wal_fpi_bytes);
appendStringInfoChar(es->str, '\n');
}
}
@@ -4313,6 +4317,8 @@ show_wal_usage(ExplainState *es, const WalUsage *usage)
usage->wal_bytes, es);
ExplainPropertyInteger("WAL Buffers Full", NULL,
usage->wal_buffers_full, es);
+ ExplainPropertyUInteger("WAL FPI Bytes Compressed", NULL,
+ usage->wal_fpi_bytes, es);
}
}
diff --git a/src/backend/executor/instrument.c b/src/backend/executor/instrument.c
index 56e635f4700..e5f9ec14ef6 100644
--- a/src/backend/executor/instrument.c
+++ b/src/backend/executor/instrument.c
@@ -281,6 +281,7 @@ WalUsageAdd(WalUsage *dst, WalUsage *add)
dst->wal_records += add->wal_records;
dst->wal_fpi += add->wal_fpi;
dst->wal_buffers_full += add->wal_buffers_full;
+ dst->wal_fpi_bytes += add->wal_fpi_bytes;
}
void
@@ -290,4 +291,5 @@ WalUsageAccumDiff(WalUsage *dst, const WalUsage *add, const WalUsage *sub)
dst->wal_records += add->wal_records - sub->wal_records;
dst->wal_fpi += add->wal_fpi - sub->wal_fpi;
dst->wal_buffers_full += add->wal_buffers_full - sub->wal_buffers_full;
+ dst->wal_fpi_bytes += add->wal_fpi_bytes - sub->wal_fpi_bytes;
}
--
2.47.3
On Thu, Oct 23, 2025 at 06:36:01PM +0900, Shinya Kato wrote:
Okay, since I'm not strongly attached to this idea, I've removed the
0003 patch for now.
The fact that we cannot access this information without a pg_waldump
or a pg_walinspect, which may not be available, and can be expensive,
is a deal-breaker for me.. Or we may not have a direct access to the
WAL segments.
Without the changes in instrument.c from patch 0002, patch 0001 that
implements the basics would not work. So.. I have moved the changes
of instrument.c to 0001, reordered the fields to be more consistent,
did two bumps (catalog, stats file), simplified the docs, then applied
the result.
By the way, Kato-san, what do you think about the attached extra
simplification? With the FPIs counted in bytes, I don't see much a
point in passing around the number of FPIs generated from
XLogRecordAssemble() to XLogInsertRecord() .
--
Michael
Attachments:
wal-num-fpi-simple.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index d12798be3d80..9b6a042a607c 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -201,7 +201,6 @@ struct XLogReaderState;
extern XLogRecPtr XLogInsertRecord(struct XLogRecData *rdata,
XLogRecPtr fpw_lsn,
uint8 flags,
- int num_fpi,
bool topxid_included);
extern void XLogFlush(XLogRecPtr record);
extern bool XLogBackgroundFlush(void);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index eceab3412558..bc1627286539 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -748,7 +748,6 @@ XLogRecPtr
XLogInsertRecord(XLogRecData *rdata,
XLogRecPtr fpw_lsn,
uint8 flags,
- int num_fpi,
bool topxid_included)
{
XLogCtlInsert *Insert = &XLogCtl->Insert;
@@ -1080,7 +1079,6 @@ XLogInsertRecord(XLogRecData *rdata,
{
pgWalUsage.wal_bytes += rechdr->xl_tot_len;
pgWalUsage.wal_records++;
- pgWalUsage.wal_fpi += num_fpi;
/* Required for the flush of pending stats WAL data */
pgstat_report_fixed = true;
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index b3abf386f801..6b47c13a2c25 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -138,7 +138,7 @@ static MemoryContext xloginsert_cxt;
static XLogRecData *XLogRecordAssemble(RmgrId rmid, uint8 info,
XLogRecPtr RedoRecPtr, bool doPageWrites,
- XLogRecPtr *fpw_lsn, int *num_fpi,
+ XLogRecPtr *fpw_lsn,
bool *topxid_included);
static bool XLogCompressBackupBlock(const PageData *page, uint16 hole_offset,
uint16 hole_length, void *dest, uint16 *dlen);
@@ -511,7 +511,6 @@ XLogInsert(RmgrId rmid, uint8 info)
bool topxid_included = false;
XLogRecPtr fpw_lsn;
XLogRecData *rdt;
- int num_fpi = 0;
/*
* Get values needed to decide whether to do full-page writes. Since
@@ -521,9 +520,9 @@ XLogInsert(RmgrId rmid, uint8 info)
GetFullPageWriteInfo(&RedoRecPtr, &doPageWrites);
rdt = XLogRecordAssemble(rmid, info, RedoRecPtr, doPageWrites,
- &fpw_lsn, &num_fpi, &topxid_included);
+ &fpw_lsn, &topxid_included);
- EndPos = XLogInsertRecord(rdt, fpw_lsn, curinsert_flags, num_fpi,
+ EndPos = XLogInsertRecord(rdt, fpw_lsn, curinsert_flags,
topxid_included);
} while (EndPos == InvalidXLogRecPtr);
@@ -562,7 +561,7 @@ XLogSimpleInsertInt64(RmgrId rmid, uint8 info, int64 value)
static XLogRecData *
XLogRecordAssemble(RmgrId rmid, uint8 info,
XLogRecPtr RedoRecPtr, bool doPageWrites,
- XLogRecPtr *fpw_lsn, int *num_fpi, bool *topxid_included)
+ XLogRecPtr *fpw_lsn, bool *topxid_included)
{
XLogRecData *rdt;
uint64 total_len = 0;
@@ -715,9 +714,6 @@ XLogRecordAssemble(RmgrId rmid, uint8 info,
*/
bkpb.fork_flags |= BKPBLOCK_HAS_IMAGE;
- /* Report a full page image constructed for the WAL record */
- *num_fpi += 1;
-
/*
* Construct XLogRecData entries for the page content.
*/
@@ -799,6 +795,8 @@ XLogRecordAssemble(RmgrId rmid, uint8 info,
total_len += bimg.length;
+ /* Report a full page image constructed for the WAL record */
+ pgWalUsage.wal_fpi += 1;
/* Track the WAL full page images in bytes */
pgWalUsage.wal_fpi_bytes += bimg.length;
pgstat_report_fixed = true;
On Tue, Oct 28, 2025 at 4:32 PM Michael Paquier <michael@paquier.xyz> wrote:
Without the changes in instrument.c from patch 0002, patch 0001 that
implements the basics would not work. So.. I have moved the changes
of instrument.c to 0001, reordered the fields to be more consistent,
did two bumps (catalog, stats file), simplified the docs, then applied
the result.
Sorry for the inconvenience, and thank you for committing. I have
revised patch 0002, which adds wal_fpi_bytes to EXPLAIN (WAL).
By the way, Kato-san, what do you think about the attached extra
simplification? With the FPIs counted in bytes, I don't see much a
point in passing around the number of FPIs generated from
XLogRecordAssemble() to XLogInsertRecord() .
I investigated previous discussions and found [0]/messages/by-id/20200329121944.GA79261@nol. This thread
mentioned that XLogInsert() calls XLogRecordAssemble() multiple times
in its do-while loop, so the value might be invalid.
Based on the discussion above, it seems my previous patch also has the
same issue.
[0]: /messages/by-id/20200329121944.GA79261@nol
--
Best regards,
Shinya Kato
NTT OSS Center
Attachments:
v3-0002-Expose-WAL-FPI-byte-totals-in-EXPLAIN.patchapplication/octet-stream; name=v3-0002-Expose-WAL-FPI-byte-totals-in-EXPLAIN.patchDownload
From 91e98ced946d4668d005cad0294874ec5e7d27af Mon Sep 17 00:00:00 2001
From: Shinya Kato <shinya11.kato@gmail.com>
Date: Tue, 28 Oct 2025 19:14:14 +0900
Subject: [PATCH v3] Expose WAL FPI byte totals in EXPLAIN
Show the new wal_fpi_bytes counter when EXPLAIN (ANALYZE, WAL) reports
per-plan WAL usage, emitting it alongside the existing counters in both
text and JSON/YAML output. Extend WalUsage aggregation helpers so the
value accumulates correctly.
Author: Shinya Kato <shinya11.kato@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Discusssion: https://postgr.es/m/CAOzEurQtZEAfg6P0kU3Wa-f9BWQOi0RzJEMPN56wNTOmJLmfaQ@mail.gmail.com
---
doc/src/sgml/ref/explain.sgml | 4 +++-
src/backend/commands/explain.c | 8 +++++++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml
index 6dda680aa0d..ead4017d172 100644
--- a/doc/src/sgml/ref/explain.sgml
+++ b/doc/src/sgml/ref/explain.sgml
@@ -241,7 +241,9 @@ ROLLBACK;
<para>
Include information on WAL record generation. Specifically, include the
number of records, number of full page images (fpi), the amount of WAL
- generated in bytes and the number of times the WAL buffers became full.
+ generated in bytes, the number of times the WAL buffers became full, the
+ amount of WAL fpi generated in bytes (if <varname>wal_compression</varname>
+ is enabled, this reflects the compressed size).
In text format, only non-zero values are printed.
This parameter may only be used when <literal>ANALYZE</literal> is also
enabled. It defaults to <literal>FALSE</literal>.
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index e6edae0845c..68c3d20353c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -4283,7 +4283,8 @@ show_wal_usage(ExplainState *es, const WalUsage *usage)
{
/* Show only positive counter values. */
if ((usage->wal_records > 0) || (usage->wal_fpi > 0) ||
- (usage->wal_bytes > 0) || (usage->wal_buffers_full > 0))
+ (usage->wal_bytes > 0) || (usage->wal_buffers_full > 0) ||
+ (usage->wal_fpi_bytes > 0))
{
ExplainIndentText(es);
appendStringInfoString(es->str, "WAL:");
@@ -4300,6 +4301,9 @@ show_wal_usage(ExplainState *es, const WalUsage *usage)
if (usage->wal_buffers_full > 0)
appendStringInfo(es->str, " buffers full=%" PRId64,
usage->wal_buffers_full);
+ if (usage->wal_fpi_bytes > 0)
+ appendStringInfo(es->str, " fpi bytes=%" PRIu64,
+ usage->wal_fpi_bytes);
appendStringInfoChar(es->str, '\n');
}
}
@@ -4313,6 +4317,8 @@ show_wal_usage(ExplainState *es, const WalUsage *usage)
usage->wal_bytes, es);
ExplainPropertyInteger("WAL Buffers Full", NULL,
usage->wal_buffers_full, es);
+ ExplainPropertyUInteger("WAL FPI Bytes Compressed", NULL,
+ usage->wal_fpi_bytes, es);
}
}
--
2.47.3
On Tue, Oct 28, 2025 at 07:33:00PM +0900, Shinya Kato wrote:
I investigated previous discussions and found [0]. This thread
mentioned that XLogInsert() calls XLogRecordAssemble() multiple times
in its do-while loop, so the value might be invalid.Based on the discussion above, it seems my previous patch also has the
same issue.
Dammit, you are right. I didn't see through this one. Even on HEAD
it is true that we may trigger an early exit of XLogInsertRecord() and
call XLogRecordAssemble() multiple times with a different FPI setup.
So we cannot do direct manipulations of pgWalUsage in
XLogRecordAssemble(), we must do these once the record is inserted.
I'll clean up that tomorrow, which can be summarized as something like
the attached (quick fix, need to double-check).
--
Michael
Attachments:
wal-fpi-fix.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index d12798be3d80..a12757e46e5b 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -202,6 +202,7 @@ extern XLogRecPtr XLogInsertRecord(struct XLogRecData *rdata,
XLogRecPtr fpw_lsn,
uint8 flags,
int num_fpi,
+ uint64 fpi_bytes,
bool topxid_included);
extern void XLogFlush(XLogRecPtr record);
extern bool XLogBackgroundFlush(void);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index eceab3412558..fd91bcd68ecd 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -749,6 +749,7 @@ XLogInsertRecord(XLogRecData *rdata,
XLogRecPtr fpw_lsn,
uint8 flags,
int num_fpi,
+ uint64 fpi_bytes,
bool topxid_included)
{
XLogCtlInsert *Insert = &XLogCtl->Insert;
@@ -1081,6 +1082,7 @@ XLogInsertRecord(XLogRecData *rdata,
pgWalUsage.wal_bytes += rechdr->xl_tot_len;
pgWalUsage.wal_records++;
pgWalUsage.wal_fpi += num_fpi;
+ pgWalUsage.wal_fpi_bytes += fpi_bytes;
/* Required for the flush of pending stats WAL data */
pgstat_report_fixed = true;
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index b3abf386f801..58cb4b1b00c9 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -139,6 +139,7 @@ static MemoryContext xloginsert_cxt;
static XLogRecData *XLogRecordAssemble(RmgrId rmid, uint8 info,
XLogRecPtr RedoRecPtr, bool doPageWrites,
XLogRecPtr *fpw_lsn, int *num_fpi,
+ uint64 *fpi_bytes,
bool *topxid_included);
static bool XLogCompressBackupBlock(const PageData *page, uint16 hole_offset,
uint16 hole_length, void *dest, uint16 *dlen);
@@ -512,6 +513,7 @@ XLogInsert(RmgrId rmid, uint8 info)
XLogRecPtr fpw_lsn;
XLogRecData *rdt;
int num_fpi = 0;
+ uint64 fpi_bytes = 0;
/*
* Get values needed to decide whether to do full-page writes. Since
@@ -521,10 +523,11 @@ XLogInsert(RmgrId rmid, uint8 info)
GetFullPageWriteInfo(&RedoRecPtr, &doPageWrites);
rdt = XLogRecordAssemble(rmid, info, RedoRecPtr, doPageWrites,
- &fpw_lsn, &num_fpi, &topxid_included);
+ &fpw_lsn, &num_fpi, &fpi_bytes,
+ &topxid_included);
EndPos = XLogInsertRecord(rdt, fpw_lsn, curinsert_flags, num_fpi,
- topxid_included);
+ fpi_bytes, topxid_included);
} while (EndPos == InvalidXLogRecPtr);
XLogResetInsertion();
@@ -562,7 +565,8 @@ XLogSimpleInsertInt64(RmgrId rmid, uint8 info, int64 value)
static XLogRecData *
XLogRecordAssemble(RmgrId rmid, uint8 info,
XLogRecPtr RedoRecPtr, bool doPageWrites,
- XLogRecPtr *fpw_lsn, int *num_fpi, bool *topxid_included)
+ XLogRecPtr *fpw_lsn, int *num_fpi, uint64 *fpi_bytes,
+ bool *topxid_included)
{
XLogRecData *rdt;
uint64 total_len = 0;
@@ -800,8 +804,7 @@ XLogRecordAssemble(RmgrId rmid, uint8 info,
total_len += bimg.length;
/* Track the WAL full page images in bytes */
- pgWalUsage.wal_fpi_bytes += bimg.length;
- pgstat_report_fixed = true;
+ *fpi_bytes += bimg.length;
}
if (needs_data)
On Tue, Oct 28, 2025 at 10:04:20PM +0900, Michael Paquier wrote:
I'll clean up that tomorrow, which can be summarized as something like
the attached (quick fix, need to double-check).
Done this cleanup as d3111cb753e8, and tweaked a bit the second patch
(order of the fields and docs) before applying it as 5ab0b6a24807.
On top of what has been already done here, should we also update the
logs generated by do_analyze_rel() and heap_vacuum_rel()?
--
Michael
On Thu, Oct 30, 2025 at 3:57 PM Michael Paquier <michael@paquier.xyz> wrote:
On Tue, Oct 28, 2025 at 10:04:20PM +0900, Michael Paquier wrote:
I'll clean up that tomorrow, which can be summarized as something like
the attached (quick fix, need to double-check).Done this cleanup as d3111cb753e8, and tweaked a bit the second patch
(order of the fields and docs) before applying it as 5ab0b6a24807.
Thank you for the fix and committing.
On top of what has been already done here, should we also update the
logs generated by do_analyze_rel() and heap_vacuum_rel()?
You're right, I missed them. I have created a patch that addresses the
above. Similar to EXPLAIN, I wanted to use 'fpi' and 'fpi bytes', but
the VACUUM/ANALYZE logs showed 'full pages images', so I used 'full
page image bytes'.
postgres=# vacuum (verbose) pg_class;
~snip~
WAL usage: 1 records, 1 full page images, 7935 bytes, 7816 full page
image bytes, 0 buffers full
postgres=# analyze (verbose) pg_class;
~snip~
WAL usage: 92 records, 6 full page images, 49416 bytes, 37488 full
page image bytes, 0 buffers full
--
Best regards,
Shinya Kato
NTT OSS Center
Attachments:
v4-0003-Add-wal_fpi_bytes-to-VACUUM-and-ANALYZE-logs.patchapplication/octet-stream; name=v4-0003-Add-wal_fpi_bytes-to-VACUUM-and-ANALYZE-logs.patchDownload
From 1051b9d1ce90b729cf2af87537db3f92cb396f64 Mon Sep 17 00:00:00 2001
From: Shinya Kato <shinya11.kato@gmail.com>
Date: Fri, 31 Oct 2025 10:52:18 +0900
Subject: [PATCH v4] Add wal_fpi_bytes to VACUUM and ANALYZE logs
The new wal_fpi_bytes counter calculates the total amount of full page
images inserted in WAL records, in bytes. This commit adds this
information to VACUUM and ANALYZE logs alongside the existing counters,
building upon f9a09aa29520.
Author: Shinya Kato <shinya11.kato@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Discussion: https://postgr.es/m/CAOzEurQtZEAfg6P0kU3Wa-f9BWQOi0RzJEMPN56wNTOmJLmfaQ@mail.gmail.com
---
src/backend/access/heap/vacuumlazy.c | 3 ++-
src/backend/commands/analyze.c | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index d2b031fdd06..61fe623cc60 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -1148,10 +1148,11 @@ heap_vacuum_rel(Relation rel, const VacuumParams params,
total_blks_read,
total_blks_dirtied);
appendStringInfo(&buf,
- _("WAL usage: %" PRId64 " records, %" PRId64 " full page images, %" PRIu64 " bytes, %" PRId64 " buffers full\n"),
+ _("WAL usage: %" PRId64 " records, %" PRId64 " full page images, %" PRIu64 " bytes, %" PRIu64 " full page image bytes, %" PRId64 " buffers full\n"),
walusage.wal_records,
walusage.wal_fpi,
walusage.wal_bytes,
+ walusage.wal_fpi_bytes,
walusage.wal_buffers_full);
appendStringInfo(&buf, _("system usage: %s"), pg_rusage_show(&ru0));
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c2e216563c6..25089fae3e0 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -831,10 +831,11 @@ do_analyze_rel(Relation onerel, const VacuumParams params,
total_blks_read,
total_blks_dirtied);
appendStringInfo(&buf,
- _("WAL usage: %" PRId64 " records, %" PRId64 " full page images, %" PRIu64 " bytes, %" PRId64 " buffers full\n"),
+ _("WAL usage: %" PRId64 " records, %" PRId64 " full page images, %" PRIu64 " bytes, %" PRIu64 " full page image bytes, %" PRId64 " buffers full\n"),
walusage.wal_records,
walusage.wal_fpi,
walusage.wal_bytes,
+ walusage.wal_fpi_bytes,
walusage.wal_buffers_full);
appendStringInfo(&buf, _("system usage: %s"), pg_rusage_show(&ru0));
--
2.47.3
On Fri, Oct 31, 2025 at 11:15:03AM +0900, Shinya Kato wrote:
You're right, I missed them. I have created a patch that addresses the
above. Similar to EXPLAIN, I wanted to use 'fpi' and 'fpi bytes', but
the VACUUM/ANALYZE logs showed 'full pages images', so I used 'full
page image bytes'.
I am cool with "full page image bytes". Applied.
--
Michael