Coverage Report

Created: 2026-08-14 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/access/transam/xlogprefetcher.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * xlogprefetcher.c
4
 *    Prefetching support for recovery.
5
 *
6
 * Portions Copyright (c) 2022-2026, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/access/transam/xlogprefetcher.c
12
 *
13
 * This module provides a drop-in replacement for an XLogReader that tries to
14
 * minimize I/O stalls by looking ahead in the WAL.  If blocks that will be
15
 * accessed in the near future are not already in the buffer pool, it initiates
16
 * I/Os that might complete before the caller eventually needs the data.  When
17
 * referenced blocks are found in the buffer pool already, the buffer is
18
 * recorded in the decoded record so that XLogReadBufferForRedo() can try to
19
 * avoid a second buffer mapping table lookup.
20
 *
21
 * Currently, only the main fork is considered for prefetching.  Currently,
22
 * prefetching is only effective on systems where PrefetchBuffer() does
23
 * something useful (mainly Linux).
24
 *
25
 *-------------------------------------------------------------------------
26
 */
27
28
#include "postgres.h"
29
30
#include "access/xlogprefetcher.h"
31
#include "access/xlogreader.h"
32
#include "catalog/pg_control.h"
33
#include "catalog/storage_xlog.h"
34
#include "commands/dbcommands_xlog.h"
35
#include "funcapi.h"
36
#include "miscadmin.h"
37
#include "port/atomics.h"
38
#include "storage/bufmgr.h"
39
#include "storage/fd.h"
40
#include "storage/shmem.h"
41
#include "storage/smgr.h"
42
#include "storage/subsystems.h"
43
#include "utils/fmgrprotos.h"
44
#include "utils/guc_hooks.h"
45
#include "utils/hsearch.h"
46
#include "utils/timestamp.h"
47
#include "utils/tuplestore.h"
48
49
/*
50
 * Every time we process this much WAL, we'll update the values in
51
 * pg_stat_recovery_prefetch.
52
 */
53
0
#define XLOGPREFETCHER_STATS_DISTANCE BLCKSZ
54
55
/*
56
 * To detect repeated access to the same block and skip useless extra system
57
 * calls, we remember a small window of recently prefetched blocks.
58
 */
59
0
#define XLOGPREFETCHER_SEQ_WINDOW_SIZE 4
60
61
/*
62
 * When maintenance_io_concurrency is not saturated, we're prepared to look
63
 * ahead up to N times that number of block references.
64
 */
65
0
#define XLOGPREFETCHER_DISTANCE_MULTIPLIER 4
66
67
/* Define to log internal debugging messages. */
68
/* #define XLOGPREFETCHER_DEBUG_LEVEL LOG */
69
70
/* GUCs */
71
int     recovery_prefetch = RECOVERY_PREFETCH_TRY;
72
73
#ifdef USE_PREFETCH
74
#define RecoveryPrefetchEnabled() \
75
0
    (recovery_prefetch != RECOVERY_PREFETCH_OFF && \
76
0
     maintenance_io_concurrency > 0)
77
#else
78
#define RecoveryPrefetchEnabled() false
79
#endif
80
81
static int  XLogPrefetchReconfigureCount = 0;
82
83
/*
84
 * Enum used to report whether an IO should be started.
85
 */
86
typedef enum
87
{
88
  LRQ_NEXT_NO_IO,
89
  LRQ_NEXT_IO,
90
  LRQ_NEXT_AGAIN,
91
} LsnReadQueueNextStatus;
92
93
/*
94
 * Type of callback that can decide which block to prefetch next.  For now
95
 * there is only one.
96
 */
97
typedef LsnReadQueueNextStatus (*LsnReadQueueNextFun) (uintptr_t lrq_private,
98
                             XLogRecPtr *lsn);
99
100
/*
101
 * A simple circular queue of LSNs, using to control the number of
102
 * (potentially) inflight IOs.  This stands in for a later more general IO
103
 * control mechanism, which is why it has the apparently unnecessary
104
 * indirection through a function pointer.
105
 */
106
typedef struct LsnReadQueue
107
{
108
  LsnReadQueueNextFun next;
109
  uintptr_t lrq_private;
110
  uint32    max_inflight;
111
  uint32    inflight;
112
  uint32    completed;
113
  uint32    head;
114
  uint32    tail;
115
  uint32    size;
116
  struct
117
  {
118
    bool    io;
119
    XLogRecPtr  lsn;
120
  }     queue[FLEXIBLE_ARRAY_MEMBER];
121
} LsnReadQueue;
122
123
/*
124
 * A prefetcher.  This is a mechanism that wraps an XLogReader, prefetching
125
 * blocks that will be soon be referenced, to try to avoid IO stalls.
126
 */
127
struct XLogPrefetcher
128
{
129
  /* WAL reader and current reading state. */
130
  XLogReaderState *reader;
131
  DecodedXLogRecord *record;
132
  int     next_block_id;
133
134
  /* When to publish stats. */
135
  XLogRecPtr  next_stats_shm_lsn;
136
137
  /* Book-keeping to avoid accessing blocks that don't exist yet. */
138
  HTAB     *filter_table;
139
  dlist_head  filter_queue;
140
141
  /* Book-keeping to avoid repeat prefetches. */
142
  RelFileLocator recent_rlocator[XLOGPREFETCHER_SEQ_WINDOW_SIZE];
143
  BlockNumber recent_block[XLOGPREFETCHER_SEQ_WINDOW_SIZE];
144
  int     recent_idx;
145
146
  /* Book-keeping to disable prefetching temporarily. */
147
  XLogRecPtr  no_readahead_until;
148
149
  /* IO depth manager. */
150
  LsnReadQueue *streaming_read;
151
152
  XLogRecPtr  begin_ptr;
153
154
  int     reconfigure_count;
155
};
156
157
/*
158
 * A temporary filter used to track block ranges that haven't been created
159
 * yet, whole relations that haven't been created yet, and whole relations
160
 * that (we assume) have already been dropped, or will be created by bulk WAL
161
 * operators.
162
 */
