From 9ea122eeb21307f537c777950396feb0da35c51b Mon Sep 17 00:00:00 2001 From: Bharath Rupireddy Date: Mon, 14 Sep 2026 00:02:52 +0000 Subject: [PATCH v9 5/5] Use WALReadFromBuffers() for the WAL summarizer. Commit 91f2cae7a4 introduced WALReadFromBuffers() for physical replication walsenders, and the previous commits used it for logical replication walsenders and for the callers that read WAL through the shared page-read callback. The WAL summarizer has its own page-read callback, so it was left out. This commit uses it there as well. With WAL summarization enabled, the summarizer reads back all the WAL the server writes, one page at a time, which is a second pass over the WAL stream. When it keeps up with WAL generation, the requested WAL is often still in the WAL buffers, so it can be read from there instead of from a file. The gain is largest with WAL direct I/O, where a file read is a physical disk read. Without direct I/O it still saves a syscall and does not regress. How often that succeeds depends on the WAL generation rate and on wal_writer_delay rather than on wal_buffers, because the WAL writer hands already-written buffer pages out for reuse as future pages at the end of XLogBackgroundFlush(). The WAL that a reader can still find in the buffers is therefore roughly what has been generated since the WAL writer's last cycle. The timeline passed to WALReadFromBuffers() is the one the read targets, the same one passed to WALRead(), so that a read on a historic timeline never comes from the WAL buffers. The summarizer never reads past the flush point, so the requested WAL has always been inserted into the WAL buffers. During recovery WALReadFromBuffers() reads nothing, so summarization on a standby is unchanged. As for the other callers, a read that comes entirely from the WAL buffers can leave the segment file open on the wrong segment. Fix this by closing the open segment when such a read is not in the open segment, so that the next file read reopens the correct one. With this change, pg_stat_io reports hits on object wal for the WAL summarizer, which shows how often reading a WAL file is avoided. pg_walsummary's test that the summarizer generates read statistics now counts both hits and reads, because either path can serve the reads it makes. Author: Bharath Rupireddy Discussion: https://www.postgresql.org/message-id/CALj2ACVfF2Uj9NoFy-5m98HNtjHpuD17EDE9twVeJng-jTAe7A%40mail.gmail.com --- src/backend/postmaster/walsummarizer.c | 30 +++++++++++++++++++++++--- src/bin/pg_walsummary/t/002_blocks.pl | 5 +++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c index ff246b07a21..2eec003afd2 100644 --- a/src/backend/postmaster/walsummarizer.c +++ b/src/backend/postmaster/walsummarizer.c @@ -1658,6 +1658,7 @@ summarizer_read_local_xlog_page(XLogReaderState *state, int count; WALReadError errinfo; SummarizerReadLocalXLogPrivate *private_data; + Size rbytes; ProcessWalSummarizerInterrupts(); @@ -1749,9 +1750,32 @@ summarizer_read_local_xlog_page(XLogReaderState *state, } } - if (!WALRead(state, cur_page, targetPagePtr, count, - private_data->tli, &errinfo)) - WALReadRaiseError(&errinfo); + /* attempt to read WAL from WAL buffers first */ + rbytes = WALReadFromBuffers(cur_page, targetPagePtr, count, + private_data->tli); + + /* now read the remaining WAL from WAL file */ + if (rbytes < count) + { + if (!WALRead(state, + cur_page + rbytes, + targetPagePtr + rbytes, + count - rbytes, + private_data->tli, + &errinfo)) + WALReadRaiseError(&errinfo); + } + else if (state->seg.ws_file >= 0 && + !XLByteInSeg(targetPagePtr, state->seg.ws_segno, + state->segcxt.ws_segsize)) + { + /* + * Close the segment when a read that comes entirely from the WAL + * buffers is not in the open segment, so that the next file read + * reopens the correct one. + */ + state->routine.segment_close(state); + } /* Track that we read a page, for sleep time calculation. */ ++pages_read_since_last_sleep; diff --git a/src/bin/pg_walsummary/t/002_blocks.pl b/src/bin/pg_walsummary/t/002_blocks.pl index bebc1137c0f..a9fffe3026e 100644 --- a/src/bin/pg_walsummary/t/002_blocks.pl +++ b/src/bin/pg_walsummary/t/002_blocks.pl @@ -46,10 +46,11 @@ SELECT EXISTS ( EOM ok($result, "WAL summarization caught up after insert"); -# The WAL summarizer should have generated some IO statistics. +# The WAL summarizer should have generated some IO statistics. The WAL it +# reads back comes either from the WAL buffers or from a file, so count both. $node1->poll_query_until( 'postgres', - q{SELECT sum(reads) > 0 FROM pg_stat_io + q{SELECT sum(hits) + sum(reads) > 0 FROM pg_stat_io WHERE backend_type = 'walsummarizer' AND object = 'wal'}) or die "Timed out while waiting for WAL summarizer to generate statistics for WAL reads"; -- 2.47.3