Improve read_stream.c's fast path.
authorThomas Munro <[email protected]>
Sat, 6 Apr 2024 04:14:12 +0000 (17:14 +1300)
committerThomas Munro <[email protected]>
Sat, 6 Apr 2024 05:00:35 +0000 (18:00 +1300)
The "fast path" for well cached scans that don't do any I/O was
accidentally coded in a way that could only be triggered by pg_prewarm's
usage pattern, which starts out with a higher distance because of the
flags it passes in.  We want it to work for streaming sequential scans
too, once that patch is committed.  Adjust.

Reviewed-by: Melanie Plageman <[email protected]>
Discussion: https://postgr.es/m/CA%2BhUKGKXZALJ%3D6aArUsXRJzBm%3Dqvc4AWp7%3DiJNXJQqpbRLnD_w%40mail.gmail.com

src/backend/storage/aio/read_stream.c

index 4f21262ff5efb50d1046ffcf7721e365cf7b83ab..b9e11a28312d21ff421351635cf96b24bee65404 100644 (file)
@@ -169,7 +169,7 @@ get_per_buffer_data(ReadStream *stream, int16 buffer_index)
 /*
  * Ask the callback which block it would like us to read next, with a small
  * buffer in front to allow read_stream_unget_block() to work and to allow the
- * fast path to work in batches.
+ * fast path to skip this function and work directly from the array.
  */
 static inline BlockNumber
 read_stream_get_block(ReadStream *stream, void *per_buffer_data)
@@ -578,13 +578,12 @@ read_stream_next_buffer(ReadStream *stream, void **per_buffer_data)
    if (likely(stream->fast_path))
    {
        BlockNumber next_blocknum;
-       bool        need_wait;
 
        /* Fast path assumptions. */
        Assert(stream->ios_in_progress == 0);
        Assert(stream->pinned_buffers == 1);
        Assert(stream->distance == 1);
-       Assert(stream->pending_read_nblocks == 1);
+       Assert(stream->pending_read_nblocks == 0);
        Assert(stream->per_buffer_data_size == 0);
 
        /* We're going to return the buffer we pinned last time. */
@@ -594,40 +593,29 @@ read_stream_next_buffer(ReadStream *stream, void **per_buffer_data)
        buffer = stream->buffers[oldest_buffer_index];
        Assert(buffer != InvalidBuffer);
 
-       /*
-        * Pin a buffer for the next call.  Same buffer entry, and arbitrary
-        * I/O entry (they're all free).
-        */
-       need_wait = StartReadBuffer(&stream->ios[0].op,
-                                   &stream->buffers[oldest_buffer_index],
-                                   stream->pending_read_blocknum,
-                                   stream->advice_enabled ?
-                                   READ_BUFFERS_ISSUE_ADVICE : 0);
-
-       /* Choose the block the next call will pin. */
+       /* Choose the next block to pin. */
        if (unlikely(stream->blocknums_next == stream->blocknums_count))
            read_stream_fill_blocknums(stream);
        next_blocknum = stream->blocknums[stream->blocknums_next++];
 
-       /*
-        * Fast return if the next call doesn't require I/O for the buffer we
-        * just pinned, and we have a block number to give it as a pending
-        * read.
-        */
-       if (likely(!need_wait && next_blocknum != InvalidBlockNumber))
+       if (likely(next_blocknum != InvalidBlockNumber))
        {
-           stream->pending_read_blocknum = next_blocknum;
-           return buffer;
-       }
-
-       /*
-        * For anything more complex, set up some more state and take the slow
-        * path next time.
-        */
-       stream->fast_path = false;
+           /*
+            * Pin a buffer for the next call.  Same buffer entry, and
+            * arbitrary I/O entry (they're all free).  We don't have to
+            * adjust pinned_buffers because we're transferring one to caller
+            * but pinning one more.
+            */
+           if (likely(!StartReadBuffer(&stream->ios[0].op,
+                                       &stream->buffers[oldest_buffer_index],
+                                       next_blocknum,
+                                       stream->advice_enabled ?
+                                       READ_BUFFERS_ISSUE_ADVICE : 0)))
+           {
+               /* Fast return. */
+               return buffer;
+           }
 
-       if (need_wait)
-       {
            /* Next call must wait for I/O for the newly pinned buffer. */
            stream->oldest_io_index = 0;
            stream->next_io_index = stream->max_ios > 1 ? 1 : 0;
@@ -635,17 +623,15 @@ read_stream_next_buffer(ReadStream *stream, void **per_buffer_data)
            stream->ios[0].buffer_index = oldest_buffer_index;
            stream->seq_blocknum = next_blocknum + 1;
        }
-       if (next_blocknum == InvalidBlockNumber)
-       {
-           /* Next call hits end of stream and can't pin anything more. */
-           stream->distance = 0;
-           stream->pending_read_nblocks = 0;
-       }
        else
        {
-           /* Set up the pending read. */
-           stream->pending_read_blocknum = next_blocknum;
+           /* No more blocks, end of stream. */
+           stream->distance = 0;
+           stream->oldest_buffer_index = stream->next_buffer_index;
+           stream->pinned_buffers = 0;
        }
+
+       stream->fast_path = false;
        return buffer;
    }
 #endif
@@ -762,15 +748,11 @@ read_stream_next_buffer(ReadStream *stream, void **per_buffer_data)
    if (stream->ios_in_progress == 0 &&
        stream->pinned_buffers == 1 &&
        stream->distance == 1 &&
-       stream->pending_read_nblocks == 1 &&
+       stream->pending_read_nblocks == 0 &&
        stream->per_buffer_data_size == 0)
    {
        stream->fast_path = true;
    }
-   else
-   {
-       stream->fast_path = false;
-   }
 #endif
 
    return buffer;
@@ -790,6 +772,11 @@ read_stream_reset(ReadStream *stream)
    /* Stop looking ahead. */
    stream->distance = 0;
 
+   /* Forget buffered block numbers and fast path state. */
+   stream->blocknums_next = 0;
+   stream->blocknums_count = 0;
+   stream->fast_path = false;
+
    /* Unpin anything that wasn't consumed. */
    while ((buffer = read_stream_next_buffer(stream, NULL)) != InvalidBuffer)
        ReleaseBuffer(buffer);