163
typedef struct XLogPrefetcherFilter
164
{
165
  RelFileLocator rlocator;
166
  XLogRecPtr  filter_until_replayed;
167
  BlockNumber filter_from_block;
168
  dlist_node  link;
169
} XLogPrefetcherFilter;
170
171
/*
172
 * Counters exposed in shared memory for pg_stat_recovery_prefetch.
173
 */
174
typedef struct XLogPrefetchStats
175
{
176
  pg_atomic_uint64 reset_time;  /* Time of last reset. */
177
  pg_atomic_uint64 prefetch;  /* Prefetches initiated. */
178
  pg_atomic_uint64 hit;   /* Blocks already in cache. */
179
  pg_atomic_uint64 skip_init; /* Zero-inited blocks skipped. */
180
  pg_atomic_uint64 skip_new;  /* New/missing blocks filtered. */
181
  pg_atomic_uint64 skip_fpw;  /* FPWs skipped. */
182
  pg_atomic_uint64 skip_rep;  /* Repeat accesses skipped. */
183
184
  /* Dynamic values */
185
  int     wal_distance; /* Number of WAL bytes ahead. */
186
  int     block_distance; /* Number of block references ahead. */
187
  int     io_depth;   /* Number of I/Os in progress. */
188
} XLogPrefetchStats;
189
190
static inline void XLogPrefetcherAddFilter(XLogPrefetcher *prefetcher,
191
                       RelFileLocator rlocator,
192
                       BlockNumber blockno,
193
                       XLogRecPtr lsn);
194
static inline bool XLogPrefetcherIsFiltered(XLogPrefetcher *prefetcher,
195
                      RelFileLocator rlocator,
196
                      BlockNumber blockno);
197
static inline void XLogPrefetcherCompleteFilters(XLogPrefetcher *prefetcher,
198
                         XLogRecPtr replaying_lsn);
199
static LsnReadQueueNextStatus XLogPrefetcherNextBlock(uintptr_t pgsr_private,
200
                            XLogRecPtr *lsn);
201
202
static XLogPrefetchStats *SharedStats;
203
204
static void XLogPrefetchShmemRequest(void *arg);
205
static void XLogPrefetchShmemInit(void *arg);
206
207
const ShmemCallbacks XLogPrefetchShmemCallbacks = {
208
  .request_fn = XLogPrefetchShmemRequest,
209
  .init_fn = XLogPrefetchShmemInit,
210
};
211
212
static inline LsnReadQueue *
213
lrq_alloc(uint32 max_distance,
214
      uint32 max_inflight,
215
      uintptr_t lrq_private,
216
      LsnReadQueueNextFun next)
217
0
{
218
0
  LsnReadQueue *lrq;
219
0
  uint32    size;
220
221
0
  Assert(max_distance >= max_inflight);
222
223
0
  size = max_distance + 1;  /* full ring buffer has a gap */
224
0
  lrq = palloc(offsetof(LsnReadQueue, queue) + sizeof(lrq->queue[0]) * size);
225
0
  lrq->lrq_private = lrq_private;
226
0
  lrq->max_inflight = max_inflight;
227
0
  lrq->size = size;
228
0
  lrq->next = next;
229
0
  lrq->head = 0;
230
0
  lrq->tail = 0;
231
0
  lrq->inflight = 0;
232
0
  lrq->completed = 0;
233
234
0
  return lrq;
235
0
}
236
237
static inline void
238
lrq_free(LsnReadQueue *lrq)
239
0
{
240
0
  pfree(lrq);
241
0
}
242
243
static inline uint32
244
lrq_inflight(LsnReadQueue *lrq)
245
0
{
246
0
  return lrq->inflight;
247
0
}
248
249
static inline uint32
250
lrq_completed(LsnReadQueue *lrq)
251
0
{
252
0
  return lrq->completed;
253
0
}
254
255
static inline void
256
lrq_prefetch(LsnReadQueue *lrq)
257
0
{
258
  /* Try to start as many IOs as we can within our limits. */
259
0
  while (lrq->inflight < lrq->max_inflight &&
260
0
       lrq->inflight + lrq->completed < lrq->size - 1)
261
0
  {
262
0
    Assert(((lrq->head + 1) % lrq->size) != lrq->tail);
263
0
    switch (lrq->next(lrq->lrq_private, &lrq->queue[lrq->head].lsn))
264
0
    {
265
0
      case LRQ_NEXT_AGAIN:
266
0
        return;
267
0
      case LRQ_NEXT_IO:
268
0
        lrq->queue[lrq->head].io = true;
269
0
        lrq->inflight++;
270
0
        break;
271
0
      case LRQ_NEXT_NO_IO:
272
0
        lrq->queue[lrq->head].io = false;
273
0
        lrq->completed++;
274
0
        break;
275
0
    }
276
0
    lrq->head++;
277
0
    if (lrq->head == lrq->size)
278
0
      lrq->head = 0;
279
0
  }
280
0
}
281
282
static inline void
283
lrq_complete_lsn(LsnReadQueue *lrq, XLogRecPtr lsn)
284
0
{
285
  /*
286
   * We know that LSNs before 'lsn' have been replayed, so we can now assume
287
   * that any IOs that were started before then have finished.
288
   */
289
0
  while (lrq->tail != lrq->head &&
290
0
       lrq->queue[lrq->tail].lsn < lsn)
291
0
  {
292
0
    if (lrq->queue[lrq->tail].io)
293
0
      lrq->inflight--;
294
0
    else
295
0
      lrq->completed--;
296
0
    lrq->tail++;
297
0
    if (lrq->tail == lrq->size)
298
0
      lrq->tail = 0;
299
0
  }
300
0
  if (RecoveryPrefetchEnabled())
301
0
    lrq_prefetch(lrq);
302
0
}
303
304
static void
305
XLogPrefetchShmemRequest(void *arg)
306
0
{
307
0
  ShmemRequestStruct(.name = "XLogPrefetchStats",
308
0
             .size = sizeof(XLogPrefetchStats),
309
0
             .ptr = (void **) &SharedStats,
310
0
    );
311
0
}
312
313
static void
314
XLogPrefetchShmemInit(void *arg)
315
0
{
316
0
  pg_atomic_init_u64(&SharedStats->reset_time, GetCurrentTimestamp());
317
0
  pg_atomic_init_u64(&SharedStats->prefetch, 0);
318
0
  pg_atomic_init_u64(&SharedStats->hit, 0);
319
0
  pg_atomic_init_u64(&SharedStats->skip_init, 0);
320
0
  pg_atomic_init_u64(&SharedStats->skip_new, 0);
321
0
  pg_atomic_init_u64(&SharedStats->skip_fpw, 0);
322
0
  pg_atomic_init_u64(&SharedStats->skip_rep, 0);
323
0
}
324
325
/*
326
 * Reset all counters to zero.
327
 */
