From c5b1250a379fb005209b763bd19a075009ddb6d1 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Mon, 3 Aug 2026 17:31:49 -0400
Subject: [PATCH] Fix VACUUM failsafe mode's dropping of the buffer access
 strategy

Since 4830f1024325, when VACUUM's wraparound failsafe triggers, it stops
using the BAS_VACUUM buffer access strategy so that the rest of the
vacuum may use all of shared buffers and reclaim transaction IDs as
quickly as possible.

However, when 9256822608f3 made vacuum's first phase use the read stream,
it accidentally disabled this functionality.  To re-enable it, we have to
clear the strategy actually being used by the read stream.  We must do
this only for newly started reads -- reads already in progress must be
accounted under the IO context they were started in.  To achieve this in
a backpatchable way, track the BufferAccessStrategy in the ReadStream
object itself and set the ReadBuffersOperation's strategy member just
before each read is started -- in case it has changed.
---
 src/backend/access/heap/vacuumlazy.c  | 19 ++++++++++++++++---
 src/backend/storage/aio/read_stream.c | 19 ++++++++++++++++++-
 src/include/storage/read_stream.h     |  1 +
 3 files changed, 35 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 39395aed0d5..2dc9a9de6a9 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -1385,6 +1385,17 @@ lazy_scan_heap(LVRelState *vacrel)
 										 PROGRESS_VACUUM_PHASE_SCAN_HEAP);
 		}
 
+		/*
+		 * If the wraparound failsafe has engaged -- either via the check
+		 * above or during index vacuuming invoked from this loop -- stop
+		 * using the buffer access strategy. Currently in-progress reads are
+		 * not affected. This only affects phase I vacuum scan of the heap, so
+		 * we cannot clear the read stream strategy in
+		 * lazy_check_wraparound_failsafe().
+		 */
+		if (unlikely(VacuumFailsafeActive))
+			read_stream_clear_strategy(stream);
+
 		buf = read_stream_next_buffer(stream, &per_buffer_data);
 
 		/* The relation is exhausted. */
@@ -2905,9 +2916,11 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel)
 		VacuumFailsafeActive = true;
 
 		/*
-		 * Abandon use of a buffer access strategy to allow use of all of
-		 * shared buffers.  We assume the caller who allocated the memory for
-		 * the BufferAccessStrategy will free it.
+		 * Clear this just for tidiness. An ongoing phase I heap scan already
+		 * has its own copy of the strategy (which it will clear itself) and
+		 * none of the other vacuum phases will read from the strategy member
+		 * once failsafe mode is engaged. We assume the caller who allocated
+		 * memory for the BufferAccessStrategy will free it.
 		 */
 		vacrel->bstrategy = NULL;
 
diff --git a/src/backend/storage/aio/read_stream.c b/src/backend/storage/aio/read_stream.c
index a318539e56c..fff17eab3cf 100644
--- a/src/backend/storage/aio/read_stream.c
+++ b/src/backend/storage/aio/read_stream.c
@@ -119,6 +119,7 @@ struct ReadStream
 	int16		resume_readahead_distance;
 	int16		resume_combine_distance;
 	int			read_buffers_flags;
+	BufferAccessStrategy strategy;
 	bool		sync_mode;		/* using io_method=sync */
 	bool		batch_mode;		/* READ_STREAM_USE_BATCHING */
 	bool		advice_enabled;
@@ -441,6 +442,8 @@ read_stream_start_pending_read(ReadStream *stream)
 	while (stream->initialized_buffers < buffer_index + nblocks)
 		stream->buffers[stream->initialized_buffers++] = InvalidBuffer;
 	requested_nblocks = nblocks;
+	/* Set this here in case the BufferAccessStrategy has changed */
+	stream->ios[io_index].op.strategy = stream->strategy;
 	need_wait = StartReadBuffers(&stream->ios[io_index].op,
 								 &stream->buffers[buffer_index],
 								 stream->pending_read_blocknum,
@@ -932,6 +935,7 @@ read_stream_begin_impl(int flags,
 	stream->seq_until_processed = InvalidBlockNumber;
 	stream->temporary = SmgrIsTemp(smgr);
 	stream->distance_decay_holdoff = 0;
+	stream->strategy = strategy;
 
 	/*
 	 * Skip the initial ramp-up phase if the caller says we're going to be
@@ -1376,7 +1380,7 @@ read_stream_next_buffer(ReadStream *stream, void **per_buffer_data)
 BlockNumber
 read_stream_next_block(ReadStream *stream, BufferAccessStrategy *strategy)
 {
-	*strategy = stream->ios[0].op.strategy;
+	*strategy = stream->strategy;
 	return read_stream_get_block(stream, NULL);
 }
 
@@ -1407,6 +1411,19 @@ read_stream_resume(ReadStream *stream)
 	stream->combine_distance = stream->resume_combine_distance;
 }
 
+/*
+ * Stop using a buffer access strategy for future reads from this stream.
+ *
+ * This does not change whether or not any in-progress IOs are using the
+ * buffer access strategy. Note that the caller is still responsible for
+ * freeing the memory.
+ */
+void
+read_stream_clear_strategy(ReadStream *stream)
+{
+	stream->strategy = NULL;
+}
+
 /*
  * Reset a read stream by releasing any queued up buffers, allowing the stream
  * to be used again for different blocks.  This can be used to clear an
diff --git a/src/include/storage/read_stream.h b/src/include/storage/read_stream.h
index 48995c6d534..e2dcf1be50a 100644
--- a/src/include/storage/read_stream.h
+++ b/src/include/storage/read_stream.h
@@ -102,6 +102,7 @@ extern ReadStream *read_stream_begin_smgr_relation(int flags,
 												   size_t per_buffer_data_size);
 extern BlockNumber read_stream_pause(ReadStream *stream);
 extern void read_stream_resume(ReadStream *stream);
+extern void read_stream_clear_strategy(ReadStream *stream);
 extern void read_stream_reset(ReadStream *stream);
 extern void read_stream_end(ReadStream *stream);
 extern void read_stream_enable_stats(ReadStream *stream, struct IOStats *stats);
-- 
2.47.3

