Coverage Report

Created: 2026-10-16 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/access/heap/pruneheap.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * pruneheap.c
4
 *    heap page pruning and HOT-chain management code
5
 *
6
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/access/heap/pruneheap.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#include "access/heapam.h"
18
#include "access/heapam_xlog.h"
19
#include "access/htup_details.h"
20
#include "access/multixact.h"
21
#include "access/transam.h"
22
#include "access/visibilitymap.h"
23
#include "access/xlog.h"
24
#include "access/xloginsert.h"
25
#include "commands/vacuum.h"
26
#include "executor/instrument.h"
27
#include "miscadmin.h"
28
#include "pgstat.h"
29
#include "storage/bufmgr.h"
30
#include "storage/freespace.h"
31
#include "utils/rel.h"
32
#include "utils/snapmgr.h"
33
34
/* Working data for heap_page_prune_and_freeze() and subroutines */
35
typedef struct
36
{
37
  /*-------------------------------------------------------
38
   * Arguments passed to heap_page_prune_and_freeze()
39
   *-------------------------------------------------------
40
   */
41
42
  /* tuple visibility test, initialized for the relation */
43
  GlobalVisState *vistest;
44
  /* whether or not dead items can be set LP_UNUSED during pruning */
45
  bool    mark_unused_now;
46
  /* whether to attempt freezing tuples */
47
  bool    attempt_freeze;
48
  /* whether to attempt setting the VM */
49
  bool    attempt_set_vm;
50
  struct VacuumCutoffs *cutoffs;
51
  Relation  relation;
52
53
  /*
54
   * Keep the buffer, block, and page handy so that helpers needing to
55
   * access them don't need to make repeated calls to BufferGetBlockNumber()
56
   * and BufferGetPage().
57
   */
58
  BlockNumber block;
59
  Buffer    buffer;
60
  Page    page;
61
62
  /*-------------------------------------------------------
63
   * Fields describing what to do to the page
64
   *-------------------------------------------------------
65
   */
66
  TransactionId new_prune_xid;  /* new prune hint value */
67
  TransactionId latest_xid_removed;
68
  int     nredirected;  /* numbers of entries in arrays below */
69
  int     ndead;
70
  int     nunused;
71
  int     nfrozen;
72
  /* arrays that accumulate indexes of items to be changed */
73
  OffsetNumber redirected[MaxHeapTuplesPerPage * 2];
74
  OffsetNumber nowdead[MaxHeapTuplesPerPage];
75
  OffsetNumber nowunused[MaxHeapTuplesPerPage];
76
  HeapTupleFreeze frozen[MaxHeapTuplesPerPage];
77
78
  /*
79
   * set_all_visible and set_all_frozen indicate if the all-visible and
80
   * all-frozen bits in the visibility map can be set for this page after
81
   * pruning. They are only tracked when the caller requests VM updates
82
   * (attempt_set_vm); otherwise they remain false throughout.
83
   *
84
   * NOTE: set_all_visible and set_all_frozen initially don't include
85
   * LP_DEAD items. That's convenient for heap_page_prune_and_freeze() to
86
   * use them to decide whether to opportunistically freeze the page or not.
87
   * The set_all_visible and set_all_frozen values ultimately used to set
88
   * the VM are adjusted to include LP_DEAD items after we determine whether
89
   * or not to opportunistically freeze.
90
   */
91
  bool    set_all_visible;
92
  bool    set_all_frozen;
93
94
  /*-------------------------------------------------------
95
   * Working state for HOT chain processing
96
   *-------------------------------------------------------
97
   */
98
99
  /*
100
   * 'root_items' contains offsets of all LP_REDIRECT line pointers and
101
   * normal non-HOT tuples.  They can be stand-alone items or the first item
102
   * in a HOT chain.  'heaponly_items' contains heap-only tuples which can
103
   * only be removed as part of a HOT chain.
104
   */
105
  int     nroot_items;
106
  OffsetNumber root_items[MaxHeapTuplesPerPage];
107
  int     nheaponly_items;
108
  OffsetNumber heaponly_items[MaxHeapTuplesPerPage];
109
110
  /*
111
   * processed[offnum] is true if item at offnum has been processed.
112
   *
113
   * This needs to be MaxHeapTuplesPerPage + 1 long as FirstOffsetNumber is
114
   * 1. Otherwise every access would need to subtract 1.
115
   */
116
  bool    processed[MaxHeapTuplesPerPage + 1];
117
118
  /*
119
   * Tuple visibility is only computed once for each tuple, for correctness
120
   * and efficiency reasons; see comment in heap_page_prune_and_freeze() for
121
   * details.  This is of type int8[], instead of HTSV_Result[], so we can
122
   * use -1 to indicate no visibility has been computed, e.g. for LP_DEAD
123
   * items.
124
   *
125
   * This needs to be MaxHeapTuplesPerPage + 1 long as FirstOffsetNumber is
126
   * 1. Otherwise every access would need to subtract 1.
127
   */
128
  int8    htsv[MaxHeapTuplesPerPage + 1];
129
130
  /*-------------------------------------------------------
131
   * Working state for freezing
132
   *-------------------------------------------------------
133
   */
134
  HeapPageFreeze pagefrz;
135
136
  /*-------------------------------------------------------
137
   * Working state for visibility map processing
138
   *-------------------------------------------------------
139
   */
140
141
  /*
142
   * Caller must provide a pinned vmbuffer corresponding to the heap block
143
   * passed to heap_page_prune_and_freeze(). We will fix any corruption
144
   * found in the VM and set the VM if the page is all-visible/all-frozen.
145
   */
146
  Buffer    vmbuffer;
147
148
  /*
149
   * The state of the VM bits at the beginning of pruning and the state they
150
   * will be in at the end.
151
   */
152
  uint8   old_vmbits;
153
  uint8   new_vmbits;
154
155
  /* The newest xmin of live tuples on the page */
156
  TransactionId newest_live_xid;
157
158
  /*-------------------------------------------------------
159
   * Information about what was done
160
   *
161
   * These fields are not used by pruning itself for the most part, but are
162
   * used to collect information about what was pruned and what state the
163
   * page is in after pruning, for the benefit of the caller.  They are
164
   * copied to the caller's PruneFreezeResult at the end.
165
   * -------------------------------------------------------
166
   */
167
168
  int     ndeleted;   /* Number of tuples deleted from the page */
169
170
  /* Number of live and recently dead tuples, after pruning */
171
  int     live_tuples;
172
  int     recently_dead_tuples;
173
174
  /* Whether or not the page makes rel truncation unsafe */
175
  bool    hastup;
176
177
  /*
178
   * LP_DEAD items on the page after pruning.  Includes existing LP_DEAD
179
   * items
180
   */
181
  int     lpdead_items; /* number of items in the array */
182
  OffsetNumber *deadoffsets;  /* points directly to presult->deadoffsets */
183
} PruneState;
184
185
/*
186
 * Type of visibility map corruption detected on a heap page and its
187
 * associated VM page. Passed to heap_page_fix_vm_corruption() so the caller
188
 * can specify what it found rather than having the function rederive the
189
 * corruption from page state.
190
 */
191
typedef enum VMCorruptionType
192
{
193
  /* VM bits are set but the heap page-level PD_ALL_VISIBLE flag is not */
194
  VM_CORRUPT_MISSING_PAGE_HINT,
195
  /* LP_DEAD line pointers found on a page marked all-visible */
196
  VM_CORRUPT_LPDEAD,
197
  /* Tuple not visible to all transactions on a page marked all-visible */
198
  VM_CORRUPT_TUPLE_VISIBILITY,
199
} VMCorruptionType;
200
201
/* Local functions */
202
static void prune_freeze_setup(PruneFreezeParams *params,
203
                 TransactionId *new_relfrozen_xid,
204
                 MultiXactId *new_relmin_mxid,
205
                 PruneFreezeResult *presult,
206
                 PruneState *prstate);
207
static void heap_page_fix_vm_corruption(PruneState *prstate,
208
                    OffsetNumber offnum,
209
                    VMCorruptionType corruption_type);
210
static void prune_freeze_fast_path(PruneState *prstate,
211
                   PruneFreezeResult *presult);
212
static void prune_freeze_plan(PruneState *prstate,
213
                OffsetNumber *off_loc);
214
static HTSV_Result heap_prune_satisfies_vacuum(PruneState *prstate,
215
                         HeapTuple tup);
216
static inline HTSV_Result htsv_get_valid_status(int status);
217
static void heap_prune_chain(OffsetNumber maxoff,
218
               OffsetNumber rootoffnum, PruneState *prstate);
219
static void heap_prune_record_prunable(PruneState *prstate, TransactionId xid,
220
                     OffsetNumber offnum);
221
static void heap_prune_record_redirect(PruneState *prstate,
222
                     OffsetNumber offnum, OffsetNumber rdoffnum,
223
                     bool was_normal);
224
static void heap_prune_record_dead(PruneState *prstate, OffsetNumber offnum,
225
                   bool was_normal);
226
static void heap_prune_record_dead_or_unused(PruneState *prstate, OffsetNumber offnum,
227
                       bool was_normal);
228
static void heap_prune_record_unused(PruneState *prstate, OffsetNumber offnum, bool was_normal);
229
230
static void heap_prune_record_unchanged_lp_unused(PruneState *prstate, OffsetNumber offnum);
231
static void heap_prune_record_unchanged_lp_normal(PruneState *prstate, OffsetNumber offnum);
232
static void heap_prune_record_unchanged_lp_dead(PruneState *prstate, OffsetNumber offnum);
233
static void heap_prune_record_unchanged_lp_redirect(PruneState *prstate, OffsetNumber offnum);
234
235
static void page_verify_redirects(Page page);
236
237
static bool heap_page_will_freeze(bool did_tuple_hint_fpi, bool do_prune, bool do_hint_prune,
238
                  PruneState *prstate);
239
static bool heap_page_will_set_vm(PruneState *prstate, PruneReason reason,
240
                  bool do_prune, bool do_freeze);
241
242
243
/*
244
 * Optionally prune and repair fragmentation in the specified page.
245
 *
246
 * This is an opportunistic function.  It will perform housekeeping
247
 * only if the page heuristically looks like a candidate for pruning and we
248
 * can acquire buffer cleanup lock without blocking.
249
 *
250
 * Note: this is called quite often.  It's important that it fall out quickly
251
 * if there's not any use in pruning.
252
 *
253
 * Caller must have pin on the buffer, and must *not* have a lock on it.
254
 *
255
 * This function may pin *vmbuffer. It's passed by reference so the caller can
256
 * reuse the pin across calls, avoiding repeated pin/unpin cycles. If we find
257
 * VM corruption during pruning, we will fix it. Caller is responsible for
258
 * unpinning *vmbuffer.
259
 *
260
 * rel_read_only is true if we determined at plan time that the query does not
261
 * modify the relation. It is counterproductive to set the VM if the query
262
 * will immediately clear it.
263
 *
264
 * As noted in ScanRelIsReadOnly(), INSERT ... SELECT from the same table will
265
 * report the scan relation as read-only. This is usually harmless in
266
 * practice. It is useful to set scanned pages all-visible that won't be
267
 * inserted into. Pages it does insert to will rarely meet the criteria for
268
 * pruning, and those that do are likely to contain in-progress inserts which
269
 * make the page not fully all-visible.
270
 */
271
void
272
heap_page_prune_opt(Relation relation, Buffer buffer, Buffer *vmbuffer,
273
          bool rel_read_only)
274
0
{
275
0
  Page    page = BufferGetPage(buffer);
276
0
  TransactionId prune_xid;
277
0
  GlobalVisState *vistest;
278
0
  Size    minfree;
279
280
  /*
281
   * We can't write WAL in recovery mode, so there's no point trying to
282
   * clean the page. The primary will likely issue a cleaning WAL record
283
   * soon anyway, so this is no particular loss.
284
   */
285
0
  if (RecoveryInProgress())
286
0
    return;
287
288
  /*
289
   * First check whether there's any chance there's something to prune,
290
   * determining the appropriate horizon is a waste if there's no prune_xid
291
   * (i.e. no updates/deletes left potentially dead tuples around and no
292
   * inserts inserted new tuples that may be visible to all).
293
   */
294
0
  prune_xid = PageGetPruneXid(page);
295
0
  if (!TransactionIdIsValid(prune_xid))
296
0
    return;
297
298
  /*
299
   * Check whether prune_xid indicates that there may be dead rows that can
300
   * be cleaned up.
301
   */
302
0
  vistest = GlobalVisTestFor(relation);
303
304
0
  if (!GlobalVisTestIsRemovableXid(vistest, prune_xid, true))
305
0
    return;
306
307
  /*
308
   * We prune when a previous UPDATE failed to find enough space on the page
309
   * for a new tuple version, or when free space falls below the relation's
310
   * fill-factor target (but not less than 10%).
311
   *
312
   * Checking free space here is questionable since we aren't holding any
313
   * lock on the buffer; in the worst case we could get a bogus answer. It's
314
   * unlikely to be *seriously* wrong, though, since reading either pd_lower
315
   * or pd_upper is probably atomic.  Avoiding taking a lock seems more
316
   * important than sometimes getting a wrong answer in what is after all
317
   * just a heuristic estimate.
318
   */
319
0
  minfree = RelationGetTargetPageFreeSpace(relation,
320
0
                       HEAP_DEFAULT_FILLFACTOR);
321
0
  minfree = Max(minfree, BLCKSZ / 10);
322
323
0
  if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
324
0
  {
325
0
    bool    record_free_space = false;
326
0
    Size    freespace = 0;
327
328
    /*
329
     * Pin the VM page before taking the heap cleanup lock. This may
330
     * occasionally lead to an unnecessary pin when the buffer is
331
     * contended, but the same VM page covers many heap pages, so there is
332
     * a good chance for the work to be reusable.
333
     */
334
0
    visibilitymap_pin(relation, BufferGetBlockNumber(buffer), vmbuffer);
335
336
    /* OK, try to get exclusive buffer lock */
337
0
    if (!ConditionalLockBufferForCleanup(buffer))
338
0
      return;
339
340
    /*
341
     * Now that we have buffer lock, get accurate information about the
342
     * page's free space, and recheck the heuristic about whether to
343
     * prune.
344
     */
345
0
    if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
346
0
    {
347
0
      OffsetNumber dummy_off_loc;
348
0
      PruneFreezeResult presult;
349
0
      PruneFreezeParams params;
350
351
0
      params.relation = relation;
352
0
      params.buffer = buffer;
353
0
      params.vmbuffer = *vmbuffer;
354
0
      params.reason = PRUNE_ON_ACCESS;
355
0
      params.vistest = vistest;
356
0
      params.cutoffs = NULL;
357
358
      /*
359
       * We don't pass the HEAP_PAGE_PRUNE_MARK_UNUSED_NOW option
360
       * regardless of whether or not the relation has indexes, since we
361
       * cannot safely determine that during on-access pruning with the
362
       * current implementation.
363
       */
364
0
      params.options = HEAP_PAGE_PRUNE_ALLOW_FAST_PATH;
365
0
      if (rel_read_only)
366
0
        params.options |= HEAP_PAGE_PRUNE_SET_VM;
367
368
0
      heap_page_prune_and_freeze(&params, &presult, &dummy_off_loc,
369
0
                     NULL, NULL);
370
371
      /*
372
       * Report the number of tuples reclaimed to pgstats.  This is
373
       * presult.ndeleted minus the number of newly-LP_DEAD-set items.
374
       *
375
       * We derive the number of dead tuples like this to avoid totally
376
       * forgetting about items that were set to LP_DEAD, since they
377
       * still need to be cleaned up by VACUUM.  We only want to count
378
       * heap-only tuples that just became LP_UNUSED in our report,
379
       * which don't.
380
       *
381
       * VACUUM doesn't have to compensate in the same way when it
382
       * tracks ndeleted, since it will set the same LP_DEAD items to
383
       * LP_UNUSED separately.
384
       */
385
0
      if (presult.ndeleted > presult.nnewlpdead)
386
0
        pgstat_update_heap_dead_tuples(relation,
387
0
                         presult.ndeleted - presult.nnewlpdead);
388
389
      /*
390
       * If this prune newly set the page all-visible, VACUUM may later
391
       * skip the page and not update the free space map (FSM) for it.
392
       * Keep the FSM from going stale by recording it now. We do not
393
       * want to update the freespace map otherwise, to reserve
394
       * freespace on this page for HOT updates.
395
       */
396
0
      if (presult.newly_all_visible)
397
0
      {
398
0
        record_free_space = true;
399
0
        freespace = PageGetHeapFreeSpace(page);
400
0
      }
401
0
    }
402
403
    /* And release buffer lock */
404
0
    LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
405
406
    /*
407
     * RecordPageWithFreeSpace() only dirties the FSM when the recorded
408
     * free-space category actually changes. Note that vacuum will still
409
     * do FreeSpaceMapVacuum() for ranges of pages that are skipped, so we
410
     * don't have to worry about that here.
411
     */
412
0
    if (record_free_space)
413
0
      RecordPageWithFreeSpace(relation, BufferGetBlockNumber(buffer), freespace);
414
0
  }
415
0
}
416
417
/*
418
 * Helper for heap_page_prune_and_freeze() to initialize the PruneState using
419
 * the provided parameters.
420
 *
421
 * params, new_relfrozen_xid, new_relmin_mxid, and presult are input
422
 * parameters and are not modified by this function. Only prstate is modified.
423
 */
424
static void
425
prune_freeze_setup(PruneFreezeParams *params,
426
           TransactionId *new_relfrozen_xid,
427
           MultiXactId *new_relmin_mxid,
428
           PruneFreezeResult *presult,
429
           PruneState *prstate)
430
0
{
431
  /* Copy parameters to prstate */
432
0
  prstate->vistest = params->vistest;
433
0
  prstate->mark_unused_now =
434
0
    (params->options & HEAP_PAGE_PRUNE_MARK_UNUSED_NOW) != 0;
435
436
  /* cutoffs must be provided if we will attempt freezing */
437
0
  Assert(!(params->options & HEAP_PAGE_PRUNE_FREEZE) || params->cutoffs);
438
0
  prstate->attempt_freeze = (params->options & HEAP_PAGE_PRUNE_FREEZE) != 0;
439
0
  prstate->attempt_set_vm = (params->options & HEAP_PAGE_PRUNE_SET_VM) != 0;
440
0
  prstate->cutoffs = params->cutoffs;
441
0
  prstate->relation = params->relation;
442
0
  prstate->block = BufferGetBlockNumber(params->buffer);
443
0
  prstate->buffer = params->buffer;
444
0
  prstate->page = BufferGetPage(params->buffer);
445
446
  /*
447
   * The caller must have pinned the VM page covering this heap block. If it
448
   * doesn't have the correct page pinned, visibilitymap_get_status() will
449
   * silently release the caller's pin and take its own, leaving the caller
450
   * holding a stale buffer and leaking ours.
451
   */
452
0
  Assert(visibilitymap_pin_ok(prstate->block, params->vmbuffer));
453
0
  prstate->vmbuffer = params->vmbuffer;
454
0
  prstate->new_vmbits = 0;
455
0
  prstate->old_vmbits = visibilitymap_get_status(prstate->relation,
456
0
                           prstate->block,
457
0
                           &prstate->vmbuffer);
458
459
  /*
460
   * Our strategy is to scan the page and make lists of items to change,
461
   * then apply the changes within a critical section.  This keeps as much
462
   * logic as possible out of the critical section, and also ensures that
463
   * WAL replay will work the same as the normal case.
464
   *
465
   * First, initialize the new pd_prune_xid value to zero (indicating no
466
   * prunable tuples).  If we find any tuples which may soon become
467
   * prunable, we will save the lowest relevant XID in new_prune_xid. Also
468
   * initialize the rest of our working state.
469
   */
470
0
  prstate->new_prune_xid = InvalidTransactionId;
471
0
  prstate->latest_xid_removed = InvalidTransactionId;
472
0
  prstate->nredirected = prstate->ndead = prstate->nunused = 0;
473
0
  prstate->nfrozen = 0;
474
0
  prstate->nroot_items = 0;
475
0
  prstate->nheaponly_items = 0;
476
477
  /* initialize page freezing working state */
478
0
  prstate->pagefrz.freeze_required = false;
479
0
  prstate->pagefrz.FreezePageConflictXid = InvalidTransactionId;
480
0
  if (prstate->attempt_freeze)
481
0
  {
482
0
    Assert(new_relfrozen_xid && new_relmin_mxid);
483
0
    prstate->pagefrz.FreezePageRelfrozenXid = *new_relfrozen_xid;
484
0
    prstate->pagefrz.NoFreezePageRelfrozenXid = *new_relfrozen_xid;
485
0
    prstate->pagefrz.FreezePageRelminMxid = *new_relmin_mxid;
486
0
    prstate->pagefrz.NoFreezePageRelminMxid = *new_relmin_mxid;
487
0
  }
488
0
  else
489
0
  {
490
0
    Assert(!new_relfrozen_xid && !new_relmin_mxid);
491
0
    prstate->pagefrz.FreezePageRelminMxid = InvalidMultiXactId;
492
0
    prstate->pagefrz.NoFreezePageRelminMxid = InvalidMultiXactId;
493
0
    prstate->pagefrz.FreezePageRelfrozenXid = InvalidTransactionId;
494
0
    prstate->pagefrz.NoFreezePageRelfrozenXid = InvalidTransactionId;
495
0
  }
496
497
0
  prstate->ndeleted = 0;
498
0
  prstate->live_tuples = 0;
499
0
  prstate->recently_dead_tuples = 0;
500
0
  prstate->hastup = false;
501
0
  prstate->lpdead_items = 0;
502
503
  /*
504
   * deadoffsets are filled in during pruning but are only used to populate
505
   * PruneFreezeResult->deadoffsets. To avoid needing two copies of the
506
   * array, just save a pointer to the result offsets array in the
507
   * PruneState.
508
   */
509
0
  prstate->deadoffsets = presult->deadoffsets;
510
511
  /*
512
   * We track whether the page will be all-visible/all-frozen at the end of
513
   * pruning and freezing. While examining tuple visibility, we'll set
514
   * set_all_visible to false if there are tuples on the page not visible to
515
   * all running and future transactions. If setting the VM is enabled for
516
   * this scan, we will do so if the page ends up being all-visible.
517
   *
518
   * We also keep track of the newest live XID, which is used to calculate
519
   * the snapshot conflict horizon for a WAL record setting the VM.
520
   */
521
0
  prstate->set_all_visible = prstate->attempt_set_vm;
522
0
  prstate->newest_live_xid = InvalidTransactionId;
523
524
  /*
525
   * Currently, only VACUUM performs freezing, but other callers may in the
526
   * future. We must initialize set_all_frozen based on whether or not the
527
   * caller passed HEAP_PAGE_PRUNE_FREEZE, because if they did not, we won't
528
   * call heap_prepare_freeze_tuple() for each tuple, and set_all_frozen
529
   * will never be cleared for tuples that need freezing. This would lead to
530
   * incorrectly setting the visibility map all-frozen for this page. We
531
   * can't set the page all-frozen in the VM if the caller didn't pass
532
   * HEAP_PAGE_PRUNE_SET_VM.
533
   *
534
   * When freezing is not required (no XIDs/MXIDs older than the freeze
535
   * cutoff), we may still choose to "opportunistically" freeze if doing so
536
   * would make the page all-frozen.
537
   *
538
   * We will not be able to freeze the whole page at the end of vacuum if
539
   * there are tuples present that are not visible to everyone or if there
540
   * are dead tuples which will not be removable. However, dead tuples that
541
   * will be removed by the end of vacuum should not prevent this
542
   * opportunistic freezing.
543
   *
544
   * Therefore, we do not clear set_all_visible and set_all_frozen when we
545
   * encounter LP_DEAD items. Instead, we correct them after deciding
546
   * whether to freeze, but before updating the VM, to avoid setting the VM
547
   * bits incorrectly.
548
   */
549
0
  prstate->set_all_frozen = prstate->attempt_freeze && prstate->attempt_set_vm;
550
0
}
551
552
/*
553
 * Helper for heap_page_prune_and_freeze(). Iterates over every tuple on the
554
 * page, examines its visibility information, and determines the appropriate
555
 * action for each tuple. All tuples are processed and classified during this
556
 * phase, but no modifications are made to the page until the later execution
557
 * stage.
558
 *
559
 * *off_loc is used for error callback and cleared before returning.
560
 */
561
static void
562
prune_freeze_plan(PruneState *prstate, OffsetNumber *off_loc)
563
0
{
564
0
  Page    page = prstate->page;
565
0
  BlockNumber blockno = prstate->block;
566
0
  OffsetNumber maxoff = PageGetMaxOffsetNumber(prstate->page);
567
0
  OffsetNumber offnum;
568
0
  HeapTupleData tup;
569
570
0
  tup.t_tableOid = RelationGetRelid(prstate->relation);
571
572
  /*
573
   * Determine HTSV for all tuples, and queue them up for processing as HOT
574
   * chain roots or as heap-only items.
575
   *
576
   * Determining HTSV only once for each tuple is required for correctness,
577
   * to deal with cases where running HTSV twice could result in different
578
   * results.  For example, RECENTLY_DEAD can turn to DEAD if another
579
   * checked item causes GlobalVisTestIsRemovableFullXid() to update the
580
   * horizon, or INSERT_IN_PROGRESS can change to DEAD if the inserting
581
   * transaction aborts.
582
   *
583
   * It's also good for performance. Most commonly tuples within a page are
584
   * stored at decreasing offsets (while the items are stored at increasing
585
   * offsets). When processing all tuples on a page this leads to reading
586
   * memory at decreasing offsets within a page, with a variable stride.
587
   * That's hard for CPU prefetchers to deal with. Processing the items in
588
   * reverse order (and thus the tuples in increasing order) increases
589
   * prefetching efficiency significantly / decreases the number of cache
590
   * misses.
591
   */
592
0
  for (offnum = maxoff;
593
0
     offnum >= FirstOffsetNumber;
594
0
     offnum = OffsetNumberPrev(offnum))
595
0
  {
596
0
    ItemId    itemid = PageGetItemId(page, offnum);
597
0
    HeapTupleHeader htup;
598
599
    /*
600
     * Set the offset number so that we can display it along with any
601
     * error that occurred while processing this tuple.
602
     */
603
0
    *off_loc = offnum;
604
605
0
    prstate->processed[offnum] = false;
606
0
    prstate->htsv[offnum] = -1;
607
608
    /* Nothing to do if slot doesn't contain a tuple */
609
0
    if (!ItemIdIsUsed(itemid))
610
0
    {
611
0
      heap_prune_record_unchanged_lp_unused(prstate, offnum);
612
0
      continue;
613
0
    }
614
615
0
    if (ItemIdIsDead(itemid))
616
0
    {
617
      /*
618
       * If the caller set mark_unused_now true, we can set dead line
619
       * pointers LP_UNUSED now.
620
       */
621
0
      if (unlikely(prstate->mark_unused_now))
622
0
        heap_prune_record_unused(prstate, offnum, false);
623
0
      else
624
0
        heap_prune_record_unchanged_lp_dead(prstate, offnum);
625
0
      continue;
626
0
    }
627
628
0
    if (ItemIdIsRedirected(itemid))
629
0
    {
630
      /* This is the start of a HOT chain */
631
0
      prstate->root_items[prstate->nroot_items++] = offnum;
632
0
      continue;
633
0
    }
634
635
0
    Assert(ItemIdIsNormal(itemid));
636
637
    /*
638
     * Get the tuple's visibility status and queue it up for processing.
639
     */
640
0
    htup = (HeapTupleHeader) PageGetItem(page, itemid);
641
0
    tup.t_data = htup;
642
0
    tup.t_len = ItemIdGetLength(itemid);
643
0
    ItemPointerSet(&tup.t_self, blockno, offnum);
644
645
0
    prstate->htsv[offnum] = heap_prune_satisfies_vacuum(prstate, &tup);
646
647
0
    if (!HeapTupleHeaderIsHeapOnly(htup))
648
0
      prstate->root_items[prstate->nroot_items++] = offnum;
649
0
    else
650
0
      prstate->heaponly_items[prstate->nheaponly_items++] = offnum;
651
0
  }
652
653
  /*
654
   * Process HOT chains.
655
   *
656
   * We added the items to the array starting from 'maxoff', so by
657
   * processing the array in reverse order, we process the items in
658
   * ascending offset number order.  The order doesn't matter for
659
   * correctness, but some quick micro-benchmarking suggests that this is
660
   * faster.  (Earlier PostgreSQL versions, which scanned all the items on
661
   * the page instead of using the root_items array, also did it in
662
   * ascending offset number order.)
663
   */
664
0
  for (int i = prstate->nroot_items - 1; i >= 0; i--)
665
0
  {
666
0
    offnum = prstate->root_items[i];
667
668
    /* Ignore items already processed as part of an earlier chain */
669
0
    if (prstate->processed[offnum])
670
0
      continue;
671
672
    /* see preceding loop */
673
0
    *off_loc = offnum;
674
675
    /* Process this item or chain of items */
676
0
    heap_prune_chain(maxoff, offnum, prstate);
677
0
  }
678
679
  /*
680
   * Process any heap-only tuples that were not already processed as part of
681
   * a HOT chain.
682
   */
683
0
  for (int i = prstate->nheaponly_items - 1; i >= 0; i--)
684
0
  {
685
0
    offnum = prstate->heaponly_items[i];
686
687
0
    if (prstate->processed[offnum])
688
0
      continue;
689
690
    /* see preceding loop */
691
0
    *off_loc = offnum;
692
693
    /*
694
     * If the tuple is DEAD and doesn't chain to anything else, mark it
695
     * unused.  (If it does chain, we can only remove it as part of
696
     * pruning its chain.)
697
     *
698
     * We need this primarily to handle aborted HOT updates, that is,
699
     * XMIN_INVALID heap-only tuples.  Those might not be linked to by any
700
     * chain, since the parent tuple might be re-updated before any
701
     * pruning occurs.  So we have to be able to reap them separately from
702
     * chain-pruning.  (Note that HeapTupleHeaderIsHotUpdated will never
703
     * return true for an XMIN_INVALID tuple, so this code will work even
704
     * when there were sequential updates within the aborted transaction.)
705
     */
706
0
    if (prstate->htsv[offnum] == HEAPTUPLE_DEAD)
707
0
    {
708
0
      ItemId    itemid = PageGetItemId(page, offnum);
709
0
      HeapTupleHeader htup = (HeapTupleHeader) PageGetItem(page, itemid);
710
711
0
      if (likely(!HeapTupleHeaderIsHotUpdated(htup)))
712
0
      {
713
0
        HeapTupleHeaderAdvanceConflictHorizon(htup,
714
0
                            &prstate->latest_xid_removed);
715
0
        heap_prune_record_unused(prstate, offnum, true);
716
0
      }
717
0
      else
718
0
      {
719
        /*
720
         * This tuple should've been processed and removed as part of
721
         * a HOT chain, so something's wrong.  To preserve evidence,
722
         * we don't dare to remove it.  We cannot leave behind a DEAD
723
         * tuple either, because that will cause VACUUM to error out.
724
         * Throwing an error with a distinct error message seems like
725
         * the least bad option.
726
         */
727
0
        elog(ERROR, "dead heap-only tuple (%u, %d) is not linked to from any HOT chain",
728
0
           blockno, offnum);
729
0
      }
730
0
    }
731
0
    else
732
0
      heap_prune_record_unchanged_lp_normal(prstate, offnum);
733
0
  }
734
735
  /* We should now have processed every tuple exactly once  */
736
#ifdef USE_ASSERT_CHECKING
737
  for (offnum = FirstOffsetNumber;
738
     offnum <= maxoff;
739
     offnum = OffsetNumberNext(offnum))
740
  {
741
    *off_loc = offnum;
742
743
    Assert(prstate->processed[offnum]);
744
  }
745
#endif
746
747
  /* Clear the offset information once we have processed the given page. */
748
0
  *off_loc = InvalidOffsetNumber;
749
0
}
750
751
/*
752
 * Decide whether to proceed with freezing according to the freeze plans
753
 * prepared for the current heap buffer. If freezing is chosen, this function
754
 * performs several pre-freeze checks.
755
 *
756
 * The values of do_prune, do_hint_prune, and did_tuple_hint_fpi must be
757
 * determined before calling this function.
758
 *
759
 * prstate is both an input and output parameter.
760
 *
761
 * Returns true if we should apply the freeze plans and freeze tuples on the
762
 * page, and false otherwise.
763
 */
764
static bool
765
heap_page_will_freeze(bool did_tuple_hint_fpi,
766
            bool do_prune,
767
            bool do_hint_prune,
768
            PruneState *prstate)
769
0
{
770
0
  bool    do_freeze = false;
771
772
  /*
773
   * If the caller specified we should not attempt to freeze any tuples,
774
   * validate that everything is in the right state and return.
775
   */
776
0
  if (!prstate->attempt_freeze)
777
0
  {
778
0
    Assert(!prstate->set_all_frozen && prstate->nfrozen == 0);
779
0
    return false;
780
0
  }
781
782
0
  if (prstate->pagefrz.freeze_required)
783
0
  {
784
    /*
785
     * heap_prepare_freeze_tuple indicated that at least one XID/MXID from
786
     * before FreezeLimit/MultiXactCutoff is present.  Must freeze to
787
     * advance relfrozenxid/relminmxid.
788
     */
789
0
    do_freeze = true;
790
0
  }
791
0
  else
792
0
  {
793
    /*
794
     * Opportunistically freeze the page if we are generating an FPI
795
     * anyway and if doing so means that we can set the page all-frozen
796
     * afterwards (might not happen until VACUUM's final heap pass).
797
     *
798
     * XXX: Previously, we knew if pruning emitted an FPI by checking
799
     * pgWalUsage.wal_fpi before and after pruning.  Once the freeze and
800
     * prune records were combined, this heuristic couldn't be used
801
     * anymore.  The opportunistic freeze heuristic must be improved;
802
     * however, for now, try to approximate the old logic.
803
     */
804
0
    if (prstate->set_all_frozen && prstate->nfrozen > 0)
805
0
    {
806
0
      Assert(prstate->set_all_visible);
807
808
      /*
809
       * Freezing would make the page all-frozen.  Have already emitted
810
       * an FPI or will do so anyway?
811
       */
812
0
      if (RelationNeedsWAL(prstate->relation))
813
0
      {
814
0
        if (did_tuple_hint_fpi)
815
0
          do_freeze = true;
816
0
        else if (do_prune)
817
0
        {
818
0
          if (XLogCheckBufferNeedsBackup(prstate->buffer))
819
0
            do_freeze = true;
820
0
        }
821
0
        else if (do_hint_prune)
822
0
        {
823
0
          if (XLogHintBitIsNeeded() &&
824
0
            XLogCheckBufferNeedsBackup(prstate->buffer))
825
0
            do_freeze = true;
826
0
        }
827
0
      }
828
0
    }
829
0
  }
830
831
0
  if (do_freeze)
832
0
  {
833
    /*
834
     * Validate the tuples we will be freezing before entering the
835
     * critical section.
836
     */
837
0
    heap_pre_freeze_checks(prstate->buffer, prstate->frozen, prstate->nfrozen);
838
0
    Assert(TransactionIdPrecedes(prstate->pagefrz.FreezePageConflictXid,
839
0
                   prstate->cutoffs->OldestXmin));
840
0
  }
841
0
  else if (prstate->nfrozen > 0)
842
0
  {
843
    /*
844
     * The page contained some tuples that were not already frozen, and we
845
     * chose not to freeze them now.  The page won't be all-frozen then.
846
     */
847
0
    Assert(!prstate->pagefrz.freeze_required);
848
849
0
    prstate->set_all_frozen = false;
850
0
    prstate->nfrozen = 0; /* avoid miscounts in instrumentation */
851
0
  }
852
0
  else
853
0
  {
854
    /*
855
     * We have no freeze plans to execute.  The page might already be
856
     * all-frozen (perhaps only following pruning), though.  Such pages
857
     * can be marked all-frozen in the VM by our caller, even though none
858
     * of its tuples were newly frozen here.
859
     */
860
0
  }
861
862
0
  return do_freeze;
863
0
}
864
865
/*
866
 * Emit a warning about and fix visibility map corruption on the given page.
867
 *
868
 * The caller specifies the type of corruption it has already detected via
869
 * corruption_type, so that we can emit the appropriate warning. All cases
870
 * result in the VM bits being cleared; corruption types where PD_ALL_VISIBLE
871
 * is incorrectly set also clear PD_ALL_VISIBLE.
872
 *
873
 * Must be called while holding an exclusive lock on the heap buffer. Dead
874
 * items and not all-visible tuples must have been discovered under that same
875
 * lock. Although we do not hold a lock on the VM buffer, it is pinned, and
876
 * the heap buffer is exclusively locked, ensuring that no other backend can
877
 * update the VM bits corresponding to this heap page.
878
 *
879
 * This function makes changes to the VM and, potentially, the heap page, but
880
 * it does not need to be done in a critical section.
881
 */
882
static void
883
heap_page_fix_vm_corruption(PruneState *prstate, OffsetNumber offnum,
884
              VMCorruptionType corruption_type)
885
0
{
886
0
  const char *relname = RelationGetRelationName(prstate->relation);
887
0
  bool    do_clear_vm = false;
888
0
  bool    do_clear_heap = false;
889
890
0
  Assert(BufferIsLockedByMeInMode(prstate->buffer, BUFFER_LOCK_EXCLUSIVE));
891
892
0
  switch (corruption_type)
893
0
  {
894
0
    case VM_CORRUPT_LPDEAD:
895
0
      ereport(WARNING,
896
0
          (errcode(ERRCODE_DATA_CORRUPTED),
897
0
           errmsg("dead line pointer found on page marked all-visible"),
898
0
           errcontext("relation \"%s\", page %u, tuple %u",
899
0
                relname, prstate->block, offnum)));
900
0
      do_clear_vm = true;
901
0
      do_clear_heap = true;
902
0
      break;
903
904
0
    case VM_CORRUPT_TUPLE_VISIBILITY:
905
906
      /*
907
       * A HEAPTUPLE_LIVE tuple on an all-visible page can appear to not
908
       * be visible to everyone when
909
       * GetOldestNonRemovableTransactionId() returns a conservative
910
       * value that's older than the real safe xmin. That is not
911
       * corruption -- the PD_ALL_VISIBLE flag is still correct.
912
       *
913
       * However, dead tuple versions, in-progress inserts, and
914
       * in-progress deletes should never appear on a page marked
915
       * all-visible. That indicates real corruption. PD_ALL_VISIBLE
916
       * should have been cleared by the DML operation that deleted or
917
       * inserted the tuple.
918
       */
919
0
      ereport(WARNING,
920
0
          (errcode(ERRCODE_DATA_CORRUPTED),
921
0
           errmsg("tuple not visible to all transactions found on page marked all-visible"),
922
0
           errcontext("relation \"%s\", page %u, tuple %u",
923
0
                relname, prstate->block, offnum)));
924
0
      do_clear_vm = true;
925
0
      do_clear_heap = true;
926
0
      break;
927
928
0
    case VM_CORRUPT_MISSING_PAGE_HINT:
929
930
      /*
931
       * As of PostgreSQL 9.2, the visibility map bit should never be
932
       * set if the page-level bit is clear. However, for vacuum, it's
933
       * possible that the bit got cleared after
934
       * heap_vac_scan_next_block() was called, so we must recheck now
935
       * that we have the buffer lock before concluding that the VM is
936
       * corrupt.
937
       */
938
0
      Assert(!PageIsAllVisible(prstate->page));
939
0
      Assert(prstate->old_vmbits & VISIBILITYMAP_VALID_BITS);
940
0
      ereport(WARNING,
941
0
          (errcode(ERRCODE_DATA_CORRUPTED),
942
0
           errmsg("page is not marked all-visible but visibility map bit is set"),
943
0
           errcontext("relation \"%s\", page %u",
944
0
                relname, prstate->block)));
945
0
      do_clear_vm = true;
946
0
      break;
947
0
  }
948
949
0
  Assert(do_clear_heap || do_clear_vm);
950
951
  /* Avoid marking the buffer dirty if PD_ALL_VISIBLE is already clear */
952
0
  if (do_clear_heap)
953
0
  {
954
0
    Assert(PageIsAllVisible(prstate->page));
955
0
    PageClearAllVisible(prstate->page);
956
0
    MarkBufferDirtyHint(prstate->buffer, true);
957
0
  }
958
959
0
  if (do_clear_vm)
960
0
  {
961
0
    LockBuffer(prstate->vmbuffer, BUFFER_LOCK_EXCLUSIVE);
962
    /* This VM clear is not WAL-logged, so its return value is not needed. */
963
0
    (void) visibilitymap_clear(prstate->relation->rd_locator,
964
0
                   prstate->block, prstate->vmbuffer,
965
0
                   VISIBILITYMAP_VALID_BITS);
966
0
    LockBuffer(prstate->vmbuffer, BUFFER_LOCK_UNLOCK);
967
0
    prstate->old_vmbits = 0;
968
0
  }
969
0
}
970
971
/*
972
 * Decide whether to set the visibility map bits (all-visible and all-frozen)
973
 * for the current page using information from the PruneState and VM.
974
 *
975
 * This function does not actually set the VM bits or page-level visibility
976
 * hint, PD_ALL_VISIBLE.
977
 *
978
 * This should be called only after do_freeze has been decided (and do_prune
979
 * has been set), as these factor into our heuristic-based decision.
980
 *
981
 * Returns true if one or both VM bits should be set and false otherwise.
982
 */
983
static bool
984
heap_page_will_set_vm(PruneState *prstate, PruneReason reason,
985
            bool do_prune, bool do_freeze)
986
0
{
987
0
  if (!prstate->attempt_set_vm)
988
0
    return false;
989
990
0
  if (!prstate->set_all_visible)
991
0
    return false;
992
993
  /*
994
   * If this is an on-access call and we're not actually pruning or
995
   * freezing, consider whether setting the VM would cost us an additional
996
   * heap page FPI. If the relation isn't WAL-logged, or if hint bits are
997
   * not WAL-logged, setting the VM won't include a heap page FPI (the
998
   * latter passes REGBUF_NO_IMAGE for the heap page), apart from a page
999
   * that has never been WAL-logged, which we don't bother about here.
1000
   */
1001
0
  if (reason == PRUNE_ON_ACCESS && !do_prune && !do_freeze &&
1002
0
    RelationNeedsWAL(prstate->relation) && XLogHintBitIsNeeded())
1003
0
  {
1004
    /*
1005
     * Because the page is known to be all-visible, we will clear
1006
     * pd_prune_xid regardless of whether we actually set the page
1007
     * all-visible in the VM. That clear is a hint update which is not
1008
     * WAL-logged, other than an FPI for torn-page protection, so in some
1009
     * cases we want to avoid setting the VM if doing so would cost us a
1010
     * heap page FPI that clearing pd_prune_xid wouldn't have.
1011
     *
1012
     * Since hint bits are WAL-logged, if the buffer is clean, clearing
1013
     * pd_prune_xid will already emit a heap page FPI if one is needed, so
1014
     * there's no reason to avoid setting the VM.
1015
     *
1016
     * However, if the heap buffer is already dirty, clearing pd_prune_xid
1017
     * will never emit an FPI. So avoid setting the VM if the page hasn't
1018
     * been WAL-logged since the current checkpoint began, as the record
1019
     * setting the VM would then include a heap page FPI.
1020
     */
1021
0
    if (BufferIsDirty(prstate->buffer) &&
1022
0
      XLogCheckBufferNeedsBackup(prstate->buffer))
1023
0
    {
1024
0
      prstate->set_all_visible = prstate->set_all_frozen = false;
1025
0
      return false;
1026
0
    }
1027
0
  }
1028
1029
0
  prstate->new_vmbits = VISIBILITYMAP_ALL_VISIBLE;
1030
1031
0
  if (prstate->set_all_frozen)
1032
0
    prstate->new_vmbits |= VISIBILITYMAP_ALL_FROZEN;
1033
1034
0
  if (prstate->new_vmbits == prstate->old_vmbits)
1035
0
  {
1036
0
    prstate->new_vmbits = 0;
1037
0
    return false;
1038
0
  }
1039
1040
0
  return true;
1041
0
}
1042
1043
/*
1044
 * If the page is already all-frozen, or already all-visible and freezing
1045
 * won't be attempted, there is no remaining work and we can use the fast path
1046
 * to avoid the expensive overhead of heap_page_prune_and_freeze().
1047
 *
1048
 * This can happen when the page has a stale prune hint, or if VACUUM is
1049
 * scanning an already all-frozen page due to SKIP_PAGES_THRESHOLD.
1050
 *
1051
 * The caller must already have examined the visibility map and saved the
1052
 * status of the page's VM bits in prstate->old_vmbits. Caller must hold a
1053
 * content lock on the heap page since it will examine line pointers.
1054
 *
1055
 * Before calling prune_freeze_fast_path(), the caller should first
1056
 * check for and fix any discrepancy between the page-level visibility hint
1057
 * and the visibility map. Otherwise, the fast path will always prevent us
1058
 * from getting them in sync. Note that if there are tuples on the page that
1059
 * are not visible to all but the VM is incorrectly marked
1060
 * all-visible/all-frozen, we will not get the chance to fix that corruption
1061
 * when using the fast path.
1062
 */
1063
static void
1064
prune_freeze_fast_path(PruneState *prstate, PruneFreezeResult *presult)
1065
0
{
1066
0
  OffsetNumber maxoff = PageGetMaxOffsetNumber(prstate->page);
1067
0
  Page    page = prstate->page;
1068
1069
0
  Assert((prstate->old_vmbits & VISIBILITYMAP_ALL_FROZEN) ||
1070
0
       ((prstate->old_vmbits & VISIBILITYMAP_ALL_VISIBLE) &&
1071
0
      !prstate->attempt_freeze));
1072
1073
  /* We'll fill in presult for the caller */
1074
0
  memset(presult, 0, sizeof(PruneFreezeResult));
1075
1076
  /* Clear any stale prune hint */
1077
0
  if (TransactionIdIsValid(PageGetPruneXid(page)))
1078
0
  {
1079
0
    PageClearPrunable(page);
1080
0
    MarkBufferDirtyHint(prstate->buffer, true);
1081
0
  }
1082
1083
0
  if (PageIsEmpty(page))
1084
0
    return;
1085
1086
  /*
1087
   * Since the page is all-visible, a count of the normal ItemIds on the
1088
   * page should be sufficient for vacuum's live tuple count.
1089
   */
1090
0
  for (OffsetNumber off = FirstOffsetNumber;
1091
0
     off <= maxoff;
1092
0
     off = OffsetNumberNext(off))
1093
0
  {
1094
0
    ItemId    lp = PageGetItemId(page, off);
1095
1096
0
    if (!ItemIdIsUsed(lp))
1097
0
      continue;
1098
1099
0
    presult->hastup = true;
1100
1101
0
    if (ItemIdIsNormal(lp))
1102
0
      prstate->live_tuples++;
1103
0
  }
1104
1105
0
  presult->live_tuples = prstate->live_tuples;
1106
0
}
1107
1108
/*
1109
 * Prune and repair fragmentation and potentially freeze tuples on the
1110
 * specified page. If the page's visibility status has changed, update it in
1111
 * the VM.
1112
 *
1113
 * Caller must have pin and buffer cleanup lock on the page.  Note that we
1114
 * don't update the FSM information for page on caller's behalf.  Caller might
1115
 * also need to account for a reduction in the length of the line pointer
1116
 * array following array truncation by us.
1117
 *
1118
 * params contains the input parameters used to control freezing and pruning
1119
 * behavior. See the definition of PruneFreezeParams for more on what each
1120
 * parameter does.
1121
 *
1122
 * If the HEAP_PAGE_PRUNE_FREEZE option is set in params, we will freeze
1123
 * tuples if it's required in order to advance relfrozenxid / relminmxid, or
1124
 * if it's considered advantageous for overall system performance to do so
1125
 * now.  The 'params.cutoffs', 'presult', 'new_relfrozen_xid' and
1126
 * 'new_relmin_mxid' arguments are required when freezing.
1127
 *
1128
 * A vmbuffer corresponding to the heap page is also passed and if the page is
1129
 * found to be all-visible/all-frozen, we will set it in the VM.
1130
 *
1131
 * presult contains output parameters needed by callers, such as the number of
1132
 * tuples removed and the offsets of dead items on the page after pruning.
1133
 * heap_page_prune_and_freeze() is responsible for initializing it.  Required
1134
 * by all callers.
1135
 *
1136
 * off_loc is the offset location required by the caller to use in error
1137
 * callback.
1138
 *
1139
 * new_relfrozen_xid and new_relmin_mxid must be provided by the caller if the
1140
 * HEAP_PAGE_PRUNE_FREEZE option is set in params.  On entry, they contain the
1141
 * oldest XID and multi-XID seen on the relation so far.  They will be updated
1142
 * with the oldest values present on the page after pruning.  After processing
1143
 * the whole relation, VACUUM can use these values as the new
1144
 * relfrozenxid/relminmxid for the relation.
1145
 */
1146
void
1147
heap_page_prune_and_freeze(PruneFreezeParams *params,
1148
               PruneFreezeResult *presult,
1149
               OffsetNumber *off_loc,
1150
               TransactionId *new_relfrozen_xid,
1151
               MultiXactId *new_relmin_mxid)
1152
0
{
1153
0
  PruneState  prstate;
1154
0
  bool    do_freeze;
1155
0
  bool    do_prune;
1156
0
  bool    do_hint_prune;
1157
0
  bool    do_set_vm;
1158
0
  bool    did_tuple_hint_fpi;
1159
0
  int64   fpi_before = pgWalUsage.wal_fpi;
1160
0
  TransactionId conflict_xid;
1161
1162
  /* Initialize prstate */
1163
0
  prune_freeze_setup(params,
1164
0
             new_relfrozen_xid, new_relmin_mxid,
1165
0
             presult, &prstate);
1166
1167
  /*
1168
   * If the VM is set but PD_ALL_VISIBLE is clear, fix that corruption
1169
   * before pruning and freezing so that the page and VM start out in a
1170
   * consistent state.
1171
   */
1172
0
  if ((prstate.old_vmbits & VISIBILITYMAP_VALID_BITS) &&
1173
0
    !PageIsAllVisible(prstate.page))
1174
0
    heap_page_fix_vm_corruption(&prstate, InvalidOffsetNumber,
1175
0
                  VM_CORRUPT_MISSING_PAGE_HINT);
1176
1177
  /*
1178
   * If the page is already all-frozen, or already all-visible when freezing
1179
   * is not being attempted, take the fast path, skipping pruning and
1180
   * freezing code entirely. This must be done after fixing any discrepancy
1181
   * between the page-level visibility hint and the VM, since that may have
1182
   * cleared old_vmbits.
1183
   */
1184
0
  if ((params->options & HEAP_PAGE_PRUNE_ALLOW_FAST_PATH) != 0 &&
1185
0
    ((prstate.old_vmbits & VISIBILITYMAP_ALL_FROZEN) ||
1186
0
     ((prstate.old_vmbits & VISIBILITYMAP_ALL_VISIBLE) &&
1187
0
      !prstate.attempt_freeze)))
1188
0
  {
1189
0
    prune_freeze_fast_path(&prstate, presult);
1190
0
    return;
1191
0
  }
1192
1193
  /*
1194
   * Examine all line pointers and tuple visibility information to determine
1195
   * which line pointers should change state and which tuples may be frozen.
1196
   * Prepare queue of state changes to later be executed in a critical
1197
   * section.
1198
   */
1199
0
  prune_freeze_plan(&prstate, off_loc);
1200
1201
  /*
1202
   * After processing all the live tuples on the page, if the newest xmin
1203
   * amongst them may be considered running by any snapshot, the page cannot
1204
   * be all-visible. This should be done before determining whether or not
1205
   * to opportunistically freeze.
1206
   */
1207
0
  if (prstate.set_all_visible &&
1208
0
    TransactionIdIsNormal(prstate.newest_live_xid) &&
1209
0
    GlobalVisTestXidConsideredRunning(prstate.vistest,
1210
0
                      prstate.newest_live_xid,
1211
0
                      true))
1212
0
    prstate.set_all_visible = prstate.set_all_frozen = false;
1213
1214
  /*
1215
   * If checksums are enabled, calling heap_prune_satisfies_vacuum() while
1216
   * checking tuple visibility information in prune_freeze_plan() may have
1217
   * caused an FPI to be emitted.
1218
   */
1219
0
  did_tuple_hint_fpi = fpi_before != pgWalUsage.wal_fpi;
1220
1221
0
  do_prune = prstate.nredirected > 0 ||
1222
0
    prstate.ndead > 0 ||
1223
0
    prstate.nunused > 0;
1224
1225
  /*
1226
   * Even if we don't prune anything, if we found a new value for the
1227
   * pd_prune_xid field or the page was marked full, we will update the hint
1228
   * bit.
1229
   */
1230
0
  do_hint_prune = PageGetPruneXid(prstate.page) != prstate.new_prune_xid ||
1231
0
    PageIsFull(prstate.page);
1232
1233
  /*
1234
   * Decide if we want to go ahead with freezing according to the freeze
1235
   * plans we prepared, or not.
1236
   */
1237
0
  do_freeze = heap_page_will_freeze(did_tuple_hint_fpi,
1238
0
                    do_prune,
1239
0
                    do_hint_prune,
1240
0
                    &prstate);
1241
1242
  /*
1243
   * While scanning the line pointers, we did not clear
1244
   * set_all_visible/set_all_frozen when encountering LP_DEAD items because
1245
   * we wanted the decision whether or not to freeze the page to be
1246
   * unaffected by the short-term presence of LP_DEAD items.  These LP_DEAD
1247
   * items are effectively assumed to be LP_UNUSED items in the making.  It
1248
   * doesn't matter which vacuum heap pass (initial pass or final pass) ends
1249
   * up setting the page all-frozen, as long as the ongoing VACUUM does it.
1250
   *
1251
   * Now that we finished determining whether or not to freeze the page,
1252
   * update set_all_visible and set_all_frozen so that they reflect the true
1253
   * state of the page for setting PD_ALL_VISIBLE and VM bits.
1254
   */
1255
0
  if (prstate.lpdead_items > 0)
1256
0
    prstate.set_all_visible = prstate.set_all_frozen = false;
1257
1258
0
  Assert(!prstate.set_all_frozen || prstate.set_all_visible);
1259
0
  Assert(!prstate.set_all_visible || prstate.attempt_set_vm);
1260
0
  Assert(!prstate.set_all_visible || (prstate.lpdead_items == 0));
1261
1262
0
  do_set_vm = heap_page_will_set_vm(&prstate, params->reason, do_prune, do_freeze);
1263
1264
  /*
1265
   * new_vmbits should be 0 regardless of whether or not the page is
1266
   * all-visible if we do not intend to set the VM.
1267
   */
1268
0
  Assert(do_set_vm || prstate.new_vmbits == 0);
1269
1270
  /*
1271
   * The snapshot conflict horizon for the whole record is the most
1272
   * conservative (newest) horizon required by any change in the record.
1273
   */
1274
0
  conflict_xid = InvalidTransactionId;
1275
0
  if (do_set_vm)
1276
0
    conflict_xid = prstate.newest_live_xid;
1277
0
  if (do_freeze && TransactionIdFollows(prstate.pagefrz.FreezePageConflictXid, conflict_xid))
1278
0
    conflict_xid = prstate.pagefrz.FreezePageConflictXid;
1279
0
  if (do_prune && TransactionIdFollows(prstate.latest_xid_removed, conflict_xid))
1280
0
    conflict_xid = prstate.latest_xid_removed;
1281
1282
  /* Lock vmbuffer before entering a critical section */
1283
0
  if (do_set_vm)
1284
0
    LockBuffer(prstate.vmbuffer, BUFFER_LOCK_EXCLUSIVE);
1285
1286
  /* Any error while applying the changes is critical */
1287
0
  START_CRIT_SECTION();
1288
1289
0
  if (do_hint_prune)
1290
0
  {
1291
    /*
1292
     * Update the page's pd_prune_xid field to either zero, or the lowest
1293
     * XID of any soon-prunable tuple.
1294
     */
1295
0
    ((PageHeader) prstate.page)->pd_prune_xid = prstate.new_prune_xid;
1296
1297
    /*
1298
     * Also clear the "page is full" flag, since there's no point in
1299
     * repeating the prune/defrag process until something else happens to
1300
     * the page.
1301
     */
1302
0
    PageClearFull(prstate.page);
1303
1304
    /*
1305
     * If that's all we had to do to the page, this is a non-WAL-logged
1306
     * hint. If we are going to freeze or prune the page or set
1307
     * PD_ALL_VISIBLE, we will mark the buffer dirty below.
1308
     *
1309
     * Setting PD_ALL_VISIBLE is fully WAL-logged because it is forbidden
1310
     * for the VM to be set and PD_ALL_VISIBLE to be clear.
1311
     */
1312
0
    if (!do_freeze && !do_prune && !do_set_vm)
1313
0
      MarkBufferDirtyHint(prstate.buffer, true);
1314
0
  }
1315
1316
0
  if (do_prune || do_freeze || do_set_vm)
1317
0
  {
1318
    /* Apply the planned item changes and repair page fragmentation. */
1319
0
    if (do_prune)
1320
0
    {
1321
0
      heap_page_prune_execute(prstate.buffer, false,
1322
0
                  prstate.redirected, prstate.nredirected,
1323
0
                  prstate.nowdead, prstate.ndead,
1324
0
                  prstate.nowunused, prstate.nunused);
1325
0
    }
1326
1327
0
    if (do_freeze)
1328
0
      heap_freeze_prepared_tuples(prstate.buffer, prstate.frozen, prstate.nfrozen);
1329
1330
    /* Set the visibility map and page visibility hint */
1331
0
    if (do_set_vm)
1332
0
    {
1333
      /*
1334
       * While it is valid for PD_ALL_VISIBLE to be set when the
1335
       * corresponding VM bit is clear, we strongly prefer to keep them
1336
       * in sync.
1337
       *
1338
       * The heap buffer must be marked dirty before adding it to the
1339
       * WAL chain when setting the VM. We don't worry about
1340
       * unnecessarily dirtying the heap buffer if PD_ALL_VISIBLE is
1341
       * already set, though. It is extremely rare to have a clean heap
1342
       * buffer with PD_ALL_VISIBLE already set and the VM bits clear,
1343
       * so there is no point in optimizing it.
1344
       */
1345
0
      PageSetAllVisible(prstate.page);
1346
0
      PageClearPrunable(prstate.page);
1347
0
      (void) visibilitymap_set(prstate.block, prstate.vmbuffer,
1348
0
                   prstate.new_vmbits,
1349
0
                   prstate.relation->rd_locator);
1350
0
    }
1351
1352
0
    MarkBufferDirty(prstate.buffer);
1353
1354
    /*
1355
     * Emit a WAL XLOG_HEAP2_PRUNE* record showing what we did
1356
     */
1357
0
    if (RelationNeedsWAL(prstate.relation))
1358
0
    {
1359
0
      log_heap_prune_and_freeze(prstate.relation, prstate.buffer,
1360
0
                    do_set_vm ? prstate.vmbuffer : InvalidBuffer,
1361
0
                    do_set_vm ? prstate.new_vmbits : 0,
1362
0
                    conflict_xid,
1363
0
                    do_prune, /* cleanup lock */
1364
0
                    params->reason,
1365
0
                    prstate.frozen, prstate.nfrozen,
1366
0
                    prstate.redirected, prstate.nredirected,
1367
0
                    prstate.nowdead, prstate.ndead,
1368
0
                    prstate.nowunused, prstate.nunused);
1369
0
    }
1370
0
  }
1371
1372
0
  END_CRIT_SECTION();
1373
1374
0
  if (do_set_vm)
1375
0
    LockBuffer(prstate.vmbuffer, BUFFER_LOCK_UNLOCK);
1376
1377
  /*
1378
   * During its second pass over the heap, VACUUM calls
1379
   * heap_page_would_be_all_visible() to determine whether a page is
1380
   * all-visible and all-frozen. The logic here is similar. After completing
1381
   * pruning and freezing, use an assertion to verify that our results
1382
   * remain consistent with heap_page_would_be_all_visible(). It's also a
1383
   * valuable cross-check of the page state after pruning and freezing.
1384
   */
1385
#ifdef USE_ASSERT_CHECKING
1386
  if (prstate.set_all_visible)
1387
  {
1388
    TransactionId debug_cutoff;
1389
    bool    debug_all_frozen;
1390
1391
    Assert(prstate.lpdead_items == 0);
1392
1393
    Assert(heap_page_is_all_visible(prstate.relation, prstate.buffer,
1394
                    prstate.vistest,
1395
                    &debug_all_frozen,
1396
                    &debug_cutoff, off_loc));
1397
1398
    Assert(!TransactionIdIsValid(debug_cutoff) ||
1399
         debug_cutoff == prstate.newest_live_xid);
1400
1401
    /*
1402
     * It's possible the page is composed entirely of frozen tuples but is
1403
     * not set all-frozen in the VM and did not pass
1404
     * HEAP_PAGE_PRUNE_FREEZE. In this case, it's possible
1405
     * heap_page_is_all_visible() finds the page completely frozen, even
1406
     * though prstate.set_all_frozen is false.
1407
     */
1408
    Assert(!prstate.set_all_frozen || debug_all_frozen);
1409
  }
1410
#endif
1411
1412
  /* Copy information back for caller */
1413
0
  presult->ndeleted = prstate.ndeleted;
1414
0
  presult->nnewlpdead = prstate.ndead;
1415
0
  presult->nfrozen = prstate.nfrozen;
1416
0
  presult->live_tuples = prstate.live_tuples;
1417
0
  presult->recently_dead_tuples = prstate.recently_dead_tuples;
1418
0
  presult->hastup = prstate.hastup;
1419
1420
0
  presult->lpdead_items = prstate.lpdead_items;
1421
  /* the presult->deadoffsets array was already filled in */
1422
1423
0
  presult->newly_all_visible = false;
1424
0
  presult->newly_all_frozen = false;
1425
0
  presult->newly_all_visible_frozen = false;
1426
0
  if (do_set_vm)
1427
0
  {
1428
0
    if ((prstate.old_vmbits & VISIBILITYMAP_ALL_VISIBLE) == 0)
1429
0
    {
1430
0
      presult->newly_all_visible = true;
1431
0
      if (prstate.set_all_frozen)
1432
0
        presult->newly_all_visible_frozen = true;
1433
0
    }
1434
0
    else if ((prstate.old_vmbits & VISIBILITYMAP_ALL_FROZEN) == 0 &&
1435
0
         prstate.set_all_frozen)
1436
0
      presult->newly_all_frozen = true;
1437
0
  }
1438
1439
0
  if (prstate.attempt_freeze)
1440
0
  {
1441
0
    if (presult->nfrozen > 0)
1442
0
    {
1443
0
      *new_relfrozen_xid = prstate.pagefrz.FreezePageRelfrozenXid;
1444
0
      *new_relmin_mxid = prstate.pagefrz.FreezePageRelminMxid;
1445
0
    }
1446
0
    else
1447
0
    {
1448
0
      *new_relfrozen_xid = prstate.pagefrz.NoFreezePageRelfrozenXid;
1449
0
      *new_relmin_mxid = prstate.pagefrz.NoFreezePageRelminMxid;
1450
0
    }
1451
0
  }
1452
0
}
1453
1454
1455
/*
1456
 * Perform visibility checks for heap pruning.
1457
 */
1458
static HTSV_Result
1459
heap_prune_satisfies_vacuum(PruneState *prstate, HeapTuple tup)
1460
0
{
1461
0
  HTSV_Result res;
1462
0
  TransactionId dead_after;
1463
1464
0
  res = HeapTupleSatisfiesVacuumHorizon(tup, prstate->buffer, &dead_after);
1465
1466
0
  if (res != HEAPTUPLE_RECENTLY_DEAD)
1467
0
    return res;
1468
1469
  /*
1470
   * For VACUUM, we must be sure to prune tuples with xmax older than
1471
   * OldestXmin -- a visibility cutoff determined at the beginning of
1472
   * vacuuming the relation. OldestXmin is used for freezing determination
1473
   * and we cannot freeze dead tuples' xmaxes.
1474
   */
1475
0
  if (prstate->cutoffs &&
1476
0
    TransactionIdIsValid(prstate->cutoffs->OldestXmin) &&
1477
0
    NormalTransactionIdPrecedes(dead_after, prstate->cutoffs->OldestXmin))
1478
0
    return HEAPTUPLE_DEAD;
1479
1480
  /*
1481
   * Determine whether or not the tuple is considered dead when compared
1482
   * with the provided GlobalVisState. On-access pruning does not provide
1483
   * VacuumCutoffs. And for vacuum, even if the tuple's xmax is not older
1484
   * than OldestXmin, GlobalVisTestIsRemovableXid() could find the row dead
1485
   * if the GlobalVisState has been updated since the beginning of vacuuming
1486
   * the relation.
1487
   */
1488
0
  if (GlobalVisTestIsRemovableXid(prstate->vistest, dead_after, true))
1489
0
    return HEAPTUPLE_DEAD;
1490
1491
0
  return res;
1492
0
}
1493
1494
1495
/*
1496
 * Pruning calculates tuple visibility once and saves the results in an array
1497
 * of int8.  See PruneState.htsv for details.  This helper function is meant
1498
 * to guard against examining visibility status array members which have not
1499
 * yet been computed.
1500
 */
1501
static inline HTSV_Result
1502
htsv_get_valid_status(int status)
1503
0
{
1504
0
  Assert(status >= HEAPTUPLE_DEAD &&
1505
0
       status <= HEAPTUPLE_DELETE_IN_PROGRESS);
1506
0
  return (HTSV_Result) status;
1507
0
}
1508
1509
/*
1510
 * Prune specified line pointer or a HOT chain originating at line pointer.
1511
 *
1512
 * Tuple visibility information is provided in prstate->htsv.
1513
 *
1514
 * If the item is an index-referenced tuple (i.e. not a heap-only tuple),
1515
 * the HOT chain is pruned by removing all DEAD tuples at the start of the HOT
1516
 * chain.  We also prune any RECENTLY_DEAD tuples preceding a DEAD tuple.
1517
 * This is OK because a RECENTLY_DEAD tuple preceding a DEAD tuple is really
1518
 * DEAD, our visibility test is just too coarse to detect it.
1519
 *
1520
 * Pruning must never leave behind a DEAD tuple that still has tuple storage.
1521
 * VACUUM isn't prepared to deal with that case.
1522
 *
1523
 * The root line pointer is redirected to the tuple immediately after the
1524
 * latest DEAD tuple.  If all tuples in the chain are DEAD, the root line
1525
 * pointer is marked LP_DEAD.  (This includes the case of a DEAD simple
1526
 * tuple, which we treat as a chain of length 1.)
1527
 *
1528
 * We don't actually change the page here. We just add entries to the arrays in
1529
 * prstate showing the changes to be made.  Items to be redirected are added
1530
 * to the redirected[] array (two entries per redirection); items to be set to
1531
 * LP_DEAD state are added to nowdead[]; and items to be set to LP_UNUSED
1532
 * state are added to nowunused[].  We perform bookkeeping of live tuples,
1533
 * visibility etc. based on what the page will look like after the changes
1534
 * applied.  All that bookkeeping is performed in the heap_prune_record_*()
1535
 * subroutines.  The division of labor is that heap_prune_chain() decides the
1536
 * fate of each tuple, ie. whether it's going to be removed, redirected or
1537
 * left unchanged, and the heap_prune_record_*() subroutines update PruneState
1538
 * based on that outcome.
1539
 */
1540
static void
1541
heap_prune_chain(OffsetNumber maxoff, OffsetNumber rootoffnum,
1542
         PruneState *prstate)
1543
0
{
1544
0
  TransactionId priorXmax = InvalidTransactionId;
1545
0
  ItemId    rootlp;
1546
0
  OffsetNumber offnum;
1547
0
  OffsetNumber chainitems[MaxHeapTuplesPerPage];
1548
0
  Page    page = prstate->page;
1549
1550
  /*
1551
   * After traversing the HOT chain, ndeadchain is the index in chainitems
1552
   * of the first live successor after the last dead item.
1553
   */
1554
0
  int     ndeadchain = 0,
1555
0
        nchain = 0;
1556
1557
0
  rootlp = PageGetItemId(page, rootoffnum);
1558
1559
  /* Start from the root tuple */
1560
0
  offnum = rootoffnum;
1561
1562
  /* while not end of the chain */
1563
0
  for (;;)
1564
0
  {
1565
0
    HeapTupleHeader htup;
1566
0
    ItemId    lp;
1567
1568
    /* Sanity check (pure paranoia) */
1569
0
    if (offnum < FirstOffsetNumber)
1570
0
      break;
1571
1572
    /*
1573
     * An offset past the end of page's line pointer array is possible
1574
     * when the array was truncated (original item must have been unused)
1575
     */
1576
0
    if (offnum > maxoff)
1577
0
      break;
1578
1579
    /* If item is already processed, stop --- it must not be same chain */
1580
0
    if (prstate->processed[offnum])
1581
0
      break;
1582
1583
0
    lp = PageGetItemId(page, offnum);
1584
1585
    /*
1586
     * Unused item obviously isn't part of the chain. Likewise, a dead
1587
     * line pointer can't be part of the chain.  Both of those cases were
1588
     * already marked as processed.
1589
     */
1590
0
    Assert(ItemIdIsUsed(lp));
1591
0
    Assert(!ItemIdIsDead(lp));
1592
1593
    /*
1594
     * If we are looking at the redirected root line pointer, jump to the
1595
     * first normal tuple in the chain.  If we find a redirect somewhere
1596
     * else, stop --- it must not be same chain.
1597
     */
1598
0
    if (ItemIdIsRedirected(lp))
1599
0
    {
1600
0
      if (nchain > 0)
1601
0
        break;     /* not at start of chain */
1602
0
      chainitems[nchain++] = offnum;
1603
0
      offnum = ItemIdGetRedirect(rootlp);
1604
0
      continue;
1605
0
    }
1606
1607
0
    Assert(ItemIdIsNormal(lp));
1608
1609
0
    htup = (HeapTupleHeader) PageGetItem(page, lp);
1610
1611
    /*
1612
     * Check the tuple XMIN against prior XMAX, if any
1613
     */
1614
0
    if (TransactionIdIsValid(priorXmax) &&
1615
0
      !TransactionIdEquals(HeapTupleHeaderGetXmin(htup), priorXmax))
1616
0
      break;
1617
1618
    /*
1619
     * OK, this tuple is indeed a member of the chain.
1620
     */
1621
0
    chainitems[nchain++] = offnum;
1622
1623
0
    switch (htsv_get_valid_status(prstate->htsv[offnum]))
1624
0
    {
1625
0
      case HEAPTUPLE_DEAD:
1626
1627
        /* Remember the last DEAD tuple seen */
1628
0
        ndeadchain = nchain;
1629
0
        HeapTupleHeaderAdvanceConflictHorizon(htup,
1630
0
                            &prstate->latest_xid_removed);
1631
        /* Advance to next chain member */
1632
0
        break;
1633
1634
0
      case HEAPTUPLE_RECENTLY_DEAD:
1635
1636
        /*
1637
         * We don't need to advance the conflict horizon for
1638
         * RECENTLY_DEAD tuples, even if we are removing them.  This
1639
         * is because we only remove RECENTLY_DEAD tuples if they
1640
         * precede a DEAD tuple, and the DEAD tuple must have been
1641
         * inserted by a newer transaction than the RECENTLY_DEAD
1642
         * tuple by virtue of being later in the chain.  We will have
1643
         * advanced the conflict horizon for the DEAD tuple.
1644
         */
1645
1646
        /*
1647
         * Advance past RECENTLY_DEAD tuples just in case there's a
1648
         * DEAD one after them.  We have to make sure that we don't
1649
         * miss any DEAD tuples, since DEAD tuples that still have
1650
         * tuple storage after pruning will confuse VACUUM.
1651
         */
1652
0
        break;
1653
1654
0
      case HEAPTUPLE_DELETE_IN_PROGRESS:
1655
0
      case HEAPTUPLE_LIVE:
1656
0
      case HEAPTUPLE_INSERT_IN_PROGRESS:
1657
0
        goto process_chain;
1658
1659
0
      default:
1660
0
        elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
1661
0
        goto process_chain;
1662
0
    }
1663
1664
    /*
1665
     * If the tuple is not HOT-updated, then we are at the end of this
1666
     * HOT-update chain.
1667
     */
1668
0
    if (!HeapTupleHeaderIsHotUpdated(htup))
1669
0
      goto process_chain;
1670
1671
    /* HOT implies it can't have moved to different partition */
1672
0
    Assert(!HeapTupleHeaderIndicatesMovedPartitions(htup));
1673
1674
    /*
1675
     * Advance to next chain member.
1676
     */
1677
0
    Assert(ItemPointerGetBlockNumber(&htup->t_ctid) == prstate->block);
1678
0
    offnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
1679
0
    priorXmax = HeapTupleHeaderGetUpdateXid(htup);
1680
0
  }
1681
1682
0
  if (ItemIdIsRedirected(rootlp) && nchain < 2)
1683
0
  {
1684
    /*
1685
     * We found a redirect item that doesn't point to a valid follow-on
1686
     * item.  This can happen if the loop in heap_page_prune_and_freeze()
1687
     * caused us to visit the dead successor of a redirect item before
1688
     * visiting the redirect item.  We can clean up by setting the
1689
     * redirect item to LP_DEAD state or LP_UNUSED if the caller
1690
     * indicated.
1691
     */
1692
0
    heap_prune_record_dead_or_unused(prstate, rootoffnum, false);
1693
0
    return;
1694
0
  }
1695
1696
0
process_chain:
1697
1698
0
  if (ndeadchain == 0)
1699
0
  {
1700
    /*
1701
     * No DEAD tuple was found, so the chain is entirely composed of
1702
     * normal, unchanged tuples.  Leave it alone.
1703
     */
1704
0
    int     i = 0;
1705
1706
0
    if (ItemIdIsRedirected(rootlp))
1707
0
    {
1708
0
      heap_prune_record_unchanged_lp_redirect(prstate, rootoffnum);
1709
0
      i++;
1710
0
    }
1711
0
    for (; i < nchain; i++)
1712
0
      heap_prune_record_unchanged_lp_normal(prstate, chainitems[i]);
1713
0
  }
1714
0
  else if (ndeadchain == nchain)
1715
0
  {
1716
    /*
1717
     * The entire chain is dead.  Mark the root line pointer LP_DEAD, and
1718
     * fully remove the other tuples in the chain.
1719
     */
1720
0
    heap_prune_record_dead_or_unused(prstate, rootoffnum, ItemIdIsNormal(rootlp));
1721
0
    for (int i = 1; i < nchain; i++)
1722
0
      heap_prune_record_unused(prstate, chainitems[i], true);
1723
0
  }
1724
0
  else
1725
0
  {
1726
    /*
1727
     * We found a DEAD tuple in the chain.  Redirect the root line pointer
1728
     * to the first non-DEAD tuple, and mark as unused each intermediate
1729
     * item that we are able to remove from the chain.
1730
     */
1731
0
    heap_prune_record_redirect(prstate, rootoffnum, chainitems[ndeadchain],
1732
0
                   ItemIdIsNormal(rootlp));
1733
0
    for (int i = 1; i < ndeadchain; i++)
1734
0
      heap_prune_record_unused(prstate, chainitems[i], true);
1735
1736
    /* the rest of tuples in the chain are normal, unchanged tuples */
1737
0
    for (int i = ndeadchain; i < nchain; i++)
1738
0
      heap_prune_record_unchanged_lp_normal(prstate, chainitems[i]);
1739
0
  }
1740
0
}
1741
1742
/* Record lowest soon-prunable XID */
1743
static void
1744
heap_prune_record_prunable(PruneState *prstate, TransactionId xid,
1745
               OffsetNumber offnum)
1746
0
{
1747
  /*
1748
   * This should exactly match the PageSetPrunable macro.  We can't store
1749
   * directly into the page header yet, so we update working state.
1750
   */
1751
0
  Assert(TransactionIdIsNormal(xid));
1752
0
  if (!TransactionIdIsValid(prstate->new_prune_xid) ||
1753
0
    TransactionIdPrecedes(xid, prstate->new_prune_xid))
1754
0
    prstate->new_prune_xid = xid;
1755
1756
  /*
1757
   * It's incorrect for a page to be marked all-visible if it contains
1758
   * prunable items.
1759
   */
1760
0
  if (PageIsAllVisible(prstate->page))
1761
0
    heap_page_fix_vm_corruption(prstate, offnum,
1762
0
                  VM_CORRUPT_TUPLE_VISIBILITY);
1763
0
}
1764
1765
/* Record line pointer to be redirected */
1766
static void
1767
heap_prune_record_redirect(PruneState *prstate,
1768
               OffsetNumber offnum, OffsetNumber rdoffnum,
1769
               bool was_normal)
1770
0
{
1771
0
  Assert(!prstate->processed[offnum]);
1772
0
  prstate->processed[offnum] = true;
1773
1774
  /*
1775
   * Do not mark the redirect target here.  It needs to be counted
1776
   * separately as an unchanged tuple.
1777
   */
1778
1779
0
  Assert(prstate->nredirected < MaxHeapTuplesPerPage);
1780
0
  prstate->redirected[prstate->nredirected * 2] = offnum;
1781
0
  prstate->redirected[prstate->nredirected * 2 + 1] = rdoffnum;
1782
1783
0
  prstate->nredirected++;
1784
1785
  /*
1786
   * If the root entry had been a normal tuple, we are deleting it, so count
1787
   * it in the result.  But changing a redirect (even to DEAD state) doesn't
1788
   * count.
1789
   */
1790
0
  if (was_normal)
1791
0
    prstate->ndeleted++;
1792
1793
0
  prstate->hastup = true;
1794
0
}
1795
1796
/* Record line pointer to be marked dead */
1797
static void
1798
heap_prune_record_dead(PruneState *prstate, OffsetNumber offnum,
1799
             bool was_normal)
1800
0
{
1801
0
  Assert(!prstate->processed[offnum]);
1802
0
  prstate->processed[offnum] = true;
1803
1804
0
  Assert(prstate->ndead < MaxHeapTuplesPerPage);
1805
0
  prstate->nowdead[prstate->ndead] = offnum;
1806
0
  prstate->ndead++;
1807
1808
  /*
1809
   * Deliberately delay unsetting set_all_visible and set_all_frozen until
1810
   * later during pruning. Removable dead tuples shouldn't preclude freezing
1811
   * the page.
1812
   */
1813
1814
  /* Record the dead offset for vacuum */
1815
0
  prstate->deadoffsets[prstate->lpdead_items++] = offnum;
1816
1817
  /*
1818
   * If the root entry had been a normal tuple, we are deleting it, so count
1819
   * it in the result.  But changing a redirect (even to DEAD state) doesn't
1820
   * count.
1821
   */
1822
0
  if (was_normal)
1823
0
    prstate->ndeleted++;
1824
0
}
1825
1826
/*
1827
 * Depending on whether or not the caller set mark_unused_now to true, record that a
1828
 * line pointer should be marked LP_DEAD or LP_UNUSED. There are other cases in
1829
 * which we will mark line pointers LP_UNUSED, but we will not mark line
1830
 * pointers LP_DEAD if mark_unused_now is true.
1831
 */
1832
static void
1833
heap_prune_record_dead_or_unused(PruneState *prstate, OffsetNumber offnum,
1834
                 bool was_normal)
1835
0
{
1836
  /*
1837
   * If the caller set mark_unused_now to true, we can remove dead tuples
1838
   * during pruning instead of marking their line pointers dead. Set this
1839
   * tuple's line pointer LP_UNUSED. We hint that this option is less
1840
   * likely.
1841
   */
1842
0
  if (unlikely(prstate->mark_unused_now))
1843
0
    heap_prune_record_unused(prstate, offnum, was_normal);
1844
0
  else
1845
0
    heap_prune_record_dead(prstate, offnum, was_normal);
1846
1847
  /*
1848
   * It's incorrect for the page to be set all-visible if it contains dead
1849
   * items. Fix that on the heap page and check the VM for corruption as
1850
   * well. Do that here rather than in heap_prune_record_dead() so we also
1851
   * cover tuples that are directly marked LP_UNUSED via mark_unused_now.
1852
   */
1853
0
  if (PageIsAllVisible(prstate->page))
1854
0
    heap_page_fix_vm_corruption(prstate, offnum, VM_CORRUPT_LPDEAD);
1855
0
}
1856
1857
/* Record line pointer to be marked unused */
1858
static void
1859
heap_prune_record_unused(PruneState *prstate, OffsetNumber offnum, bool was_normal)
1860
0
{
1861
0
  Assert(!prstate->processed[offnum]);
1862
0
  prstate->processed[offnum] = true;
1863
1864
0
  Assert(prstate->nunused < MaxHeapTuplesPerPage);
1865
0
  prstate->nowunused[prstate->nunused] = offnum;
1866
0
  prstate->nunused++;
1867
1868
  /*
1869
   * If the root entry had been a normal tuple, we are deleting it, so count
1870
   * it in the result.  But changing a redirect (even to DEAD state) doesn't
1871
   * count.
1872
   */
1873
0
  if (was_normal)
1874
0
    prstate->ndeleted++;
1875
0
}
1876
1877
/*
1878
 * Record an unused line pointer that is left unchanged.
1879
 */
1880
static void
1881
heap_prune_record_unchanged_lp_unused(PruneState *prstate, OffsetNumber offnum)
1882
0
{
1883
0
  Assert(!prstate->processed[offnum]);
1884
0
  prstate->processed[offnum] = true;
1885
0
}
1886
1887
/*
1888
 * Record line pointer that is left unchanged.  We consider freezing it, and
1889
 * update bookkeeping of tuple counts and page visibility.
1890
 */
1891
static void
1892
heap_prune_record_unchanged_lp_normal(PruneState *prstate, OffsetNumber offnum)
1893
0
{
1894
0
  HeapTupleHeader htup;
1895
0
  TransactionId xmin;
1896
0
  Page    page = prstate->page;
1897
1898
0
  Assert(!prstate->processed[offnum]);
1899
0
  prstate->processed[offnum] = true;
1900
1901
0
  prstate->hastup = true;   /* the page is not empty */
1902
1903
  /*
1904
   * The criteria for counting a tuple as live in this block need to match
1905
   * what analyze.c's acquire_sample_rows() does, otherwise VACUUM and
1906
   * ANALYZE may produce wildly different reltuples values, e.g. when there
1907
   * are many recently-dead tuples.
1908
   *
1909
   * The logic here is a bit simpler than acquire_sample_rows(), as VACUUM
1910
   * can't run inside a transaction block, which makes some cases impossible
1911
   * (e.g. in-progress insert from the same transaction).
1912
   *
1913
   * HEAPTUPLE_DEAD are handled by the other heap_prune_record_*()
1914
   * subroutines.  They don't count dead items like acquire_sample_rows()
1915
   * does, because we assume that all dead items will become LP_UNUSED
1916
   * before VACUUM finishes.  This difference is only superficial.  VACUUM
1917
   * effectively agrees with ANALYZE about DEAD items, in the end.  VACUUM
1918
   * won't remember LP_DEAD items, but only because they're not supposed to
1919
   * be left behind when it is done. (Cases where we bypass index vacuuming
1920
   * will violate this optimistic assumption, but the overall impact of that
1921
   * should be negligible.)
1922
   */
1923
0
  htup = (HeapTupleHeader) PageGetItem(page, PageGetItemId(page, offnum));
1924
1925
0
  switch (prstate->htsv[offnum])
1926
0
  {
1927
0
    case HEAPTUPLE_LIVE:
1928
1929
      /*
1930
       * Count it as live.  Not only is this natural, but it's also what
1931
       * acquire_sample_rows() does.
1932
       */
1933
0
      prstate->live_tuples++;
1934
1935
      /*
1936
       * Is the tuple definitely visible to all transactions?
1937
       *
1938
       * NB: Like with per-tuple hint bits, we can't set the
1939
       * PD_ALL_VISIBLE flag if the inserter committed asynchronously.
1940
       * See SetHintBits for more info.  Check that the tuple is hinted
1941
       * xmin-committed because of that.
1942
       */
1943
0
      if (!HeapTupleHeaderXminCommitted(htup))
1944
0
      {
1945
0
        prstate->set_all_visible = false;
1946
0
        prstate->set_all_frozen = false;
1947
0
        break;
1948
0
      }
1949
1950
      /*
1951
       * The inserter definitely committed. But we don't know if it is
1952
       * old enough that everyone sees it as committed. Later, after
1953
       * processing all the tuples on the page, we'll check if there is
1954
       * any snapshot that still considers the newest xid on the page to
1955
       * be running. If so, we don't consider the page all-visible.
1956
       */
1957
0
      xmin = HeapTupleHeaderGetXmin(htup);
1958
1959
      /* Track newest xmin on page. */
1960
0
      if (TransactionIdFollows(xmin, prstate->newest_live_xid) &&
1961
0
        TransactionIdIsNormal(xmin))
1962
0
        prstate->newest_live_xid = xmin;
1963
1964
0
      break;
1965
1966
0
    case HEAPTUPLE_RECENTLY_DEAD:
1967
0
      prstate->recently_dead_tuples++;
1968
0
      prstate->set_all_visible = false;
1969
0
      prstate->set_all_frozen = false;
1970
1971
      /*
1972
       * This tuple will soon become DEAD.  Update the hint field so
1973
       * that the page is reconsidered for pruning in future.
1974
       */
1975
0
      heap_prune_record_prunable(prstate,
1976
0
                     HeapTupleHeaderGetUpdateXid(htup),
1977
0
                     offnum);
1978
0
      break;
1979
1980
0
    case HEAPTUPLE_INSERT_IN_PROGRESS:
1981
1982
      /*
1983
       * We do not count these rows as live, because we expect the
1984
       * inserting transaction to update the counters at commit, and we
1985
       * assume that will happen only after we report our results.  This
1986
       * assumption is a bit shaky, but it is what acquire_sample_rows()
1987
       * does, so be consistent.
1988
       */
1989
0
      prstate->set_all_visible = false;
1990
0
      prstate->set_all_frozen = false;
1991
1992
      /*
1993
       * Though there is nothing "prunable" on the page, we maintain
1994
       * pd_prune_xid for inserts so that we have the opportunity to
1995
       * mark them all-visible during the next round of pruning.
1996
       */
1997
0
      heap_prune_record_prunable(prstate,
1998
0
                     HeapTupleHeaderGetXmin(htup),
1999
0
                     offnum);
2000
0
      break;
2001
2002
0
    case HEAPTUPLE_DELETE_IN_PROGRESS:
2003
2004
      /*
2005
       * This an expected case during concurrent vacuum.  Count such
2006
       * rows as live.  As above, we assume the deleting transaction
2007
       * will commit and update the counters after we report.
2008
       */
2009
0
      prstate->live_tuples++;
2010
0
      prstate->set_all_visible = false;
2011
0
      prstate->set_all_frozen = false;
2012
2013
      /*
2014
       * This tuple may soon become DEAD.  Update the hint field so that
2015
       * the page is reconsidered for pruning in future.
2016
       */
2017
0
      heap_prune_record_prunable(prstate,
2018
0
                     HeapTupleHeaderGetUpdateXid(htup),
2019
0
                     offnum);
2020
0
      break;
2021
2022
0
    default:
2023
2024
      /*
2025
       * DEAD tuples should've been passed to heap_prune_record_dead()
2026
       * or heap_prune_record_unused() instead.
2027
       */
2028
0
      elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result %d",
2029
0
         prstate->htsv[offnum]);
2030
0
      break;
2031
0
  }
2032
2033
  /* Consider freezing any normal tuples which will not be removed */
2034
0
  if (prstate->attempt_freeze)
2035
0
  {
2036
0
    bool    totally_frozen;
2037
2038
0
    if ((heap_prepare_freeze_tuple(htup,
2039
0
                     prstate->cutoffs,
2040
0
                     &prstate->pagefrz,
2041
0
                     &prstate->frozen[prstate->nfrozen],
2042
0
                     &totally_frozen)))
2043
0
    {
2044
      /* Save prepared freeze plan for later */
2045
0
      prstate->frozen[prstate->nfrozen++].offset = offnum;
2046
0
    }
2047
2048
    /*
2049
     * If any tuple isn't either totally frozen already or eligible to
2050
     * become totally frozen (according to its freeze plan), then the page
2051
     * definitely cannot be set all-frozen in the visibility map later on.
2052
     */
2053
0
    if (!totally_frozen)
2054
0
      prstate->set_all_frozen = false;
2055
0
  }
2056
0
}
2057
2058
2059
/*
2060
 * Record line pointer that was already LP_DEAD and is left unchanged.
2061
 */
2062
static void
2063
heap_prune_record_unchanged_lp_dead(PruneState *prstate, OffsetNumber offnum)
2064
0
{
2065
0
  Assert(!prstate->processed[offnum]);
2066
0
  prstate->processed[offnum] = true;
2067
2068
  /*
2069
   * Deliberately don't set hastup for LP_DEAD items.  We make the soft
2070
   * assumption that any LP_DEAD items encountered here will become
2071
   * LP_UNUSED later on, before count_nondeletable_pages is reached.  If we
2072
   * don't make this assumption then rel truncation will only happen every
2073
   * other VACUUM, at most.  Besides, VACUUM must treat
2074
   * hastup/nonempty_pages as provisional no matter how LP_DEAD items are
2075
   * handled (handled here, or handled later on).
2076
   *
2077
   * Similarly, don't unset set_all_visible and set_all_frozen until later,
2078
   * at the end of heap_page_prune_and_freeze().  This will allow us to
2079
   * attempt to freeze the page after pruning.  As long as we unset it
2080
   * before updating the visibility map, this will be correct.
2081
   */
2082
2083
  /* Record the dead offset for vacuum */
2084
0
  prstate->deadoffsets[prstate->lpdead_items++] = offnum;
2085
2086
  /*
2087
   * It's incorrect for a page to be marked all-visible if it contains dead
2088
   * items.
2089
   */
2090
0
  if (PageIsAllVisible(prstate->page))
2091
0
    heap_page_fix_vm_corruption(prstate, offnum, VM_CORRUPT_LPDEAD);
2092
0
}
2093
2094
/*
2095
 * Record LP_REDIRECT that is left unchanged.
2096
 */
2097
static void
2098
heap_prune_record_unchanged_lp_redirect(PruneState *prstate, OffsetNumber offnum)
2099
0
{
2100
  /*
2101
   * A redirect line pointer doesn't count as a live tuple.
2102
   *
2103
   * If we leave a redirect line pointer in place, there will be another
2104
   * tuple on the page that it points to.  We will do the bookkeeping for
2105
   * that separately.  So we have nothing to do here, except remember that
2106
   * we processed this item.
2107
   */
2108
0
  Assert(!prstate->processed[offnum]);
2109
0
  prstate->processed[offnum] = true;
2110
0
}
2111
2112
/*
2113
 * Perform the actual page changes needed by heap_page_prune_and_freeze().
2114
 *
2115
 * If 'lp_truncate_only' is set, we are merely marking LP_DEAD line pointers
2116
 * as unused, not redirecting or removing anything else.  The
2117
 * PageRepairFragmentation() call is skipped in that case.
2118
 *
2119
 * If 'lp_truncate_only' is not set, the caller must hold a cleanup lock on
2120
 * the buffer.  If it is set, an ordinary exclusive lock suffices.
2121
 */
2122
void
2123
heap_page_prune_execute(Buffer buffer, bool lp_truncate_only,
2124
            OffsetNumber *redirected, int nredirected,
2125
            OffsetNumber *nowdead, int ndead,
2126
            OffsetNumber *nowunused, int nunused)
2127
0
{
2128
0
  Page    page = BufferGetPage(buffer);
2129
0
  OffsetNumber *offnum;
2130
0
  HeapTupleHeader htup PG_USED_FOR_ASSERTS_ONLY;
2131
2132
  /* Shouldn't be called unless there's something to do */
2133
0
  Assert(nredirected > 0 || ndead > 0 || nunused > 0);
2134
2135
  /* If 'lp_truncate_only', we can only remove already-dead line pointers */
2136
0
  Assert(!lp_truncate_only || (nredirected == 0 && ndead == 0));
2137
2138
  /* Update all redirected line pointers */
2139
0
  offnum = redirected;
2140
0
  for (int i = 0; i < nredirected; i++)
2141
0
  {
2142
0
    OffsetNumber fromoff = *offnum++;
2143
0
    OffsetNumber tooff = *offnum++;
2144
0
    ItemId    fromlp = PageGetItemId(page, fromoff);
2145
0
    ItemId    tolp PG_USED_FOR_ASSERTS_ONLY;
2146
2147
#ifdef USE_ASSERT_CHECKING
2148
2149
    /*
2150
     * Any existing item that we set as an LP_REDIRECT (any 'from' item)
2151
     * must be the first item from a HOT chain.  If the item has tuple
2152
     * storage then it can't be a heap-only tuple.  Otherwise we are just
2153
     * maintaining an existing LP_REDIRECT from an existing HOT chain that
2154
     * has been pruned at least once before now.
2155
     */
2156
    if (!ItemIdIsRedirected(fromlp))
2157
    {
2158
      Assert(ItemIdHasStorage(fromlp) && ItemIdIsNormal(fromlp));
2159
2160
      htup = (HeapTupleHeader) PageGetItem(page, fromlp);
2161
      Assert(!HeapTupleHeaderIsHeapOnly(htup));
2162
    }
2163
    else
2164
    {
2165
      /* We shouldn't need to redundantly set the redirect */
2166
      Assert(ItemIdGetRedirect(fromlp) != tooff);
2167
    }
2168
2169
    /*
2170
     * The item that we're about to set as an LP_REDIRECT (the 'from'
2171
     * item) will point to an existing item (the 'to' item) that is
2172
     * already a heap-only tuple.  There can be at most one LP_REDIRECT
2173
     * item per HOT chain.
2174
     *
2175
     * We need to keep around an LP_REDIRECT item (after original
2176
     * non-heap-only root tuple gets pruned away) so that it's always
2177
     * possible for VACUUM to easily figure out what TID to delete from
2178
     * indexes when an entire HOT chain becomes dead.  A heap-only tuple
2179
     * can never become LP_DEAD; an LP_REDIRECT item or a regular heap
2180
     * tuple can.
2181
     *
2182
     * This check may miss problems, e.g. the target of a redirect could
2183
     * be marked as unused subsequently. The page_verify_redirects() check
2184
     * below will catch such problems.
2185
     */
2186
    tolp = PageGetItemId(page, tooff);
2187
    Assert(ItemIdHasStorage(tolp) && ItemIdIsNormal(tolp));
2188
    htup = (HeapTupleHeader) PageGetItem(page, tolp);
2189
    Assert(HeapTupleHeaderIsHeapOnly(htup));
2190
#endif
2191
2192
0
    ItemIdSetRedirect(fromlp, tooff);
2193
0
  }
2194
2195
  /* Update all now-dead line pointers */
2196
0
  offnum = nowdead;
2197
0
  for (int i = 0; i < ndead; i++)
2198
0
  {
2199
0
    OffsetNumber off = *offnum++;
2200
0
    ItemId    lp = PageGetItemId(page, off);
2201
2202
#ifdef USE_ASSERT_CHECKING
2203
2204
    /*
2205
     * An LP_DEAD line pointer must be left behind when the original item
2206
     * (which is dead to everybody) could still be referenced by a TID in
2207
     * an index.  This should never be necessary with any individual
2208
     * heap-only tuple item, though. (It's not clear how much of a problem
2209
     * that would be, but there is no reason to allow it.)
2210
     */
2211
    if (ItemIdHasStorage(lp))
2212
    {
2213
      Assert(ItemIdIsNormal(lp));
2214
      htup = (HeapTupleHeader) PageGetItem(page, lp);
2215
      Assert(!HeapTupleHeaderIsHeapOnly(htup));
2216
    }
2217
    else
2218
    {
2219
      /* Whole HOT chain becomes dead */
2220
      Assert(ItemIdIsRedirected(lp));
2221
    }
2222
#endif
2223
2224
0
    ItemIdSetDead(lp);
2225
0
  }
2226
2227
  /* Update all now-unused line pointers */
2228
0
  offnum = nowunused;
2229
0
  for (int i = 0; i < nunused; i++)
2230
0
  {
2231
0
    OffsetNumber off = *offnum++;
2232
0
    ItemId    lp = PageGetItemId(page, off);
2233
2234
#ifdef USE_ASSERT_CHECKING
2235
2236
    if (lp_truncate_only)
2237
    {
2238
      /* Setting LP_DEAD to LP_UNUSED in vacuum's second pass */
2239
      Assert(ItemIdIsDead(lp) && !ItemIdHasStorage(lp));
2240
    }
2241
    else
2242
    {
2243
      /*
2244
       * When heap_page_prune_and_freeze() was called, mark_unused_now
2245
       * may have been passed as true, which allows would-be LP_DEAD
2246
       * items to be made LP_UNUSED instead.  This is only possible if
2247
       * the relation has no indexes.  If there are any dead items, then
2248
       * mark_unused_now was not true and every item being marked
2249
       * LP_UNUSED must refer to a heap-only tuple.
2250
       */
2251
      if (ndead > 0)
2252
      {
2253
        Assert(ItemIdHasStorage(lp) && ItemIdIsNormal(lp));
2254
        htup = (HeapTupleHeader) PageGetItem(page, lp);
2255
        Assert(HeapTupleHeaderIsHeapOnly(htup));
2256
      }
2257
      else
2258
        Assert(ItemIdIsUsed(lp));
2259
    }
2260
2261
#endif
2262
2263
0
    ItemIdSetUnused(lp);
2264
0
  }
2265
2266
0
  if (lp_truncate_only)
2267
0
    PageTruncateLinePointerArray(page);
2268
0
  else
2269
0
  {
2270
    /*
2271
     * Finally, repair any fragmentation, and update the page's hint bit
2272
     * about whether it has free pointers.
2273
     */
2274
0
    PageRepairFragmentation(page);
2275
2276
    /*
2277
     * Now that the page has been modified, assert that redirect items
2278
     * still point to valid targets.
2279
     */
2280
0
    page_verify_redirects(page);
2281
0
  }
2282
0
}
2283
2284
2285
/*
2286
 * If built with assertions, verify that all LP_REDIRECT items point to a
2287
 * valid item.
2288
 *
2289
 * One way that bugs related to HOT pruning show is redirect items pointing to
2290
 * removed tuples. It's not trivial to reliably check that marking an item
2291
 * unused will not orphan a redirect item during heap_prune_chain() /
2292
 * heap_page_prune_execute(), so we additionally check the whole page after
2293
 * pruning. Without this check such bugs would typically only cause asserts
2294
 * later, potentially well after the corruption has been introduced.
2295
 *
2296
 * Also check comments in heap_page_prune_execute()'s redirection loop.
2297
 */
2298
static void
2299
page_verify_redirects(Page page)
2300
0
{
2301
#ifdef USE_ASSERT_CHECKING
2302
  OffsetNumber offnum;
2303
  OffsetNumber maxoff;
2304
2305
  maxoff = PageGetMaxOffsetNumber(page);
2306
  for (offnum = FirstOffsetNumber;
2307
     offnum <= maxoff;
2308
     offnum = OffsetNumberNext(offnum))
2309
  {
2310
    ItemId    itemid = PageGetItemId(page, offnum);
2311
    OffsetNumber targoff;
2312
    ItemId    targitem;
2313
    HeapTupleHeader htup;
2314
2315
    if (!ItemIdIsRedirected(itemid))
2316
      continue;
2317
2318
    targoff = ItemIdGetRedirect(itemid);
2319
    targitem = PageGetItemId(page, targoff);
2320
2321
    Assert(ItemIdIsUsed(targitem));
2322
    Assert(ItemIdIsNormal(targitem));
2323
    Assert(ItemIdHasStorage(targitem));
2324
    htup = (HeapTupleHeader) PageGetItem(page, targitem);
2325
    Assert(HeapTupleHeaderIsHeapOnly(htup));
2326
  }
2327
#endif
2328
0
}
2329
2330
2331
/*
2332
 * For all items in this page, find their respective root line pointers.
2333
 * If item k is part of a HOT-chain with root at item j, then we set
2334
 * root_offsets[k - 1] = j.
2335
 *
2336
 * The passed-in root_offsets array must have MaxHeapTuplesPerPage entries.
2337
 * Unused entries are filled with InvalidOffsetNumber (zero).
2338
 *
2339
 * The function must be called with at least share lock on the buffer, to
2340
 * prevent concurrent prune operations.
2341
 *
2342
 * Note: The information collected here is valid only as long as the caller
2343
 * holds a pin on the buffer. Once pin is released, a tuple might be pruned
2344
 * and reused by a completely unrelated tuple.
2345
 */
2346
void
2347
heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
2348
0
{
2349
0
  OffsetNumber offnum,
2350
0
        maxoff;
2351
2352
0
  MemSet(root_offsets, InvalidOffsetNumber,
2353
0
       MaxHeapTuplesPerPage * sizeof(OffsetNumber));
2354
2355
0
  maxoff = PageGetMaxOffsetNumber(page);
2356
0
  for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum))
2357
0
  {
2358
0
    ItemId    lp = PageGetItemId(page, offnum);
2359
0
    HeapTupleHeader htup;
2360
0
    OffsetNumber nextoffnum;
2361
0
    TransactionId priorXmax;
2362
2363
    /* skip unused and dead items */
2364
0
    if (!ItemIdIsUsed(lp) || ItemIdIsDead(lp))
2365
0
      continue;
2366
2367
0
    if (ItemIdIsNormal(lp))
2368
0
    {
2369
0
      htup = (HeapTupleHeader) PageGetItem(page, lp);
2370
2371
      /*
2372
       * Check if this tuple is part of a HOT-chain rooted at some other
2373
       * tuple. If so, skip it for now; we'll process it when we find
2374
       * its root.
2375
       */
2376
0
      if (HeapTupleHeaderIsHeapOnly(htup))
2377
0
        continue;
2378
2379
      /*
2380
       * This is either a plain tuple or the root of a HOT-chain.
2381
       * Remember it in the mapping.
2382
       */
2383
0
      root_offsets[offnum - 1] = offnum;
2384
2385
      /* If it's not the start of a HOT-chain, we're done with it */
2386
0
      if (!HeapTupleHeaderIsHotUpdated(htup))
2387
0
        continue;
2388
2389
      /* Set up to scan the HOT-chain */
2390
0
      nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
2391
0
      priorXmax = HeapTupleHeaderGetUpdateXid(htup);
2392
0
    }
2393
0
    else
2394
0
    {
2395
      /* Must be a redirect item. We do not set its root_offsets entry */
2396
0
      Assert(ItemIdIsRedirected(lp));
2397
      /* Set up to scan the HOT-chain */
2398
0
      nextoffnum = ItemIdGetRedirect(lp);
2399
0
      priorXmax = InvalidTransactionId;
2400
0
    }
2401
2402
    /*
2403
     * Now follow the HOT-chain and collect other tuples in the chain.
2404
     *
2405
     * Note: Even though this is a nested loop, the complexity of the
2406
     * function is O(N) because a tuple in the page should be visited not
2407
     * more than twice, once in the outer loop and once in HOT-chain
2408
     * chases.
2409
     */
2410
0
    for (;;)
2411
0
    {
2412
      /* Sanity check (pure paranoia) */
2413
0
      if (nextoffnum < FirstOffsetNumber)
2414
0
        break;
2415
2416
      /*
2417
       * An offset past the end of page's line pointer array is possible
2418
       * when the array was truncated
2419
       */
2420
0
      if (nextoffnum > maxoff)
2421
0
        break;
2422
2423
0
      lp = PageGetItemId(page, nextoffnum);
2424
2425
      /* Check for broken chains */
2426
0
      if (!ItemIdIsNormal(lp))
2427
0
        break;
2428
2429
0
      htup = (HeapTupleHeader) PageGetItem(page, lp);
2430
2431
0
      if (TransactionIdIsValid(priorXmax) &&
2432
0
        !TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(htup)))
2433
0
        break;
2434
2435
      /* Remember the root line pointer for this item */
2436
0
      root_offsets[nextoffnum - 1] = offnum;
2437
2438
      /* Advance to next chain member, if any */
2439
0
      if (!HeapTupleHeaderIsHotUpdated(htup))
2440
0
        break;
2441
2442
      /* HOT implies it can't have moved to different partition */
2443
0
      Assert(!HeapTupleHeaderIndicatesMovedPartitions(htup));
2444
2445
0
      nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
2446
0
      priorXmax = HeapTupleHeaderGetUpdateXid(htup);
2447
0
    }
2448
0
  }
2449
0
}
2450
2451
2452
/*
2453
 * Compare fields that describe actions required to freeze tuple with caller's
2454
 * open plan.  If everything matches then the frz tuple plan is equivalent to
2455
 * caller's plan.
2456
 */
2457
static inline bool
2458
heap_log_freeze_eq(xlhp_freeze_plan *plan, HeapTupleFreeze *frz)
2459
0
{
2460
0
  if (plan->xmax == frz->xmax &&
2461
0
    plan->t_infomask2 == frz->t_infomask2 &&
2462
0
    plan->t_infomask == frz->t_infomask &&
2463
0
    plan->frzflags == frz->frzflags)
2464
0
    return true;
2465
2466
  /* Caller must call heap_log_freeze_new_plan again for frz */
2467
0
  return false;
2468
0
}
2469
2470
/*
2471
 * Comparator used to deduplicate the freeze plans used in WAL records.
2472
 */
2473
static int
2474
heap_log_freeze_cmp(const void *arg1, const void *arg2)
2475
0
{
2476
0
  const HeapTupleFreeze *frz1 = arg1;
2477
0
  const HeapTupleFreeze *frz2 = arg2;
2478
2479
0
  if (frz1->xmax < frz2->xmax)
2480
0
    return -1;
2481
0
  else if (frz1->xmax > frz2->xmax)
2482
0
    return 1;
2483
2484
0
  if (frz1->t_infomask2 < frz2->t_infomask2)
2485
0
    return -1;
2486
0
  else if (frz1->t_infomask2 > frz2->t_infomask2)
2487
0
    return 1;
2488
2489
0
  if (frz1->t_infomask < frz2->t_infomask)
2490
0
    return -1;
2491
0
  else if (frz1->t_infomask > frz2->t_infomask)
2492
0
    return 1;
2493
2494
0
  if (frz1->frzflags < frz2->frzflags)
2495
0
    return -1;
2496
0
  else if (frz1->frzflags > frz2->frzflags)
2497
0
    return 1;
2498
2499
  /*
2500
   * heap_log_freeze_eq would consider these tuple-wise plans to be equal.
2501
   * (So the tuples will share a single canonical freeze plan.)
2502
   *
2503
   * We tiebreak on page offset number to keep each freeze plan's page
2504
   * offset number array individually sorted. (Unnecessary, but be tidy.)
2505
   */
2506
0
  if (frz1->offset < frz2->offset)
2507
0
    return -1;
2508
0
  else if (frz1->offset > frz2->offset)
2509
0
    return 1;
2510
2511
0
  Assert(false);
2512
0
  return 0;
2513
0
}
2514
2515
/*
2516
 * Start new plan initialized using tuple-level actions.  At least one tuple
2517
 * will have steps required to freeze described by caller's plan during REDO.
2518
 */
2519
static inline void
2520
heap_log_freeze_new_plan(xlhp_freeze_plan *plan, HeapTupleFreeze *frz)
2521
0
{
2522
0
  plan->xmax = frz->xmax;
2523
0
  plan->t_infomask2 = frz->t_infomask2;
2524
0
  plan->t_infomask = frz->t_infomask;
2525
0
  plan->frzflags = frz->frzflags;
2526
0
  plan->ntuples = 1;      /* for now */
2527
0
}
2528
2529
/*
2530
 * Deduplicate tuple-based freeze plans so that each distinct set of
2531
 * processing steps is only stored once in the WAL record.
2532
 * Called during original execution of freezing (for logged relations).
2533
 *
2534
 * Return value is number of plans set in *plans_out for caller.  Also writes
2535
 * an array of offset numbers into *offsets_out output argument for caller
2536
 * (actually there is one array per freeze plan, but that's not of immediate
2537
 * concern to our caller).
2538
 */
2539
static int
2540
heap_log_freeze_plan(HeapTupleFreeze *tuples, int ntuples,
2541
           xlhp_freeze_plan *plans_out,
2542
           OffsetNumber *offsets_out)
2543
0
{
2544
0
  int     nplans = 0;
2545
2546
  /* Sort tuple-based freeze plans in the order required to deduplicate */
2547
0
  qsort(tuples, ntuples, sizeof(HeapTupleFreeze), heap_log_freeze_cmp);
2548
2549
0
  for (int i = 0; i < ntuples; i++)
2550
0
  {
2551
0
    HeapTupleFreeze *frz = tuples + i;
2552
2553
0
    if (i == 0)
2554
0
    {
2555
      /* New canonical freeze plan starting with first tup */
2556
0
      heap_log_freeze_new_plan(plans_out, frz);
2557
0
      nplans++;
2558
0
    }
2559
0
    else if (heap_log_freeze_eq(plans_out, frz))
2560
0
    {
2561
      /* tup matches open canonical plan -- include tup in it */
2562
0
      Assert(offsets_out[i - 1] < frz->offset);
2563
0
      plans_out->ntuples++;
2564
0
    }
2565
0
    else
2566
0
    {
2567
      /* Tup doesn't match current plan -- done with it now */
2568
0
      plans_out++;
2569
2570
      /* New canonical freeze plan starting with this tup */
2571
0
      heap_log_freeze_new_plan(plans_out, frz);
2572
0
      nplans++;
2573
0
    }
2574
2575
    /*
2576
     * Save page offset number in dedicated buffer in passing.
2577
     *
2578
     * REDO routine relies on the record's offset numbers array grouping
2579
     * offset numbers by freeze plan.  The sort order within each grouping
2580
     * is ascending offset number order, just to keep things tidy.
2581
     */
2582
0
    offsets_out[i] = frz->offset;
2583
0
  }
2584
2585
0
  Assert(nplans > 0 && nplans <= ntuples);
2586
2587
0
  return nplans;
2588
0
}
2589
2590
/*
2591
 * Write an XLOG_HEAP2_PRUNE* WAL record
2592
 *
2593
 * This is used for several different page maintenance operations:
2594
 *
2595
 * - Page pruning, in VACUUM's 1st pass or on access: Some items are
2596
 *   redirected, some marked dead, and some removed altogether.
2597
 *
2598
 * - Freezing: Items are marked as 'frozen'.
2599
 *
2600
 * - Vacuum, 2nd pass: Items that are already LP_DEAD are marked as unused.
2601
 *
2602
 * They have enough commonalities that we use a single WAL record for them
2603
 * all.
2604
 *
2605
 * If replaying the record requires a cleanup lock, pass cleanup_lock = true.
2606
 * Replaying 'redirected' or 'dead' items always requires a cleanup lock, but
2607
 * replaying 'unused' items depends on whether they were all previously marked
2608
 * as dead.
2609
 *
2610
 * If the VM is being updated, vmflags will contain the bits to set. In this
2611
 * case, vmbuffer should already have been updated and marked dirty and should
2612
 * still be pinned and locked.
2613
 *
2614
 * Note: This function scribbles on the 'frozen' array.
2615
 *
2616
 * Note: This is called in a critical section, so careful what you do here.
2617
 */
2618
void
2619
log_heap_prune_and_freeze(Relation relation, Buffer buffer,
2620
              Buffer vmbuffer, uint8 vmflags,
2621
              TransactionId conflict_xid,
2622
              bool cleanup_lock,
2623
              PruneReason reason,
2624
              HeapTupleFreeze *frozen, int nfrozen,
2625
              OffsetNumber *redirected, int nredirected,
2626
              OffsetNumber *dead, int ndead,
2627
              OffsetNumber *unused, int nunused)
2628
0
{
2629
0
  xl_heap_prune xlrec;
2630
0
  XLogRecPtr  recptr;
2631
0
  uint8   info;
2632
0
  uint8   regbuf_flags_heap;
2633
2634
0
  Page    heap_page = BufferGetPage(buffer);
2635
2636
  /* The following local variables hold data registered in the WAL record: */
2637
0
  xlhp_freeze_plan plans[MaxHeapTuplesPerPage];
2638
0
  xlhp_freeze_plans freeze_plans;
2639
0
  xlhp_prune_items redirect_items;
2640
0
  xlhp_prune_items dead_items;
2641
0
  xlhp_prune_items unused_items;
2642
0
  OffsetNumber frz_offsets[MaxHeapTuplesPerPage];
2643
0
  bool    do_prune = nredirected > 0 || ndead > 0 || nunused > 0;
2644
0
  bool    do_set_vm = vmflags & VISIBILITYMAP_VALID_BITS;
2645
0
  bool    heap_fpi_allowed = true;
2646
2647
0
  Assert((vmflags & VISIBILITYMAP_VALID_BITS) == vmflags);
2648
2649
0
  xlrec.flags = 0;
2650
0
  regbuf_flags_heap = REGBUF_STANDARD;
2651
2652
  /*
2653
   * We can avoid an FPI of the heap page if the only modification we are
2654
   * making to it is to set PD_ALL_VISIBLE and checksums/wal_log_hints are
2655
   * disabled.
2656
   *
2657
   * However, if the page has never been WAL-logged (LSN is invalid), we
2658
   * must force an FPI regardless.  This can happen when another backend
2659
   * extends the heap, initializes the page, and then fails before WAL-
2660
   * logging it.  Since heap extension is not WAL-logged, recovery might try
2661
   * to replay our record and find that the page isn't initialized, which
2662
   * would cause a PANIC.
2663
   */
2664
0
  if (!XLogRecPtrIsValid(PageGetLSN(heap_page)))
2665
0
    regbuf_flags_heap |= REGBUF_FORCE_IMAGE;
2666
0
  else if (!do_prune && nfrozen == 0 && (!do_set_vm || !XLogHintBitIsNeeded()))
2667
0
  {
2668
0
    regbuf_flags_heap |= REGBUF_NO_IMAGE;
2669
0
    heap_fpi_allowed = false;
2670
0
  }
2671
2672
  /*
2673
   * Prepare data for the buffer.  The arrays are not actually in the
2674
   * buffer, but we pretend that they are.  When XLogInsert stores a full
2675
   * page image, the arrays can be omitted.
2676
   */
2677
0
  XLogBeginInsert();
2678
0
  XLogRegisterBuffer(0, buffer, regbuf_flags_heap);
2679
2680
0
  if (do_set_vm)
2681
0
    XLogRegisterBuffer(1, vmbuffer, 0);
2682
2683
0
  if (nfrozen > 0)
2684
0
  {
2685
0
    int     nplans;
2686
2687
0
    xlrec.flags |= XLHP_HAS_FREEZE_PLANS;
2688
2689
    /*
2690
     * Prepare deduplicated representation for use in the WAL record. This
2691
     * destructively sorts frozen tuples array in-place.
2692
     */
2693
0
    nplans = heap_log_freeze_plan(frozen, nfrozen, plans, frz_offsets);
2694
2695
0
    freeze_plans.nplans = nplans;
2696
0
    XLogRegisterBufData(0, &freeze_plans,
2697
0
              offsetof(xlhp_freeze_plans, plans));
2698
0
    XLogRegisterBufData(0, plans,
2699
0
              sizeof(xlhp_freeze_plan) * nplans);
2700
0
  }
2701
0
  if (nredirected > 0)
2702
0
  {
2703
0
    xlrec.flags |= XLHP_HAS_REDIRECTIONS;
2704
2705
0
    redirect_items.ntargets = nredirected;
2706
0
    XLogRegisterBufData(0, &redirect_items,
2707
0
              offsetof(xlhp_prune_items, data));
2708
0
    XLogRegisterBufData(0, redirected,
2709
0
              sizeof(OffsetNumber[2]) * nredirected);
2710
0
  }
2711
0
  if (ndead > 0)
2712
0
  {
2713
0
    xlrec.flags |= XLHP_HAS_DEAD_ITEMS;
2714
2715
0
    dead_items.ntargets = ndead;
2716
0
    XLogRegisterBufData(0, &dead_items,
2717
0
              offsetof(xlhp_prune_items, data));
2718
0
    XLogRegisterBufData(0, dead,
2719
0
              sizeof(OffsetNumber) * ndead);
2720
0
  }
2721
0
  if (nunused > 0)
2722
0
  {
2723
0
    xlrec.flags |= XLHP_HAS_NOW_UNUSED_ITEMS;
2724
2725
0
    unused_items.ntargets = nunused;
2726
0
    XLogRegisterBufData(0, &unused_items,
2727
0
              offsetof(xlhp_prune_items, data));
2728
0
    XLogRegisterBufData(0, unused,
2729
0
              sizeof(OffsetNumber) * nunused);
2730
0
  }
2731
0
  if (nfrozen > 0)
2732
0
    XLogRegisterBufData(0, frz_offsets,
2733
0
              sizeof(OffsetNumber) * nfrozen);
2734
2735
  /*
2736
   * Prepare the main xl_heap_prune record.  We already set the XLHP_HAS_*
2737
   * flag above.
2738
   */
2739
0
  if (vmflags & VISIBILITYMAP_ALL_VISIBLE)
2740
0
  {
2741
0
    xlrec.flags |= XLHP_VM_ALL_VISIBLE;
2742
0
    if (vmflags & VISIBILITYMAP_ALL_FROZEN)
2743
0
      xlrec.flags |= XLHP_VM_ALL_FROZEN;
2744
0
  }
2745
0
  if (RelationIsAccessibleInLogicalDecoding(relation))
2746
0
    xlrec.flags |= XLHP_IS_CATALOG_REL;
2747
0
  if (TransactionIdIsValid(conflict_xid))
2748
0
    xlrec.flags |= XLHP_HAS_CONFLICT_HORIZON;
2749
0
  if (cleanup_lock)
2750
0
    xlrec.flags |= XLHP_CLEANUP_LOCK;
2751
0
  else
2752
0
  {
2753
0
    Assert(nredirected == 0 && ndead == 0);
2754
    /* also, any items in 'unused' must've been LP_DEAD previously */
2755
0
  }
2756
0
  XLogRegisterData(&xlrec, SizeOfHeapPrune);
2757
0
  if (TransactionIdIsValid(conflict_xid))
2758
0
    XLogRegisterData(&conflict_xid, sizeof(TransactionId));
2759
2760
0
  switch (reason)
2761
0
  {
2762
0
    case PRUNE_ON_ACCESS:
2763
0
      info = XLOG_HEAP2_PRUNE_ON_ACCESS;
2764
0
      break;
2765
0
    case PRUNE_VACUUM_SCAN:
2766
0
      info = XLOG_HEAP2_PRUNE_VACUUM_SCAN;
2767
0
      break;
2768
0
    case PRUNE_VACUUM_CLEANUP:
2769
0
      info = XLOG_HEAP2_PRUNE_VACUUM_CLEANUP;
2770
0
      break;
2771
0
    default:
2772
0
      elog(ERROR, "unrecognized prune reason: %d", (int) reason);
2773
0
      break;
2774
0
  }
2775
0
  recptr = XLogInsert(RM_HEAP2_ID, info);
2776
2777
0
  if (do_set_vm)
2778
0
  {
2779
0
    Assert(BufferIsDirty(vmbuffer));
2780
0
    PageSetLSN(BufferGetPage(vmbuffer), recptr);
2781
0
  }
2782
2783
  /*
2784
   * If we explicitly skip an FPI, we must not stamp the heap page with this
2785
   * record's LSN. Recovery skips records <= the stamped LSN, so this could
2786
   * lead to skipping an earlier FPI needed to repair a torn page.
2787
   */
2788
0
  if (heap_fpi_allowed)
2789
0
  {
2790
    Assert(BufferIsDirty(buffer));
2791
0
    PageSetLSN(heap_page, recptr);
2792
0
  }
2793
0
}