328
void
329
XLogPrefetchResetStats(void)
330
0
{
331
0
  pg_atomic_write_u64(&SharedStats->reset_time, GetCurrentTimestamp());
332
0
  pg_atomic_write_u64(&SharedStats->prefetch, 0);
333
0
  pg_atomic_write_u64(&SharedStats->hit, 0);
334
0
  pg_atomic_write_u64(&SharedStats->skip_init, 0);
335
0
  pg_atomic_write_u64(&SharedStats->skip_new, 0);
336
0
  pg_atomic_write_u64(&SharedStats->skip_fpw, 0);
337
0
  pg_atomic_write_u64(&SharedStats->skip_rep, 0);
338
0
}
339
340
341
/*
342
 * Called when any GUC is changed that affects prefetching.
343
 */
344
void
345
XLogPrefetchReconfigure(void)
346
0
{
347
0
  XLogPrefetchReconfigureCount++;
348
0
}
349
350
/*
351
 * Increment a counter in shared memory.  This is equivalent to *counter++ on a
352
 * plain uint64 without any memory barrier or locking, except on platforms
353
 * where readers can't read uint64 without possibly observing a torn value.
354
 */
355
static inline void
356
XLogPrefetchIncrement(pg_atomic_uint64 *counter)
357
0
{
358
0
  Assert(AmStartupProcess() || !IsUnderPostmaster);
359
0
  pg_atomic_write_u64(counter, pg_atomic_read_u64(counter) + 1);
360
0
}
361
362
/*
363
 * Create a prefetcher that is ready to begin prefetching blocks referenced by
364
 * WAL records.
365
 */
366
XLogPrefetcher *
367
XLogPrefetcherAllocate(XLogReaderState *reader)
368
0
{
369
0
  XLogPrefetcher *prefetcher;
370
0
  HASHCTL   ctl;
371
372
0
  prefetcher = palloc0_object(XLogPrefetcher);
373
0
  prefetcher->reader = reader;
374
375
0
  ctl.keysize = sizeof(RelFileLocator);
376
0
  ctl.entrysize = sizeof(XLogPrefetcherFilter);
377
0
  prefetcher->filter_table = hash_create("XLogPrefetcherFilterTable", 1024,
378
0
                       &ctl, HASH_ELEM | HASH_BLOBS);
379
0
  dlist_init(&prefetcher->filter_queue);
380
381
0
  SharedStats->wal_distance = 0;
382
0
  SharedStats->block_distance = 0;
383
0
  SharedStats->io_depth = 0;
384
385
  /* First usage will cause streaming_read to be allocated. */
386
0
  prefetcher->reconfigure_count = XLogPrefetchReconfigureCount - 1;
387
388
0
  return prefetcher;
389
0
}
390
391
/*
392
 * Destroy a prefetcher and release all resources.
393
 */
394
void
395
XLogPrefetcherFree(XLogPrefetcher *prefetcher)
396
0
{
397
0
  lrq_free(prefetcher->streaming_read);
398
0
  hash_destroy(prefetcher->filter_table);
399
0
  pfree(prefetcher);
400
0
}
401
402
/*
403
 * Provide access to the reader.
404
 */
405
XLogReaderState *
406
XLogPrefetcherGetReader(XLogPrefetcher *prefetcher)
407
0
{
408
0
  return prefetcher->reader;
409
0
}
410
411
/*
412
 * Update the statistics visible in the pg_stat_recovery_prefetch view.
413
 */
414
void
415
XLogPrefetcherComputeStats(XLogPrefetcher *prefetcher)
416
0
{
417
0
  uint32    io_depth;
418
0
  uint32    completed;
419
0
  int64   wal_distance;
420
421
422
  /* How far ahead of replay are we now? */
423
0
  if (prefetcher->reader->decode_queue_tail)
424
0
  {
425
0
    wal_distance =
426
0
      prefetcher->reader->decode_queue_tail->lsn -
427
0
      prefetcher->reader->decode_queue_head->lsn;
428
0
  }
429
0
  else
430
0
  {
431
0
    wal_distance = 0;
432
0
  }
433
434
  /* How many IOs are currently in flight and completed? */
435
0
  io_depth = lrq_inflight(prefetcher->streaming_read);
436
0
  completed = lrq_completed(prefetcher->streaming_read);
437
438
  /* Update the instantaneous stats visible in pg_stat_recovery_prefetch. */
439
0
  SharedStats->io_depth = io_depth;
440
0
  SharedStats->block_distance = io_depth + completed;
441
0
  SharedStats->wal_distance = wal_distance;
442
443
0
  prefetcher->next_stats_shm_lsn =
444
0
    prefetcher->reader->ReadRecPtr + XLOGPREFETCHER_STATS_DISTANCE;
445
0
}
446
447
/*
448
 * A callback that examines the next block reference in the WAL, and possibly
449
 * starts an IO so that a later read will be fast.
450
 *
451
 * Returns LRQ_NEXT_AGAIN if no more WAL data is available yet.
452
 *
453
 * Returns LRQ_NEXT_IO if the next block reference is for a main fork block
454
 * that isn't in the buffer pool, and the kernel has been asked to start
455
 * reading it to make a future read system call faster. An LSN is written to
456
 * *lsn, and the I/O will be considered to have completed once that LSN is
457
 * replayed.
458
 *
459
 * Returns LRQ_NEXT_NO_IO if we examined the next block reference and found
460
 * that it was already in the buffer pool, or we decided for various reasons
461
 * not to prefetch.
462
 */
463
static LsnReadQueueNextStatus
464
XLogPrefetcherNextBlock(uintptr_t pgsr_private, XLogRecPtr *lsn)
465
0
{
466
0
  XLogPrefetcher *prefetcher = (XLogPrefetcher *) pgsr_private;
467
0
  XLogReaderState *reader = prefetcher->reader;
468
0
  XLogRecPtr  replaying_lsn = reader->ReadRecPtr;
469
470
  /*
471
   * We keep track of the record and block we're up to between calls with
472
   * prefetcher->record and prefetcher->next_block_id.
473
   */
474
0
  for (;;)
475
0
  {
476
0
    DecodedXLogRecord *record;
477
478
    /* Try to read a new future record, if we don't already have one. */
479
0
    if (prefetcher->record == NULL)
480
0
    {
481
0
      bool    nonblocking;
482
483
      /*
484
       * If there are already records or an error queued up that could
485
       * be replayed, we don't want to block here.  Otherwise, it's OK
486
       * to block waiting for more data: presumably the caller has
487
       * nothing else to do.
488
       */
489
0
      nonblocking = XLogReaderHasQueuedRecordOrError(reader);
490
491
      /* Readahead is disabled until we replay past a certain point. */
492
0
      if (nonblocking && replaying_lsn <= prefetcher->no_readahead_until)
493
0
        return LRQ_NEXT_AGAIN;
494
495
0
      record = XLogReadAhead(prefetcher->reader, nonblocking);
496
0
      if (record == NULL)
497
0
      {
498
        /*
499
         * We can't read any more, due to an error or lack of data in
500
         * nonblocking mode.  Don't try to read ahead again until
501
         * we've replayed everything already decoded.
502
         */
503
0
        if (nonblocking && prefetcher->reader->decode_queue_tail)
504
0
          prefetcher->no_readahead_until =
505
0
            prefetcher->reader->decode_queue_tail->lsn;
506
507
0
        return LRQ_NEXT_AGAIN;
508
0
      }
509
510
      /*
511
       * If prefetching is disabled, we don't need to analyze the record
512
       * or issue any prefetches.  We just need to cause one record to
513
       * be decoded.
514
       */
515
0
      if (!RecoveryPrefetchEnabled())
516
0
      {
517
0
        *lsn = InvalidXLogRecPtr;
518
0
        return LRQ_NEXT_NO_IO;
519
0
      }
520
521
      /* We have a new record to process. */
522
0
      prefetcher->record = record;
523
0
      prefetcher->next_block_id = 0;
524
0
    }
525
0
    else
526
0
    {
527
      /* Continue to process from last call, or last loop. */
528
0
      record = prefetcher->record;
529
0
    }
530
531
    /*
532
     * Check for operations that require us to filter out block ranges, or
533
     * pause readahead completely.
534
     */
535
0
    if (replaying_lsn < record->lsn)
536
0
    {
537
0
      uint8   rmid = record->header.xl_rmid;
538
0
      uint8   record_type = record->header.xl_info & ~XLR_INFO_MASK;
539
540
0
      if (rmid == RM_XLOG_ID)
541
0
      {
542
0
        if (record_type == XLOG_CHECKPOINT_SHUTDOWN ||
543
0
          record_type == XLOG_END_OF_RECOVERY)
544
0
        {
545
          /*
546
           * These records might change the TLI.  Avoid potential
547
           * bugs if we were to allow "read TLI" and "replay TLI" to
548
           * differ without more analysis.
549
           */
550
0
          prefetcher->no_readahead_until = record->lsn;
551
552
#ifdef XLOGPREFETCHER_DEBUG_LEVEL
553
          elog(XLOGPREFETCHER_DEBUG_LEVEL,
554
             "suppressing all readahead until %X/%08X is replayed due to possible TLI change",
555
             LSN_FORMAT_ARGS(record->lsn));
556
#endif
557
558
          /* Fall through so we move past this record. */
559
0
        }
560
0
      }
561
0
      else if (rmid == RM_DBASE_ID)
562
0
      {
563
        /*
564
         * When databases are created with the file-copy strategy,
565
         * there are no WAL records to tell us about the creation of
566
         * individual relations.
567
         */
568
0
        if (record_type == XLOG_DBASE_CREATE_FILE_COPY)
569
0
        {
570
0
          xl_dbase_create_file_copy_rec *xlrec =
571
0
            (xl_dbase_create_file_copy_rec *) record->main_data;
572
0
          RelFileLocator rlocator =
573
0
          {InvalidOid, xlrec->db_id, InvalidRelFileNumber};
574
575
          /*
576
           * Don't try to prefetch anything in this database until
577
           * it has been created, or we might confuse the blocks of
578
           * different generations, if a database OID or
579
           * relfilenumber is reused.  It's also more efficient than
580
           * discovering that relations don't exist on disk yet with
581
           * ENOENT errors.
582
           */
583
0
          XLogPrefetcherAddFilter(prefetcher, rlocator, 0, record->lsn);
584
585
#ifdef XLOGPREFETCHER_DEBUG_LEVEL
586
          elog(XLOGPREFETCHER_DEBUG_LEVEL,
587
             "suppressing prefetch in database %u until %X/%08X is replayed due to raw file copy",
588
             rlocator.dbOid,
589
             LSN_FORMAT_ARGS(record->lsn));
590
#endif
591
0
        }
592
0
      }
593
0
      else if (rmid == RM_SMGR_ID)
594
0
      {
595
0
        if (record_type == XLOG_SMGR_CREATE)
596
0
        {
597
0
          xl_smgr_create *xlrec = (xl_smgr_create *)
598
0
            record->main_data;
599
600
0
          if (xlrec->forkNum == MAIN_FORKNUM)
601
0
          {
602
            /*
603
             * Don't prefetch anything for this whole relation
604
             * until it has been created.  Otherwise we might
605
             * confuse the blocks of different generations, if a
606
             * relfilenumber is reused.  This also avoids the need
607
             * to discover the problem via extra syscalls that
608
             * report ENOENT.
609
             */
610
0
            XLogPrefetcherAddFilter(prefetcher, xlrec->rlocator, 0,
611
0
                        record->lsn);
612
613
#ifdef XLOGPREFETCHER_DEBUG_LEVEL
614
            elog(XLOGPREFETCHER_DEBUG_LEVEL,
615
               "suppressing prefetch in relation %u/%u/%u until %X/%08X is replayed, which creates the relation",
616
               xlrec->rlocator.spcOid,
617
               xlrec->rlocator.dbOid,
618
               xlrec->rlocator.relNumber,
619
               LSN_FORMAT_ARGS(record->lsn));
620
#endif
621
0
          }
622
0
        }
623
0
        else if (record_type == XLOG_SMGR_TRUNCATE)
624
0
        {
625
0
          xl_smgr_truncate *xlrec = (xl_smgr_truncate *)
626
0
            record->main_data;
627
628
          /*
629
           * Don't consider prefetching anything in the truncated
630
           * range until the truncation has been performed.
631
           */
632
0
          XLogPrefetcherAddFilter(prefetcher, xlrec->rlocator,
633
0
                      xlrec->blkno,
634
0
                      record->lsn);
635
636
#ifdef XLOGPREFETCHER_DEBUG_LEVEL
637
          elog(XLOGPREFETCHER_DEBUG_LEVEL,
638
             "suppressing prefetch in relation %u/%u/%u from block %u until %X/%08X is replayed, which truncates the relation",
639
             xlrec->rlocator.spcOid,
640
             xlrec->rlocator.dbOid,
641
             xlrec->rlocator.relNumber,
642
             xlrec->blkno,
643
             LSN_FORMAT_ARGS(record->lsn));
644
#endif
645
0
        }
646
0
      }
647
0
    }
648
649
    /* Scan the block references, starting where we left off last time. */
650
0
    while (prefetcher->next_block_id <= record->max_block_id)
651
0
    {
652
0
      int     block_id = prefetcher->next_block_id++;
653
0
      DecodedBkpBlock *block = &record->blocks[block_id];
654
0
      SMgrRelation reln;
655
0
      PrefetchBufferResult result;
656
657
0
      if (!block->in_use)
658
0
        continue;
659
660
0
      Assert(!BufferIsValid(block->prefetch_buffer));
661
662
      /*
663
       * Record the LSN of this record.  When it's replayed,
664
       * LsnReadQueue will consider any IOs submitted for earlier LSNs
665
       * to be finished.
666
       */
667
0
      *lsn = record->lsn;
668
669
      /* We don't try to prefetch anything but the main fork for now. */
670
0
      if (block->forknum != MAIN_FORKNUM)
671
0
      {
672
0
        return LRQ_NEXT_NO_IO;
673
0
      }
674
675
      /*
676
       * If there is a full page image attached, we won't be reading the
677
       * page, so don't bother trying to prefetch.
678
       */
679
0
      if (block->has_image)
680
0
      {
681
0
        XLogPrefetchIncrement(&SharedStats->skip_fpw);
682
0
        return LRQ_NEXT_NO_IO;
683
0
      }
684
685
      /* There is no point in reading a page that will be zeroed. */
686
0
      if (block->flags & BKPBLOCK_WILL_INIT)
687
0
      {
688
0
        XLogPrefetchIncrement(&SharedStats->skip_init);
689
0
        return LRQ_NEXT_NO_IO;
690
0
      }
691
692
      /* Should we skip prefetching this block due to a filter? */
693
0
      if (XLogPrefetcherIsFiltered(prefetcher, block->rlocator, block->blkno))
694
0
      {
695
0
        XLogPrefetchIncrement(&SharedStats->skip_new);
696
0
        return LRQ_NEXT_NO_IO;
697
0
      }
698
699
      /* There is no point in repeatedly prefetching the same block. */
700
0
      for (int i = 0; i < XLOGPREFETCHER_SEQ_WINDOW_SIZE; ++i)
701
0
      {
702
0
        if (block->blkno == prefetcher->recent_block[i] &&
703
0
          RelFileLocatorEquals(block->rlocator, prefetcher->recent_rlocator[i]))
704
0
        {
705
          /*
706
           * XXX If we also remembered where it was, we could set
707
           * recent_buffer so that recovery could skip smgropen()
708
           * and a buffer table lookup.
709
           */
710
0
          XLogPrefetchIncrement(&SharedStats->skip_rep);
711
0
          return LRQ_NEXT_NO_IO;
712
0
        }
713
0
      }
714
0
      prefetcher->recent_rlocator[prefetcher->recent_idx] = block->rlocator;
715
0
      prefetcher->recent_block[prefetcher->recent_idx] = block->blkno;
716
0
      prefetcher->recent_idx =
717
0
        (prefetcher->recent_idx + 1) % XLOGPREFETCHER_SEQ_WINDOW_SIZE;
718
719
      /*
720
       * We could try to have a fast path for repeated references to the
721
       * same relation (with some scheme to handle invalidations
722
       * safely), but for now we'll call smgropen() every time.
723
       */
724
0
      reln = smgropen(block->rlocator, INVALID_PROC_NUMBER);
725
726
      /*
727
       * If the relation file doesn't exist on disk, for example because
728
       * we're replaying after a crash and the file will be created and
729
       * then unlinked by WAL that hasn't been replayed yet, suppress
730
       * further prefetching in the relation until this record is
731
       * replayed.
732
       */
733
0
      if (!smgrexists(reln, MAIN_FORKNUM))
734
0
      {
735
#ifdef XLOGPREFETCHER_DEBUG_LEVEL
736
        elog(XLOGPREFETCHER_DEBUG_LEVEL,
737
           "suppressing all prefetch in relation %u/%u/%u until %X/%08X is replayed, because the relation does not exist on disk",
738
           reln->smgr_rlocator.locator.spcOid,
739
           reln->smgr_rlocator.locator.dbOid,
740
           reln->smgr_rlocator.locator.relNumber,
741
           LSN_FORMAT_ARGS(record->lsn));
742
#endif
743
0
        XLogPrefetcherAddFilter(prefetcher, block->rlocator, 0,
744
0
                    record->lsn);
745
0
        XLogPrefetchIncrement(&SharedStats->skip_new);
746
0
        return LRQ_NEXT_NO_IO;
747
0
      }
748
749
      /*
750
       * If the relation isn't big enough to contain the referenced
751
       * block yet, suppress prefetching of this block and higher until
752
       * this record is replayed.
753
       */
754
0
      if (block->blkno >= smgrnblocks(reln, block->forknum))
755
0
      {
756
#ifdef XLOGPREFETCHER_DEBUG_LEVEL
757
        elog(XLOGPREFETCHER_DEBUG_LEVEL,
758
           "suppressing prefetch in relation %u/%u/%u from block %u until %X/%08X is replayed, because the relation is too small",
759
           reln->smgr_rlocator.locator.spcOid,
760
           reln->smgr_rlocator.locator.dbOid,
761
           reln->smgr_rlocator.locator.relNumber,
762
           block->blkno,
763
           LSN_FORMAT_ARGS(record->lsn));
764
#endif
765
0
        XLogPrefetcherAddFilter(prefetcher, block->rlocator, block->blkno,
766
0
                    record->lsn);
767
0
        XLogPrefetchIncrement(&SharedStats->skip_new);
768
0
        return LRQ_NEXT_NO_IO;
769
0
      }
770
771
      /* Try to initiate prefetching. */
772
0
      result = PrefetchSharedBuffer(reln, block->forknum, block->blkno);
773
0
      if (BufferIsValid(result.recent_buffer))
774
0
      {
775
        /* Cache hit, nothing to do. */
776
0
        XLogPrefetchIncrement(&SharedStats->hit);
777
0
        block->prefetch_buffer = result.recent_buffer;
778
0
        return LRQ_NEXT_NO_IO;
779
0
      }
780
0
      else if (result.initiated_io)
781
0
      {
782
        /* Cache miss, I/O (presumably) started. */
783
0
        XLogPrefetchIncrement(&SharedStats->prefetch);
784
0
        block->prefetch_buffer = InvalidBuffer;
785
0
        return LRQ_NEXT_IO;
786
0
      }
787
0
      else if ((io_direct_flags & IO_DIRECT_DATA) == 0)
788
0
      {
789
        /*
790
         * This shouldn't be possible, because we already determined
791
         * that the relation exists on disk and is big enough.
792
         * Something is wrong with the cache invalidation for
793
         * smgrexists(), smgrnblocks(), or the file was unlinked or
794
         * truncated beneath our feet?
795
         */
796
0
        elog(ERROR,
797
0
           "could not prefetch relation %u/%u/%u block %u",
798
0
           reln->smgr_rlocator.locator.spcOid,
799
0
           reln->smgr_rlocator.locator.dbOid,
800
0
           reln->smgr_rlocator.locator.relNumber,
801
0
           block->blkno);
802
0
      }
803
0
    }
804
805
    /*
806
     * Several callsites need to be able to read exactly one record
807
     * without any internal readahead.  Examples: PerformWalRecovery()
808
     * trying to read the first record that follows a checkpoint has emode
809
     * set to PANIC, which might otherwise cause XLogPageRead() to panic
810
     * on some future page, and FinishWalRecovery() determining where to
811
     * start writing WAL next, which depends on the contents of the
812
     * reader's internal buffer after reading one record. Therefore, don't
813
     * even think about prefetching until the first record after
814
     * XLogPrefetcherBeginRead() has been consumed.
815
     */
816
0
    if (prefetcher->reader->decode_queue_tail &&
817
0
      prefetcher->reader->decode_queue_tail->lsn == prefetcher->begin_ptr)
818
0
      return LRQ_NEXT_AGAIN;
819
820
    /* Advance to the next record. */
821
0
    prefetcher->record = NULL;
822
0
  }
823
0
  pg_unreachable();
824
0
}
825
826
/*
827
 * Expose statistics about recovery prefetching.
828
 */
829
Datum
830
pg_stat_get_recovery_prefetch(PG_FUNCTION_ARGS)
831
0
{
832
0
#define PG_STAT_GET_RECOVERY_PREFETCH_COLS 10
833
0
  ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
834
0
  Datum   values[PG_STAT_GET_RECOVERY_PREFETCH_COLS];
835
0
  bool    nulls[PG_STAT_GET_RECOVERY_PREFETCH_COLS];
836
837
0
  InitMaterializedSRF(fcinfo, 0);
838
839
0
  for (int i = 0; i < PG_STAT_GET_RECOVERY_PREFETCH_COLS; ++i)
840
0
    nulls[i] = false;
841
842
0
  values[0] = TimestampTzGetDatum(pg_atomic_read_u64(&SharedStats->reset_time));
843
0
  values[1] = Int64GetDatum(pg_atomic_read_u64(&SharedStats->prefetch));
844
0
  values[2] = Int64GetDatum(pg_atomic_read_u64(&SharedStats->hit));
845
0
  values[3] = Int64GetDatum(pg_atomic_read_u64(&SharedStats->skip_init));
846
0
  values[4] = Int64GetDatum(pg_atomic_read_u64(&SharedStats->skip_new));
847
0
  values[5] = Int64GetDatum(pg_atomic_read_u64(&SharedStats->skip_fpw));
848
0
  values[6] = Int64GetDatum(pg_atomic_read_u64(&SharedStats->skip_rep));
849
0
  values[7] = Int32GetDatum(SharedStats->wal_distance);
850
0
  values[8] = Int32GetDatum(SharedStats->block_distance);
851
0
  values[9] = Int32GetDatum(SharedStats->io_depth);
852
0
  tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
853
854
0
  return (Datum) 0;
855
0
}
856
857
/*
858
 * Don't prefetch any blocks >= 'blockno' from a given 'rlocator', until 'lsn'
859
 * has been replayed.
860
 */
861
static inline void
862
XLogPrefetcherAddFilter(XLogPrefetcher *prefetcher, RelFileLocator rlocator,
863
            BlockNumber blockno, XLogRecPtr lsn)
864
0
{
865
0
  XLogPrefetcherFilter *filter;
866
0
  bool    found;
867
868
0
  filter = hash_search(prefetcher->filter_table, &rlocator, HASH_ENTER, &found);
869
0
  if (!found)
870
0
  {
871
    /*
872
     * Don't allow any prefetching of this block or higher until replayed.
873
     */
874
0
    filter->filter_until_replayed = lsn;
875
0
    filter->filter_from_block = blockno;
876
0
    dlist_push_head(&prefetcher->filter_queue, &filter->link);
877
0
  }
878
0
  else
879
0
  {
880
    /*
881
     * We were already filtering this rlocator.  Extend the filter's
882
     * lifetime to cover this WAL record, but leave the lower of the block
883
     * numbers there because we don't want to have to track individual
884
     * blocks.
885
     */
886
0
    filter->filter_until_replayed = lsn;
887
0
    dlist_delete(&filter->link);
888
0
    dlist_push_head(&prefetcher->filter_queue, &filter->link);
889
0
    filter->filter_from_block = Min(filter->filter_from_block, blockno);
890
0
  }
891
0
}
892
893
/*
894
 * Have we replayed any records that caused us to begin filtering a block
895
 * range?  That means that relations should have been created, extended or
896
 * dropped as required, so we can stop filtering out accesses to a given
897
 * relfilenumber.
898
 */
899
static inline void
900
XLogPrefetcherCompleteFilters(XLogPrefetcher *prefetcher, XLogRecPtr replaying_lsn)
901
0
{
902
0
  while (unlikely(!dlist_is_empty(&prefetcher->filter_queue)))
903
0
  {
904
0
    XLogPrefetcherFilter *filter = dlist_tail_element(XLogPrefetcherFilter,
905
0
                              link,
906
0
                              &prefetcher->filter_queue);
907
908
0
    if (filter->filter_until_replayed >= replaying_lsn)
909
0
      break;
910
911
0
    dlist_delete(&filter->link);
912
0
    hash_search(prefetcher->filter_table, filter, HASH_REMOVE, NULL);
913
0
  }
914
0
}
915
916
/*
917
 * Check if a given block should be skipped due to a filter.
918
 */
919
static inline bool
920
XLogPrefetcherIsFiltered(XLogPrefetcher *prefetcher, RelFileLocator rlocator,
921
             BlockNumber blockno)
922
0
{
923
  /*
924
   * Test for empty queue first, because we expect it to be empty most of
925
   * the time and we can avoid the hash table lookup in that case.
926
   */
927
0
  if (unlikely(!dlist_is_empty(&prefetcher->filter_queue)))
928
0
  {
929
0
    XLogPrefetcherFilter *filter;
930
931
    /* See if the block range is filtered. */
932
0
    filter = hash_search(prefetcher->filter_table, &rlocator, HASH_FIND, NULL);
933
0
    if (filter && filter->filter_from_block <= blockno)
934
0
    {
935
#ifdef XLOGPREFETCHER_DEBUG_LEVEL
936
      elog(XLOGPREFETCHER_DEBUG_LEVEL,
937
         "prefetch of %u/%u/%u block %u suppressed; filtering until LSN %X/%08X is replayed (blocks >= %u filtered)",
938
         rlocator.spcOid, rlocator.dbOid, rlocator.relNumber, blockno,
939
         LSN_FORMAT_ARGS(filter->filter_until_replayed),
940
         filter->filter_from_block);
941
#endif
942
0
      return true;
943
0
    }
944
945
    /* See if the whole database is filtered. */
946
0
    rlocator.relNumber = InvalidRelFileNumber;
947
0
    rlocator.spcOid = InvalidOid;
948
0
    filter = hash_search(prefetcher->filter_table, &rlocator, HASH_FIND, NULL);
949
0
    if (filter)
950
0
    {
951
#ifdef XLOGPREFETCHER_DEBUG_LEVEL
952
      elog(XLOGPREFETCHER_DEBUG_LEVEL,
953
         "prefetch of %u/%u/%u block %u suppressed; filtering until LSN %X/%08X is replayed (whole database)",
954
         rlocator.spcOid, rlocator.dbOid, rlocator.relNumber, blockno,
955
         LSN_FORMAT_ARGS(filter->filter_until_replayed));
956
#endif
957
0
      return true;
958
0
    }
959
0
  }
960
961
0
  return false;
962
0
}
963
964
/*
965
 * A wrapper for XLogBeginRead() that also resets the prefetcher.
966
 */
967
void
968
XLogPrefetcherBeginRead(XLogPrefetcher *prefetcher, XLogRecPtr recPtr)
969
0
{
970
  /* This will forget about any in-flight IO. */
971
0
  prefetcher->reconfigure_count--;
972
973
  /* Book-keeping to avoid readahead on first read. */
974
0
  prefetcher->begin_ptr = recPtr;
975
976
0
  prefetcher->no_readahead_until = InvalidXLogRecPtr;
977
978
  /* This will forget about any queued up records in the decoder. */
979
0
  XLogBeginRead(prefetcher->reader, recPtr);
980
0
}
981
982
/*
983
 * A wrapper for XLogReadRecord() that provides the same interface, but also
984
 * tries to initiate I/O for blocks referenced in future WAL records.
985
 */
986
XLogRecord *
987
XLogPrefetcherReadRecord(XLogPrefetcher *prefetcher, char **errmsg)
988
0
{
989
0
  DecodedXLogRecord *record;
990
0
  XLogRecPtr  replayed_up_to;
991
992
  /*
993
   * See if it's time to reset the prefetching machinery, because a relevant
994
   * GUC was changed.
995
   */
996
0
  if (unlikely(XLogPrefetchReconfigureCount != prefetcher->reconfigure_count))
997
0
  {
998
0
    uint32    max_distance;
999
0
    uint32    max_inflight;
1000
1001
0
    if (prefetcher->streaming_read)
1002
0
      lrq_free(prefetcher->streaming_read);
1003
1004
0
    if (RecoveryPrefetchEnabled())
1005
0
    {
1006
0
      Assert(maintenance_io_concurrency > 0);
1007
0
      max_inflight = maintenance_io_concurrency;
1008
0
      max_distance = max_inflight * XLOGPREFETCHER_DISTANCE_MULTIPLIER;
1009
0
    }
1010
0
    else
1011
0
    {
1012
0
      max_inflight = 1;
1013
0
      max_distance = 1;
1014
0
    }
1015
1016
0
    prefetcher->streaming_read = lrq_alloc(max_distance,
1017
0
                         max_inflight,
1018
0
                         (uintptr_t) prefetcher,
1019
0
                         XLogPrefetcherNextBlock);
1020
1021
0
    prefetcher->reconfigure_count = XLogPrefetchReconfigureCount;
1022
0
  }
1023
1024
  /*
1025
   * Release last returned record, if there is one, as it's now been
1026
   * replayed.
1027
   */
1028
0
  replayed_up_to = XLogReleasePreviousRecord(prefetcher->reader);
1029
1030
  /*
1031
   * Can we drop any filters yet?  If we were waiting for a relation to be
1032
   * created or extended, it is now OK to access blocks in the covered
1033
   * range.
1034
   */
1035
0
  XLogPrefetcherCompleteFilters(prefetcher, replayed_up_to);
1036
1037
  /*
1038
   * All IO initiated by earlier WAL is now completed.  This might trigger
1039
   * further prefetching.
1040
   */
1041
0
  lrq_complete_lsn(prefetcher->streaming_read, replayed_up_to);
1042
1043
  /*
1044
   * If there's nothing queued yet, then start prefetching to cause at least
1045
   * one record to be queued.
1046
   */
1047
0
  if (!XLogReaderHasQueuedRecordOrError(prefetcher->reader))
1048
0
  {
1049
0
    Assert(lrq_inflight(prefetcher->streaming_read) == 0);
1050
0
    Assert(lrq_completed(prefetcher->streaming_read) == 0);
1051
0
    lrq_prefetch(prefetcher->streaming_read);
1052
0
  }
1053
1054
  /* Read the next record. */
1055
0
  record = XLogNextRecord(prefetcher->reader, errmsg);
1056
0
  if (!record)
1057
0
    return NULL;
1058
1059
  /*
1060
   * The record we just got is the "current" one, for the benefit of the
1061
   * XLogRecXXX() macros.
1062
   */
1063
0
  Assert(record == prefetcher->reader->record);
1064
1065
  /*
1066
   * If maintenance_io_concurrency is set very low, we might have started
1067
   * prefetching some but not all of the blocks referenced in the record
1068
   * we're about to return.  Forget about the rest of the blocks in this
1069
   * record by dropping the prefetcher's reference to it.
1070
   */
1071
0
  if (record == prefetcher->record)
1072
0
    prefetcher->record = NULL;
1073
1074
  /*
1075
   * See if it's time to compute some statistics, because enough WAL has
1076
   * been processed.
1077
   */
1078
0
  if (unlikely(record->lsn >= prefetcher->next_stats_shm_lsn))
1079
0
    XLogPrefetcherComputeStats(prefetcher);
1080
1081
0
  Assert(record == prefetcher->reader->record);
1082
1083
0
  return &record->header;
1084
0
}
1085
1086
bool
1087
check_recovery_prefetch(int *new_value, void **extra, GucSource source)
1088
2
{
1089
#ifndef USE_PREFETCH
1090
  if (*new_value == RECOVERY_PREFETCH_ON)
1091
  {
1092
    GUC_check_errdetail("\"recovery_prefetch\" is not supported on platforms that lack support for issuing read-ahead advice.");
1093
    return false;
1094
  }
1095
#endif
1096
1097
2
  return true;
1098
2
}
1099
1100
void
1101
assign_recovery_prefetch(int new_value, void *extra)
1102
2
{
1103
  /* Reconfigure prefetching, because a setting it depends on changed. */
1104
2
  recovery_prefetch = new_value;
1105
2
  if (AmStartupProcess())
1106
0
    XLogPrefetchReconfigure();
1107
2
}