agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH 2/5] Remove TLI from some argument lists. 15+ messages / 3 participants [nested] [flat]
* [PATCH 2/5] Remove TLI from some argument lists. @ 2019-07-09 09:54 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 15+ messages in thread From: Antonin Houska @ 2019-07-09 09:54 UTC (permalink / raw) The timeline information is available to caller via XLogReaderState. Now that XLogRead() is gonna be (sometimes) responsible for determining the TLI, it would have to be added the (TimeLineID *) argument too, just to be consistent with the current coding style. Since XLogRead() updates also other position-specific fields of XLogReaderState, it seems simpler if we remove the output argument from XLogPageReadCB and always report the TLI via XLogReaderState. --- src/backend/access/transam/xlog.c | 7 +++---- src/backend/access/transam/xlogreader.c | 6 +++--- src/backend/access/transam/xlogutils.c | 11 ++++++----- src/backend/replication/logical/logicalfuncs.c | 4 ++-- src/backend/replication/walsender.c | 2 +- src/bin/pg_rewind/parsexlog.c | 9 ++++----- src/bin/pg_waldump/pg_waldump.c | 2 +- src/include/access/xlogreader.h | 8 +++----- src/include/access/xlogutils.h | 5 ++--- src/include/replication/logicalfuncs.h | 2 +- 10 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index b6c9353cbd..f30c2ce0ce 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -885,8 +885,7 @@ static int XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli, int source, bool notfoundOk); static int XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source); static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *readBuf, - TimeLineID *readTLI); + int reqLen, XLogRecPtr targetRecPtr, char *readBuf); static bool WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, bool fetching_ckpt, XLogRecPtr tliRecPtr); static int emode_for_corrupt_record(int emode, XLogRecPtr RecPtr); @@ -11520,7 +11519,7 @@ CancelBackup(void) */ static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen, - XLogRecPtr targetRecPtr, char *readBuf, TimeLineID *readTLI) + XLogRecPtr targetRecPtr, char *readBuf) { XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data; @@ -11637,7 +11636,7 @@ retry: Assert(targetPageOff == readOff); Assert(reqLen <= readLen); - *readTLI = curFileTLI; + xlogreader->readPageTLI = curFileTLI; /* * Check the page header immediately, so that we can retry immediately if diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index 08bc9695c1..ed7a72bc14 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -557,7 +557,7 @@ ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen) readLen = state->read_page(state, targetSegmentPtr, XLOG_BLCKSZ, state->currRecPtr, - state->readBuf, &state->readPageTLI); + state->readBuf); if (readLen < 0) goto err; @@ -575,7 +575,7 @@ ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen) */ readLen = state->read_page(state, pageptr, Max(reqLen, SizeOfXLogShortPHD), state->currRecPtr, - state->readBuf, &state->readPageTLI); + state->readBuf); if (readLen < 0) goto err; @@ -594,7 +594,7 @@ ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen) { readLen = state->read_page(state, pageptr, XLogPageHeaderSize(hdr), state->currRecPtr, - state->readBuf, &state->readPageTLI); + state->readBuf); if (readLen < 0) goto err; } diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 10a663bae6..cba180912b 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -909,12 +909,12 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa */ int read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *cur_page, - TimeLineID *pageTLI) + int reqLen, XLogRecPtr targetRecPtr, char *cur_page) { XLogRecPtr read_upto, loc; int count; + TimeLineID pageTLI; loc = targetPagePtr + reqLen; @@ -934,7 +934,7 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, else read_upto = GetXLogReplayRecPtr(&ThisTimeLineID); - *pageTLI = ThisTimeLineID; + pageTLI = ThisTimeLineID; /* * Check which timeline to get the record from. @@ -991,7 +991,7 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, * nothing cares so long as the timeline doesn't go backwards. We * should read the page header instead; FIXME someday. */ - *pageTLI = state->currTLI; + pageTLI = state->currTLI; /* No need to wait on a historical timeline */ break; @@ -1022,8 +1022,9 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, * as 'count', read the whole page anyway. It's guaranteed to be * zero-padded up to the page boundary if it's incomplete. */ - XLogRead(cur_page, state->wal_segment_size, *pageTLI, targetPagePtr, + XLogRead(cur_page, state->wal_segment_size, pageTLI, targetPagePtr, XLOG_BLCKSZ); + state->readPageTLI = pageTLI; /* number of valid bytes in the buffer */ return count; diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c index d974400d6e..d1cf80d441 100644 --- a/src/backend/replication/logical/logicalfuncs.c +++ b/src/backend/replication/logical/logicalfuncs.c @@ -116,10 +116,10 @@ check_permissions(void) int logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *cur_page, TimeLineID *pageTLI) + int reqLen, XLogRecPtr targetRecPtr, char *cur_page) { return read_local_xlog_page(state, targetPagePtr, reqLen, - targetRecPtr, cur_page, pageTLI); + targetRecPtr, cur_page); } /* diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index e7a59b0a92..c8802bbc1f 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -763,7 +763,7 @@ StartReplication(StartReplicationCmd *cmd) */ static int logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, - XLogRecPtr targetRecPtr, char *cur_page, TimeLineID *pageTLI) + XLogRecPtr targetRecPtr, char *cur_page) { XLogRecPtr flushptr; int count; diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c index 287af60c4e..00168c27dc 100644 --- a/src/bin/pg_rewind/parsexlog.c +++ b/src/bin/pg_rewind/parsexlog.c @@ -47,10 +47,10 @@ typedef struct XLogPageReadPrivate int tliIndex; } XLogPageReadPrivate; + static int SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *readBuf, - TimeLineID *pageTLI); + int reqLen, XLogRecPtr targetRecPtr, char *readBuf); /* * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline @@ -238,8 +238,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex, /* XLogreader callback function, to read a WAL page */ static int SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *readBuf, - TimeLineID *pageTLI) + int reqLen, XLogRecPtr targetRecPtr, char *readBuf) { XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data; uint32 targetPageOff; @@ -322,7 +321,7 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, Assert(targetSegNo == xlogreadsegno); - *pageTLI = targetHistory[private->tliIndex].tli; + xlogreader->readPageTLI = targetHistory[private->tliIndex].tli; return XLOG_BLCKSZ; } diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c index b95d467805..40c64a0bbf 100644 --- a/src/bin/pg_waldump/pg_waldump.c +++ b/src/bin/pg_waldump/pg_waldump.c @@ -423,7 +423,7 @@ XLogDumpXLogRead(const char *directory, TimeLineID timeline_id, */ static int XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, - XLogRecPtr targetPtr, char *readBuff, TimeLineID *curFileTLI) + XLogRecPtr targetPtr, char *readBuff) { XLogDumpPrivate *private = state->private_data; int count = XLOG_BLCKSZ; diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h index a3d3cc1e7b..e23223ba5b 100644 --- a/src/include/access/xlogreader.h +++ b/src/include/access/xlogreader.h @@ -34,8 +34,7 @@ typedef int (*XLogPageReadCB) (XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen, XLogRecPtr targetRecPtr, - char *readBuf, - TimeLineID *pageTLI); + char *readBuf); typedef struct { @@ -95,9 +94,8 @@ struct XLogReaderState * actual WAL record it's interested in. In that case, targetRecPtr can * be used to determine which timeline to read the page from. * - * The callback shall set *pageTLI to the TLI of the file the page was - * read from. It is currently used only for error reporting purposes, to - * reconstruct the name of the WAL file where an error occurred. + * The callback shall set ->readPageTLI to the TLI of the file the page + * was read from. */ XLogPageReadCB read_page; diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h index 4105b59904..4fb305bafd 100644 --- a/src/include/access/xlogutils.h +++ b/src/include/access/xlogutils.h @@ -47,12 +47,11 @@ extern Buffer XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum, extern Relation CreateFakeRelcacheEntry(RelFileNode rnode); extern void FreeFakeRelcacheEntry(Relation fakerel); + extern int read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, - XLogRecPtr targetRecPtr, char *cur_page, - TimeLineID *pageTLI); + XLogRecPtr targetRecPtr, char *cur_page); extern void XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wantLength); - #endif diff --git a/src/include/replication/logicalfuncs.h b/src/include/replication/logicalfuncs.h index a9c178a9e6..012096f183 100644 --- a/src/include/replication/logicalfuncs.h +++ b/src/include/replication/logicalfuncs.h @@ -14,6 +14,6 @@ extern int logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, XLogRecPtr targetRecPtr, - char *cur_page, TimeLineID *pageTLI); + char *cur_page); #endif -- 2.16.4 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v04-0003-Introduce-XLogSegment-structure.patch ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH 1/4] Remove TLI from some argument lists. @ 2019-09-09 09:53 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 15+ messages in thread From: Antonin Houska @ 2019-09-09 09:53 UTC (permalink / raw) The timeline information is available to caller via XLogReaderState. Now that XLogRead() is gonna be (sometimes) responsible for determining the TLI, it would have to be added the (TimeLineID *) argument too, just to be consistent with the current coding style. Since XLogRead() updates also other position-specific fields of XLogReaderState, it seems simpler if we remove the output argument from XLogPageReadCB and always report the TLI via XLogReaderState. --- src/backend/access/transam/xlog.c | 7 +++---- src/backend/access/transam/xlogreader.c | 6 +++--- src/backend/access/transam/xlogutils.c | 11 ++++++----- src/backend/replication/logical/logicalfuncs.c | 4 ++-- src/backend/replication/walsender.c | 2 +- src/bin/pg_rewind/parsexlog.c | 9 ++++----- src/bin/pg_waldump/pg_waldump.c | 2 +- src/include/access/xlogreader.h | 8 +++----- src/include/access/xlogutils.h | 5 ++--- src/include/replication/logicalfuncs.h | 2 +- 10 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 6876537b62..cd948dbefc 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -885,8 +885,7 @@ static int XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli, int source, bool notfoundOk); static int XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source); static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *readBuf, - TimeLineID *readTLI); + int reqLen, XLogRecPtr targetRecPtr, char *readBuf); static bool WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, bool fetching_ckpt, XLogRecPtr tliRecPtr); static int emode_for_corrupt_record(int emode, XLogRecPtr RecPtr); @@ -11523,7 +11522,7 @@ CancelBackup(void) */ static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen, - XLogRecPtr targetRecPtr, char *readBuf, TimeLineID *readTLI) + XLogRecPtr targetRecPtr, char *readBuf) { XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data; @@ -11640,7 +11639,7 @@ retry: Assert(targetPageOff == readOff); Assert(reqLen <= readLen); - *readTLI = curFileTLI; + xlogreader->readPageTLI = curFileTLI; /* * Check the page header immediately, so that we can retry immediately if diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index a66e3324b1..2184f4291d 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -559,7 +559,7 @@ ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen) readLen = state->read_page(state, targetSegmentPtr, XLOG_BLCKSZ, state->currRecPtr, - state->readBuf, &state->readPageTLI); + state->readBuf); if (readLen < 0) goto err; @@ -577,7 +577,7 @@ ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen) */ readLen = state->read_page(state, pageptr, Max(reqLen, SizeOfXLogShortPHD), state->currRecPtr, - state->readBuf, &state->readPageTLI); + state->readBuf); if (readLen < 0) goto err; @@ -596,7 +596,7 @@ ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen) { readLen = state->read_page(state, pageptr, XLogPageHeaderSize(hdr), state->currRecPtr, - state->readBuf, &state->readPageTLI); + state->readBuf); if (readLen < 0) goto err; } diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 1fc39333f1..680bed8278 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -909,12 +909,12 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa */ int read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *cur_page, - TimeLineID *pageTLI) + int reqLen, XLogRecPtr targetRecPtr, char *cur_page) { XLogRecPtr read_upto, loc; int count; + TimeLineID pageTLI; loc = targetPagePtr + reqLen; @@ -934,7 +934,7 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, else read_upto = GetXLogReplayRecPtr(&ThisTimeLineID); - *pageTLI = ThisTimeLineID; + pageTLI = ThisTimeLineID; /* * Check which timeline to get the record from. @@ -991,7 +991,7 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, * nothing cares so long as the timeline doesn't go backwards. We * should read the page header instead; FIXME someday. */ - *pageTLI = state->currTLI; + pageTLI = state->currTLI; /* No need to wait on a historical timeline */ break; @@ -1022,8 +1022,9 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, * as 'count', read the whole page anyway. It's guaranteed to be * zero-padded up to the page boundary if it's incomplete. */ - XLogRead(cur_page, state->wal_segment_size, *pageTLI, targetPagePtr, + XLogRead(cur_page, state->wal_segment_size, pageTLI, targetPagePtr, XLOG_BLCKSZ); + state->readPageTLI = pageTLI; /* number of valid bytes in the buffer */ return count; diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c index d974400d6e..d1cf80d441 100644 --- a/src/backend/replication/logical/logicalfuncs.c +++ b/src/backend/replication/logical/logicalfuncs.c @@ -116,10 +116,10 @@ check_permissions(void) int logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *cur_page, TimeLineID *pageTLI) + int reqLen, XLogRecPtr targetRecPtr, char *cur_page) { return read_local_xlog_page(state, targetPagePtr, reqLen, - targetRecPtr, cur_page, pageTLI); + targetRecPtr, cur_page); } /* diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 23870a25a5..28d8c31af8 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -763,7 +763,7 @@ StartReplication(StartReplicationCmd *cmd) */ static int logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, - XLogRecPtr targetRecPtr, char *cur_page, TimeLineID *pageTLI) + XLogRecPtr targetRecPtr, char *cur_page) { XLogRecPtr flushptr; int count; diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c index 63c3879ead..0a89f9c02a 100644 --- a/src/bin/pg_rewind/parsexlog.c +++ b/src/bin/pg_rewind/parsexlog.c @@ -47,10 +47,10 @@ typedef struct XLogPageReadPrivate int tliIndex; } XLogPageReadPrivate; + static int SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *readBuf, - TimeLineID *pageTLI); + int reqLen, XLogRecPtr targetRecPtr, char *readBuf); /* * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline @@ -237,8 +237,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex, /* XLogReader callback function, to read a WAL page */ static int SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *readBuf, - TimeLineID *pageTLI) + int reqLen, XLogRecPtr targetRecPtr, char *readBuf) { XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data; uint32 targetPageOff; @@ -321,7 +320,7 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, Assert(targetSegNo == xlogreadsegno); - *pageTLI = targetHistory[private->tliIndex].tli; + xlogreader->readPageTLI = targetHistory[private->tliIndex].tli; return XLOG_BLCKSZ; } diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c index b95d467805..40c64a0bbf 100644 --- a/src/bin/pg_waldump/pg_waldump.c +++ b/src/bin/pg_waldump/pg_waldump.c @@ -423,7 +423,7 @@ XLogDumpXLogRead(const char *directory, TimeLineID timeline_id, */ static int XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, - XLogRecPtr targetPtr, char *readBuff, TimeLineID *curFileTLI) + XLogRecPtr targetPtr, char *readBuff) { XLogDumpPrivate *private = state->private_data; int count = XLOG_BLCKSZ; diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h index 735b1bd2fd..d64a9ad82f 100644 --- a/src/include/access/xlogreader.h +++ b/src/include/access/xlogreader.h @@ -38,8 +38,7 @@ typedef int (*XLogPageReadCB) (XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen, XLogRecPtr targetRecPtr, - char *readBuf, - TimeLineID *pageTLI); + char *readBuf); typedef struct { @@ -99,9 +98,8 @@ struct XLogReaderState * actual WAL record it's interested in. In that case, targetRecPtr can * be used to determine which timeline to read the page from. * - * The callback shall set *pageTLI to the TLI of the file the page was - * read from. It is currently used only for error reporting purposes, to - * reconstruct the name of the WAL file where an error occurred. + * The callback shall set ->readPageTLI to the TLI of the file the page + * was read from. */ XLogPageReadCB read_page; diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h index 4105b59904..4fb305bafd 100644 --- a/src/include/access/xlogutils.h +++ b/src/include/access/xlogutils.h @@ -47,12 +47,11 @@ extern Buffer XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum, extern Relation CreateFakeRelcacheEntry(RelFileNode rnode); extern void FreeFakeRelcacheEntry(Relation fakerel); + extern int read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, - XLogRecPtr targetRecPtr, char *cur_page, - TimeLineID *pageTLI); + XLogRecPtr targetRecPtr, char *cur_page); extern void XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wantLength); - #endif diff --git a/src/include/replication/logicalfuncs.h b/src/include/replication/logicalfuncs.h index a9c178a9e6..012096f183 100644 --- a/src/include/replication/logicalfuncs.h +++ b/src/include/replication/logicalfuncs.h @@ -14,6 +14,6 @@ extern int logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, XLogRecPtr targetRecPtr, - char *cur_page, TimeLineID *pageTLI); + char *cur_page); #endif -- 2.22.0 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v05-0003-Introduce-XLogSegment-structure.patch ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH 2/6] Remove TLI from some argument lists. @ 2019-09-23 05:40 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 15+ messages in thread From: Antonin Houska @ 2019-09-23 05:40 UTC (permalink / raw) The timeline information is available to caller via XLogReaderState. Now that XLogRead() is gonna be (sometimes) responsible for determining the TLI, it would have to be added the (TimeLineID *) argument too, just to be consistent with the current coding style. Since XLogRead() updates also other position-specific fields of XLogReaderState, it seems simpler if we remove the output argument from XLogPageReadCB and always report the TLI via XLogReaderState. --- src/backend/access/transam/xlog.c | 7 +++---- src/backend/access/transam/xlogreader.c | 6 +++--- src/backend/access/transam/xlogutils.c | 11 ++++++----- src/backend/replication/logical/logicalfuncs.c | 4 ++-- src/backend/replication/walsender.c | 2 +- src/bin/pg_rewind/parsexlog.c | 8 +++----- src/bin/pg_waldump/pg_waldump.c | 2 +- src/include/access/xlogreader.h | 8 +++----- src/include/access/xlogutils.h | 5 ++--- src/include/replication/logicalfuncs.h | 2 +- 10 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index b7ff004234..7a89dfed7f 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -885,8 +885,7 @@ static int XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli, int source, bool notfoundOk); static int XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source); static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *readBuf, - TimeLineID *readTLI); + int reqLen, XLogRecPtr targetRecPtr, char *readBuf); static bool WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, bool fetching_ckpt, XLogRecPtr tliRecPtr); static int emode_for_corrupt_record(int emode, XLogRecPtr RecPtr); @@ -11523,7 +11522,7 @@ CancelBackup(void) */ static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen, - XLogRecPtr targetRecPtr, char *readBuf, TimeLineID *readTLI) + XLogRecPtr targetRecPtr, char *readBuf) { XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data; @@ -11640,7 +11639,7 @@ retry: Assert(targetPageOff == readOff); Assert(reqLen <= readLen); - *readTLI = curFileTLI; + xlogreader->readPageTLI = curFileTLI; /* * Check the page header immediately, so that we can retry immediately if diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c index a66e3324b1..2184f4291d 100644 --- a/src/backend/access/transam/xlogreader.c +++ b/src/backend/access/transam/xlogreader.c @@ -559,7 +559,7 @@ ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen) readLen = state->read_page(state, targetSegmentPtr, XLOG_BLCKSZ, state->currRecPtr, - state->readBuf, &state->readPageTLI); + state->readBuf); if (readLen < 0) goto err; @@ -577,7 +577,7 @@ ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen) */ readLen = state->read_page(state, pageptr, Max(reqLen, SizeOfXLogShortPHD), state->currRecPtr, - state->readBuf, &state->readPageTLI); + state->readBuf); if (readLen < 0) goto err; @@ -596,7 +596,7 @@ ReadPageInternal(XLogReaderState *state, XLogRecPtr pageptr, int reqLen) { readLen = state->read_page(state, pageptr, XLogPageHeaderSize(hdr), state->currRecPtr, - state->readBuf, &state->readPageTLI); + state->readBuf); if (readLen < 0) goto err; } diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c index 1fc39333f1..680bed8278 100644 --- a/src/backend/access/transam/xlogutils.c +++ b/src/backend/access/transam/xlogutils.c @@ -909,12 +909,12 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa */ int read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *cur_page, - TimeLineID *pageTLI) + int reqLen, XLogRecPtr targetRecPtr, char *cur_page) { XLogRecPtr read_upto, loc; int count; + TimeLineID pageTLI; loc = targetPagePtr + reqLen; @@ -934,7 +934,7 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, else read_upto = GetXLogReplayRecPtr(&ThisTimeLineID); - *pageTLI = ThisTimeLineID; + pageTLI = ThisTimeLineID; /* * Check which timeline to get the record from. @@ -991,7 +991,7 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, * nothing cares so long as the timeline doesn't go backwards. We * should read the page header instead; FIXME someday. */ - *pageTLI = state->currTLI; + pageTLI = state->currTLI; /* No need to wait on a historical timeline */ break; @@ -1022,8 +1022,9 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, * as 'count', read the whole page anyway. It's guaranteed to be * zero-padded up to the page boundary if it's incomplete. */ - XLogRead(cur_page, state->wal_segment_size, *pageTLI, targetPagePtr, + XLogRead(cur_page, state->wal_segment_size, pageTLI, targetPagePtr, XLOG_BLCKSZ); + state->readPageTLI = pageTLI; /* number of valid bytes in the buffer */ return count; diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c index d974400d6e..d1cf80d441 100644 --- a/src/backend/replication/logical/logicalfuncs.c +++ b/src/backend/replication/logical/logicalfuncs.c @@ -116,10 +116,10 @@ check_permissions(void) int logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *cur_page, TimeLineID *pageTLI) + int reqLen, XLogRecPtr targetRecPtr, char *cur_page) { return read_local_xlog_page(state, targetPagePtr, reqLen, - targetRecPtr, cur_page, pageTLI); + targetRecPtr, cur_page); } /* diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 23870a25a5..28d8c31af8 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -763,7 +763,7 @@ StartReplication(StartReplicationCmd *cmd) */ static int logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, - XLogRecPtr targetRecPtr, char *cur_page, TimeLineID *pageTLI) + XLogRecPtr targetRecPtr, char *cur_page) { XLogRecPtr flushptr; int count; diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c index 63c3879ead..33e2ba2a03 100644 --- a/src/bin/pg_rewind/parsexlog.c +++ b/src/bin/pg_rewind/parsexlog.c @@ -49,8 +49,7 @@ typedef struct XLogPageReadPrivate static int SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *readBuf, - TimeLineID *pageTLI); + int reqLen, XLogRecPtr targetRecPtr, char *readBuf); /* * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline @@ -237,8 +236,7 @@ findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex, /* XLogReader callback function, to read a WAL page */ static int SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, - int reqLen, XLogRecPtr targetRecPtr, char *readBuf, - TimeLineID *pageTLI) + int reqLen, XLogRecPtr targetRecPtr, char *readBuf) { XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data; uint32 targetPageOff; @@ -321,7 +319,7 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, Assert(targetSegNo == xlogreadsegno); - *pageTLI = targetHistory[private->tliIndex].tli; + xlogreader->readPageTLI = targetHistory[private->tliIndex].tli; return XLOG_BLCKSZ; } diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c index b95d467805..40c64a0bbf 100644 --- a/src/bin/pg_waldump/pg_waldump.c +++ b/src/bin/pg_waldump/pg_waldump.c @@ -423,7 +423,7 @@ XLogDumpXLogRead(const char *directory, TimeLineID timeline_id, */ static int XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, - XLogRecPtr targetPtr, char *readBuff, TimeLineID *curFileTLI) + XLogRecPtr targetPtr, char *readBuff) { XLogDumpPrivate *private = state->private_data; int count = XLOG_BLCKSZ; diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h index 735b1bd2fd..d64a9ad82f 100644 --- a/src/include/access/xlogreader.h +++ b/src/include/access/xlogreader.h @@ -38,8 +38,7 @@ typedef int (*XLogPageReadCB) (XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen, XLogRecPtr targetRecPtr, - char *readBuf, - TimeLineID *pageTLI); + char *readBuf); typedef struct { @@ -99,9 +98,8 @@ struct XLogReaderState * actual WAL record it's interested in. In that case, targetRecPtr can * be used to determine which timeline to read the page from. * - * The callback shall set *pageTLI to the TLI of the file the page was - * read from. It is currently used only for error reporting purposes, to - * reconstruct the name of the WAL file where an error occurred. + * The callback shall set ->readPageTLI to the TLI of the file the page + * was read from. */ XLogPageReadCB read_page; diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h index 4105b59904..4fb305bafd 100644 --- a/src/include/access/xlogutils.h +++ b/src/include/access/xlogutils.h @@ -47,12 +47,11 @@ extern Buffer XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum, extern Relation CreateFakeRelcacheEntry(RelFileNode rnode); extern void FreeFakeRelcacheEntry(Relation fakerel); + extern int read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, - XLogRecPtr targetRecPtr, char *cur_page, - TimeLineID *pageTLI); + XLogRecPtr targetRecPtr, char *cur_page); extern void XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wantLength); - #endif diff --git a/src/include/replication/logicalfuncs.h b/src/include/replication/logicalfuncs.h index a9c178a9e6..012096f183 100644 --- a/src/include/replication/logicalfuncs.h +++ b/src/include/replication/logicalfuncs.h @@ -14,6 +14,6 @@ extern int logical_read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen, XLogRecPtr targetRecPtr, - char *cur_page, TimeLineID *pageTLI); + char *cur_page); #endif -- 2.20.1 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v06-0003-Introduce-WALOpenSegment-structure.patch ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH v8] Add OR REPLACE option to CREATE MATERIALIZED VIEW @ 2024-05-21 16:35 Erik Wienhold <ewie@ewie.name> 0 siblings, 0 replies; 15+ messages in thread From: Erik Wienhold @ 2024-05-21 16:35 UTC (permalink / raw) Add WITH OLD DATA to keep the current data when replacing a materialized view which is useful when running REFRESH MATERIALIZED VIEW CONCURRENTLY afterwards. --- .../sgml/ref/create_materialized_view.sgml | 30 +- src/backend/commands/createas.c | 229 +++++++++++++++- src/backend/commands/matview.c | 80 ++++++ src/backend/commands/tablecmds.c | 11 +- src/backend/parser/gram.y | 25 ++ src/bin/psql/tab-complete.in.c | 38 ++- src/include/commands/matview.h | 2 + src/include/nodes/parsenodes.h | 4 +- src/include/nodes/primnodes.h | 10 + src/test/regress/expected/matview.out | 258 ++++++++++++++++++ src/test/regress/sql/matview.sql | 152 +++++++++++ src/tools/pgindent/typedefs.list | 1 + 12 files changed, 813 insertions(+), 27 deletions(-) diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml index 62d897931c3..e6c7b7c7d37 100644 --- a/doc/src/sgml/ref/create_materialized_view.sgml +++ b/doc/src/sgml/ref/create_materialized_view.sgml @@ -21,13 +21,13 @@ PostgreSQL documentation <refsynopsisdiv> <synopsis> -CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable> +CREATE [ OR REPLACE ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable> [ (<replaceable>column_name</replaceable> [, ...] ) ] [ USING <replaceable class="parameter">method</replaceable> ] [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ] [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ] AS <replaceable>query</replaceable> - [ WITH [ NO ] DATA ] + [ WITH [ NO | OLD ] DATA ] </synopsis> </refsynopsisdiv> @@ -37,7 +37,8 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable> <para> <command>CREATE MATERIALIZED VIEW</command> defines a materialized view of a query. The query is executed and used to populate the view at the time - the command is issued (unless <command>WITH NO DATA</command> is used) and may be + the command is issued (unless <command>WITH NO DATA</command> or + <command>WITH OLD DATA</command> is used) and may be refreshed later using <command>REFRESH MATERIALIZED VIEW</command>. </para> @@ -60,6 +61,17 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable> <title>Parameters</title> <variablelist> + <varlistentry> + <term><literal>OR REPLACE</literal></term> + <listitem> + <para> + Replaces a materialized view if it already exists. + Specifying <literal>OR REPLACE</literal> together with + <literal>IF NOT EXISTS</literal> is an error. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><literal>IF NOT EXISTS</literal></term> <listitem> @@ -67,7 +79,7 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable> Do not throw an error if a materialized view with the same name already exists. A notice is issued in this case. Note that there is no guarantee that the existing materialized view is anything like the one that would - have been created. + have been created, unlike when using <literal>OR REPLACE</literal>. </para> </listitem> </varlistentry> @@ -151,7 +163,7 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable> </varlistentry> <varlistentry> - <term><literal>WITH [ NO ] DATA</literal></term> + <term><literal>WITH [ NO | OLD ] DATA</literal></term> <listitem> <para> This clause specifies whether or not the materialized view should be @@ -159,6 +171,14 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable> flagged as unscannable and cannot be queried until <command>REFRESH MATERIALIZED VIEW</command> is used. </para> + + <para> + The form <command>WITH OLD DATA</command> keeps the already stored data + when replacing an existing materialized view to keep it populated. Use + this form if you want to use <command>REFRESH MATERIALIZED VIEW CONCURRENTLY</command> + as it requires a populated materialized view. It is an error to use this + form when creating a new materialized view. + </para> </listitem> </varlistentry> diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 6dbb831ca89..cbc384dd3f7 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -34,9 +34,11 @@ #include "commands/matview.h" #include "commands/prepare.h" #include "commands/tablecmds.h" +#include "commands/tablespace.h" #include "commands/view.h" #include "executor/execdesc.h" #include "executor/executor.h" +#include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "nodes/queryjumble.h" @@ -63,6 +65,7 @@ typedef struct /* utility functions for CTAS definition creation */ static ObjectAddress create_ctas_internal(List *attrList, IntoClause *into); static ObjectAddress create_ctas_nodata(List *tlist, IntoClause *into); +static ObjectAddress create_ctas_replace(List *tlist, IntoClause *into, Oid matviewOid); /* DestReceiver routines for collecting data */ static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo); @@ -70,6 +73,7 @@ static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self); static void intorel_shutdown(DestReceiver *self); static void intorel_destroy(DestReceiver *self); +static bool CreateTableAsRelReplaceable(CreateTableAsStmt *ctas); /* * create_ctas_internal @@ -157,6 +161,8 @@ create_ctas_nodata(List *tlist, IntoClause *into) List *attrList; ListCell *t, *lc; + bool is_matview = (into->viewQuery != NULL); + Oid matviewOid = InvalidOid; /* * Build list of ColumnDefs from non-junk elements of the tlist. If a @@ -211,8 +217,146 @@ create_ctas_nodata(List *tlist, IntoClause *into) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("too many column names were specified"))); - /* Create the relation definition using the ColumnDef list */ - return create_ctas_internal(attrList, into); + /* Get the existing matview to be replaced */ + if (is_matview && into->replace) + (void) RangeVarGetAndCheckCreationNamespace(into->rel, + AccessExclusiveLock, + &matviewOid); + + if (OidIsValid(matviewOid)) + /* Replace the existing matview */ + return create_ctas_replace(attrList, into, matviewOid); + else + /* Create the relation definition using the ColumnDef list */ + return create_ctas_internal(attrList, into); +} + + +/* + * create_ctas_replace + * + * Internal utility used for replacing the definition of a materialized view. + * Caller needs to provide a list of attributes (ColumnDef nodes) and the + * materialized view OID. + */ +static ObjectAddress +create_ctas_replace(List *attrList, IntoClause *into, Oid matviewOid) +{ + ObjectAddress intoRelationAddr; + Relation rel; + List *atcmds = NIL; + AlterTableCmd *atcmd; + TupleDesc descriptor; + Query *query; + + /* Relation is already locked, but we must build a relcache entry. */ + rel = relation_open(matviewOid, NoLock); + + /* Make sure it *is* a matview. */ + if (rel->rd_rel->relkind != RELKIND_MATVIEW) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a materialized view", + RelationGetRelationName(rel))); + + /* Also check it's not in use already */ + CheckTableNotInUse(rel, "CREATE OR REPLACE MATERIALIZED VIEW"); + + descriptor = BuildDescForRelation(attrList); + checkMatviewColumns(descriptor, rel->rd_att); + + /* + * If new attributes have been added, we must add pg_attribute entries for + * them. It is convenient (although overkill) to use the ALTER TABLE ADD + * COLUMN infrastructure for this. + * + * Note that we must do this before updating the query for the matview, + * since the rules system requires that the correct matview columns be in + * place when defining the new rules. + * + * Also note that ALTER TABLE doesn't run parse transformation on + * AT_AddColumnToView commands. The ColumnDef we supply must be ready to + * execute as-is. + */ + if (list_length(attrList) > rel->rd_att->natts) + { + ListCell *c; + + for_each_from(c, attrList, rel->rd_att->natts) + { + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_AddColumnToView; + atcmd->def = (Node *) lfirst(c); + atcmds = lappend(atcmds, atcmd); + } + } + + /* + * Use ALTER TABLE to set access method, tablespace, and storage options. + * When replacing an existing matview we need to alter the relation such + * that the defaults apply as if they have not been specified at all by + * the CREATE OR REPLACE MATERIALIZED VIEW statement. + */ + + /* access method */ + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_SetAccessMethod; + atcmd->name = into->accessMethod + ? into->accessMethod : default_table_access_method; + atcmds = lappend(atcmds, atcmd); + + /* tablespace */ + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_SetTableSpace; + if (into->tableSpaceName) + atcmd->name = into->tableSpaceName; + else + { + Oid spcOid; + char *spcName; + + /* + * Must use the default tablespace if no explicit tablespace is + * specified. + */ + spcOid = GetDefaultTablespace(RELPERSISTENCE_PERMANENT, false); + if (!OidIsValid(spcOid)) + spcOid = MyDatabaseTableSpace; + + spcName = get_tablespace_name(spcOid); + if (!spcName) /* should not happen */ + elog(ERROR, "could not find tuple for tablespace %u", spcOid); + + atcmd->name = spcName; + } + atcmds = lappend(atcmds, atcmd); + + /* storage options */ + atcmd = makeNode(AlterTableCmd); + atcmd->subtype = AT_ReplaceRelOptions; + atcmd->def = (Node *) into->options; + atcmds = lappend(atcmds, atcmd); + + /* EventTriggerAlterTableStart called by ProcessUtilitySlow */ + AlterTableInternal(matviewOid, atcmds, true); + + /* Make the new matview columns visible */ + CommandCounterIncrement(); + + relation_close(rel, NoLock); + ObjectAddressSet(intoRelationAddr, RelationRelationId, matviewOid); + + /* + * Replace the "view" part of the matview. StoreViewQuery scribbles on + * tree, so make a copy. + */ + query = copyObject(into->viewQuery); + StoreViewQuery(intoRelationAddr.objectId, query, true); + + /* Make the new matview query visible */ + CommandCounterIncrement(); + + return intoRelationAddr; } @@ -232,8 +376,37 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, DestReceiver *dest; ObjectAddress address; - /* Check if the relation exists or not */ - if (CreateTableAsRelExists(stmt)) + /* + * Check if the relation exists or not. An existing materialized view can + * be replaced. + */ + if (is_matview && into->replace) + { + if (CreateTableAsRelReplaceable(stmt)) + { + /* Change the relation to match the new query and other options. */ + address = create_ctas_nodata(query->targetList, into); + + /* + * Refresh the materialized view with a fake statement unless we + * must keep the old data. + */ + if (into->data != WITHDATA_OLD) + { + RefreshMatViewStmt *refresh; + + refresh = makeNode(RefreshMatViewStmt); + refresh->relation = into->rel; + refresh->skipData = into->skipData; + refresh->concurrent = false; + + address = ExecRefreshMatView(refresh, pstate->p_sourcetext, qc); + } + + return address; + } + } + else if (CreateTableAsRelExists(stmt)) return InvalidObjectAddress; /* @@ -273,6 +446,11 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, */ if (is_matview) { + if (into->data == WITHDATA_OLD) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("must not specify WITH OLD DATA when creating a new materialized view"))); + do_refresh = !into->skipData; into->skipData = true; } @@ -429,6 +607,49 @@ CreateTableAsRelExists(CreateTableAsStmt *ctas) return false; } +/* + * CreateTableAsRelReplaceable --- check existence of replaceable relation for + * CreateTableAsStmt + * + * Utility wrapper checking if the relation pending for creation in this + * CreateTableAsStmt query already exists or not. Returns true if the relation + * exists and should be replaced, otherwise false. + */ +static bool +CreateTableAsRelReplaceable(CreateTableAsStmt *ctas) +{ + Oid nspid; + Oid oldrelid; + ObjectAddress address; + IntoClause *into = ctas->into; + + nspid = RangeVarGetCreationNamespace(into->rel); + + oldrelid = get_relname_relid(into->rel->relname, nspid); + if (OidIsValid(oldrelid)) + { + if (!into->replace) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_TABLE), + errmsg("relation \"%s\" already exists", + into->rel->relname))); + + /* + * The relation exists and OR REPLACE has been specified. + * + * If we are in an extension script, insist that the pre-existing + * object be a member of the extension, to avoid security risks. + */ + ObjectAddressSet(address, RelationRelationId, oldrelid); + checkMembershipInCurrentExtension(&address); + + return true; + } + + /* Relation does not exist, it can be created */ + return false; +} + /* * CreateIntoRelDestReceiver -- create a suitable DestReceiver object * diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index f7d8007f796..853eae37057 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -967,3 +967,83 @@ CloseMatViewIncrementalMaintenance(void) matview_maintenance_depth--; Assert(matview_maintenance_depth >= 0); } + +/* + * Verify that the columns associated with proposed new matview definition + * match the columns of the old matview. This is similar to equalRowTypes(), + * with code added to generate specific complaints. Also, we allow the new + * matview to have more columns than the old. + */ +void +checkMatviewColumns(TupleDesc newdesc, TupleDesc olddesc) +{ + int i; + + if (newdesc->natts < olddesc->natts) + { + ereport(ERROR, + errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot drop columns from materialized view")); + } + + for (i = 0; i < olddesc->natts; i++) + { + Form_pg_attribute newattr = TupleDescAttr(newdesc, i); + Form_pg_attribute oldattr = TupleDescAttr(olddesc, i); + + /* XXX msg not right, but we don't support DROP COL on matview anyway */ + if (newattr->attisdropped != oldattr->attisdropped) + { + ereport(ERROR, + errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot drop columns from materialized view")); + } + + if (strcmp(NameStr(newattr->attname), NameStr(oldattr->attname)) != 0) + { + ereport(ERROR, + errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot change name of materialized view column \"%s\" to \"%s\"", + NameStr(oldattr->attname), + NameStr(newattr->attname)), + errhint("Use ALTER MATERIALIZED VIEW ... RENAME COLUMN ... to change name of materialized view column instead.")); + } + + /* + * We cannot allow type, typmod, or collation to change, since these + * properties may be embedded in Vars of other views/rules referencing + * this one. Other column attributes can be ignored. + */ + if (newattr->atttypid != oldattr->atttypid || + newattr->atttypmod != oldattr->atttypmod) + { + ereport(ERROR, + errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot change data type of materialized view column \"%s\" from %s to %s", + NameStr(oldattr->attname), + format_type_with_typemod(oldattr->atttypid, + oldattr->atttypmod), + format_type_with_typemod(newattr->atttypid, + newattr->atttypmod))); + } + + /* + * At this point, attcollations should be both valid or both invalid, + * so applying get_collation_name unconditionally should be fine. + */ + if (newattr->attcollation != oldattr->attcollation) + { + ereport(ERROR, + errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot change collation of materialized view column \"%s\" from \"%s\" to \"%s\"", + NameStr(oldattr->attname), + get_collation_name(oldattr->attcollation), + get_collation_name(newattr->attcollation))); + } + } + + /* + * We ignore the constraint fields since the new matview desc can't have + * any constraints. + */ +} diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 472db112fa7..5bf322257c5 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4720,7 +4720,8 @@ AlterTableGetLockLevel(List *cmds) * Subcommands that may be visible to concurrent SELECTs */ case AT_DropColumn: /* change visible to SELECT */ - case AT_AddColumnToView: /* CREATE VIEW */ + case AT_AddColumnToView: /* via CREATE OR REPLACE + * [MATERIALIZED] VIEW */ case AT_DropOids: /* used to equiv to DropColumn */ case AT_EnableAlwaysRule: /* may change SELECT rules */ case AT_EnableReplicaRule: /* may change SELECT rules */ @@ -5020,8 +5021,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* Recursion occurs during execution phase */ pass = AT_PASS_ADD_COL; break; - case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */ - ATSimplePermissions(cmd->subtype, rel, ATT_VIEW); + case AT_AddColumnToView: /* via CREATE OR REPLACE [MATERIALIZED] + * VIEW */ + ATSimplePermissions(cmd->subtype, rel, ATT_VIEW | ATT_MATVIEW); ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd, lockmode, context); /* Recursion occurs during execution phase */ @@ -5458,7 +5460,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, switch (cmd->subtype) { case AT_AddColumn: /* ADD COLUMN */ - case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */ + case AT_AddColumnToView: /* via CREATE OR REPLACE [MATERIALIZED] + * VIEW */ address = ATExecAddColumn(wqueue, tab, rel, &cmd, cmd->recurse, false, lockmode, cur_pass, context); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index ff4e1388c55..91e52689d7a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -250,6 +250,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); Alias *alias; RangeVar *range; IntoClause *into; + WithDataOption withdata; WithClause *with; InferClause *infer; OnConflictClause *onconflict; @@ -358,6 +359,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); opt_grant_grant_option opt_nowait opt_if_exists opt_with_data opt_transaction_chain +%type <withdata> opt_with_no_or_old_data %type <list> grant_role_opt_list %type <defelt> grant_role_opt %type <node> grant_role_opt_value @@ -5030,6 +5032,13 @@ opt_with_data: | /*EMPTY*/ { $$ = true; } ; +opt_with_no_or_old_data: + WITH DATA_P { $$ = WITHDATA_DEFAULT; } + | WITH NO DATA_P { $$ = WITHDATA_NONE; } + | WITH OLD DATA_P { $$ = WITHDATA_OLD; } + | /*EMPTY*/ { $$ = WITHDATA_DEFAULT; } + ; + /***************************************************************************** * @@ -5067,6 +5076,22 @@ CreateMatViewStmt: $8->skipData = !($11); $$ = (Node *) ctas; } + | CREATE OR REPLACE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_no_or_old_data + { + CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt); + + ctas->query = $9; + ctas->into = $7; + ctas->objtype = OBJECT_MATVIEW; + ctas->is_select_into = false; + ctas->if_not_exists = false; + /* cram additional flags into the IntoClause */ + $7->rel->relpersistence = $4; + $7->skipData = $10 == WITHDATA_NONE; + $7->data = $10; + $7->replace = true; + $$ = (Node *) ctas; + } ; create_mv_target: diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 46b9add0604..e683e4b1b33 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -2225,7 +2225,7 @@ match_previous_words(int pattern_id, /* complete with something you can create or replace */ else if (TailMatches("CREATE", "OR", "REPLACE")) COMPLETE_WITH("FUNCTION", "PROCEDURE", "LANGUAGE", "RULE", "VIEW", - "AGGREGATE", "TRANSFORM", "TRIGGER"); + "AGGREGATE", "TRANSFORM", "TRIGGER", "MATERIALIZED VIEW"); /* DROP, but not DROP embedded in other commands */ /* complete with something you can drop */ @@ -4256,28 +4256,42 @@ match_previous_words(int pattern_id, COMPLETE_WITH("SELECT"); /* CREATE MATERIALIZED VIEW */ - else if (Matches("CREATE", "MATERIALIZED")) + else if (Matches("CREATE", "MATERIALIZED") || + Matches("CREATE", "OR", "REPLACE", "MATERIALIZED")) COMPLETE_WITH("VIEW"); - /* Complete CREATE MATERIALIZED VIEW <name> with AS or USING */ - else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny)) + + /* + * Complete CREATE [ OR REPLACE ] MATERIALIZED VIEW <name> with AS or + * USING + */ + else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny) || + Matches("CREATE", "OR", "REPLACE", "MATERIALIZED", "VIEW", MatchAny)) COMPLETE_WITH("AS", "USING"); /* - * Complete CREATE MATERIALIZED VIEW <name> USING with list of access - * methods + * Complete CREATE [ OR REPLACE ] MATERIALIZED VIEW <name> USING with list + * of access methods */ - else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "USING")) + else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "USING") || + Matches("CREATE", "OR", "REPLACE", "MATERIALIZED", "VIEW", MatchAny, "USING")) COMPLETE_WITH_QUERY(Query_for_list_of_table_access_methods); - /* Complete CREATE MATERIALIZED VIEW <name> USING <access method> with AS */ - else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "USING", MatchAny)) + + /* + * Complete CREATE [ OR REPLACE ] MATERIALIZED VIEW <name> USING <access + * method> with AS + */ + else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "USING", MatchAny) || + Matches("CREATE", "OR", "REPLACE", "MATERIALIZED", "VIEW", MatchAny, "USING", MatchAny)) COMPLETE_WITH("AS"); /* - * Complete CREATE MATERIALIZED VIEW <name> [USING <access method> ] AS - * with "SELECT" + * Complete CREATE [ OR REPLACE ] MATERIALIZED VIEW <name> [USING <access + * method> ] AS with "SELECT" */ else if (Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "AS") || - Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "USING", MatchAny, "AS")) + Matches("CREATE", "OR", "REPLACE", "MATERIALIZED", "VIEW", MatchAny, "AS") || + Matches("CREATE", "MATERIALIZED", "VIEW", MatchAny, "USING", MatchAny, "AS") || + Matches("CREATE", "OR", "REPLACE", "MATERIALIZED", "VIEW", MatchAny, "USING", MatchAny, "AS")) COMPLETE_WITH("SELECT"); /* CREATE EVENT TRIGGER */ diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h index 738c731c1a9..140f7e96696 100644 --- a/src/include/commands/matview.h +++ b/src/include/commands/matview.h @@ -33,4 +33,6 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid transientoid); extern bool MatViewIncrementalMaintenanceIsEnabled(void); +extern void checkMatviewColumns(TupleDesc newdesc, TupleDesc olddesc); + #endif /* MATVIEW_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 4133c404a6b..7c629d09fb1 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2519,7 +2519,8 @@ typedef struct AlterTableStmt typedef enum AlterTableType { AT_AddColumn, /* add column */ - AT_AddColumnToView, /* implicitly via CREATE OR REPLACE VIEW */ + AT_AddColumnToView, /* implicitly via CREATE OR REPLACE + * [MATERIALIZED] VIEW */ AT_ColumnDefault, /* alter column default */ AT_CookedColumnDefault, /* add a pre-cooked column default */ AT_DropNotNull, /* alter column drop not null */ @@ -4595,5 +4596,4 @@ typedef struct WaitStmt List *options; /* List of DefElem nodes */ } WaitStmt; - #endif /* PARSENODES_H */ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index bb05aeebee4..3b916993dbe 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -147,6 +147,14 @@ typedef struct TableFunc ParseLoc location; } TableFunc; +/* WITH DATA option of CREATE MATERIALIZED VIEW */ +typedef enum WithDataOption +{ + WITHDATA_DEFAULT, /* WITH DATA */ + WITHDATA_NONE, /* WITH NO DATA */ + WITHDATA_OLD, /* WITH OLD DATA */ +} WithDataOption; + /* * IntoClause - target information for SELECT INTO, CREATE TABLE AS, and * CREATE MATERIALIZED VIEW @@ -170,6 +178,8 @@ typedef struct IntoClause /* materialized view's SELECT query */ struct Query *viewQuery pg_node_attr(query_jumble_ignore); bool skipData; /* true for WITH NO DATA */ + WithDataOption data; /* WITH [ NO | OLD ] DATA */ + bool replace; /* replace existing matview? */ } IntoClause; diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out index 0355720dfc6..e923aa4f0a7 100644 --- a/src/test/regress/expected/matview.out +++ b/src/test/regress/expected/matview.out @@ -699,3 +699,261 @@ NOTICE: relation "matview_ine_tab" already exists, skipping (0 rows) DROP MATERIALIZED VIEW matview_ine_tab; +-- +-- Test CREATE OR REPLACE MATERIALIZED VIEW +-- +-- Matview does not already exist +DROP MATERIALIZED VIEW IF EXISTS mvtest_replace; +NOTICE: materialized view "mvtest_replace" does not exist, skipping +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 1 AS a; +SELECT * FROM mvtest_replace; + a +--- + 1 +(1 row) + +-- Replace query with data +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 2 AS a; +SELECT * FROM mvtest_replace; + a +--- + 2 +(1 row) + +-- Replace query without data +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 3 AS a + WITH NO DATA; +SELECT * FROM mvtest_replace; -- error: not populated +ERROR: materialized view "mvtest_replace" has not been populated +HINT: Use the REFRESH MATERIALIZED VIEW command. +REFRESH MATERIALIZED VIEW mvtest_replace; +SELECT * FROM mvtest_replace; + a +--- + 3 +(1 row) + +-- Replace query but keep old data +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 5 AS a + WITH OLD DATA; +SELECT * FROM mvtest_replace; + a +--- + 3 +(1 row) + +REFRESH MATERIALIZED VIEW mvtest_replace; +SELECT * FROM mvtest_replace; + a +--- + 5 +(1 row) + +-- Add column +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 4 AS a, 1 b; +SELECT * FROM mvtest_replace; + a | b +---+--- + 4 | 1 +(1 row) + +-- Replace table options +SELECT m.*, c.relname, c.reloptions, s.spcname, a.amname + FROM mvtest_replace m + CROSS JOIN pg_class c + LEFT JOIN pg_tablespace s ON s.oid = c.reltablespace + LEFT JOIN pg_am a ON a.oid = c.relam + WHERE c.relname = 'mvtest_replace'; + a | b | relname | reloptions | spcname | amname +---+---+----------------+------------+---------+-------- + 4 | 1 | mvtest_replace | | | heap +(1 row) + +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace + USING heap2 + WITH (fillfactor = 50) + TABLESPACE regress_tblspace + AS SELECT 5 AS a, 1 AS b; +SELECT m.*, c.relname, c.reloptions, s.spcname, a.amname + FROM mvtest_replace m + CROSS JOIN pg_class c + LEFT JOIN pg_tablespace s ON s.oid = c.reltablespace + LEFT JOIN pg_am a ON a.oid = c.relam + WHERE c.relname = 'mvtest_replace'; + a | b | relname | reloptions | spcname | amname +---+---+----------------+-----------------+------------------+-------- + 5 | 1 | mvtest_replace | {fillfactor=50} | regress_tblspace | heap2 +(1 row) + +-- Restore default options +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace + AS SELECT 5 AS a, 1 AS b; +SELECT m.*, c.relname, c.reloptions, s.spcname, a.amname + FROM mvtest_replace m + CROSS JOIN pg_class c + LEFT JOIN pg_tablespace s ON s.oid = c.reltablespace + LEFT JOIN pg_am a ON a.oid = c.relam + WHERE c.relname = 'mvtest_replace'; + a | b | relname | reloptions | spcname | amname +---+---+----------------+------------+---------+-------- + 5 | 1 | mvtest_replace | | | heap +(1 row) + +-- Can replace matview that has a dependent view +CREATE VIEW mvtest_replace_v AS + SELECT * FROM mvtest_replace; +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 6 AS a, 1 AS b; +SELECT * FROM mvtest_replace, mvtest_replace_v; + a | b | a | b +---+---+---+--- + 6 | 1 | 6 | 1 +(1 row) + +DROP VIEW mvtest_replace_v; +-- Index gets rebuilt when replacing with data +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 7 AS a, 1 AS b; +CREATE UNIQUE INDEX ON mvtest_replace (b); +SELECT * FROM mvtest_replace; + a | b +---+--- + 7 | 1 +(1 row) + +SET enable_seqscan = off; -- force index scan +EXPLAIN (COSTS OFF) SELECT * FROM mvtest_replace WHERE b = 1; + QUERY PLAN +--------------------------------------------------------- + Index Scan using mvtest_replace_b_idx on mvtest_replace + Index Cond: (b = 1) +(2 rows) + +SELECT * FROM mvtest_replace WHERE b = 1; + a | b +---+--- + 7 | 1 +(1 row) + +RESET enable_seqscan; +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 8 AS a, 1 AS b; +SET enable_seqscan = off; -- force index scan +EXPLAIN (COSTS OFF) SELECT * FROM mvtest_replace WHERE b = 1; + QUERY PLAN +--------------------------------------------------------- + Index Scan using mvtest_replace_b_idx on mvtest_replace + Index Cond: (b = 1) +(2 rows) + +SELECT * FROM mvtest_replace WHERE b = 1; + a | b +---+--- + 8 | 1 +(1 row) + +RESET enable_seqscan; +-- Cannot change column data type +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 9 AS a, 'x' AS b; -- error +ERROR: cannot change data type of materialized view column "b" from integer to text +SELECT * FROM mvtest_replace; + a | b +---+--- + 8 | 1 +(1 row) + +-- Cannot rename column +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 10 AS a, 1 AS b2; -- error +ERROR: cannot change name of materialized view column "b" to "b2" +HINT: Use ALTER MATERIALIZED VIEW ... RENAME COLUMN ... to change name of materialized view column instead. +SELECT * FROM mvtest_replace; + a | b +---+--- + 8 | 1 +(1 row) + +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 11 AS a, 1 AS b, 'y' COLLATE "C" AS c; +SELECT * FROM mvtest_replace; + a | b | c +----+---+--- + 11 | 1 | y +(1 row) + +-- Cannot change column collation +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 12 AS a, 1 AS b, 'x' COLLATE "POSIX" AS c; -- error +ERROR: cannot change collation of materialized view column "c" from "C" to "POSIX" +SELECT * FROM mvtest_replace; + a | b | c +----+---+--- + 11 | 1 | y +(1 row) + +-- Cannot drop column +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 13 AS a, 1 AS b; -- error +ERROR: cannot drop columns from materialized view +SELECT * FROM mvtest_replace; + a | b | c +----+---+--- + 11 | 1 | y +(1 row) + +-- Must target a matview +CREATE VIEW mvtest_not_mv AS + SELECT 1 AS a; +CREATE OR REPLACE MATERIALIZED VIEW mvtest_not_mv AS + SELECT 1 AS a; -- error +ERROR: "mvtest_not_mv" is not a materialized view +DROP VIEW mvtest_not_mv; +-- Cannot use OR REPLACE with IF NOT EXISTS +CREATE OR REPLACE MATERIALIZED VIEW IF NOT EXISTS mvtest_replace AS + SELECT 1 AS a; +ERROR: syntax error at or near "NOT" +LINE 1: CREATE OR REPLACE MATERIALIZED VIEW IF NOT EXISTS mvtest_rep... + ^ +DROP MATERIALIZED VIEW mvtest_replace; +-- Clause WITH OLD DATA is not allowed when creating a new matview +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 17 AS a + WITH OLD DATA; -- error +ERROR: must not specify WITH OLD DATA when creating a new materialized view +CREATE MATERIALIZED VIEW mvtest_replace AS + SELECT 1 AS c1, 2 AS c2, 3 AS c3, 4 AS c4, + 5 AS c5, 6 AS c6, 7 AS c7, null AS c8; +-- Add a ninth column (exceeding the old t_bits) +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 1 AS c1, 2 AS c2, 3 AS c3, 4 AS c4, + 5 AS c5, 6 AS c6, 7 AS c7, null AS c8, + null AS c9 + WITH OLD DATA; +SELECT c9 FROM mvtest_replace; + c9 +---- + +(1 row) + +-- Test constraint violation on WITH OLD DATA +DROP MATERIALIZED VIEW mvtest_replace; +CREATE DOMAIN mvtest_dom AS int + CONSTRAINT mvtest_dom_nn NOT NULL; +CREATE MATERIALIZED VIEW mvtest_replace AS + SELECT 1::mvtest_dom AS a; +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 2::mvtest_dom AS a, 3::mvtest_dom AS b + WITH OLD DATA; -- error: new column "b" cannot be null +ERROR: domain mvtest_dom does not allow null values +SELECT a FROM mvtest_replace; + a +--- + 1 +(1 row) + diff --git a/src/test/regress/sql/matview.sql b/src/test/regress/sql/matview.sql index 934426b9ae8..abc354a13f8 100644 --- a/src/test/regress/sql/matview.sql +++ b/src/test/regress/sql/matview.sql @@ -318,3 +318,155 @@ EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF) CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS SELECT 1 / 0 WITH NO DATA; -- ok DROP MATERIALIZED VIEW matview_ine_tab; + +-- +-- Test CREATE OR REPLACE MATERIALIZED VIEW +-- + +-- Matview does not already exist +DROP MATERIALIZED VIEW IF EXISTS mvtest_replace; +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 1 AS a; +SELECT * FROM mvtest_replace; + +-- Replace query with data +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 2 AS a; +SELECT * FROM mvtest_replace; + +-- Replace query without data +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 3 AS a + WITH NO DATA; +SELECT * FROM mvtest_replace; -- error: not populated +REFRESH MATERIALIZED VIEW mvtest_replace; +SELECT * FROM mvtest_replace; + +-- Replace query but keep old data +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 5 AS a + WITH OLD DATA; +SELECT * FROM mvtest_replace; +REFRESH MATERIALIZED VIEW mvtest_replace; +SELECT * FROM mvtest_replace; + +-- Add column +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 4 AS a, 1 b; +SELECT * FROM mvtest_replace; + +-- Replace table options +SELECT m.*, c.relname, c.reloptions, s.spcname, a.amname + FROM mvtest_replace m + CROSS JOIN pg_class c + LEFT JOIN pg_tablespace s ON s.oid = c.reltablespace + LEFT JOIN pg_am a ON a.oid = c.relam + WHERE c.relname = 'mvtest_replace'; +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace + USING heap2 + WITH (fillfactor = 50) + TABLESPACE regress_tblspace + AS SELECT 5 AS a, 1 AS b; +SELECT m.*, c.relname, c.reloptions, s.spcname, a.amname + FROM mvtest_replace m + CROSS JOIN pg_class c + LEFT JOIN pg_tablespace s ON s.oid = c.reltablespace + LEFT JOIN pg_am a ON a.oid = c.relam + WHERE c.relname = 'mvtest_replace'; +-- Restore default options +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace + AS SELECT 5 AS a, 1 AS b; +SELECT m.*, c.relname, c.reloptions, s.spcname, a.amname + FROM mvtest_replace m + CROSS JOIN pg_class c + LEFT JOIN pg_tablespace s ON s.oid = c.reltablespace + LEFT JOIN pg_am a ON a.oid = c.relam + WHERE c.relname = 'mvtest_replace'; + +-- Can replace matview that has a dependent view +CREATE VIEW mvtest_replace_v AS + SELECT * FROM mvtest_replace; +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 6 AS a, 1 AS b; +SELECT * FROM mvtest_replace, mvtest_replace_v; +DROP VIEW mvtest_replace_v; + +-- Index gets rebuilt when replacing with data +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 7 AS a, 1 AS b; +CREATE UNIQUE INDEX ON mvtest_replace (b); +SELECT * FROM mvtest_replace; +SET enable_seqscan = off; -- force index scan +EXPLAIN (COSTS OFF) SELECT * FROM mvtest_replace WHERE b = 1; +SELECT * FROM mvtest_replace WHERE b = 1; +RESET enable_seqscan; +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 8 AS a, 1 AS b; +SET enable_seqscan = off; -- force index scan +EXPLAIN (COSTS OFF) SELECT * FROM mvtest_replace WHERE b = 1; +SELECT * FROM mvtest_replace WHERE b = 1; +RESET enable_seqscan; + +-- Cannot change column data type +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 9 AS a, 'x' AS b; -- error +SELECT * FROM mvtest_replace; + +-- Cannot rename column +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 10 AS a, 1 AS b2; -- error +SELECT * FROM mvtest_replace; + +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 11 AS a, 1 AS b, 'y' COLLATE "C" AS c; +SELECT * FROM mvtest_replace; + +-- Cannot change column collation +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 12 AS a, 1 AS b, 'x' COLLATE "POSIX" AS c; -- error +SELECT * FROM mvtest_replace; + +-- Cannot drop column +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 13 AS a, 1 AS b; -- error +SELECT * FROM mvtest_replace; + +-- Must target a matview +CREATE VIEW mvtest_not_mv AS + SELECT 1 AS a; +CREATE OR REPLACE MATERIALIZED VIEW mvtest_not_mv AS + SELECT 1 AS a; -- error +DROP VIEW mvtest_not_mv; + +-- Cannot use OR REPLACE with IF NOT EXISTS +CREATE OR REPLACE MATERIALIZED VIEW IF NOT EXISTS mvtest_replace AS + SELECT 1 AS a; + +DROP MATERIALIZED VIEW mvtest_replace; + +-- Clause WITH OLD DATA is not allowed when creating a new matview +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 17 AS a + WITH OLD DATA; -- error + +CREATE MATERIALIZED VIEW mvtest_replace AS + SELECT 1 AS c1, 2 AS c2, 3 AS c3, 4 AS c4, + 5 AS c5, 6 AS c6, 7 AS c7, null AS c8; +-- Add a ninth column (exceeding the old t_bits) +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 1 AS c1, 2 AS c2, 3 AS c3, 4 AS c4, + 5 AS c5, 6 AS c6, 7 AS c7, null AS c8, + null AS c9 + WITH OLD DATA; +SELECT c9 FROM mvtest_replace; + +-- Test constraint violation on WITH OLD DATA +DROP MATERIALIZED VIEW mvtest_replace; +CREATE DOMAIN mvtest_dom AS int + CONSTRAINT mvtest_dom_nn NOT NULL; +CREATE MATERIALIZED VIEW mvtest_replace AS + SELECT 1::mvtest_dom AS a; +CREATE OR REPLACE MATERIALIZED VIEW mvtest_replace AS + SELECT 2::mvtest_dom AS a, 3::mvtest_dom AS b + WITH OLD DATA; -- error: new column "b" cannot be null +SELECT a FROM mvtest_replace; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index c5db6ca6705..2223a658899 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3471,6 +3471,7 @@ WindowStatePerFunc WindowStatePerFuncData WithCheckOption WithClause +WithDataOption WordBoundaryNext WordEntry WordEntryIN -- 2.54.0 --5k2efyjyy5ysz4p6-- ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH v5 2/4] Add GUC to specify non-transactional statistics flush interval @ 2026-01-28 07:53 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 15+ messages in thread From: Bertrand Drouvot @ 2026-01-28 07:53 UTC (permalink / raw) Adding pgstat_flush_interval, a new GUC to set the interval between flushes of non-transactional statistics. --- doc/src/sgml/config.sgml | 32 +++++++++++++++++++ src/backend/access/transam/xlog.c | 4 +-- src/backend/utils/activity/pgstat.c | 16 +++++++++- src/backend/utils/activity/pgstat_backend.c | 4 +-- src/backend/utils/activity/pgstat_io.c | 2 +- src/backend/utils/activity/pgstat_slru.c | 2 +- src/backend/utils/misc/guc_parameters.dat | 10 ++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/pgstat.h | 1 + src/include/utils/guc_hooks.h | 1 + 10 files changed, 66 insertions(+), 7 deletions(-) 45.9% doc/src/sgml/ 6.5% src/backend/access/transam/ 31.8% src/backend/utils/activity/ 12.3% src/backend/utils/misc/ 3.2% src/include/ diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 5560b95ee60..3136816a933 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8834,6 +8834,38 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-stats-flush-interval" xreflabel="stats_flush_interval"> + <term><varname>stats_flush_interval</varname> (<type>integer</type>) + <indexterm> + <primary><varname>stats_flush_interval</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Sets the interval at which non-transactional statistics are made visible + during running transactions. Non-transactional statistics include, for + example, WAL activity and I/O operations. + They become visible at that interval in monitoring views such as + <link linkend="monitoring-pg-stat-io-view"> <structname>pg_stat_io</structname></link> + and <link linkend="monitoring-pg-stat-wal-view"> <structname>pg_stat_wal</structname></link> + during running transactions. + If this value is specified without units, it is taken as milliseconds. + The default is 10 seconds (<literal>10s</literal>), which is probably + about the smallest value you would want in practice for long running + transactions. + </para> + <note> + <para> + This parameter does not affect transactional statistics such as + <structname>pg_stat_all_tables</structname> columns (like + <structfield>n_tup_ins</structfield>, <structfield>n_tup_upd</structfield>, + <structfield>n_tup_del</structfield>), which are always flushed at transaction + boundaries to maintain consistency. + </para> + </note> + </listitem> + </varlistentry> + </variablelist> </sect2> diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 9503aea5b4d..31523dea923 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -1087,7 +1087,7 @@ XLogInsertRecord(XLogRecData *rdata, /* Schedule next anytime stats update timeout */ if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); /* Required for the flush of pending stats WAL data */ pgstat_report_fixed = true; @@ -2073,7 +2073,7 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli, bool opportunistic) /* Schedule next anytime stats update timeout */ if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, - PGSTAT_MIN_INTERVAL); + pgstat_flush_interval); /* * Required for the flush of pending stats WAL data, per diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index 2c9454677e9..dd174129403 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -203,6 +203,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind); bool pgstat_track_counts = false; int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE; +int pgstat_flush_interval = 10000; /* ---------- @@ -1304,7 +1305,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat /* Schedule next anytime stats update timeout */ if (kind_info->flush_mode == FLUSH_ANYTIME && IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); } return entry_ref; @@ -2172,6 +2173,19 @@ assign_stats_fetch_consistency(int newval, void *extra) force_stats_snapshot_clear = true; } +/* + * GUC assign_hook for stats_flush_interval. + */ +void +assign_stats_flush_interval(int newval, void *extra) +{ + if (get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) + { + disable_timeout(ANYTIME_STATS_UPDATE_TIMEOUT, false); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, newval); + } +} + /* * Flushes only FLUSH_ANYTIME stats using non-blocking locks. Transactional * stats (FLUSH_AT_TXN_BOUNDARY) remain pending until transaction boundary. diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c index 9dcb24db975..f5b8c7b039c 100644 --- a/src/backend/utils/activity/pgstat_backend.c +++ b/src/backend/utils/activity/pgstat_backend.c @@ -69,7 +69,7 @@ pgstat_count_backend_io_op_time(IOObject io_object, IOContext io_context, /* Schedule next anytime stats update timeout */ if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); backend_has_iostats = true; pgstat_report_fixed = true; @@ -89,7 +89,7 @@ pgstat_count_backend_io_op(IOObject io_object, IOContext io_context, /* Schedule next anytime stats update timeout */ if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); backend_has_iostats = true; pgstat_report_fixed = true; diff --git a/src/backend/utils/activity/pgstat_io.c b/src/backend/utils/activity/pgstat_io.c index 53dbf2a514b..b69a1e26f7d 100644 --- a/src/backend/utils/activity/pgstat_io.c +++ b/src/backend/utils/activity/pgstat_io.c @@ -82,7 +82,7 @@ pgstat_count_io_op(IOObject io_object, IOContext io_context, IOOp io_op, /* Schedule next anytime stats update timeout */ if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); have_iostats = true; pgstat_report_fixed = true; diff --git a/src/backend/utils/activity/pgstat_slru.c b/src/backend/utils/activity/pgstat_slru.c index 1d16cde1889..36231ee874b 100644 --- a/src/backend/utils/activity/pgstat_slru.c +++ b/src/backend/utils/activity/pgstat_slru.c @@ -226,7 +226,7 @@ get_slru_entry(int slru_idx) /* Schedule next anytime stats update timeout */ if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); have_slrustats = true; pgstat_report_fixed = true; diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index f0260e6e412..3bb43362e51 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -2782,6 +2782,16 @@ assign_hook => 'assign_stats_fetch_consistency', }, +{ name => 'stats_flush_interval', type => 'int', context => 'PGC_USERSET', group => 'STATS_CUMULATIVE', + short_desc => 'Sets the interval between flushes of non-transactional statistics.', + flags => 'GUC_UNIT_MS', + variable => 'pgstat_flush_interval', + boot_val => '10000', + min => '1000', + max => 'INT_MAX', + assign_hook => 'assign_stats_flush_interval' +}, + { name => 'subtransaction_buffers', type => 'int', context => 'PGC_POSTMASTER', group => 'RESOURCES_MEM', short_desc => 'Sets the size of the dedicated buffer pool used for the subtransaction cache.', long_desc => '0 means use a fraction of "shared_buffers".', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index c4f92fcdac8..6ce5a250170 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -669,6 +669,7 @@ #track_wal_io_timing = off #track_functions = none # none, pl, all #stats_fetch_consistency = cache # cache, none, snapshot +#stats_flush_interval = 10s # in milliseconds # - Monitoring - diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 1651f16f966..e0f222695bf 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -816,6 +816,7 @@ extern PgStat_WalStats *pgstat_fetch_stat_wal(void); extern PGDLLIMPORT bool pgstat_track_counts; extern PGDLLIMPORT int pgstat_track_functions; extern PGDLLIMPORT int pgstat_fetch_consistency; +extern PGDLLIMPORT int pgstat_flush_interval; /* diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index b6ecb0e769f..3a2ae6c41cd 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -132,6 +132,7 @@ extern bool check_session_authorization(char **newval, void **extra, GucSource s extern void assign_session_authorization(const char *newval, void *extra); extern void assign_session_replication_role(int newval, void *extra); extern void assign_stats_fetch_consistency(int newval, void *extra); +extern void assign_stats_flush_interval(int newval, void *extra); extern bool check_ssl(bool *newval, void **extra, GucSource source); extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source); extern bool check_standard_conforming_strings(bool *newval, void **extra, -- 2.34.1 --WLtTrIUEfQDmERxy Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v5-0003-Remove-useless-calls-to-flush-some-stats.patch" ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH v4 2/4] Add GUC to specify non-transactional statistics flush interval @ 2026-01-28 07:53 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 15+ messages in thread From: Bertrand Drouvot @ 2026-01-28 07:53 UTC (permalink / raw) Adding pgstat_flush_interval, a new GUC to set the interval between flushes of non-transactional statistics. --- doc/src/sgml/config.sgml | 32 +++++++++++++++++++ src/backend/storage/lmgr/proc.c | 2 +- src/backend/tcop/postgres.c | 2 +- src/backend/utils/activity/pgstat.c | 15 +++++++++ src/backend/utils/init/postinit.c | 2 +- src/backend/utils/misc/guc_parameters.dat | 10 ++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/pgstat.h | 1 + src/include/utils/guc_hooks.h | 1 + 9 files changed, 63 insertions(+), 3 deletions(-) 55.5% doc/src/sgml/ 5.3% src/backend/storage/lmgr/ 12.6% src/backend/utils/activity/ 5.3% src/backend/utils/init/ 14.9% src/backend/utils/misc/ 3.9% src/include/ diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 5560b95ee60..3136816a933 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8834,6 +8834,38 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-stats-flush-interval" xreflabel="stats_flush_interval"> + <term><varname>stats_flush_interval</varname> (<type>integer</type>) + <indexterm> + <primary><varname>stats_flush_interval</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Sets the interval at which non-transactional statistics are made visible + during running transactions. Non-transactional statistics include, for + example, WAL activity and I/O operations. + They become visible at that interval in monitoring views such as + <link linkend="monitoring-pg-stat-io-view"> <structname>pg_stat_io</structname></link> + and <link linkend="monitoring-pg-stat-wal-view"> <structname>pg_stat_wal</structname></link> + during running transactions. + If this value is specified without units, it is taken as milliseconds. + The default is 10 seconds (<literal>10s</literal>), which is probably + about the smallest value you would want in practice for long running + transactions. + </para> + <note> + <para> + This parameter does not affect transactional statistics such as + <structname>pg_stat_all_tables</structname> columns (like + <structfield>n_tup_ins</structfield>, <structfield>n_tup_upd</structfield>, + <structfield>n_tup_del</structfield>), which are always flushed at transaction + boundaries to maintain consistency. + </para> + </note> + </listitem> + </varlistentry> + </variablelist> </sect2> diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 012705a2ee6..caa6eecca88 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -1669,7 +1669,7 @@ ProcSleep(LOCALLOCK *locallock) } while (myWaitStatus == PROC_WAIT_STATUS_WAITING); if (anytime_timeout_was_active) - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); /* * Disable the timers, if they are still running. As in LockErrorCleanup, diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 132fae61423..c0e81cb13d0 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -3543,7 +3543,7 @@ ProcessInterrupts(void) /* Schedule next timeout */ enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, - PGSTAT_MIN_INTERVAL); + pgstat_flush_interval); } if (ProcSignalBarrierPending) diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index ab4d9088a9a..ca08dd49cd7 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -113,6 +113,7 @@ #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/timestamp.h" +#include "utils/timeout.h" /* ---------- @@ -202,6 +203,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind); bool pgstat_track_counts = false; int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE; +int pgstat_flush_interval = 10000; /* ---------- @@ -2165,6 +2167,19 @@ assign_stats_fetch_consistency(int newval, void *extra) force_stats_snapshot_clear = true; } +/* + * GUC assign_hook for stats_flush_interval. + */ +void +assign_stats_flush_interval(int newval, void *extra) +{ + if (get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) + { + disable_timeout(ANYTIME_STATS_UPDATE_TIMEOUT, false); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, newval); + } +} + /* * Flushes only FLUSH_ANYTIME stats using non-blocking locks. Transactional * stats (FLUSH_AT_TXN_BOUNDARY) remain pending until transaction boundary. diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 6076f531c4a..c7c0d618671 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -768,7 +768,7 @@ InitPostgres(const char *in_dbname, Oid dboid, IdleStatsUpdateTimeoutHandler); RegisterTimeout(ANYTIME_STATS_UPDATE_TIMEOUT, AnytimeStatsUpdateTimeoutHandler); - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); } /* diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index f0260e6e412..3bb43362e51 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -2782,6 +2782,16 @@ assign_hook => 'assign_stats_fetch_consistency', }, +{ name => 'stats_flush_interval', type => 'int', context => 'PGC_USERSET', group => 'STATS_CUMULATIVE', + short_desc => 'Sets the interval between flushes of non-transactional statistics.', + flags => 'GUC_UNIT_MS', + variable => 'pgstat_flush_interval', + boot_val => '10000', + min => '1000', + max => 'INT_MAX', + assign_hook => 'assign_stats_flush_interval' +}, + { name => 'subtransaction_buffers', type => 'int', context => 'PGC_POSTMASTER', group => 'RESOURCES_MEM', short_desc => 'Sets the size of the dedicated buffer pool used for the subtransaction cache.', long_desc => '0 means use a fraction of "shared_buffers".', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index c4f92fcdac8..6ce5a250170 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -669,6 +669,7 @@ #track_wal_io_timing = off #track_functions = none # none, pl, all #stats_fetch_consistency = cache # cache, none, snapshot +#stats_flush_interval = 10s # in milliseconds # - Monitoring - diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 1651f16f966..e0f222695bf 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -816,6 +816,7 @@ extern PgStat_WalStats *pgstat_fetch_stat_wal(void); extern PGDLLIMPORT bool pgstat_track_counts; extern PGDLLIMPORT int pgstat_track_functions; extern PGDLLIMPORT int pgstat_fetch_consistency; +extern PGDLLIMPORT int pgstat_flush_interval; /* diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index b6ecb0e769f..3a2ae6c41cd 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -132,6 +132,7 @@ extern bool check_session_authorization(char **newval, void **extra, GucSource s extern void assign_session_authorization(const char *newval, void *extra); extern void assign_session_replication_role(int newval, void *extra); extern void assign_stats_fetch_consistency(int newval, void *extra); +extern void assign_stats_flush_interval(int newval, void *extra); extern bool check_ssl(bool *newval, void **extra, GucSource source); extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source); extern bool check_standard_conforming_strings(bool *newval, void **extra, -- 2.34.1 --wR/mWGXukst4NraF Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v4-0003-Remove-useless-calls-to-flush-some-stats.patch" ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH v7 3/5] Add GUC to specify non-transactional statistics flush interval @ 2026-01-28 07:53 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 15+ messages in thread From: Bertrand Drouvot @ 2026-01-28 07:53 UTC (permalink / raw) Adding pgstat_flush_interval, a new GUC to set the interval between flushes of non-transactional statistics. --- doc/src/sgml/config.sgml | 32 +++++++++++++++++++ src/backend/utils/activity/pgstat.c | 16 ++++++++++ src/backend/utils/misc/guc_parameters.dat | 10 ++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/pgstat.h | 6 ++-- src/include/utils/guc_hooks.h | 1 + .../test_custom_stats/t/001_custom_stats.pl | 6 ++-- 7 files changed, 66 insertions(+), 6 deletions(-) 51.8% doc/src/sgml/ 13.3% src/backend/utils/activity/ 13.6% src/backend/utils/misc/ 11.3% src/include/ 9.8% src/test/modules/test_custom_stats/t/ diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 6bc2690ce07..383bbe3a132 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8923,6 +8923,38 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-stats-flush-interval" xreflabel="stats_flush_interval"> + <term><varname>stats_flush_interval</varname> (<type>integer</type>) + <indexterm> + <primary><varname>stats_flush_interval</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Sets the interval at which certain statistics, which can be updated while a + transaction is in progress, are made visible. These include WAL activity + and I/O operations. + Such statistics are refreshed at the specified interval and can be observed + during active transactions in monitoring views such as + <link linkend="monitoring-pg-stat-wal-view"><structname>pg_stat_wal</structname></link> + and + <link linkend="monitoring-pg-stat-io-view"><structname>pg_stat_io</structname></link>. + If the value is specified without a unit, milliseconds are assumed. + The default is 10 seconds (<literal>10s</literal>), which is generally + the smallest practical value for long-running transactions. + </para> + <note> + <para> + This parameter does not affect statistics that are only reported at + transaction end, such as the columns of <structname>pg_stat_all_tables</structname> + (for example, <structfield>n_tup_ins</structfield>, <structfield>n_tup_upd</structfield>, + and <structfield>n_tup_del</structfield>). These statistics are always + flushed at the end of a transaction. + </para> + </note> + </listitem> + </varlistentry> + </variablelist> </sect2> diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index a4ff64dc5ce..dd85a27c52f 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -123,6 +123,8 @@ * ---------- */ +/* minimum interval non-forced stats flushes.*/ +#define PGSTAT_MIN_INTERVAL 1000 /* how long until to block flushing pending stats updates */ #define PGSTAT_MAX_INTERVAL 60000 /* when to call pgstat_report_stat() again, even when idle */ @@ -203,6 +205,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind); bool pgstat_track_counts = false; int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE; +int pgstat_flush_interval = 10000; /* ---------- @@ -2164,6 +2167,19 @@ assign_stats_fetch_consistency(int newval, void *extra) force_stats_snapshot_clear = true; } +/* + * GUC assign_hook for stats_flush_interval. + */ +void +assign_stats_flush_interval(int newval, void *extra) +{ + if (get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) + { + disable_timeout(ANYTIME_STATS_UPDATE_TIMEOUT, false); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, newval); + } +} + /* * Flushes only FLUSH_ANYTIME stats using non-blocking locks. Transactional * stats (FLUSH_AT_TXN_BOUNDARY) remain pending until transaction boundary. diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 271c033952e..d2734caafea 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -2801,6 +2801,16 @@ assign_hook => 'assign_stats_fetch_consistency', }, +{ name => 'stats_flush_interval', type => 'int', context => 'PGC_USERSET', group => 'STATS_CUMULATIVE', + short_desc => 'Sets the interval between flushes of non-transactional statistics.', + flags => 'GUC_UNIT_MS', + variable => 'pgstat_flush_interval', + boot_val => '10000', + min => '1000', + max => 'INT_MAX', + assign_hook => 'assign_stats_flush_interval' +}, + { name => 'subtransaction_buffers', type => 'int', context => 'PGC_POSTMASTER', group => 'RESOURCES_MEM', short_desc => 'Sets the size of the dedicated buffer pool used for the subtransaction cache.', long_desc => '0 means use a fraction of "shared_buffers".', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index f938cc65a3a..8bd37a25b38 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -688,6 +688,7 @@ #track_wal_io_timing = off #track_functions = none # none, pl, all #stats_fetch_consistency = cache # cache, none, snapshot +#stats_flush_interval = 10s # in milliseconds # - Monitoring - diff --git a/src/include/pgstat.h b/src/include/pgstat.h index b340a680614..ef856dbf55b 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -35,9 +35,6 @@ /* Default directory to store temporary statistics data in */ #define PG_STAT_TMP_DIR "pg_stat_tmp" -/* Minimum interval non-forced stats flushes */ -#define PGSTAT_MIN_INTERVAL 1000 - /* Values for track_functions GUC variable --- order is significant! */ typedef enum TrackFunctionsLevel { @@ -548,7 +545,7 @@ extern void pgstat_force_next_flush(void); #define pgstat_schedule_anytime_update() \ do { \ if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) \ - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); \ + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); \ } while (0) extern void pgstat_reset_counters(void); @@ -828,6 +825,7 @@ extern PgStat_WalStats *pgstat_fetch_stat_wal(void); extern PGDLLIMPORT bool pgstat_track_counts; extern PGDLLIMPORT int pgstat_track_functions; extern PGDLLIMPORT int pgstat_fetch_consistency; +extern PGDLLIMPORT int pgstat_flush_interval; /* diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 9c90670d9b8..9b5d2a90387 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -132,6 +132,7 @@ extern bool check_session_authorization(char **newval, void **extra, GucSource s extern void assign_session_authorization(const char *newval, void *extra); extern void assign_session_replication_role(int newval, void *extra); extern void assign_stats_fetch_consistency(int newval, void *extra); +extern void assign_stats_flush_interval(int newval, void *extra); extern bool check_ssl(bool *newval, void **extra, GucSource source); extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source); extern bool check_standard_conforming_strings(bool *newval, void **extra, diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 7be1b281776..22e2a75dcb9 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -164,10 +164,11 @@ $node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); my $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; -- Accumulate stats select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select 'anytime:'||numcalls from test_custom_stats_fixed_report(); @@ -183,11 +184,12 @@ like($result, qr/^anytime:2/m, $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; -- Accumulate stats select test_custom_stats_var_anytime_update('entry2'); select test_custom_stats_var_anytime_update('entry2'); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select * from test_custom_stats_var_report('entry2'); -- 2.34.1 --C2xzVbxFmVrP7k6O Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v7-0004-Remove-useless-calls-to-flush-some-stats.patch" ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH v10 3/5] Add GUC to specify non-transactional statistics flush interval @ 2026-01-28 07:53 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 15+ messages in thread From: Bertrand Drouvot @ 2026-01-28 07:53 UTC (permalink / raw) Adding pgstat_flush_interval, a new GUC to set the interval between flushes of non-transactional statistics. --- doc/src/sgml/config.sgml | 32 +++++++++++++++++++ src/backend/utils/activity/pgstat.c | 13 ++++++++ src/backend/utils/misc/guc_parameters.dat | 10 ++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/backend/utils/misc/timeout.c | 6 ++++ src/include/pgstat.h | 6 ++-- src/include/utils/guc_hooks.h | 1 + src/include/utils/timeout.h | 1 + .../test_custom_stats/t/001_custom_stats.pl | 6 ++-- 9 files changed, 70 insertions(+), 6 deletions(-) 51.0% doc/src/sgml/ 10.6% src/backend/utils/activity/ 15.9% src/backend/utils/misc/ 3.6% src/include/utils/ 9.0% src/include/ 9.6% src/test/modules/test_custom_stats/t/ diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 20dbcaeb3ee..1eed71007a7 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8929,6 +8929,38 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-stats-flush-interval" xreflabel="stats_flush_interval"> + <term><varname>stats_flush_interval</varname> (<type>integer</type>) + <indexterm> + <primary><varname>stats_flush_interval</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Sets the interval at which certain statistics, which can be updated while a + transaction is in progress, are made visible. These include WAL activity + and I/O operations. + Such statistics are refreshed at the specified interval and can be observed + during active transactions in monitoring views such as + <link linkend="monitoring-pg-stat-wal-view"><structname>pg_stat_wal</structname></link> + and + <link linkend="monitoring-pg-stat-io-view"><structname>pg_stat_io</structname></link>. + If the value is specified without a unit, milliseconds are assumed. + The default is 10 seconds (<literal>10s</literal>), which is generally + the smallest practical value for long-running transactions. + </para> + <note> + <para> + This parameter does not affect statistics that are only reported at + transaction end, such as the columns of <structname>pg_stat_all_tables</structname> + (for example, <structfield>n_tup_ins</structfield>, <structfield>n_tup_upd</structfield>, + and <structfield>n_tup_del</structfield>). These statistics are always + flushed at the end of a transaction. + </para> + </note> + </listitem> + </varlistentry> + </variablelist> </sect2> diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index ddd331e2c81..fd6ab0db16f 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -124,6 +124,8 @@ * ---------- */ +/* minimum interval non-forced stats flushes.*/ +#define PGSTAT_MIN_INTERVAL 1000 /* how long until to block flushing pending stats updates */ #define PGSTAT_MAX_INTERVAL 60000 /* when to call pgstat_report_stat() again, even when idle */ @@ -204,6 +206,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind); bool pgstat_track_counts = false; int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE; +int pgstat_flush_interval = 10000; /* ---------- @@ -2171,6 +2174,16 @@ assign_stats_fetch_consistency(int newval, void *extra) force_stats_snapshot_clear = true; } +/* + * GUC assign_hook for stats_flush_interval. + */ +void +assign_stats_flush_interval(int newval, void *extra) +{ + if (get_all_timeouts_initialized()) + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, newval); +} + /* * Flushes only FLUSH_ANYTIME stats using non-blocking locks. Transactional * stats (FLUSH_AT_TXN_BOUNDARY) remain pending until transaction boundary. diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 9507778415d..073e08c7892 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -2801,6 +2801,16 @@ assign_hook => 'assign_stats_fetch_consistency', }, +{ name => 'stats_flush_interval', type => 'int', context => 'PGC_USERSET', group => 'STATS_CUMULATIVE', + short_desc => 'Sets the interval between flushes of non-transactional statistics.', + flags => 'GUC_UNIT_MS', + variable => 'pgstat_flush_interval', + boot_val => '10000', + min => '1000', + max => 'INT_MAX', + assign_hook => 'assign_stats_flush_interval' +}, + { name => 'subtransaction_buffers', type => 'int', context => 'PGC_POSTMASTER', group => 'RESOURCES_MEM', short_desc => 'Sets the size of the dedicated buffer pool used for the subtransaction cache.', long_desc => '0 means use a fraction of "shared_buffers".', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index f938cc65a3a..8bd37a25b38 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -688,6 +688,7 @@ #track_wal_io_timing = off #track_functions = none # none, pl, all #stats_fetch_consistency = cache # cache, none, snapshot +#stats_flush_interval = 10s # in milliseconds # - Monitoring - diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c index ddba5dc607c..85c4260d1db 100644 --- a/src/backend/utils/misc/timeout.c +++ b/src/backend/utils/misc/timeout.c @@ -828,3 +828,9 @@ get_timeout_finish_time(TimeoutId id) { return all_timeouts[id].fin_time; } + +bool +get_all_timeouts_initialized(void) +{ + return all_timeouts_initialized; +} diff --git a/src/include/pgstat.h b/src/include/pgstat.h index b011a315679..90237c70829 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -34,9 +34,6 @@ /* Default directory to store temporary statistics data in */ #define PG_STAT_TMP_DIR "pg_stat_tmp" -/* Minimum interval non-forced stats flushes */ -#define PGSTAT_MIN_INTERVAL 1000 - /* Values for track_functions GUC variable --- order is significant! */ typedef enum TrackFunctionsLevel { @@ -548,7 +545,7 @@ extern void pgstat_force_next_flush(void); do { \ if (IsUnderPostmaster && !pgstat_pending_anytime) \ { \ - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); \ + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); \ pgstat_pending_anytime = true; \ } \ } while (0) @@ -831,6 +828,7 @@ extern PGDLLIMPORT bool pgstat_pending_anytime; extern PGDLLIMPORT bool pgstat_track_counts; extern PGDLLIMPORT int pgstat_track_functions; extern PGDLLIMPORT int pgstat_fetch_consistency; +extern PGDLLIMPORT int pgstat_flush_interval; /* diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 9c90670d9b8..9b5d2a90387 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -132,6 +132,7 @@ extern bool check_session_authorization(char **newval, void **extra, GucSource s extern void assign_session_authorization(const char *newval, void *extra); extern void assign_session_replication_role(int newval, void *extra); extern void assign_stats_fetch_consistency(int newval, void *extra); +extern void assign_stats_flush_interval(int newval, void *extra); extern bool check_ssl(bool *newval, void **extra, GucSource source); extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source); extern bool check_standard_conforming_strings(bool *newval, void **extra, diff --git a/src/include/utils/timeout.h b/src/include/utils/timeout.h index 10723bb664c..fe7327de209 100644 --- a/src/include/utils/timeout.h +++ b/src/include/utils/timeout.h @@ -93,5 +93,6 @@ extern bool get_timeout_active(TimeoutId id); extern bool get_timeout_indicator(TimeoutId id, bool reset_indicator); extern TimestampTz get_timeout_start_time(TimeoutId id); extern TimestampTz get_timeout_finish_time(TimeoutId id); +extern bool get_all_timeouts_initialized(void); #endif /* TIMEOUT_H */ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 7be1b281776..22e2a75dcb9 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -164,10 +164,11 @@ $node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); my $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; -- Accumulate stats select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select 'anytime:'||numcalls from test_custom_stats_fixed_report(); @@ -183,11 +184,12 @@ like($result, qr/^anytime:2/m, $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; -- Accumulate stats select test_custom_stats_var_anytime_update('entry2'); select test_custom_stats_var_anytime_update('entry2'); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select * from test_custom_stats_var_report('entry2'); -- 2.34.1 --NVvBxFuyV/R+1/8/ Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v10-0004-Remove-useless-calls-to-flush-some-stats.patch" ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH v11 3/5] Add GUC to specify non-transactional statistics flush interval @ 2026-01-28 07:53 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 15+ messages in thread From: Bertrand Drouvot @ 2026-01-28 07:53 UTC (permalink / raw) Adding pgstat_flush_interval, a new GUC to set the interval between flushes of non-transactional statistics. --- doc/src/sgml/config.sgml | 32 +++++++++++++++++++ src/backend/utils/activity/pgstat.c | 13 ++++++++ src/backend/utils/misc/guc_parameters.dat | 10 ++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/backend/utils/misc/timeout.c | 6 ++++ src/include/pgstat.h | 6 ++-- src/include/utils/guc_hooks.h | 1 + src/include/utils/timeout.h | 1 + .../test_custom_stats/t/001_custom_stats.pl | 6 ++-- 9 files changed, 70 insertions(+), 6 deletions(-) 51.0% doc/src/sgml/ 10.6% src/backend/utils/activity/ 15.9% src/backend/utils/misc/ 3.6% src/include/utils/ 9.0% src/include/ 9.6% src/test/modules/test_custom_stats/t/ diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 20dbcaeb3ee..1eed71007a7 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8929,6 +8929,38 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-stats-flush-interval" xreflabel="stats_flush_interval"> + <term><varname>stats_flush_interval</varname> (<type>integer</type>) + <indexterm> + <primary><varname>stats_flush_interval</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Sets the interval at which certain statistics, which can be updated while a + transaction is in progress, are made visible. These include WAL activity + and I/O operations. + Such statistics are refreshed at the specified interval and can be observed + during active transactions in monitoring views such as + <link linkend="monitoring-pg-stat-wal-view"><structname>pg_stat_wal</structname></link> + and + <link linkend="monitoring-pg-stat-io-view"><structname>pg_stat_io</structname></link>. + If the value is specified without a unit, milliseconds are assumed. + The default is 10 seconds (<literal>10s</literal>), which is generally + the smallest practical value for long-running transactions. + </para> + <note> + <para> + This parameter does not affect statistics that are only reported at + transaction end, such as the columns of <structname>pg_stat_all_tables</structname> + (for example, <structfield>n_tup_ins</structfield>, <structfield>n_tup_upd</structfield>, + and <structfield>n_tup_del</structfield>). These statistics are always + flushed at the end of a transaction. + </para> + </note> + </listitem> + </varlistentry> + </variablelist> </sect2> diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index ddd331e2c81..fd6ab0db16f 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -124,6 +124,8 @@ * ---------- */ +/* minimum interval non-forced stats flushes.*/ +#define PGSTAT_MIN_INTERVAL 1000 /* how long until to block flushing pending stats updates */ #define PGSTAT_MAX_INTERVAL 60000 /* when to call pgstat_report_stat() again, even when idle */ @@ -204,6 +206,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind); bool pgstat_track_counts = false; int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE; +int pgstat_flush_interval = 10000; /* ---------- @@ -2171,6 +2174,16 @@ assign_stats_fetch_consistency(int newval, void *extra) force_stats_snapshot_clear = true; } +/* + * GUC assign_hook for stats_flush_interval. + */ +void +assign_stats_flush_interval(int newval, void *extra) +{ + if (get_all_timeouts_initialized()) + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, newval); +} + /* * Flushes only FLUSH_ANYTIME stats using non-blocking locks. Transactional * stats (FLUSH_AT_TXN_BOUNDARY) remain pending until transaction boundary. diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 9507778415d..073e08c7892 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -2801,6 +2801,16 @@ assign_hook => 'assign_stats_fetch_consistency', }, +{ name => 'stats_flush_interval', type => 'int', context => 'PGC_USERSET', group => 'STATS_CUMULATIVE', + short_desc => 'Sets the interval between flushes of non-transactional statistics.', + flags => 'GUC_UNIT_MS', + variable => 'pgstat_flush_interval', + boot_val => '10000', + min => '1000', + max => 'INT_MAX', + assign_hook => 'assign_stats_flush_interval' +}, + { name => 'subtransaction_buffers', type => 'int', context => 'PGC_POSTMASTER', group => 'RESOURCES_MEM', short_desc => 'Sets the size of the dedicated buffer pool used for the subtransaction cache.', long_desc => '0 means use a fraction of "shared_buffers".', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index f938cc65a3a..8bd37a25b38 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -688,6 +688,7 @@ #track_wal_io_timing = off #track_functions = none # none, pl, all #stats_fetch_consistency = cache # cache, none, snapshot +#stats_flush_interval = 10s # in milliseconds # - Monitoring - diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c index ddba5dc607c..85c4260d1db 100644 --- a/src/backend/utils/misc/timeout.c +++ b/src/backend/utils/misc/timeout.c @@ -828,3 +828,9 @@ get_timeout_finish_time(TimeoutId id) { return all_timeouts[id].fin_time; } + +bool +get_all_timeouts_initialized(void) +{ + return all_timeouts_initialized; +} diff --git a/src/include/pgstat.h b/src/include/pgstat.h index b011a315679..90237c70829 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -34,9 +34,6 @@ /* Default directory to store temporary statistics data in */ #define PG_STAT_TMP_DIR "pg_stat_tmp" -/* Minimum interval non-forced stats flushes */ -#define PGSTAT_MIN_INTERVAL 1000 - /* Values for track_functions GUC variable --- order is significant! */ typedef enum TrackFunctionsLevel { @@ -548,7 +545,7 @@ extern void pgstat_force_next_flush(void); do { \ if (IsUnderPostmaster && !pgstat_pending_anytime) \ { \ - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); \ + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); \ pgstat_pending_anytime = true; \ } \ } while (0) @@ -831,6 +828,7 @@ extern PGDLLIMPORT bool pgstat_pending_anytime; extern PGDLLIMPORT bool pgstat_track_counts; extern PGDLLIMPORT int pgstat_track_functions; extern PGDLLIMPORT int pgstat_fetch_consistency; +extern PGDLLIMPORT int pgstat_flush_interval; /* diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 9c90670d9b8..9b5d2a90387 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -132,6 +132,7 @@ extern bool check_session_authorization(char **newval, void **extra, GucSource s extern void assign_session_authorization(const char *newval, void *extra); extern void assign_session_replication_role(int newval, void *extra); extern void assign_stats_fetch_consistency(int newval, void *extra); +extern void assign_stats_flush_interval(int newval, void *extra); extern bool check_ssl(bool *newval, void **extra, GucSource source); extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source); extern bool check_standard_conforming_strings(bool *newval, void **extra, diff --git a/src/include/utils/timeout.h b/src/include/utils/timeout.h index 10723bb664c..fe7327de209 100644 --- a/src/include/utils/timeout.h +++ b/src/include/utils/timeout.h @@ -93,5 +93,6 @@ extern bool get_timeout_active(TimeoutId id); extern bool get_timeout_indicator(TimeoutId id, bool reset_indicator); extern TimestampTz get_timeout_start_time(TimeoutId id); extern TimestampTz get_timeout_finish_time(TimeoutId id); +extern bool get_all_timeouts_initialized(void); #endif /* TIMEOUT_H */ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 6ba4022418f..920443487c0 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -164,11 +164,12 @@ $node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); my $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; SET LOCAL stats_fetch_consistency = none; -- Accumulate stats select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select 'fixed_anytime:'||numcalls from test_custom_stats_fixed_report(); @@ -184,12 +185,13 @@ like($result, qr/^fixed_anytime:2/m, $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; SET LOCAL stats_fetch_consistency = none; -- Accumulate stats select test_custom_stats_var_anytime_update('entry2'); select test_custom_stats_var_anytime_update('entry2'); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select 'var_anytime:'||calls from test_custom_stats_var_report('entry2'); -- 2.34.1 --2MEBAGW8+kohXisi Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v11-0004-Remove-useless-calls-to-flush-some-stats.patch" ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH v11 3/5] Add GUC to specify non-transactional statistics flush interval @ 2026-01-28 07:53 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 15+ messages in thread From: Bertrand Drouvot @ 2026-01-28 07:53 UTC (permalink / raw) Adding pgstat_flush_interval, a new GUC to set the interval between flushes of non-transactional statistics. --- doc/src/sgml/config.sgml | 32 +++++++++++++++++++ src/backend/utils/activity/pgstat.c | 13 ++++++++ src/backend/utils/misc/guc_parameters.dat | 10 ++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/backend/utils/misc/timeout.c | 6 ++++ src/include/pgstat.h | 6 ++-- src/include/utils/guc_hooks.h | 1 + src/include/utils/timeout.h | 1 + .../test_custom_stats/t/001_custom_stats.pl | 6 ++-- 9 files changed, 70 insertions(+), 6 deletions(-) 51.0% doc/src/sgml/ 10.6% src/backend/utils/activity/ 15.9% src/backend/utils/misc/ 3.6% src/include/utils/ 9.0% src/include/ 9.6% src/test/modules/test_custom_stats/t/ diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 20dbcaeb3ee..1eed71007a7 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8929,6 +8929,38 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-stats-flush-interval" xreflabel="stats_flush_interval"> + <term><varname>stats_flush_interval</varname> (<type>integer</type>) + <indexterm> + <primary><varname>stats_flush_interval</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Sets the interval at which certain statistics, which can be updated while a + transaction is in progress, are made visible. These include WAL activity + and I/O operations. + Such statistics are refreshed at the specified interval and can be observed + during active transactions in monitoring views such as + <link linkend="monitoring-pg-stat-wal-view"><structname>pg_stat_wal</structname></link> + and + <link linkend="monitoring-pg-stat-io-view"><structname>pg_stat_io</structname></link>. + If the value is specified without a unit, milliseconds are assumed. + The default is 10 seconds (<literal>10s</literal>), which is generally + the smallest practical value for long-running transactions. + </para> + <note> + <para> + This parameter does not affect statistics that are only reported at + transaction end, such as the columns of <structname>pg_stat_all_tables</structname> + (for example, <structfield>n_tup_ins</structfield>, <structfield>n_tup_upd</structfield>, + and <structfield>n_tup_del</structfield>). These statistics are always + flushed at the end of a transaction. + </para> + </note> + </listitem> + </varlistentry> + </variablelist> </sect2> diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index ddd331e2c81..fd6ab0db16f 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -124,6 +124,8 @@ * ---------- */ +/* minimum interval non-forced stats flushes.*/ +#define PGSTAT_MIN_INTERVAL 1000 /* how long until to block flushing pending stats updates */ #define PGSTAT_MAX_INTERVAL 60000 /* when to call pgstat_report_stat() again, even when idle */ @@ -204,6 +206,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind); bool pgstat_track_counts = false; int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE; +int pgstat_flush_interval = 10000; /* ---------- @@ -2171,6 +2174,16 @@ assign_stats_fetch_consistency(int newval, void *extra) force_stats_snapshot_clear = true; } +/* + * GUC assign_hook for stats_flush_interval. + */ +void +assign_stats_flush_interval(int newval, void *extra) +{ + if (get_all_timeouts_initialized()) + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, newval); +} + /* * Flushes only FLUSH_ANYTIME stats using non-blocking locks. Transactional * stats (FLUSH_AT_TXN_BOUNDARY) remain pending until transaction boundary. diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 9507778415d..073e08c7892 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -2801,6 +2801,16 @@ assign_hook => 'assign_stats_fetch_consistency', }, +{ name => 'stats_flush_interval', type => 'int', context => 'PGC_USERSET', group => 'STATS_CUMULATIVE', + short_desc => 'Sets the interval between flushes of non-transactional statistics.', + flags => 'GUC_UNIT_MS', + variable => 'pgstat_flush_interval', + boot_val => '10000', + min => '1000', + max => 'INT_MAX', + assign_hook => 'assign_stats_flush_interval' +}, + { name => 'subtransaction_buffers', type => 'int', context => 'PGC_POSTMASTER', group => 'RESOURCES_MEM', short_desc => 'Sets the size of the dedicated buffer pool used for the subtransaction cache.', long_desc => '0 means use a fraction of "shared_buffers".', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index f938cc65a3a..8bd37a25b38 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -688,6 +688,7 @@ #track_wal_io_timing = off #track_functions = none # none, pl, all #stats_fetch_consistency = cache # cache, none, snapshot +#stats_flush_interval = 10s # in milliseconds # - Monitoring - diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c index ddba5dc607c..85c4260d1db 100644 --- a/src/backend/utils/misc/timeout.c +++ b/src/backend/utils/misc/timeout.c @@ -828,3 +828,9 @@ get_timeout_finish_time(TimeoutId id) { return all_timeouts[id].fin_time; } + +bool +get_all_timeouts_initialized(void) +{ + return all_timeouts_initialized; +} diff --git a/src/include/pgstat.h b/src/include/pgstat.h index b011a315679..90237c70829 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -34,9 +34,6 @@ /* Default directory to store temporary statistics data in */ #define PG_STAT_TMP_DIR "pg_stat_tmp" -/* Minimum interval non-forced stats flushes */ -#define PGSTAT_MIN_INTERVAL 1000 - /* Values for track_functions GUC variable --- order is significant! */ typedef enum TrackFunctionsLevel { @@ -548,7 +545,7 @@ extern void pgstat_force_next_flush(void); do { \ if (IsUnderPostmaster && !pgstat_pending_anytime) \ { \ - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); \ + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); \ pgstat_pending_anytime = true; \ } \ } while (0) @@ -831,6 +828,7 @@ extern PGDLLIMPORT bool pgstat_pending_anytime; extern PGDLLIMPORT bool pgstat_track_counts; extern PGDLLIMPORT int pgstat_track_functions; extern PGDLLIMPORT int pgstat_fetch_consistency; +extern PGDLLIMPORT int pgstat_flush_interval; /* diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 9c90670d9b8..9b5d2a90387 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -132,6 +132,7 @@ extern bool check_session_authorization(char **newval, void **extra, GucSource s extern void assign_session_authorization(const char *newval, void *extra); extern void assign_session_replication_role(int newval, void *extra); extern void assign_stats_fetch_consistency(int newval, void *extra); +extern void assign_stats_flush_interval(int newval, void *extra); extern bool check_ssl(bool *newval, void **extra, GucSource source); extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source); extern bool check_standard_conforming_strings(bool *newval, void **extra, diff --git a/src/include/utils/timeout.h b/src/include/utils/timeout.h index 10723bb664c..fe7327de209 100644 --- a/src/include/utils/timeout.h +++ b/src/include/utils/timeout.h @@ -93,5 +93,6 @@ extern bool get_timeout_active(TimeoutId id); extern bool get_timeout_indicator(TimeoutId id, bool reset_indicator); extern TimestampTz get_timeout_start_time(TimeoutId id); extern TimestampTz get_timeout_finish_time(TimeoutId id); +extern bool get_all_timeouts_initialized(void); #endif /* TIMEOUT_H */ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 6ba4022418f..920443487c0 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -164,11 +164,12 @@ $node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); my $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; SET LOCAL stats_fetch_consistency = none; -- Accumulate stats select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select 'fixed_anytime:'||numcalls from test_custom_stats_fixed_report(); @@ -184,12 +185,13 @@ like($result, qr/^fixed_anytime:2/m, $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; SET LOCAL stats_fetch_consistency = none; -- Accumulate stats select test_custom_stats_var_anytime_update('entry2'); select test_custom_stats_var_anytime_update('entry2'); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select 'var_anytime:'||calls from test_custom_stats_var_report('entry2'); -- 2.34.1 --2MEBAGW8+kohXisi Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v11-0004-Remove-useless-calls-to-flush-some-stats.patch" ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH v9 3/5] Add GUC to specify non-transactional statistics flush interval @ 2026-01-28 07:53 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 15+ messages in thread From: Bertrand Drouvot @ 2026-01-28 07:53 UTC (permalink / raw) Adding pgstat_flush_interval, a new GUC to set the interval between flushes of non-transactional statistics. --- doc/src/sgml/config.sgml | 32 +++++++++++++++++++ src/backend/utils/activity/pgstat.c | 13 ++++++++ src/backend/utils/misc/guc_parameters.dat | 10 ++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/backend/utils/misc/timeout.c | 6 ++++ src/include/pgstat.h | 6 ++-- src/include/utils/guc_hooks.h | 1 + src/include/utils/timeout.h | 1 + .../test_custom_stats/t/001_custom_stats.pl | 6 ++-- 9 files changed, 70 insertions(+), 6 deletions(-) 51.0% doc/src/sgml/ 10.6% src/backend/utils/activity/ 15.9% src/backend/utils/misc/ 3.6% src/include/utils/ 9.0% src/include/ 9.6% src/test/modules/test_custom_stats/t/ diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index faf0bdb62aa..03875b490b7 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8932,6 +8932,38 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-stats-flush-interval" xreflabel="stats_flush_interval"> + <term><varname>stats_flush_interval</varname> (<type>integer</type>) + <indexterm> + <primary><varname>stats_flush_interval</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Sets the interval at which certain statistics, which can be updated while a + transaction is in progress, are made visible. These include WAL activity + and I/O operations. + Such statistics are refreshed at the specified interval and can be observed + during active transactions in monitoring views such as + <link linkend="monitoring-pg-stat-wal-view"><structname>pg_stat_wal</structname></link> + and + <link linkend="monitoring-pg-stat-io-view"><structname>pg_stat_io</structname></link>. + If the value is specified without a unit, milliseconds are assumed. + The default is 10 seconds (<literal>10s</literal>), which is generally + the smallest practical value for long-running transactions. + </para> + <note> + <para> + This parameter does not affect statistics that are only reported at + transaction end, such as the columns of <structname>pg_stat_all_tables</structname> + (for example, <structfield>n_tup_ins</structfield>, <structfield>n_tup_upd</structfield>, + and <structfield>n_tup_del</structfield>). These statistics are always + flushed at the end of a transaction. + </para> + </note> + </listitem> + </varlistentry> + </variablelist> </sect2> diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index 419dc512d9b..578c575dbfb 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -123,6 +123,8 @@ * ---------- */ +/* minimum interval non-forced stats flushes.*/ +#define PGSTAT_MIN_INTERVAL 1000 /* how long until to block flushing pending stats updates */ #define PGSTAT_MAX_INTERVAL 60000 /* when to call pgstat_report_stat() again, even when idle */ @@ -203,6 +205,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind); bool pgstat_track_counts = false; int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE; +int pgstat_flush_interval = 10000; /* ---------- @@ -2170,6 +2173,16 @@ assign_stats_fetch_consistency(int newval, void *extra) force_stats_snapshot_clear = true; } +/* + * GUC assign_hook for stats_flush_interval. + */ +void +assign_stats_flush_interval(int newval, void *extra) +{ + if (get_all_timeouts_initialized()) + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, newval); +} + /* * Flushes only FLUSH_ANYTIME stats using non-blocking locks. Transactional * stats (FLUSH_AT_TXN_BOUNDARY) remain pending until transaction boundary. diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 271c033952e..d2734caafea 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -2801,6 +2801,16 @@ assign_hook => 'assign_stats_fetch_consistency', }, +{ name => 'stats_flush_interval', type => 'int', context => 'PGC_USERSET', group => 'STATS_CUMULATIVE', + short_desc => 'Sets the interval between flushes of non-transactional statistics.', + flags => 'GUC_UNIT_MS', + variable => 'pgstat_flush_interval', + boot_val => '10000', + min => '1000', + max => 'INT_MAX', + assign_hook => 'assign_stats_flush_interval' +}, + { name => 'subtransaction_buffers', type => 'int', context => 'PGC_POSTMASTER', group => 'RESOURCES_MEM', short_desc => 'Sets the size of the dedicated buffer pool used for the subtransaction cache.', long_desc => '0 means use a fraction of "shared_buffers".', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index f938cc65a3a..8bd37a25b38 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -688,6 +688,7 @@ #track_wal_io_timing = off #track_functions = none # none, pl, all #stats_fetch_consistency = cache # cache, none, snapshot +#stats_flush_interval = 10s # in milliseconds # - Monitoring - diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c index ddba5dc607c..85c4260d1db 100644 --- a/src/backend/utils/misc/timeout.c +++ b/src/backend/utils/misc/timeout.c @@ -828,3 +828,9 @@ get_timeout_finish_time(TimeoutId id) { return all_timeouts[id].fin_time; } + +bool +get_all_timeouts_initialized(void) +{ + return all_timeouts_initialized; +} diff --git a/src/include/pgstat.h b/src/include/pgstat.h index f0f546d419a..7829c563316 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -35,9 +35,6 @@ /* Default directory to store temporary statistics data in */ #define PG_STAT_TMP_DIR "pg_stat_tmp" -/* Minimum interval non-forced stats flushes */ -#define PGSTAT_MIN_INTERVAL 1000 - /* Values for track_functions GUC variable --- order is significant! */ typedef enum TrackFunctionsLevel { @@ -549,7 +546,7 @@ extern void pgstat_force_next_flush(void); do { \ if (IsUnderPostmaster && !pgstat_pending_anytime) \ { \ - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); \ + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); \ pgstat_pending_anytime = true; \ } \ } while (0) @@ -833,6 +830,7 @@ extern PGDLLIMPORT bool pgstat_pending_anytime; extern PGDLLIMPORT bool pgstat_track_counts; extern PGDLLIMPORT int pgstat_track_functions; extern PGDLLIMPORT int pgstat_fetch_consistency; +extern PGDLLIMPORT int pgstat_flush_interval; /* diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 9c90670d9b8..9b5d2a90387 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -132,6 +132,7 @@ extern bool check_session_authorization(char **newval, void **extra, GucSource s extern void assign_session_authorization(const char *newval, void *extra); extern void assign_session_replication_role(int newval, void *extra); extern void assign_stats_fetch_consistency(int newval, void *extra); +extern void assign_stats_flush_interval(int newval, void *extra); extern bool check_ssl(bool *newval, void **extra, GucSource source); extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source); extern bool check_standard_conforming_strings(bool *newval, void **extra, diff --git a/src/include/utils/timeout.h b/src/include/utils/timeout.h index 10723bb664c..fe7327de209 100644 --- a/src/include/utils/timeout.h +++ b/src/include/utils/timeout.h @@ -93,5 +93,6 @@ extern bool get_timeout_active(TimeoutId id); extern bool get_timeout_indicator(TimeoutId id, bool reset_indicator); extern TimestampTz get_timeout_start_time(TimeoutId id); extern TimestampTz get_timeout_finish_time(TimeoutId id); +extern bool get_all_timeouts_initialized(void); #endif /* TIMEOUT_H */ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 7be1b281776..22e2a75dcb9 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -164,10 +164,11 @@ $node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); my $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; -- Accumulate stats select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select 'anytime:'||numcalls from test_custom_stats_fixed_report(); @@ -183,11 +184,12 @@ like($result, qr/^anytime:2/m, $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; -- Accumulate stats select test_custom_stats_var_anytime_update('entry2'); select test_custom_stats_var_anytime_update('entry2'); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select * from test_custom_stats_var_report('entry2'); -- 2.34.1 --aq/6bi8L6WORnI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v9-0004-Remove-useless-calls-to-flush-some-stats.patch" ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH v7 3/5] Add GUC to specify non-transactional statistics flush interval @ 2026-01-28 07:53 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 15+ messages in thread From: Bertrand Drouvot @ 2026-01-28 07:53 UTC (permalink / raw) Adding pgstat_flush_interval, a new GUC to set the interval between flushes of non-transactional statistics. --- doc/src/sgml/config.sgml | 32 +++++++++++++++++++ src/backend/utils/activity/pgstat.c | 16 ++++++++++ src/backend/utils/misc/guc_parameters.dat | 10 ++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/pgstat.h | 6 ++-- src/include/utils/guc_hooks.h | 1 + .../test_custom_stats/t/001_custom_stats.pl | 6 ++-- 7 files changed, 66 insertions(+), 6 deletions(-) 51.8% doc/src/sgml/ 13.3% src/backend/utils/activity/ 13.6% src/backend/utils/misc/ 11.3% src/include/ 9.8% src/test/modules/test_custom_stats/t/ diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 6bc2690ce07..383bbe3a132 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8923,6 +8923,38 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-stats-flush-interval" xreflabel="stats_flush_interval"> + <term><varname>stats_flush_interval</varname> (<type>integer</type>) + <indexterm> + <primary><varname>stats_flush_interval</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Sets the interval at which certain statistics, which can be updated while a + transaction is in progress, are made visible. These include WAL activity + and I/O operations. + Such statistics are refreshed at the specified interval and can be observed + during active transactions in monitoring views such as + <link linkend="monitoring-pg-stat-wal-view"><structname>pg_stat_wal</structname></link> + and + <link linkend="monitoring-pg-stat-io-view"><structname>pg_stat_io</structname></link>. + If the value is specified without a unit, milliseconds are assumed. + The default is 10 seconds (<literal>10s</literal>), which is generally + the smallest practical value for long-running transactions. + </para> + <note> + <para> + This parameter does not affect statistics that are only reported at + transaction end, such as the columns of <structname>pg_stat_all_tables</structname> + (for example, <structfield>n_tup_ins</structfield>, <structfield>n_tup_upd</structfield>, + and <structfield>n_tup_del</structfield>). These statistics are always + flushed at the end of a transaction. + </para> + </note> + </listitem> + </varlistentry> + </variablelist> </sect2> diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index a4ff64dc5ce..dd85a27c52f 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -123,6 +123,8 @@ * ---------- */ +/* minimum interval non-forced stats flushes.*/ +#define PGSTAT_MIN_INTERVAL 1000 /* how long until to block flushing pending stats updates */ #define PGSTAT_MAX_INTERVAL 60000 /* when to call pgstat_report_stat() again, even when idle */ @@ -203,6 +205,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind); bool pgstat_track_counts = false; int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE; +int pgstat_flush_interval = 10000; /* ---------- @@ -2164,6 +2167,19 @@ assign_stats_fetch_consistency(int newval, void *extra) force_stats_snapshot_clear = true; } +/* + * GUC assign_hook for stats_flush_interval. + */ +void +assign_stats_flush_interval(int newval, void *extra) +{ + if (get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) + { + disable_timeout(ANYTIME_STATS_UPDATE_TIMEOUT, false); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, newval); + } +} + /* * Flushes only FLUSH_ANYTIME stats using non-blocking locks. Transactional * stats (FLUSH_AT_TXN_BOUNDARY) remain pending until transaction boundary. diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 271c033952e..d2734caafea 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -2801,6 +2801,16 @@ assign_hook => 'assign_stats_fetch_consistency', }, +{ name => 'stats_flush_interval', type => 'int', context => 'PGC_USERSET', group => 'STATS_CUMULATIVE', + short_desc => 'Sets the interval between flushes of non-transactional statistics.', + flags => 'GUC_UNIT_MS', + variable => 'pgstat_flush_interval', + boot_val => '10000', + min => '1000', + max => 'INT_MAX', + assign_hook => 'assign_stats_flush_interval' +}, + { name => 'subtransaction_buffers', type => 'int', context => 'PGC_POSTMASTER', group => 'RESOURCES_MEM', short_desc => 'Sets the size of the dedicated buffer pool used for the subtransaction cache.', long_desc => '0 means use a fraction of "shared_buffers".', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index f938cc65a3a..8bd37a25b38 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -688,6 +688,7 @@ #track_wal_io_timing = off #track_functions = none # none, pl, all #stats_fetch_consistency = cache # cache, none, snapshot +#stats_flush_interval = 10s # in milliseconds # - Monitoring - diff --git a/src/include/pgstat.h b/src/include/pgstat.h index b340a680614..ef856dbf55b 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -35,9 +35,6 @@ /* Default directory to store temporary statistics data in */ #define PG_STAT_TMP_DIR "pg_stat_tmp" -/* Minimum interval non-forced stats flushes */ -#define PGSTAT_MIN_INTERVAL 1000 - /* Values for track_functions GUC variable --- order is significant! */ typedef enum TrackFunctionsLevel { @@ -548,7 +545,7 @@ extern void pgstat_force_next_flush(void); #define pgstat_schedule_anytime_update() \ do { \ if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) \ - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); \ + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); \ } while (0) extern void pgstat_reset_counters(void); @@ -828,6 +825,7 @@ extern PgStat_WalStats *pgstat_fetch_stat_wal(void); extern PGDLLIMPORT bool pgstat_track_counts; extern PGDLLIMPORT int pgstat_track_functions; extern PGDLLIMPORT int pgstat_fetch_consistency; +extern PGDLLIMPORT int pgstat_flush_interval; /* diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 9c90670d9b8..9b5d2a90387 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -132,6 +132,7 @@ extern bool check_session_authorization(char **newval, void **extra, GucSource s extern void assign_session_authorization(const char *newval, void *extra); extern void assign_session_replication_role(int newval, void *extra); extern void assign_stats_fetch_consistency(int newval, void *extra); +extern void assign_stats_flush_interval(int newval, void *extra); extern bool check_ssl(bool *newval, void **extra, GucSource source); extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source); extern bool check_standard_conforming_strings(bool *newval, void **extra, diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 7be1b281776..22e2a75dcb9 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -164,10 +164,11 @@ $node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); my $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; -- Accumulate stats select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select 'anytime:'||numcalls from test_custom_stats_fixed_report(); @@ -183,11 +184,12 @@ like($result, qr/^anytime:2/m, $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; -- Accumulate stats select test_custom_stats_var_anytime_update('entry2'); select test_custom_stats_var_anytime_update('entry2'); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select * from test_custom_stats_var_report('entry2'); -- 2.34.1 --C2xzVbxFmVrP7k6O Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v7-0004-Remove-useless-calls-to-flush-some-stats.patch" ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH v6 3/5] Add GUC to specify non-transactional statistics flush interval @ 2026-01-28 07:53 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 15+ messages in thread From: Bertrand Drouvot @ 2026-01-28 07:53 UTC (permalink / raw) Adding pgstat_flush_interval, a new GUC to set the interval between flushes of non-transactional statistics. --- doc/src/sgml/config.sgml | 34 +++++++++++++++++++ src/backend/utils/activity/pgstat.c | 16 +++++++++ src/backend/utils/misc/guc_parameters.dat | 10 ++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/pgstat.h | 6 ++-- src/include/utils/guc_hooks.h | 1 + 6 files changed, 64 insertions(+), 4 deletions(-) 59.1% doc/src/sgml/ 14.2% src/backend/utils/activity/ 14.5% src/backend/utils/misc/ 12.0% src/include/ diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index f1af1505cf3..20666679f90 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8871,6 +8871,40 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-stats-flush-interval" xreflabel="stats_flush_interval"> + <term><varname>stats_flush_interval</varname> (<type>integer</type>) + <indexterm> + <primary><varname>stats_flush_interval</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Sets the interval at which statistics that can be updated while a + transaction is still running are made visible. These include, for example, + WAL activity and I/O operations. + Such statistics are refreshed at the specified interval and can be observed + during active transactions in monitoring views such as + <link linkend="monitoring-pg-stat-io-view"><structname>pg_stat_io</structname></link> + and + <link linkend="monitoring-pg-stat-wal-view"><structname>pg_stat_wal</structname></link>. + Other statistics are only made visible at transaction end and are not + affected by this setting. + If the value is specified without a unit, milliseconds are assumed. + The default is 10 seconds (<literal>10s</literal>), which is generally + the smallest practical value for long-running transactions. + </para> + <note> + <para> + This parameter does not affect statistics that are only reported at + transaction end, such as the columns of <structname>pg_stat_all_tables</structname> + (for example, <structfield>n_tup_ins</structfield>, <structfield>n_tup_upd</structfield>, + and <structfield>n_tup_del</structfield>). These statistics are always + flushed at the end of a transaction. + </para> + </note> + </listitem> + </varlistentry> + </variablelist> </sect2> diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index 411b65aae3e..79eb59b5625 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -123,6 +123,8 @@ * ---------- */ +/* minimum interval non-forced stats flushes.*/ +#define PGSTAT_MIN_INTERVAL 1000 /* how long until to block flushing pending stats updates */ #define PGSTAT_MAX_INTERVAL 60000 /* when to call pgstat_report_stat() again, even when idle */ @@ -203,6 +205,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind); bool pgstat_track_counts = false; int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE; +int pgstat_flush_interval = 10000; /* ---------- @@ -2164,6 +2167,19 @@ assign_stats_fetch_consistency(int newval, void *extra) force_stats_snapshot_clear = true; } +/* + * GUC assign_hook for stats_flush_interval. + */ +void +assign_stats_flush_interval(int newval, void *extra) +{ + if (get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) + { + disable_timeout(ANYTIME_STATS_UPDATE_TIMEOUT, false); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, newval); + } +} + /* * Flushes only FLUSH_ANYTIME stats using non-blocking locks. Transactional * stats (FLUSH_AT_TXN_BOUNDARY) remain pending until transaction boundary. diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index c1f1603cd39..fc0e4259b36 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -2789,6 +2789,16 @@ assign_hook => 'assign_stats_fetch_consistency', }, +{ name => 'stats_flush_interval', type => 'int', context => 'PGC_USERSET', group => 'STATS_CUMULATIVE', + short_desc => 'Sets the interval between flushes of non-transactional statistics.', + flags => 'GUC_UNIT_MS', + variable => 'pgstat_flush_interval', + boot_val => '10000', + min => '1000', + max => 'INT_MAX', + assign_hook => 'assign_stats_flush_interval' +}, + { name => 'subtransaction_buffers', type => 'int', context => 'PGC_POSTMASTER', group => 'RESOURCES_MEM', short_desc => 'Sets the size of the dedicated buffer pool used for the subtransaction cache.', long_desc => '0 means use a fraction of "shared_buffers".', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 1ae594af843..3f998a0bea0 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -673,6 +673,7 @@ #track_wal_io_timing = off #track_functions = none # none, pl, all #stats_fetch_consistency = cache # cache, none, snapshot +#stats_flush_interval = 10s # in milliseconds # - Monitoring - diff --git a/src/include/pgstat.h b/src/include/pgstat.h index b340a680614..ef856dbf55b 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -35,9 +35,6 @@ /* Default directory to store temporary statistics data in */ #define PG_STAT_TMP_DIR "pg_stat_tmp" -/* Minimum interval non-forced stats flushes */ -#define PGSTAT_MIN_INTERVAL 1000 - /* Values for track_functions GUC variable --- order is significant! */ typedef enum TrackFunctionsLevel { @@ -548,7 +545,7 @@ extern void pgstat_force_next_flush(void); #define pgstat_schedule_anytime_update() \ do { \ if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) \ - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); \ + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); \ } while (0) extern void pgstat_reset_counters(void); @@ -828,6 +825,7 @@ extern PgStat_WalStats *pgstat_fetch_stat_wal(void); extern PGDLLIMPORT bool pgstat_track_counts; extern PGDLLIMPORT int pgstat_track_functions; extern PGDLLIMPORT int pgstat_fetch_consistency; +extern PGDLLIMPORT int pgstat_flush_interval; /* diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index b6ecb0e769f..3a2ae6c41cd 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -132,6 +132,7 @@ extern bool check_session_authorization(char **newval, void **extra, GucSource s extern void assign_session_authorization(const char *newval, void *extra); extern void assign_session_replication_role(int newval, void *extra); extern void assign_stats_fetch_consistency(int newval, void *extra); +extern void assign_stats_flush_interval(int newval, void *extra); extern bool check_ssl(bool *newval, void **extra, GucSource source); extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source); extern bool check_standard_conforming_strings(bool *newval, void **extra, -- 2.34.1 --DxqFthphbM+ha9Dy Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v6-0004-Remove-useless-calls-to-flush-some-stats.patch" ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH v8 3/5] Add GUC to specify non-transactional statistics flush interval @ 2026-01-28 07:53 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 15+ messages in thread From: Bertrand Drouvot @ 2026-01-28 07:53 UTC (permalink / raw) Adding pgstat_flush_interval, a new GUC to set the interval between flushes of non-transactional statistics. --- doc/src/sgml/config.sgml | 32 +++++++++++++++++++ src/backend/utils/activity/pgstat.c | 16 ++++++++++ src/backend/utils/misc/guc_parameters.dat | 10 ++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/pgstat.h | 6 ++-- src/include/utils/guc_hooks.h | 1 + .../test_custom_stats/t/001_custom_stats.pl | 6 ++-- 7 files changed, 66 insertions(+), 6 deletions(-) 51.8% doc/src/sgml/ 13.3% src/backend/utils/activity/ 13.6% src/backend/utils/misc/ 11.3% src/include/ 9.8% src/test/modules/test_custom_stats/t/ diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index faf0bdb62aa..03875b490b7 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8932,6 +8932,38 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-stats-flush-interval" xreflabel="stats_flush_interval"> + <term><varname>stats_flush_interval</varname> (<type>integer</type>) + <indexterm> + <primary><varname>stats_flush_interval</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Sets the interval at which certain statistics, which can be updated while a + transaction is in progress, are made visible. These include WAL activity + and I/O operations. + Such statistics are refreshed at the specified interval and can be observed + during active transactions in monitoring views such as + <link linkend="monitoring-pg-stat-wal-view"><structname>pg_stat_wal</structname></link> + and + <link linkend="monitoring-pg-stat-io-view"><structname>pg_stat_io</structname></link>. + If the value is specified without a unit, milliseconds are assumed. + The default is 10 seconds (<literal>10s</literal>), which is generally + the smallest practical value for long-running transactions. + </para> + <note> + <para> + This parameter does not affect statistics that are only reported at + transaction end, such as the columns of <structname>pg_stat_all_tables</structname> + (for example, <structfield>n_tup_ins</structfield>, <structfield>n_tup_upd</structfield>, + and <structfield>n_tup_del</structfield>). These statistics are always + flushed at the end of a transaction. + </para> + </note> + </listitem> + </varlistentry> + </variablelist> </sect2> diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index a4ff64dc5ce..dd85a27c52f 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -123,6 +123,8 @@ * ---------- */ +/* minimum interval non-forced stats flushes.*/ +#define PGSTAT_MIN_INTERVAL 1000 /* how long until to block flushing pending stats updates */ #define PGSTAT_MAX_INTERVAL 60000 /* when to call pgstat_report_stat() again, even when idle */ @@ -203,6 +205,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind); bool pgstat_track_counts = false; int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE; +int pgstat_flush_interval = 10000; /* ---------- @@ -2164,6 +2167,19 @@ assign_stats_fetch_consistency(int newval, void *extra) force_stats_snapshot_clear = true; } +/* + * GUC assign_hook for stats_flush_interval. + */ +void +assign_stats_flush_interval(int newval, void *extra) +{ + if (get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) + { + disable_timeout(ANYTIME_STATS_UPDATE_TIMEOUT, false); + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, newval); + } +} + /* * Flushes only FLUSH_ANYTIME stats using non-blocking locks. Transactional * stats (FLUSH_AT_TXN_BOUNDARY) remain pending until transaction boundary. diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 271c033952e..d2734caafea 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -2801,6 +2801,16 @@ assign_hook => 'assign_stats_fetch_consistency', }, +{ name => 'stats_flush_interval', type => 'int', context => 'PGC_USERSET', group => 'STATS_CUMULATIVE', + short_desc => 'Sets the interval between flushes of non-transactional statistics.', + flags => 'GUC_UNIT_MS', + variable => 'pgstat_flush_interval', + boot_val => '10000', + min => '1000', + max => 'INT_MAX', + assign_hook => 'assign_stats_flush_interval' +}, + { name => 'subtransaction_buffers', type => 'int', context => 'PGC_POSTMASTER', group => 'RESOURCES_MEM', short_desc => 'Sets the size of the dedicated buffer pool used for the subtransaction cache.', long_desc => '0 means use a fraction of "shared_buffers".', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index f938cc65a3a..8bd37a25b38 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -688,6 +688,7 @@ #track_wal_io_timing = off #track_functions = none # none, pl, all #stats_fetch_consistency = cache # cache, none, snapshot +#stats_flush_interval = 10s # in milliseconds # - Monitoring - diff --git a/src/include/pgstat.h b/src/include/pgstat.h index b340a680614..ef856dbf55b 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -35,9 +35,6 @@ /* Default directory to store temporary statistics data in */ #define PG_STAT_TMP_DIR "pg_stat_tmp" -/* Minimum interval non-forced stats flushes */ -#define PGSTAT_MIN_INTERVAL 1000 - /* Values for track_functions GUC variable --- order is significant! */ typedef enum TrackFunctionsLevel { @@ -548,7 +545,7 @@ extern void pgstat_force_next_flush(void); #define pgstat_schedule_anytime_update() \ do { \ if (IsUnderPostmaster && !get_timeout_active(ANYTIME_STATS_UPDATE_TIMEOUT)) \ - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); \ + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); \ } while (0) extern void pgstat_reset_counters(void); @@ -828,6 +825,7 @@ extern PgStat_WalStats *pgstat_fetch_stat_wal(void); extern PGDLLIMPORT bool pgstat_track_counts; extern PGDLLIMPORT int pgstat_track_functions; extern PGDLLIMPORT int pgstat_fetch_consistency; +extern PGDLLIMPORT int pgstat_flush_interval; /* diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 9c90670d9b8..9b5d2a90387 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -132,6 +132,7 @@ extern bool check_session_authorization(char **newval, void **extra, GucSource s extern void assign_session_authorization(const char *newval, void *extra); extern void assign_session_replication_role(int newval, void *extra); extern void assign_stats_fetch_consistency(int newval, void *extra); +extern void assign_stats_flush_interval(int newval, void *extra); extern bool check_ssl(bool *newval, void **extra, GucSource source); extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source); extern bool check_standard_conforming_strings(bool *newval, void **extra, diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 7be1b281776..22e2a75dcb9 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -164,10 +164,11 @@ $node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); my $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; -- Accumulate stats select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select 'anytime:'||numcalls from test_custom_stats_fixed_report(); @@ -183,11 +184,12 @@ like($result, qr/^anytime:2/m, $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; -- Accumulate stats select test_custom_stats_var_anytime_update('entry2'); select test_custom_stats_var_anytime_update('entry2'); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select * from test_custom_stats_var_report('entry2'); -- 2.34.1 --OI5irqZsxWxBW9nT Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0004-Remove-useless-calls-to-flush-some-stats.patch" ^ permalink raw reply [nested|flat] 15+ messages in thread
* [PATCH v10 3/5] Add GUC to specify non-transactional statistics flush interval @ 2026-01-28 07:53 Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 0 siblings, 0 replies; 15+ messages in thread From: Bertrand Drouvot @ 2026-01-28 07:53 UTC (permalink / raw) Adding pgstat_flush_interval, a new GUC to set the interval between flushes of non-transactional statistics. --- doc/src/sgml/config.sgml | 32 +++++++++++++++++++ src/backend/utils/activity/pgstat.c | 13 ++++++++ src/backend/utils/misc/guc_parameters.dat | 10 ++++++ src/backend/utils/misc/postgresql.conf.sample | 1 + src/backend/utils/misc/timeout.c | 6 ++++ src/include/pgstat.h | 6 ++-- src/include/utils/guc_hooks.h | 1 + src/include/utils/timeout.h | 1 + .../test_custom_stats/t/001_custom_stats.pl | 6 ++-- 9 files changed, 70 insertions(+), 6 deletions(-) 51.0% doc/src/sgml/ 10.6% src/backend/utils/activity/ 15.9% src/backend/utils/misc/ 3.6% src/include/utils/ 9.0% src/include/ 9.6% src/test/modules/test_custom_stats/t/ diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 20dbcaeb3ee..1eed71007a7 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -8929,6 +8929,38 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-stats-flush-interval" xreflabel="stats_flush_interval"> + <term><varname>stats_flush_interval</varname> (<type>integer</type>) + <indexterm> + <primary><varname>stats_flush_interval</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Sets the interval at which certain statistics, which can be updated while a + transaction is in progress, are made visible. These include WAL activity + and I/O operations. + Such statistics are refreshed at the specified interval and can be observed + during active transactions in monitoring views such as + <link linkend="monitoring-pg-stat-wal-view"><structname>pg_stat_wal</structname></link> + and + <link linkend="monitoring-pg-stat-io-view"><structname>pg_stat_io</structname></link>. + If the value is specified without a unit, milliseconds are assumed. + The default is 10 seconds (<literal>10s</literal>), which is generally + the smallest practical value for long-running transactions. + </para> + <note> + <para> + This parameter does not affect statistics that are only reported at + transaction end, such as the columns of <structname>pg_stat_all_tables</structname> + (for example, <structfield>n_tup_ins</structfield>, <structfield>n_tup_upd</structfield>, + and <structfield>n_tup_del</structfield>). These statistics are always + flushed at the end of a transaction. + </para> + </note> + </listitem> + </varlistentry> + </variablelist> </sect2> diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index ddd331e2c81..fd6ab0db16f 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -124,6 +124,8 @@ * ---------- */ +/* minimum interval non-forced stats flushes.*/ +#define PGSTAT_MIN_INTERVAL 1000 /* how long until to block flushing pending stats updates */ #define PGSTAT_MAX_INTERVAL 60000 /* when to call pgstat_report_stat() again, even when idle */ @@ -204,6 +206,7 @@ static inline bool pgstat_is_kind_valid(PgStat_Kind kind); bool pgstat_track_counts = false; int pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE; +int pgstat_flush_interval = 10000; /* ---------- @@ -2171,6 +2174,16 @@ assign_stats_fetch_consistency(int newval, void *extra) force_stats_snapshot_clear = true; } +/* + * GUC assign_hook for stats_flush_interval. + */ +void +assign_stats_flush_interval(int newval, void *extra) +{ + if (get_all_timeouts_initialized()) + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, newval); +} + /* * Flushes only FLUSH_ANYTIME stats using non-blocking locks. Transactional * stats (FLUSH_AT_TXN_BOUNDARY) remain pending until transaction boundary. diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 9507778415d..073e08c7892 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -2801,6 +2801,16 @@ assign_hook => 'assign_stats_fetch_consistency', }, +{ name => 'stats_flush_interval', type => 'int', context => 'PGC_USERSET', group => 'STATS_CUMULATIVE', + short_desc => 'Sets the interval between flushes of non-transactional statistics.', + flags => 'GUC_UNIT_MS', + variable => 'pgstat_flush_interval', + boot_val => '10000', + min => '1000', + max => 'INT_MAX', + assign_hook => 'assign_stats_flush_interval' +}, + { name => 'subtransaction_buffers', type => 'int', context => 'PGC_POSTMASTER', group => 'RESOURCES_MEM', short_desc => 'Sets the size of the dedicated buffer pool used for the subtransaction cache.', long_desc => '0 means use a fraction of "shared_buffers".', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index f938cc65a3a..8bd37a25b38 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -688,6 +688,7 @@ #track_wal_io_timing = off #track_functions = none # none, pl, all #stats_fetch_consistency = cache # cache, none, snapshot +#stats_flush_interval = 10s # in milliseconds # - Monitoring - diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c index ddba5dc607c..85c4260d1db 100644 --- a/src/backend/utils/misc/timeout.c +++ b/src/backend/utils/misc/timeout.c @@ -828,3 +828,9 @@ get_timeout_finish_time(TimeoutId id) { return all_timeouts[id].fin_time; } + +bool +get_all_timeouts_initialized(void) +{ + return all_timeouts_initialized; +} diff --git a/src/include/pgstat.h b/src/include/pgstat.h index b011a315679..90237c70829 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -34,9 +34,6 @@ /* Default directory to store temporary statistics data in */ #define PG_STAT_TMP_DIR "pg_stat_tmp" -/* Minimum interval non-forced stats flushes */ -#define PGSTAT_MIN_INTERVAL 1000 - /* Values for track_functions GUC variable --- order is significant! */ typedef enum TrackFunctionsLevel { @@ -548,7 +545,7 @@ extern void pgstat_force_next_flush(void); do { \ if (IsUnderPostmaster && !pgstat_pending_anytime) \ { \ - enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, PGSTAT_MIN_INTERVAL); \ + enable_timeout_after(ANYTIME_STATS_UPDATE_TIMEOUT, pgstat_flush_interval); \ pgstat_pending_anytime = true; \ } \ } while (0) @@ -831,6 +828,7 @@ extern PGDLLIMPORT bool pgstat_pending_anytime; extern PGDLLIMPORT bool pgstat_track_counts; extern PGDLLIMPORT int pgstat_track_functions; extern PGDLLIMPORT int pgstat_fetch_consistency; +extern PGDLLIMPORT int pgstat_flush_interval; /* diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h index 9c90670d9b8..9b5d2a90387 100644 --- a/src/include/utils/guc_hooks.h +++ b/src/include/utils/guc_hooks.h @@ -132,6 +132,7 @@ extern bool check_session_authorization(char **newval, void **extra, GucSource s extern void assign_session_authorization(const char *newval, void *extra); extern void assign_session_replication_role(int newval, void *extra); extern void assign_stats_fetch_consistency(int newval, void *extra); +extern void assign_stats_flush_interval(int newval, void *extra); extern bool check_ssl(bool *newval, void **extra, GucSource source); extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source); extern bool check_standard_conforming_strings(bool *newval, void **extra, diff --git a/src/include/utils/timeout.h b/src/include/utils/timeout.h index 10723bb664c..fe7327de209 100644 --- a/src/include/utils/timeout.h +++ b/src/include/utils/timeout.h @@ -93,5 +93,6 @@ extern bool get_timeout_active(TimeoutId id); extern bool get_timeout_indicator(TimeoutId id, bool reset_indicator); extern TimestampTz get_timeout_start_time(TimeoutId id); extern TimestampTz get_timeout_finish_time(TimeoutId id); +extern bool get_all_timeouts_initialized(void); #endif /* TIMEOUT_H */ diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 7be1b281776..22e2a75dcb9 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -164,10 +164,11 @@ $node->safe_psql('postgres', q(select test_custom_stats_fixed_reset())); $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); my $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; -- Accumulate stats select test_custom_stats_fixed_anytime_update() from generate_series(1, 2); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select 'anytime:'||numcalls from test_custom_stats_fixed_report(); @@ -183,11 +184,12 @@ like($result, qr/^anytime:2/m, $node->safe_psql('postgres', q(select pg_stat_force_next_flush())); $anytime_test = q[ + SET stats_flush_interval = '1s'; BEGIN; -- Accumulate stats select test_custom_stats_var_anytime_update('entry2'); select test_custom_stats_var_anytime_update('entry2'); - -- Wait (has to be greater than PGSTAT_MIN_INTERVAL) + -- Wait (has to be greater than stats_flush_interval) select pg_sleep(1.5); -- Check select * from test_custom_stats_var_report('entry2'); -- 2.34.1 --NVvBxFuyV/R+1/8/ Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v10-0004-Remove-useless-calls-to-flush-some-stats.patch" ^ permalink raw reply [nested|flat] 15+ messages in thread
end of thread, other threads:[~2026-01-28 07:53 UTC | newest] Thread overview: 15+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2019-07-09 09:54 [PATCH 2/5] Remove TLI from some argument lists. Antonin Houska <ah@cybertec.at> 2019-09-09 09:53 [PATCH 1/4] Remove TLI from some argument lists. Antonin Houska <ah@cybertec.at> 2019-09-23 05:40 [PATCH 2/6] Remove TLI from some argument lists. Antonin Houska <ah@cybertec.at> 2024-05-21 16:35 [PATCH v8] Add OR REPLACE option to CREATE MATERIALIZED VIEW Erik Wienhold <ewie@ewie.name> 2026-01-28 07:53 [PATCH v5 2/4] Add GUC to specify non-transactional statistics flush interval Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2026-01-28 07:53 [PATCH v4 2/4] Add GUC to specify non-transactional statistics flush interval Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2026-01-28 07:53 [PATCH v7 3/5] Add GUC to specify non-transactional statistics flush interval Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2026-01-28 07:53 [PATCH v10 3/5] Add GUC to specify non-transactional statistics flush interval Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2026-01-28 07:53 [PATCH v11 3/5] Add GUC to specify non-transactional statistics flush interval Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2026-01-28 07:53 [PATCH v11 3/5] Add GUC to specify non-transactional statistics flush interval Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2026-01-28 07:53 [PATCH v9 3/5] Add GUC to specify non-transactional statistics flush interval Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2026-01-28 07:53 [PATCH v7 3/5] Add GUC to specify non-transactional statistics flush interval Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2026-01-28 07:53 [PATCH v6 3/5] Add GUC to specify non-transactional statistics flush interval Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2026-01-28 07:53 [PATCH v8 3/5] Add GUC to specify non-transactional statistics flush interval Bertrand Drouvot <bertranddrouvot.pg@gmail.com> 2026-01-28 07:53 [PATCH v10 3/5] Add GUC to specify non-transactional statistics flush interval Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox