PostgreSQL Source Code  git master
vacuumlazy.c File Reference
#include "postgres.h"
#include <math.h>
#include "access/amapi.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/heapam_xlog.h"
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/transam.h"
#include "access/visibilitymap.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
#include "catalog/index.h"
#include "catalog/storage.h"
#include "commands/dbcommands.h"
#include "commands/progress.h"
#include "commands/vacuum.h"
#include "executor/instrument.h"
#include "miscadmin.h"
#include "optimizer/paths.h"
#include "pgstat.h"
#include "portability/instr_time.h"
#include "postmaster/autovacuum.h"
#include "storage/bufmgr.h"
#include "storage/freespace.h"
#include "storage/lmgr.h"
#include "tcop/tcopprot.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/pg_rusage.h"
#include "utils/timestamp.h"
Include dependency graph for vacuumlazy.c:

Go to the source code of this file.

Data Structures

struct  LVRelState
 
struct  LVPagePruneState
 
struct  LVSavedErrInfo
 

Macros

#define REL_TRUNCATE_MINIMUM   1000
 
#define REL_TRUNCATE_FRACTION   16
 
#define VACUUM_TRUNCATE_LOCK_CHECK_INTERVAL   20 /* ms */
 
#define VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL   50 /* ms */
 
#define VACUUM_TRUNCATE_LOCK_TIMEOUT   5000 /* ms */
 
#define BYPASS_THRESHOLD_PAGES   0.02 /* i.e. 2% of rel_pages */
 
#define FAILSAFE_EVERY_PAGES    ((BlockNumber) (((uint64) 4 * 1024 * 1024 * 1024) / BLCKSZ))
 
#define VACUUM_FSM_EVERY_PAGES    ((BlockNumber) (((uint64) 8 * 1024 * 1024 * 1024) / BLCKSZ))
 
#define SKIP_PAGES_THRESHOLD   ((BlockNumber) 32)
 
#define PREFETCH_SIZE   ((BlockNumber) 32)
 
#define ParallelVacuumIsActive(vacrel)   ((vacrel)->pvs != NULL)
 

Typedefs

typedef struct LVRelState LVRelState
 
typedef struct LVPagePruneState LVPagePruneState
 
typedef struct LVSavedErrInfo LVSavedErrInfo
 

Enumerations

enum  VacErrPhase {
  VACUUM_ERRCB_PHASE_UNKNOWN , VACUUM_ERRCB_PHASE_SCAN_HEAP , VACUUM_ERRCB_PHASE_VACUUM_INDEX , VACUUM_ERRCB_PHASE_VACUUM_HEAP ,
  VACUUM_ERRCB_PHASE_INDEX_CLEANUP , VACUUM_ERRCB_PHASE_TRUNCATE
}
 

Functions

static void lazy_scan_heap (LVRelState *vacrel)
 
static BlockNumber lazy_scan_skip (LVRelState *vacrel, Buffer *vmbuffer, BlockNumber next_block, bool *next_unskippable_allvis, bool *skipping_current_range)
 
static bool lazy_scan_new_or_empty (LVRelState *vacrel, Buffer buf, BlockNumber blkno, Page page, bool sharelock, Buffer vmbuffer)
 
static void lazy_scan_prune (LVRelState *vacrel, Buffer buf, BlockNumber blkno, Page page, LVPagePruneState *prunestate)
 
static bool lazy_scan_noprune (LVRelState *vacrel, Buffer buf, BlockNumber blkno, Page page, bool *hastup, bool *recordfreespace)
 
static void lazy_vacuum (LVRelState *vacrel)
 
static bool lazy_vacuum_all_indexes (LVRelState *vacrel)
 
static void lazy_vacuum_heap_rel (LVRelState *vacrel)
 
static int lazy_vacuum_heap_page (LVRelState *vacrel, BlockNumber blkno, Buffer buffer, int index, Buffer *vmbuffer)
 
static bool lazy_check_wraparound_failsafe (LVRelState *vacrel)
 
static void lazy_cleanup_all_indexes (LVRelState *vacrel)
 
static IndexBulkDeleteResultlazy_vacuum_one_index (Relation indrel, IndexBulkDeleteResult *istat, double reltuples, LVRelState *vacrel)
 
static IndexBulkDeleteResultlazy_cleanup_one_index (Relation indrel, IndexBulkDeleteResult *istat, double reltuples, bool estimated_count, LVRelState *vacrel)
 
static bool should_attempt_truncation (LVRelState *vacrel)
 
static void lazy_truncate_heap (LVRelState *vacrel)
 
static BlockNumber count_nondeletable_pages (LVRelState *vacrel, bool *lock_waiter_detected)
 
static void dead_items_alloc (LVRelState *vacrel, int nworkers)
 
static void dead_items_cleanup (LVRelState *vacrel)
 
static bool heap_page_is_all_visible (LVRelState *vacrel, Buffer buf, TransactionId *visibility_cutoff_xid, bool *all_frozen)
 
static void update_relstats_all_indexes (LVRelState *vacrel)
 
static void vacuum_error_callback (void *arg)
 
static void update_vacuum_error_info (LVRelState *vacrel, LVSavedErrInfo *saved_vacrel, int phase, BlockNumber blkno, OffsetNumber offnum)
 
static void restore_vacuum_error_info (LVRelState *vacrel, const LVSavedErrInfo *saved_vacrel)
 
void heap_vacuum_rel (Relation rel, VacuumParams *params, BufferAccessStrategy bstrategy)
 
static int dead_items_max_items (LVRelState *vacrel)
 

Macro Definition Documentation

◆ BYPASS_THRESHOLD_PAGES

#define BYPASS_THRESHOLD_PAGES   0.02 /* i.e. 2% of rel_pages */

Definition at line 94 of file vacuumlazy.c.

◆ FAILSAFE_EVERY_PAGES

#define FAILSAFE_EVERY_PAGES    ((BlockNumber) (((uint64) 4 * 1024 * 1024 * 1024) / BLCKSZ))

Definition at line 99 of file vacuumlazy.c.

◆ ParallelVacuumIsActive

#define ParallelVacuumIsActive (   vacrel)    ((vacrel)->pvs != NULL)

Definition at line 127 of file vacuumlazy.c.

◆ PREFETCH_SIZE

#define PREFETCH_SIZE   ((BlockNumber) 32)

Definition at line 121 of file vacuumlazy.c.

◆ REL_TRUNCATE_FRACTION

#define REL_TRUNCATE_FRACTION   16

Definition at line 77 of file vacuumlazy.c.

◆ REL_TRUNCATE_MINIMUM

#define REL_TRUNCATE_MINIMUM   1000

Definition at line 76 of file vacuumlazy.c.

◆ SKIP_PAGES_THRESHOLD

#define SKIP_PAGES_THRESHOLD   ((BlockNumber) 32)

Definition at line 115 of file vacuumlazy.c.

◆ VACUUM_FSM_EVERY_PAGES

#define VACUUM_FSM_EVERY_PAGES    ((BlockNumber) (((uint64) 8 * 1024 * 1024 * 1024) / BLCKSZ))

Definition at line 108 of file vacuumlazy.c.

◆ VACUUM_TRUNCATE_LOCK_CHECK_INTERVAL

#define VACUUM_TRUNCATE_LOCK_CHECK_INTERVAL   20 /* ms */

Definition at line 86 of file vacuumlazy.c.

◆ VACUUM_TRUNCATE_LOCK_TIMEOUT

#define VACUUM_TRUNCATE_LOCK_TIMEOUT   5000 /* ms */

Definition at line 88 of file vacuumlazy.c.

◆ VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL

#define VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL   50 /* ms */

Definition at line 87 of file vacuumlazy.c.

Typedef Documentation

◆ LVPagePruneState

◆ LVRelState

typedef struct LVRelState LVRelState

◆ LVSavedErrInfo

Enumeration Type Documentation

◆ VacErrPhase

Enumerator
VACUUM_ERRCB_PHASE_UNKNOWN 
VACUUM_ERRCB_PHASE_SCAN_HEAP 
VACUUM_ERRCB_PHASE_VACUUM_INDEX 
VACUUM_ERRCB_PHASE_VACUUM_HEAP 
VACUUM_ERRCB_PHASE_INDEX_CLEANUP 
VACUUM_ERRCB_PHASE_TRUNCATE 

Definition at line 130 of file vacuumlazy.c.

131 {
138 } VacErrPhase;
VacErrPhase
Definition: vacuumlazy.c:131
@ VACUUM_ERRCB_PHASE_SCAN_HEAP
Definition: vacuumlazy.c:133
@ VACUUM_ERRCB_PHASE_VACUUM_INDEX
Definition: vacuumlazy.c:134
@ VACUUM_ERRCB_PHASE_TRUNCATE
Definition: vacuumlazy.c:137
@ VACUUM_ERRCB_PHASE_INDEX_CLEANUP
Definition: vacuumlazy.c:136
@ VACUUM_ERRCB_PHASE_VACUUM_HEAP
Definition: vacuumlazy.c:135
@ VACUUM_ERRCB_PHASE_UNKNOWN
Definition: vacuumlazy.c:132

Function Documentation

◆ count_nondeletable_pages()

static BlockNumber count_nondeletable_pages ( LVRelState vacrel,
bool lock_waiter_detected 
)
static

Definition at line 2961 of file vacuumlazy.c.

2962 {
2963  BlockNumber blkno;
2964  BlockNumber prefetchedUntil;
2965  instr_time starttime;
2966 
2967  /* Initialize the starttime if we check for conflicting lock requests */
2968  INSTR_TIME_SET_CURRENT(starttime);
2969 
2970  /*
2971  * Start checking blocks at what we believe relation end to be and move
2972  * backwards. (Strange coding of loop control is needed because blkno is
2973  * unsigned.) To make the scan faster, we prefetch a few blocks at a time
2974  * in forward direction, so that OS-level readahead can kick in.
2975  */
2976  blkno = vacrel->rel_pages;
2978  "prefetch size must be power of 2");
2979  prefetchedUntil = InvalidBlockNumber;
2980  while (blkno > vacrel->nonempty_pages)
2981  {
2982  Buffer buf;
2983  Page page;
2984  OffsetNumber offnum,
2985  maxoff;
2986  bool hastup;
2987 
2988  /*
2989  * Check if another process requests a lock on our relation. We are
2990  * holding an AccessExclusiveLock here, so they will be waiting. We
2991  * only do this once per VACUUM_TRUNCATE_LOCK_CHECK_INTERVAL, and we
2992  * only check if that interval has elapsed once every 32 blocks to
2993  * keep the number of system calls and actual shared lock table
2994  * lookups to a minimum.
2995  */
2996  if ((blkno % 32) == 0)
2997  {
2998  instr_time currenttime;
2999  instr_time elapsed;
3000 
3001  INSTR_TIME_SET_CURRENT(currenttime);
3002  elapsed = currenttime;
3003  INSTR_TIME_SUBTRACT(elapsed, starttime);
3004  if ((INSTR_TIME_GET_MICROSEC(elapsed) / 1000)
3006  {
3008  {
3009  ereport(vacrel->verbose ? INFO : DEBUG2,
3010  (errmsg("table \"%s\": suspending truncate due to conflicting lock request",
3011  vacrel->relname)));
3012 
3013  *lock_waiter_detected = true;
3014  return blkno;
3015  }
3016  starttime = currenttime;
3017  }
3018  }
3019 
3020  /*
3021  * We don't insert a vacuum delay point here, because we have an
3022  * exclusive lock on the table which we want to hold for as short a
3023  * time as possible. We still need to check for interrupts however.
3024  */
3026 
3027  blkno--;
3028 
3029  /* If we haven't prefetched this lot yet, do so now. */
3030  if (prefetchedUntil > blkno)
3031  {
3032  BlockNumber prefetchStart;
3033  BlockNumber pblkno;
3034 
3035  prefetchStart = blkno & ~(PREFETCH_SIZE - 1);
3036  for (pblkno = prefetchStart; pblkno <= blkno; pblkno++)
3037  {
3038  PrefetchBuffer(vacrel->rel, MAIN_FORKNUM, pblkno);
3040  }
3041  prefetchedUntil = prefetchStart;
3042  }
3043 
3044  buf = ReadBufferExtended(vacrel->rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
3045  vacrel->bstrategy);
3046 
3047  /* In this phase we only need shared access to the buffer */
3049 
3050  page = BufferGetPage(buf);
3051 
3052  if (PageIsNew(page) || PageIsEmpty(page))
3053  {
3055  continue;
3056  }
3057 
3058  hastup = false;
3059  maxoff = PageGetMaxOffsetNumber(page);
3060  for (offnum = FirstOffsetNumber;
3061  offnum <= maxoff;
3062  offnum = OffsetNumberNext(offnum))
3063  {
3064  ItemId itemid;
3065 
3066  itemid = PageGetItemId(page, offnum);
3067 
3068  /*
3069  * Note: any non-unused item should be taken as a reason to keep
3070  * this page. Even an LP_DEAD item makes truncation unsafe, since
3071  * we must not have cleaned out its index entries.
3072  */
3073  if (ItemIdIsUsed(itemid))
3074  {
3075  hastup = true;
3076  break; /* can stop scanning */
3077  }
3078  } /* scan along page */
3079 
3081 
3082  /* Done scanning if we found a tuple here */
3083  if (hastup)
3084  return blkno + 1;
3085  }
3086 
3087  /*
3088  * If we fall out of the loop, all the previously-thought-to-be-empty
3089  * pages still are; we need not bother to look at the last known-nonempty
3090  * page.
3091  */
3092  return vacrel->nonempty_pages;
3093 }
uint32 BlockNumber
Definition: block.h:31
#define InvalidBlockNumber
Definition: block.h:33
int Buffer
Definition: buf.h:23
PrefetchBufferResult PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum)
Definition: bufmgr.c:592
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3938
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:4156
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:749
#define BUFFER_LOCK_SHARE
Definition: bufmgr.h:97
@ RBM_NORMAL
Definition: bufmgr.h:39
#define BufferGetPage(buffer)
Definition: bufmgr.h:169
Pointer Page
Definition: bufpage.h:78
#define PageGetMaxOffsetNumber(page)
Definition: bufpage.h:356
#define PageGetItemId(page, offsetNumber)
Definition: bufpage.h:234
#define PageIsEmpty(page)
Definition: bufpage.h:221
#define PageIsNew(page)
Definition: bufpage.h:228
#define StaticAssertStmt(condition, errmessage)
Definition: c.h:929
int errmsg(const char *fmt,...)
Definition: elog.c:904
#define DEBUG2
Definition: elog.h:23
#define INFO
Definition: elog.h:28
#define ereport(elevel,...)
Definition: elog.h:143
#define INSTR_TIME_SET_CURRENT(t)
Definition: instr_time.h:156
#define INSTR_TIME_SUBTRACT(x, y)
Definition: instr_time.h:170
struct timeval instr_time
Definition: instr_time.h:150
#define INSTR_TIME_GET_MICROSEC(t)
Definition: instr_time.h:205
#define ItemIdIsUsed(itemId)
Definition: itemid.h:92
bool LockHasWaitersRelation(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:374
#define AccessExclusiveLock
Definition: lockdefs.h:43
#define CHECK_FOR_INTERRUPTS()
Definition: miscadmin.h:121
#define OffsetNumberNext(offsetNumber)
Definition: off.h:52
uint16 OffsetNumber
Definition: off.h:24
#define FirstOffsetNumber
Definition: off.h:27
static char * buf
Definition: pg_test_fsync.c:67
@ MAIN_FORKNUM
Definition: relpath.h:43
bool verbose
Definition: vacuumlazy.c:188
BlockNumber nonempty_pages
Definition: vacuumlazy.c:203
Relation rel
Definition: vacuumlazy.c:143
BlockNumber rel_pages
Definition: vacuumlazy.c:198
BufferAccessStrategy bstrategy
Definition: vacuumlazy.c:162
char * relname
Definition: vacuumlazy.c:183
#define PREFETCH_SIZE
Definition: vacuumlazy.c:121
#define VACUUM_TRUNCATE_LOCK_CHECK_INTERVAL
Definition: vacuumlazy.c:86

References AccessExclusiveLock, LVRelState::bstrategy, buf, BUFFER_LOCK_SHARE, BufferGetPage, CHECK_FOR_INTERRUPTS, DEBUG2, ereport, errmsg(), FirstOffsetNumber, INFO, INSTR_TIME_GET_MICROSEC, INSTR_TIME_SET_CURRENT, INSTR_TIME_SUBTRACT, InvalidBlockNumber, ItemIdIsUsed, LockBuffer(), LockHasWaitersRelation(), MAIN_FORKNUM, LVRelState::nonempty_pages, OffsetNumberNext, PageGetItemId, PageGetMaxOffsetNumber, PageIsEmpty, PageIsNew, PREFETCH_SIZE, PrefetchBuffer(), RBM_NORMAL, ReadBufferExtended(), LVRelState::rel, LVRelState::rel_pages, LVRelState::relname, StaticAssertStmt, UnlockReleaseBuffer(), VACUUM_TRUNCATE_LOCK_CHECK_INTERVAL, and LVRelState::verbose.

Referenced by lazy_truncate_heap().

◆ dead_items_alloc()

static void dead_items_alloc ( LVRelState vacrel,
int  nworkers 
)
static

Definition at line 3143 of file vacuumlazy.c.

3144 {
3145  VacDeadItems *dead_items;
3146  int max_items;
3147 
3148  max_items = dead_items_max_items(vacrel);
3149  Assert(max_items >= MaxHeapTuplesPerPage);
3150 
3151  /*
3152  * Initialize state for a parallel vacuum. As of now, only one worker can
3153  * be used for an index, so we invoke parallelism only if there are at
3154  * least two indexes on a table.
3155  */
3156  if (nworkers >= 0 && vacrel->nindexes > 1 && vacrel->do_index_vacuuming)
3157  {
3158  /*
3159  * Since parallel workers cannot access data in temporary tables, we
3160  * can't perform parallel vacuum on them.
3161  */
3162  if (RelationUsesLocalBuffers(vacrel->rel))
3163  {
3164  /*
3165  * Give warning only if the user explicitly tries to perform a
3166  * parallel vacuum on the temporary table.
3167  */
3168  if (nworkers > 0)
3169  ereport(WARNING,
3170  (errmsg("disabling parallel option of vacuum on \"%s\" --- cannot vacuum temporary tables in parallel",
3171  vacrel->relname)));
3172  }
3173  else
3174  vacrel->pvs = parallel_vacuum_init(vacrel->rel, vacrel->indrels,
3175  vacrel->nindexes, nworkers,
3176  max_items,
3177  vacrel->verbose ? INFO : DEBUG2,
3178  vacrel->bstrategy);
3179 
3180  /* If parallel mode started, dead_items space is allocated in DSM */
3181  if (ParallelVacuumIsActive(vacrel))
3182  {
3183  vacrel->dead_items = parallel_vacuum_get_dead_items(vacrel->pvs);
3184  return;
3185  }
3186  }
3187 
3188  /* Serial VACUUM case */
3189  dead_items = (VacDeadItems *) palloc(vac_max_items_to_alloc_size(max_items));
3190  dead_items->max_items = max_items;
3191  dead_items->num_items = 0;
3192 
3193  vacrel->dead_items = dead_items;
3194 }
#define WARNING
Definition: elog.h:30
#define MaxHeapTuplesPerPage
Definition: htup_details.h:568
Assert(fmt[strlen(fmt) - 1] !='\n')
void * palloc(Size size)
Definition: mcxt.c:1068
#define RelationUsesLocalBuffers(relation)
Definition: rel.h:622
ParallelVacuumState * pvs
Definition: vacuumlazy.c:163
int nindexes
Definition: vacuumlazy.c:145
Relation * indrels
Definition: vacuumlazy.c:144
VacDeadItems * dead_items
Definition: vacuumlazy.c:197
bool do_index_vacuuming
Definition: vacuumlazy.c:157
int max_items
Definition: vacuum.h:243
int num_items
Definition: vacuum.h:244
Size vac_max_items_to_alloc_size(int max_items)
Definition: vacuum.c:2372
#define ParallelVacuumIsActive(vacrel)
Definition: vacuumlazy.c:127
static int dead_items_max_items(LVRelState *vacrel)
Definition: vacuumlazy.c:3104
VacDeadItems * parallel_vacuum_get_dead_items(ParallelVacuumState *pvs)
ParallelVacuumState * parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes, int nrequested_workers, int max_items, int elevel, BufferAccessStrategy bstrategy)

References Assert(), LVRelState::bstrategy, LVRelState::dead_items, dead_items_max_items(), DEBUG2, LVRelState::do_index_vacuuming, ereport, errmsg(), LVRelState::indrels, INFO, VacDeadItems::max_items, MaxHeapTuplesPerPage, LVRelState::nindexes, VacDeadItems::num_items, palloc(), parallel_vacuum_get_dead_items(), parallel_vacuum_init(), ParallelVacuumIsActive, LVRelState::pvs, LVRelState::rel, RelationUsesLocalBuffers, LVRelState::relname, vac_max_items_to_alloc_size(), LVRelState::verbose, and WARNING.

Referenced by heap_vacuum_rel().

◆ dead_items_cleanup()

static void dead_items_cleanup ( LVRelState vacrel)
static

Definition at line 3200 of file vacuumlazy.c.

3201 {
3202  if (!ParallelVacuumIsActive(vacrel))
3203  {
3204  /* Don't bother with pfree here */
3205  return;
3206  }
3207 
3208  /* End parallel mode */
3209  parallel_vacuum_end(vacrel->pvs, vacrel->indstats);
3210  vacrel->pvs = NULL;
3211 }
IndexBulkDeleteResult ** indstats
Definition: vacuumlazy.c:209
void parallel_vacuum_end(ParallelVacuumState *pvs, IndexBulkDeleteResult **istats)

References LVRelState::indstats, parallel_vacuum_end(), ParallelVacuumIsActive, and LVRelState::pvs.

Referenced by heap_vacuum_rel().

◆ dead_items_max_items()

static int dead_items_max_items ( LVRelState vacrel)
static

Definition at line 3104 of file vacuumlazy.c.

3105 {
3106  int64 max_items;
3107  int vac_work_mem = IsAutoVacuumWorkerProcess() &&
3108  autovacuum_work_mem != -1 ?
3110 
3111  if (vacrel->nindexes > 0)
3112  {
3113  BlockNumber rel_pages = vacrel->rel_pages;
3114 
3115  max_items = MAXDEADITEMS(vac_work_mem * 1024L);
3116  max_items = Min(max_items, INT_MAX);
3117  max_items = Min(max_items, MAXDEADITEMS(MaxAllocSize));
3118 
3119  /* curious coding here to ensure the multiplication can't overflow */
3120  if ((BlockNumber) (max_items / MaxHeapTuplesPerPage) > rel_pages)
3121  max_items = rel_pages * MaxHeapTuplesPerPage;
3122 
3123  /* stay sane if small maintenance_work_mem */
3124  max_items = Max(max_items, MaxHeapTuplesPerPage);
3125  }
3126  else
3127  {
3128  /* One-pass case only stores a single heap page's TIDs at a time */
3129  max_items = MaxHeapTuplesPerPage;
3130  }
3131 
3132  return (int) max_items;
3133 }
int autovacuum_work_mem
Definition: autovacuum.c:117
bool IsAutoVacuumWorkerProcess(void)
Definition: autovacuum.c:3309
#define Min(x, y)
Definition: c.h:997
#define Max(x, y)
Definition: c.h:991
int maintenance_work_mem
Definition: globals.c:127
#define MaxAllocSize
Definition: memutils.h:40
#define MAXDEADITEMS(avail_mem)
Definition: vacuum.h:250

References autovacuum_work_mem, IsAutoVacuumWorkerProcess(), maintenance_work_mem, Max, MaxAllocSize, MAXDEADITEMS, MaxHeapTuplesPerPage, Min, LVRelState::nindexes, and LVRelState::rel_pages.

Referenced by dead_items_alloc().

◆ heap_page_is_all_visible()

static bool heap_page_is_all_visible ( LVRelState vacrel,
Buffer  buf,
TransactionId visibility_cutoff_xid,
bool all_frozen 
)
static

Definition at line 3225 of file vacuumlazy.c.

3228 {
3229  Page page = BufferGetPage(buf);
3231  OffsetNumber offnum,
3232  maxoff;
3233  bool all_visible = true;
3234 
3235  *visibility_cutoff_xid = InvalidTransactionId;
3236  *all_frozen = true;
3237 
3238  maxoff = PageGetMaxOffsetNumber(page);
3239  for (offnum = FirstOffsetNumber;
3240  offnum <= maxoff && all_visible;
3241  offnum = OffsetNumberNext(offnum))
3242  {
3243  ItemId itemid;
3244  HeapTupleData tuple;
3245 
3246  /*
3247  * Set the offset number so that we can display it along with any
3248  * error that occurred while processing this tuple.
3249  */
3250  vacrel->offnum = offnum;
3251  itemid = PageGetItemId(page, offnum);
3252 
3253  /* Unused or redirect line pointers are of no interest */
3254  if (!ItemIdIsUsed(itemid) || ItemIdIsRedirected(itemid))
3255  continue;
3256 
3257  ItemPointerSet(&(tuple.t_self), blockno, offnum);
3258 
3259  /*
3260  * Dead line pointers can have index pointers pointing to them. So
3261  * they can't be treated as visible
3262  */
3263  if (ItemIdIsDead(itemid))
3264  {
3265  all_visible = false;
3266  *all_frozen = false;
3267  break;
3268  }
3269 
3270  Assert(ItemIdIsNormal(itemid));
3271 
3272  tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
3273  tuple.t_len = ItemIdGetLength(itemid);
3274  tuple.t_tableOid = RelationGetRelid(vacrel->rel);
3275 
3276  switch (HeapTupleSatisfiesVacuum(&tuple, vacrel->OldestXmin, buf))
3277  {
3278  case HEAPTUPLE_LIVE:
3279  {
3280  TransactionId xmin;
3281 
3282  /* Check comments in lazy_scan_prune. */
3284  {
3285  all_visible = false;
3286  *all_frozen = false;
3287  break;
3288  }
3289 
3290  /*
3291  * The inserter definitely committed. But is it old enough
3292  * that everyone sees it as committed?
3293  */
3294  xmin = HeapTupleHeaderGetXmin(tuple.t_data);
3295  if (!TransactionIdPrecedes(xmin, vacrel->OldestXmin))
3296  {
3297  all_visible = false;
3298  *all_frozen = false;
3299  break;
3300  }
3301 
3302  /* Track newest xmin on page. */
3303  if (TransactionIdFollows(xmin, *visibility_cutoff_xid))
3304  *visibility_cutoff_xid = xmin;
3305 
3306  /* Check whether this tuple is already frozen or not */
3307  if (all_visible && *all_frozen &&
3309  *all_frozen = false;
3310  }
3311  break;
3312 
3313  case HEAPTUPLE_DEAD:
3317  {
3318  all_visible = false;
3319  *all_frozen = false;
3320  break;
3321  }
3322  default:
3323  elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
3324  break;
3325  }
3326  } /* scan along page */
3327 
3328  /* Clear the offset information once we have processed the given page. */
3329  vacrel->offnum = InvalidOffsetNumber;
3330 
3331  return all_visible;
3332 }
BlockNumber BufferGetBlockNumber(Buffer buffer)
Definition: bufmgr.c:2755
#define PageGetItem(page, itemId)
Definition: bufpage.h:339
uint32 TransactionId
Definition: c.h:598
#define ERROR
Definition: elog.h:33
bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple)
Definition: heapam.c:7177
@ HEAPTUPLE_RECENTLY_DEAD
Definition: heapam.h:97
@ HEAPTUPLE_INSERT_IN_PROGRESS
Definition: heapam.h:98
@ HEAPTUPLE_LIVE
Definition: heapam.h:96
@ HEAPTUPLE_DELETE_IN_PROGRESS
Definition: heapam.h:99
@ HEAPTUPLE_DEAD
Definition: heapam.h:95
HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin, Buffer buffer)
HeapTupleHeaderData * HeapTupleHeader
Definition: htup.h:23
#define HeapTupleHeaderGetXmin(tup)
Definition: htup_details.h:308
#define HeapTupleHeaderXminCommitted(tup)
Definition: htup_details.h:319
#define ItemIdGetLength(itemId)
Definition: itemid.h:59
#define ItemIdIsNormal(itemId)
Definition: itemid.h:99
#define ItemIdIsDead(itemId)
Definition: itemid.h:113
#define ItemIdIsRedirected(itemId)
Definition: itemid.h:106
#define ItemPointerSet(pointer, blockNumber, offNum)
Definition: itemptr.h:127
#define InvalidOffsetNumber
Definition: off.h:26
#define RelationGetRelid(relation)
Definition: rel.h:489
ItemPointerData t_self
Definition: htup.h:65
uint32 t_len
Definition: htup.h:64
HeapTupleHeader t_data
Definition: htup.h:68
Oid t_tableOid
Definition: htup.h:66
TransactionId OldestXmin
Definition: vacuumlazy.c:171
OffsetNumber offnum
Definition: vacuumlazy.c:186
bool TransactionIdPrecedes(TransactionId id1, TransactionId id2)
Definition: transam.c:273
bool TransactionIdFollows(TransactionId id1, TransactionId id2)
Definition: transam.c:307
#define InvalidTransactionId
Definition: transam.h:31

References Assert(), buf, BufferGetBlockNumber(), BufferGetPage, elog(), ERROR, FirstOffsetNumber, heap_tuple_needs_eventual_freeze(), HEAPTUPLE_DEAD, HEAPTUPLE_DELETE_IN_PROGRESS, HEAPTUPLE_INSERT_IN_PROGRESS, HEAPTUPLE_LIVE, HEAPTUPLE_RECENTLY_DEAD, HeapTupleHeaderGetXmin, HeapTupleHeaderXminCommitted, HeapTupleSatisfiesVacuum(), InvalidOffsetNumber, InvalidTransactionId, ItemIdGetLength, ItemIdIsDead, ItemIdIsNormal, ItemIdIsRedirected, ItemIdIsUsed, ItemPointerSet, LVRelState::offnum, OffsetNumberNext, LVRelState::OldestXmin, PageGetItem, PageGetItemId, PageGetMaxOffsetNumber, LVRelState::rel, RelationGetRelid, HeapTupleData::t_data, HeapTupleData::t_len, HeapTupleData::t_self, HeapTupleData::t_tableOid, TransactionIdFollows(), and TransactionIdPrecedes().

Referenced by lazy_scan_prune(), and lazy_vacuum_heap_page().

◆ heap_vacuum_rel()

void heap_vacuum_rel ( Relation  rel,
VacuumParams params,
BufferAccessStrategy  bstrategy 
)

Definition at line 309 of file vacuumlazy.c.

311 {
312  LVRelState *vacrel;
313  bool verbose,
314  instrument,
315  aggressive,
316  skipwithvm,
317  frozenxid_updated,
318  minmulti_updated;
319  TransactionId OldestXmin,
320  FreezeLimit;
321  MultiXactId OldestMxact,
322  MultiXactCutoff;
323  BlockNumber orig_rel_pages,
324  new_rel_pages,
325  new_rel_allvisible;
326  PGRUsage ru0;
327  TimestampTz starttime = 0;
328  PgStat_Counter startreadtime = 0,
329  startwritetime = 0;
330  WalUsage startwalusage = pgWalUsage;
331  int64 StartPageHit = VacuumPageHit,
332  StartPageMiss = VacuumPageMiss,
333  StartPageDirty = VacuumPageDirty;
334  ErrorContextCallback errcallback;
335  char **indnames = NULL;
336 
337  verbose = (params->options & VACOPT_VERBOSE) != 0;
338  instrument = (verbose || (IsAutoVacuumWorkerProcess() &&
339  params->log_min_duration >= 0));
340  if (instrument)
341  {
342  pg_rusage_init(&ru0);
343  starttime = GetCurrentTimestamp();
344  if (track_io_timing)
345  {
346  startreadtime = pgStatBlockReadTime;
347  startwritetime = pgStatBlockWriteTime;
348  }
349  }
350 
352  RelationGetRelid(rel));
353 
354  /*
355  * Get OldestXmin cutoff, which is used to determine which deleted tuples
356  * are considered DEAD, not just RECENTLY_DEAD. Also get related cutoffs
357  * used to determine which XIDs/MultiXactIds will be frozen. If this is
358  * an aggressive VACUUM then lazy_scan_heap cannot leave behind unfrozen
359  * XIDs < FreezeLimit (all MXIDs < MultiXactCutoff also need to go away).
360  */
361  aggressive = vacuum_set_xid_limits(rel,
362  params->freeze_min_age,
363  params->freeze_table_age,
364  params->multixact_freeze_min_age,
366  &OldestXmin, &OldestMxact,
367  &FreezeLimit, &MultiXactCutoff);
368 
369  skipwithvm = true;
371  {
372  /*
373  * Force aggressive mode, and disable skipping blocks using the
374  * visibility map (even those set all-frozen)
375  */
376  aggressive = true;
377  skipwithvm = false;
378  }
379 
380  /*
381  * Setup error traceback support for ereport() first. The idea is to set
382  * up an error context callback to display additional information on any
383  * error during a vacuum. During different phases of vacuum, we update
384  * the state so that the error context callback always display current
385  * information.
386  *
387  * Copy the names of heap rel into local memory for error reporting
388  * purposes, too. It isn't always safe to assume that we can get the name
389  * of each rel. It's convenient for code in lazy_scan_heap to always use
390  * these temp copies.
391  */
392  vacrel = (LVRelState *) palloc0(sizeof(LVRelState));
394  vacrel->relname = pstrdup(RelationGetRelationName(rel));
395  vacrel->indname = NULL;
397  vacrel->verbose = verbose;
398  errcallback.callback = vacuum_error_callback;
399  errcallback.arg = vacrel;
400  errcallback.previous = error_context_stack;
401  error_context_stack = &errcallback;
402  if (verbose)
403  {
405  if (aggressive)
406  ereport(INFO,
407  (errmsg("aggressively vacuuming \"%s.%s.%s\"",
409  vacrel->relnamespace, vacrel->relname)));
410  else
411  ereport(INFO,
412  (errmsg("vacuuming \"%s.%s.%s\"",
414  vacrel->relnamespace, vacrel->relname)));
415  }
416 
417  /* Set up high level stuff about rel and its indexes */
418  vacrel->rel = rel;
419  vac_open_indexes(vacrel->rel, RowExclusiveLock, &vacrel->nindexes,
420  &vacrel->indrels);
421  if (instrument && vacrel->nindexes > 0)
422  {
423  /* Copy index names used by instrumentation (not error reporting) */
424  indnames = palloc(sizeof(char *) * vacrel->nindexes);
425  for (int i = 0; i < vacrel->nindexes; i++)
426  indnames[i] = pstrdup(RelationGetRelationName(vacrel->indrels[i]));
427  }
428 
429  /*
430  * The index_cleanup param either disables index vacuuming and cleanup or
431  * forces it to go ahead when we would otherwise apply the index bypass
432  * optimization. The default is 'auto', which leaves the final decision
433  * up to lazy_vacuum().
434  *
435  * The truncate param allows user to avoid attempting relation truncation,
436  * though it can't force truncation to happen.
437  */
440  params->truncate != VACOPTVALUE_AUTO);
441  vacrel->aggressive = aggressive;
442  vacrel->skipwithvm = skipwithvm;
443  vacrel->failsafe_active = false;
444  vacrel->consider_bypass_optimization = true;
445  vacrel->do_index_vacuuming = true;
446  vacrel->do_index_cleanup = true;
447  vacrel->do_rel_truncate = (params->truncate != VACOPTVALUE_DISABLED);
448  if (params->index_cleanup == VACOPTVALUE_DISABLED)
449  {
450  /* Force disable index vacuuming up-front */
451  vacrel->do_index_vacuuming = false;
452  vacrel->do_index_cleanup = false;
453  }
454  else if (params->index_cleanup == VACOPTVALUE_ENABLED)
455  {
456  /* Force index vacuuming. Note that failsafe can still bypass. */
457  vacrel->consider_bypass_optimization = false;
458  }
459  else
460  {
461  /* Default/auto, make all decisions dynamically */
463  }
464 
465  vacrel->bstrategy = bstrategy;
466  vacrel->relfrozenxid = rel->rd_rel->relfrozenxid;
467  vacrel->relminmxid = rel->rd_rel->relminmxid;
468  vacrel->old_live_tuples = rel->rd_rel->reltuples;
469 
470  /* Initialize page counters explicitly (be tidy) */
471  vacrel->scanned_pages = 0;
472  vacrel->removed_pages = 0;
473  vacrel->lpdead_item_pages = 0;
474  vacrel->missed_dead_pages = 0;
475  vacrel->nonempty_pages = 0;
476  /* dead_items_alloc allocates vacrel->dead_items later on */
477 
478  /* Allocate/initialize output statistics state */
479  vacrel->new_rel_tuples = 0;
480  vacrel->new_live_tuples = 0;
481  vacrel->indstats = (IndexBulkDeleteResult **)
482  palloc0(vacrel->nindexes * sizeof(IndexBulkDeleteResult *));
483 
484  /* Initialize remaining counters (be tidy) */
485  vacrel->num_index_scans = 0;
486  vacrel->tuples_deleted = 0;
487  vacrel->lpdead_items = 0;
488  vacrel->live_tuples = 0;
489  vacrel->recently_dead_tuples = 0;
490  vacrel->missed_dead_tuples = 0;
491 
492  /*
493  * Determine the extent of the blocks that we'll scan in lazy_scan_heap,
494  * and finalize cutoffs used for freezing and pruning in lazy_scan_prune.
495  *
496  * We expect vistest will always make heap_page_prune remove any deleted
497  * tuple whose xmax is < OldestXmin. lazy_scan_prune must never become
498  * confused about whether a tuple should be frozen or removed. (In the
499  * future we might want to teach lazy_scan_prune to recompute vistest from
500  * time to time, to increase the number of dead tuples it can prune away.)
501  *
502  * We must determine rel_pages _after_ OldestXmin has been established.
503  * lazy_scan_heap's physical heap scan (scan of pages < rel_pages) is
504  * thereby guaranteed to not miss any tuples with XIDs < OldestXmin. These
505  * XIDs must at least be considered for freezing (though not necessarily
506  * frozen) during its scan.
507  */
508  vacrel->rel_pages = orig_rel_pages = RelationGetNumberOfBlocks(rel);
509  vacrel->OldestXmin = OldestXmin;
510  vacrel->vistest = GlobalVisTestFor(rel);
511  /* FreezeLimit controls XID freezing (always <= OldestXmin) */
512  vacrel->FreezeLimit = FreezeLimit;
513  /* MultiXactCutoff controls MXID freezing (always <= OldestMxact) */
514  vacrel->MultiXactCutoff = MultiXactCutoff;
515  /* Initialize state used to track oldest extant XID/MXID */
516  vacrel->NewRelfrozenXid = OldestXmin;
517  vacrel->NewRelminMxid = OldestMxact;
518  vacrel->skippedallvis = false;
519 
520  /*
521  * Allocate dead_items array memory using dead_items_alloc. This handles
522  * parallel VACUUM initialization as part of allocating shared memory
523  * space used for dead_items. (But do a failsafe precheck first, to
524  * ensure that parallel VACUUM won't be attempted at all when relfrozenxid
525  * is already dangerously old.)
526  */
528  dead_items_alloc(vacrel, params->nworkers);
529 
530  /*
531  * Call lazy_scan_heap to perform all required heap pruning, index
532  * vacuuming, and heap vacuuming (plus related processing)
533  */
534  lazy_scan_heap(vacrel);
535 
536  /*
537  * Free resources managed by dead_items_alloc. This ends parallel mode in
538  * passing when necessary.
539  */
540  dead_items_cleanup(vacrel);
542 
543  /*
544  * Update pg_class entries for each of rel's indexes where appropriate.
545  *
546  * Unlike the later update to rel's pg_class entry, this is not critical.
547  * Maintains relpages/reltuples statistics used by the planner only.
548  */
549  if (vacrel->do_index_cleanup)
551 
552  /* Done with rel's indexes */
553  vac_close_indexes(vacrel->nindexes, vacrel->indrels, NoLock);
554 
555  /* Optionally truncate rel */
556  if (should_attempt_truncation(vacrel))
557  lazy_truncate_heap(vacrel);
558 
559  /* Pop the error context stack */
560  error_context_stack = errcallback.previous;
561 
562  /* Report that we are now doing final cleanup */
565 
566  /*
567  * Prepare to update rel's pg_class entry.
568  *
569  * Aggressive VACUUMs must always be able to advance relfrozenxid to a
570  * value >= FreezeLimit, and relminmxid to a value >= MultiXactCutoff.
571  * Non-aggressive VACUUMs may advance them by any amount, or not at all.
572  */
573  Assert(vacrel->NewRelfrozenXid == OldestXmin ||
574  TransactionIdPrecedesOrEquals(aggressive ? FreezeLimit :
575  vacrel->relfrozenxid,
576  vacrel->NewRelfrozenXid));
577  Assert(vacrel->NewRelminMxid == OldestMxact ||
578  MultiXactIdPrecedesOrEquals(aggressive ? MultiXactCutoff :
579  vacrel->relminmxid,
580  vacrel->NewRelminMxid));
581  if (vacrel->skippedallvis)
582  {
583  /*
584  * Must keep original relfrozenxid in a non-aggressive VACUUM that
585  * chose to skip an all-visible page range. The state that tracks new
586  * values will have missed unfrozen XIDs from the pages we skipped.
587  */
588  Assert(!aggressive);
591  }
592 
593  /*
594  * For safety, clamp relallvisible to be not more than what we're setting
595  * pg_class.relpages to
596  */
597  new_rel_pages = vacrel->rel_pages; /* After possible rel truncation */
598  visibilitymap_count(rel, &new_rel_allvisible, NULL);
599  if (new_rel_allvisible > new_rel_pages)
600  new_rel_allvisible = new_rel_pages;
601 
602  /*
603  * Now actually update rel's pg_class entry.
604  *
605  * In principle new_live_tuples could be -1 indicating that we (still)
606  * don't know the tuple count. In practice that can't happen, since we
607  * scan every page that isn't skipped using the visibility map.
608  */
609  vac_update_relstats(rel, new_rel_pages, vacrel->new_live_tuples,
610  new_rel_allvisible, vacrel->nindexes > 0,
611  vacrel->NewRelfrozenXid, vacrel->NewRelminMxid,
612  &frozenxid_updated, &minmulti_updated, false);
613 
614  /*
615  * Report results to the cumulative stats system, too.
616  *
617  * Deliberately avoid telling the stats system about LP_DEAD items that
618  * remain in the table due to VACUUM bypassing index and heap vacuuming.
619  * ANALYZE will consider the remaining LP_DEAD items to be dead "tuples".
620  * It seems like a good idea to err on the side of not vacuuming again too
621  * soon in cases where the failsafe prevented significant amounts of heap
622  * vacuuming.
623  */
625  rel->rd_rel->relisshared,
626  Max(vacrel->new_live_tuples, 0),
627  vacrel->recently_dead_tuples +
628  vacrel->missed_dead_tuples);
630 
631  if (instrument)
632  {
633  TimestampTz endtime = GetCurrentTimestamp();
634 
635  if (verbose || params->log_min_duration == 0 ||
636  TimestampDifferenceExceeds(starttime, endtime,
637  params->log_min_duration))
638  {
639  long secs_dur;
640  int usecs_dur;
641  WalUsage walusage;
643  char *msgfmt;
644  int32 diff;
645  int64 PageHitOp = VacuumPageHit - StartPageHit,
646  PageMissOp = VacuumPageMiss - StartPageMiss,
647  PageDirtyOp = VacuumPageDirty - StartPageDirty;
648  double read_rate = 0,
649  write_rate = 0;
650 
651  TimestampDifference(starttime, endtime, &secs_dur, &usecs_dur);
652  memset(&walusage, 0, sizeof(WalUsage));
653  WalUsageAccumDiff(&walusage, &pgWalUsage, &startwalusage);
654 
656  if (verbose)
657  {
658  /*
659  * Aggressiveness already reported earlier, in dedicated
660  * VACUUM VERBOSE ereport
661  */
662  Assert(!params->is_wraparound);
663  msgfmt = _("finished vacuuming \"%s.%s.%s\": index scans: %d\n");
664  }
665  else if (params->is_wraparound)
666  {
667  /*
668  * While it's possible for a VACUUM to be both is_wraparound
669  * and !aggressive, that's just a corner-case -- is_wraparound
670  * implies aggressive. Produce distinct output for the corner
671  * case all the same, just in case.
672  */
673  if (aggressive)
674  msgfmt = _("automatic aggressive vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n");
675  else
676  msgfmt = _("automatic vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n");
677  }
678  else
679  {
680  if (aggressive)
681  msgfmt = _("automatic aggressive vacuum of table \"%s.%s.%s\": index scans: %d\n");
682  else
683  msgfmt = _("automatic vacuum of table \"%s.%s.%s\": index scans: %d\n");
684  }
685  appendStringInfo(&buf, msgfmt,
687  vacrel->relnamespace,
688  vacrel->relname,
689  vacrel->num_index_scans);
690  appendStringInfo(&buf, _("pages: %u removed, %u remain, %u scanned (%.2f%% of total)\n"),
691  vacrel->removed_pages,
692  new_rel_pages,
693  vacrel->scanned_pages,
694  orig_rel_pages == 0 ? 100.0 :
695  100.0 * vacrel->scanned_pages / orig_rel_pages);
697  _("tuples: %lld removed, %lld remain, %lld are dead but not yet removable\n"),
698  (long long) vacrel->tuples_deleted,
699  (long long) vacrel->new_rel_tuples,
700  (long long) vacrel->recently_dead_tuples);
701  if (vacrel->missed_dead_tuples > 0)
703  _("tuples missed: %lld dead from %u pages not removed due to cleanup lock contention\n"),
704  (long long) vacrel->missed_dead_tuples,
705  vacrel->missed_dead_pages);
706  diff = (int32) (ReadNextTransactionId() - OldestXmin);
708  _("removable cutoff: %u, which was %d XIDs old when operation ended\n"),
709  OldestXmin, diff);
710  if (frozenxid_updated)
711  {
712  diff = (int32) (vacrel->NewRelfrozenXid - vacrel->relfrozenxid);
714  _("new relfrozenxid: %u, which is %d XIDs ahead of previous value\n"),
715  vacrel->NewRelfrozenXid, diff);
716  }
717  if (minmulti_updated)
718  {
719  diff = (int32) (vacrel->NewRelminMxid - vacrel->relminmxid);
721  _("new relminmxid: %u, which is %d MXIDs ahead of previous value\n"),
722  vacrel->NewRelminMxid, diff);
723  }
724  if (vacrel->do_index_vacuuming)
725  {
726  if (vacrel->nindexes == 0 || vacrel->num_index_scans == 0)
727  appendStringInfoString(&buf, _("index scan not needed: "));
728  else
729  appendStringInfoString(&buf, _("index scan needed: "));
730 
731  msgfmt = _("%u pages from table (%.2f%% of total) had %lld dead item identifiers removed\n");
732  }
733  else
734  {
735  if (!vacrel->failsafe_active)
736  appendStringInfoString(&buf, _("index scan bypassed: "));
737  else
738  appendStringInfoString(&buf, _("index scan bypassed by failsafe: "));
739 
740  msgfmt = _("%u pages from table (%.2f%% of total) have %lld dead item identifiers\n");
741  }
742  appendStringInfo(&buf, msgfmt,
743  vacrel->lpdead_item_pages,
744  orig_rel_pages == 0 ? 100.0 :
745  100.0 * vacrel->lpdead_item_pages / orig_rel_pages,
746  (long long) vacrel->lpdead_items);
747  for (int i = 0; i < vacrel->nindexes; i++)
748  {
749  IndexBulkDeleteResult *istat = vacrel->indstats[i];
750 
751  if (!istat)
752  continue;
753 
755  _("index \"%s\": pages: %u in total, %u newly deleted, %u currently deleted, %u reusable\n"),
756  indnames[i],
757  istat->num_pages,
758  istat->pages_newly_deleted,
759  istat->pages_deleted,
760  istat->pages_free);
761  }
762  if (track_io_timing)
763  {
764  double read_ms = (double) (pgStatBlockReadTime - startreadtime) / 1000;
765  double write_ms = (double) (pgStatBlockWriteTime - startwritetime) / 1000;
766 
767  appendStringInfo(&buf, _("I/O timings: read: %.3f ms, write: %.3f ms\n"),
768  read_ms, write_ms);
769  }
770  if (secs_dur > 0 || usecs_dur > 0)
771  {
772  read_rate = (double) BLCKSZ * PageMissOp / (1024 * 1024) /
773  (secs_dur + usecs_dur / 1000000.0);
774  write_rate = (double) BLCKSZ * PageDirtyOp / (1024 * 1024) /
775  (secs_dur + usecs_dur / 1000000.0);
776  }
777  appendStringInfo(&buf, _("avg read rate: %.3f MB/s, avg write rate: %.3f MB/s\n"),
778  read_rate, write_rate);
780  _("buffer usage: %lld hits, %lld misses, %lld dirtied\n"),
781  (long long) PageHitOp,
782  (long long) PageMissOp,
783  (long long) PageDirtyOp);
785  _("WAL usage: %lld records, %lld full page images, %llu bytes\n"),
786  (long long) walusage.wal_records,
787  (long long) walusage.wal_fpi,
788  (unsigned long long) walusage.wal_bytes);
789  appendStringInfo(&buf, _("system usage: %s"), pg_rusage_show(&ru0));
790 
791  ereport(verbose ? INFO : LOG,
792  (errmsg_internal("%s", buf.data)));
793  pfree(buf.data);
794  }
795  }
796 
797  /* Cleanup index statistics and index names */
798  for (int i = 0; i < vacrel->nindexes; i++)
799  {
800  if (vacrel->indstats[i])
801  pfree(vacrel->indstats[i]);
802 
803  if (instrument)
804  pfree(indnames[i]);
805  }
806 }
void TimestampDifference(TimestampTz start_time, TimestampTz stop_time, long *secs, int *microsecs)
Definition: timestamp.c:1650
bool TimestampDifferenceExceeds(TimestampTz start_time, TimestampTz stop_time, int msec)
Definition: timestamp.c:1705
TimestampTz GetCurrentTimestamp(void)
Definition: timestamp.c:1574
void pgstat_progress_start_command(ProgressCommandType cmdtype, Oid relid)
void pgstat_progress_update_param(int index, int64 val)
void pgstat_progress_end_command(void)
@ PROGRESS_COMMAND_VACUUM
bool track_io_timing
Definition: bufmgr.c:137
#define RelationGetNumberOfBlocks(reln)
Definition: bufmgr.h:218
signed int int32
Definition: c.h:440
TransactionId MultiXactId
Definition: c.h:608
int64 TimestampTz
Definition: timestamp.h:39
char * get_database_name(Oid dbid)
Definition: dbcommands.c:2994
int errmsg_internal(const char *fmt,...)
Definition: elog.c:991
ErrorContextCallback * error_context_stack
Definition: elog.c:93
#define _(x)
Definition: elog.c:89
#define LOG
Definition: elog.h:25
int64 VacuumPageHit
Definition: globals.c:148
int64 VacuumPageMiss
Definition: globals.c:149
int64 VacuumPageDirty
Definition: globals.c:150
Oid MyDatabaseId
Definition: globals.c:89
WalUsage pgWalUsage
Definition: instrument.c:22
void WalUsageAccumDiff(WalUsage *dst, const WalUsage *add, const WalUsage *sub)
Definition: instrument.c:280
int i
Definition: isn.c:73
#define NoLock
Definition: lockdefs.h:34
#define RowExclusiveLock
Definition: lockdefs.h:38
char * get_namespace_name(Oid nspid)
Definition: lsyscache.c:3326
char * pstrdup(const char *in)
Definition: mcxt.c:1305
void pfree(void *pointer)
Definition: mcxt.c:1175
void * palloc0(Size size)
Definition: mcxt.c:1099
bool MultiXactIdPrecedesOrEquals(MultiXactId multi1, MultiXactId multi2)
Definition: multixact.c:3173
#define InvalidMultiXactId
Definition: multixact.h:24
static int verbose
const char * pg_rusage_show(const PGRUsage *ru0)
Definition: pg_rusage.c:40
void pg_rusage_init(PGRUsage *ru0)
Definition: pg_rusage.c:27
int64 PgStat_Counter
Definition: pgstat.h:88
PgStat_Counter pgStatBlockReadTime
PgStat_Counter pgStatBlockWriteTime
void pgstat_report_vacuum(Oid tableoid, bool shared, PgStat_Counter livetuples, PgStat_Counter deadtuples)
GlobalVisState * GlobalVisTestFor(Relation rel)
Definition: procarray.c:4051
#define PROGRESS_VACUUM_PHASE_FINAL_CLEANUP
Definition: progress.h:35
#define PROGRESS_VACUUM_PHASE
Definition: progress.h:21
#define RelationGetRelationName(relation)
Definition: rel.h:523
#define RelationGetNamespace(relation)
Definition: rel.h:530
void appendStringInfo(StringInfo str, const char *fmt,...)
Definition: stringinfo.c:91
void appendStringInfoString(StringInfo str, const char *s)
Definition: stringinfo.c:176
void initStringInfo(StringInfo str)
Definition: stringinfo.c:59
struct ErrorContextCallback * previous
Definition: elog.h:232
void(* callback)(void *arg)
Definition: elog.h:233
BlockNumber pages_deleted
Definition: genam.h:81
BlockNumber pages_newly_deleted
Definition: genam.h:80
BlockNumber pages_free
Definition: genam.h:82
BlockNumber num_pages
Definition: genam.h:76
MultiXactId relminmxid
Definition: vacuumlazy.c:167
int64 tuples_deleted
Definition: vacuumlazy.c:214
MultiXactId MultiXactCutoff
Definition: vacuumlazy.c:175
double old_live_tuples
Definition: vacuumlazy.c:168
bool do_rel_truncate
Definition: vacuumlazy.c:159
BlockNumber scanned_pages
Definition: vacuumlazy.c:199
bool aggressive
Definition: vacuumlazy.c:148
bool failsafe_active
Definition: vacuumlazy.c:152
GlobalVisState * vistest
Definition: vacuumlazy.c:172
BlockNumber removed_pages
Definition: vacuumlazy.c:200
int num_index_scans
Definition: vacuumlazy.c:212
double new_live_tuples
Definition: vacuumlazy.c:207
double new_rel_tuples
Definition: vacuumlazy.c:206
TransactionId NewRelfrozenXid
Definition: vacuumlazy.c:177
bool consider_bypass_optimization
Definition: vacuumlazy.c:154
TransactionId FreezeLimit
Definition: vacuumlazy.c:174
int64 recently_dead_tuples
Definition: vacuumlazy.c:217
BlockNumber missed_dead_pages
Definition: vacuumlazy.c:202
char * relnamespace
Definition: vacuumlazy.c:182
int64 live_tuples
Definition: vacuumlazy.c:216
int64 lpdead_items
Definition: vacuumlazy.c:215
bool skippedallvis
Definition: vacuumlazy.c:179
BlockNumber lpdead_item_pages
Definition: vacuumlazy.c:201
bool skipwithvm
Definition: vacuumlazy.c:150
bool do_index_cleanup
Definition: vacuumlazy.c:158
MultiXactId NewRelminMxid
Definition: vacuumlazy.c:178
int64 missed_dead_tuples
Definition: vacuumlazy.c:218
TransactionId relfrozenxid
Definition: vacuumlazy.c:166
VacErrPhase phase
Definition: vacuumlazy.c:187
char * indname
Definition: vacuumlazy.c:184
Form_pg_class rd_rel
Definition: rel.h:110
int nworkers
Definition: vacuum.h:235
int freeze_table_age
Definition: vacuum.h:218
VacOptValue truncate
Definition: vacuum.h:228
bits32 options
Definition: vacuum.h:216
int freeze_min_age
Definition: vacuum.h:217
bool is_wraparound
Definition: vacuum.h:223
int multixact_freeze_min_age
Definition: vacuum.h:219
int multixact_freeze_table_age
Definition: vacuum.h:221
int log_min_duration
Definition: vacuum.h:224
VacOptValue index_cleanup
Definition: vacuum.h:227
uint64 wal_bytes
Definition: instrument.h:53
int64 wal_fpi
Definition: instrument.h:52
int64 wal_records
Definition: instrument.h:51
bool TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2)
Definition: transam.c:292
static TransactionId ReadNextTransactionId(void)
Definition: transam.h:315
void vac_open_indexes(Relation relation, LOCKMODE lockmode, int *nindexes, Relation **Irel)
Definition: vacuum.c:2143
void vac_update_relstats(Relation relation, BlockNumber num_pages, double num_tuples, BlockNumber num_all_visible_pages, bool hasindex, TransactionId frozenxid, MultiXactId minmulti, bool *frozenxid_updated, bool *minmulti_updated, bool in_outer_xact)
Definition: vacuum.c:1324
bool vacuum_set_xid_limits(Relation rel, int freeze_min_age, int freeze_table_age, int multixact_freeze_min_age, int multixact_freeze_table_age, TransactionId *oldestXmin, MultiXactId *oldestMxact, TransactionId *freezeLimit, MultiXactId *multiXactCutoff)
Definition: vacuum.c:959
void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode)
Definition: vacuum.c:2186
#define VACOPT_VERBOSE
Definition: vacuum.h:185
@ VACOPTVALUE_AUTO
Definition: vacuum.h:203
@ VACOPTVALUE_ENABLED
Definition: vacuum.h:205
@ VACOPTVALUE_UNSPECIFIED
Definition: vacuum.h:202
@ VACOPTVALUE_DISABLED
Definition: vacuum.h:204
#define VACOPT_DISABLE_PAGE_SKIPPING
Definition: vacuum.h:190
static void dead_items_cleanup(LVRelState *vacrel)
Definition: vacuumlazy.c:3200
static void update_relstats_all_indexes(LVRelState *vacrel)
Definition: vacuumlazy.c:3338
static void vacuum_error_callback(void *arg)
Definition: vacuumlazy.c:3373
static void lazy_truncate_heap(LVRelState *vacrel)
Definition: vacuumlazy.c:2830
static bool should_attempt_truncation(LVRelState *vacrel)
Definition: vacuumlazy.c:2809
static void lazy_scan_heap(LVRelState *vacrel)
Definition: vacuumlazy.c:845
static bool lazy_check_wraparound_failsafe(LVRelState *vacrel)
Definition: vacuumlazy.c:2609
static void dead_items_alloc(LVRelState *vacrel, int nworkers)
Definition: vacuumlazy.c:3143
void visibilitymap_count(Relation rel, BlockNumber *all_visible, BlockNumber *all_frozen)
bool IsInParallelMode(void)
Definition: xact.c:1065

References _, LVRelState::aggressive, appendStringInfo(), appendStringInfoString(), ErrorContextCallback::arg, Assert(), LVRelState::bstrategy, buf, ErrorContextCallback::callback, LVRelState::consider_bypass_optimization, dead_items_alloc(), dead_items_cleanup(), LVRelState::do_index_cleanup, LVRelState::do_index_vacuuming, LVRelState::do_rel_truncate, ereport, errmsg(), errmsg_internal(), error_context_stack, LVRelState::failsafe_active, VacuumParams::freeze_min_age, VacuumParams::freeze_table_age, LVRelState::FreezeLimit, get_database_name(), get_namespace_name(), GetCurrentTimestamp(), GlobalVisTestFor(), i, VacuumParams::index_cleanup, LVRelState::indname, LVRelState::indrels, LVRelState::indstats, INFO, initStringInfo(), InvalidMultiXactId, InvalidTransactionId, VacuumParams::is_wraparound, IsAutoVacuumWorkerProcess(), IsInParallelMode(), lazy_check_wraparound_failsafe(), lazy_scan_heap(), lazy_truncate_heap(), LVRelState::live_tuples, LOG, VacuumParams::log_min_duration, LVRelState::lpdead_item_pages, LVRelState::lpdead_items, Max, LVRelState::missed_dead_pages, LVRelState::missed_dead_tuples, VacuumParams::multixact_freeze_min_age, VacuumParams::multixact_freeze_table_age, LVRelState::MultiXactCutoff, MultiXactIdPrecedesOrEquals(), MyDatabaseId, LVRelState::new_live_tuples, LVRelState::new_rel_tuples, LVRelState::NewRelfrozenXid, LVRelState::NewRelminMxid, LVRelState::nindexes, NoLock, LVRelState::nonempty_pages, LVRelState::num_index_scans, IndexBulkDeleteResult::num_pages, VacuumParams::nworkers, LVRelState::old_live_tuples, LVRelState::OldestXmin, VacuumParams::options, IndexBulkDeleteResult::pages_deleted, IndexBulkDeleteResult::pages_free, IndexBulkDeleteResult::pages_newly_deleted, palloc(), palloc0(), pfree(), pg_rusage_init(), pg_rusage_show(), pgstat_progress_end_command(), pgstat_progress_start_command(), pgstat_progress_update_param(), pgstat_report_vacuum(), pgStatBlockReadTime, pgStatBlockWriteTime, pgWalUsage, LVRelState::phase, ErrorContextCallback::previous, PROGRESS_COMMAND_VACUUM, PROGRESS_VACUUM_PHASE, PROGRESS_VACUUM_PHASE_FINAL_CLEANUP, pstrdup(), RelationData::rd_rel, ReadNextTransactionId(), LVRelState::recently_dead_tuples, LVRelState::rel, LVRelState::rel_pages, RelationGetNamespace, RelationGetNumberOfBlocks, RelationGetRelationName, RelationGetRelid, LVRelState::relfrozenxid, LVRelState::relminmxid, LVRelState::relname, LVRelState::relnamespace, LVRelState::removed_pages, RowExclusiveLock, LVRelState::scanned_pages, should_attempt_truncation(), LVRelState::skippedallvis, LVRelState::skipwithvm, TimestampDifference(), TimestampDifferenceExceeds(), track_io_timing, TransactionIdPrecedesOrEquals(), VacuumParams::truncate, LVRelState::tuples_deleted, update_relstats_all_indexes(), vac_close_indexes(), vac_open_indexes(), vac_update_relstats(), VACOPT_DISABLE_PAGE_SKIPPING, VACOPT_VERBOSE, VACOPTVALUE_AUTO, VACOPTVALUE_DISABLED, VACOPTVALUE_ENABLED, VACOPTVALUE_UNSPECIFIED, VACUUM_ERRCB_PHASE_UNKNOWN, vacuum_error_callback(), vacuum_set_xid_limits(), VacuumPageDirty, VacuumPageHit, VacuumPageMiss, LVRelState::verbose, verbose, visibilitymap_count(), LVRelState::vistest, WalUsage::wal_bytes, WalUsage::wal_fpi, WalUsage::wal_records, and WalUsageAccumDiff().

◆ lazy_check_wraparound_failsafe()

static bool lazy_check_wraparound_failsafe ( LVRelState vacrel)
static

Definition at line 2609 of file vacuumlazy.c.

2610 {
2613 
2614  /* Don't warn more than once per VACUUM */
2615  if (vacrel->failsafe_active)
2616  return true;
2617 
2619  vacrel->relminmxid)))
2620  {
2621  vacrel->failsafe_active = true;
2622 
2623  /* Disable index vacuuming, index cleanup, and heap rel truncation */
2624  vacrel->do_index_vacuuming = false;
2625  vacrel->do_index_cleanup = false;
2626  vacrel->do_rel_truncate = false;
2627 
2628  ereport(WARNING,
2629  (errmsg("bypassing nonessential maintenance of table \"%s.%s.%s\" as a failsafe after %d index scans",
2631  vacrel->relnamespace,
2632  vacrel->relname,
2633  vacrel->num_index_scans),
2634  errdetail("The table's relfrozenxid or relminmxid is too far in the past."),
2635  errhint("Consider increasing configuration parameter \"maintenance_work_mem\" or \"autovacuum_work_mem\".\n"
2636  "You might also need to consider other ways for VACUUM to keep up with the allocation of transaction IDs.")));
2637 
2638  /* Stop applying cost limits from this point on */
2639  VacuumCostActive = false;
2640  VacuumCostBalance = 0;
2641 
2642  return true;
2643  }
2644 
2645  return false;
2646 }
#define unlikely(x)
Definition: c.h:284
int errdetail(const char *fmt,...)
Definition: elog.c:1037
int errhint(const char *fmt,...)
Definition: elog.c:1151
bool VacuumCostActive
Definition: globals.c:153
int VacuumCostBalance
Definition: globals.c:152
#define MultiXactIdIsValid(multi)
Definition: multixact.h:28
#define TransactionIdIsNormal(xid)
Definition: transam.h:42
bool vacuum_xid_failsafe_check(TransactionId relfrozenxid, MultiXactId relminmxid)
Definition: vacuum.c:1162

References Assert(), LVRelState::do_index_cleanup, LVRelState::do_index_vacuuming, LVRelState::do_rel_truncate, ereport, errdetail(), errhint(), errmsg(), LVRelState::failsafe_active, get_database_name(), MultiXactIdIsValid, MyDatabaseId, LVRelState::num_index_scans, LVRelState::relfrozenxid, LVRelState::relminmxid, LVRelState::relname, LVRelState::relnamespace, TransactionIdIsNormal, unlikely, vacuum_xid_failsafe_check(), VacuumCostActive, VacuumCostBalance, and WARNING.

Referenced by heap_vacuum_rel(), lazy_scan_heap(), and lazy_vacuum_all_indexes().

◆ lazy_cleanup_all_indexes()

static void lazy_cleanup_all_indexes ( LVRelState vacrel)
static

Definition at line 2652 of file vacuumlazy.c.

2653 {
2654  double reltuples = vacrel->new_rel_tuples;
2655  bool estimated_count = vacrel->scanned_pages < vacrel->rel_pages;
2656 
2657  Assert(vacrel->do_index_cleanup);
2658  Assert(vacrel->nindexes > 0);
2659 
2660  /* Report that we are now cleaning up indexes */
2663 
2664  if (!ParallelVacuumIsActive(vacrel))
2665  {
2666  for (int idx = 0; idx < vacrel->nindexes; idx++)
2667  {
2668  Relation indrel = vacrel->indrels[idx];
2669  IndexBulkDeleteResult *istat = vacrel->indstats[idx];
2670 
2671  vacrel->indstats[idx] =
2672  lazy_cleanup_one_index(indrel, istat, reltuples,
2673  estimated_count, vacrel);
2674  }
2675  }
2676  else
2677  {
2678  /* Outsource everything to parallel variant */
2679  parallel_vacuum_cleanup_all_indexes(vacrel->pvs, reltuples,
2680  vacrel->num_index_scans,
2681  estimated_count);
2682  }
2683 }
Datum idx(PG_FUNCTION_ARGS)
Definition: _int_op.c:259
#define PROGRESS_VACUUM_PHASE_INDEX_CLEANUP
Definition: progress.h:33
static IndexBulkDeleteResult * lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat, double reltuples, bool estimated_count, LVRelState *vacrel)
Definition: vacuumlazy.c:2746
void parallel_vacuum_cleanup_all_indexes(ParallelVacuumState *pvs, long num_table_tuples, int num_index_scans, bool estimated_count)

References Assert(), LVRelState::do_index_cleanup, idx(), LVRelState::indrels, LVRelState::indstats, lazy_cleanup_one_index(), LVRelState::new_rel_tuples, LVRelState::nindexes, LVRelState::num_index_scans, parallel_vacuum_cleanup_all_indexes(), ParallelVacuumIsActive, pgstat_progress_update_param(), PROGRESS_VACUUM_PHASE, PROGRESS_VACUUM_PHASE_INDEX_CLEANUP, LVRelState::pvs, LVRelState::rel_pages, and LVRelState::scanned_pages.

Referenced by lazy_scan_heap().

◆ lazy_cleanup_one_index()

static IndexBulkDeleteResult * lazy_cleanup_one_index ( Relation  indrel,
IndexBulkDeleteResult istat,
double  reltuples,
bool  estimated_count,
LVRelState vacrel 
)
static

Definition at line 2746 of file vacuumlazy.c.

2749 {
2750  IndexVacuumInfo ivinfo;
2751  LVSavedErrInfo saved_err_info;
2752 
2753  ivinfo.index = indrel;
2754  ivinfo.analyze_only = false;
2755  ivinfo.report_progress = false;
2756  ivinfo.estimated_count = estimated_count;
2757  ivinfo.message_level = DEBUG2;
2758 
2759  ivinfo.num_heap_tuples = reltuples;
2760  ivinfo.strategy = vacrel->bstrategy;
2761 
2762  /*
2763  * Update error traceback information.
2764  *
2765  * The index name is saved during this phase and restored immediately
2766  * after this phase. See vacuum_error_callback.
2767  */
2768  Assert(vacrel->indname == NULL);
2769  vacrel->indname = pstrdup(RelationGetRelationName(indrel));
2770  update_vacuum_error_info(vacrel, &saved_err_info,
2773 
2774  istat = vac_cleanup_one_index(&ivinfo, istat);
2775 
2776  /* Revert to the previous phase information for error traceback */
2777  restore_vacuum_error_info(vacrel, &saved_err_info);
2778  pfree(vacrel->indname);
2779  vacrel->indname = NULL;
2780 
2781  return istat;
2782 }
Relation index
Definition: genam.h:46
double num_heap_tuples
Definition: genam.h:51
bool analyze_only
Definition: genam.h:47
BufferAccessStrategy strategy
Definition: genam.h:52
bool report_progress
Definition: genam.h:48
int message_level
Definition: genam.h:50
bool estimated_count
Definition: genam.h:49
IndexBulkDeleteResult * vac_cleanup_one_index(IndexVacuumInfo *ivinfo, IndexBulkDeleteResult *istat)
Definition: vacuum.c:2347
static void restore_vacuum_error_info(LVRelState *vacrel, const LVSavedErrInfo *saved_vacrel)
Definition: vacuumlazy.c:3456
static void update_vacuum_error_info(LVRelState *vacrel, LVSavedErrInfo *saved_vacrel, int phase, BlockNumber blkno, OffsetNumber offnum)
Definition: vacuumlazy.c:3437

References IndexVacuumInfo::analyze_only, Assert(), LVRelState::bstrategy, DEBUG2, IndexVacuumInfo::estimated_count, IndexVacuumInfo::index, LVRelState::indname, InvalidBlockNumber, InvalidOffsetNumber, IndexVacuumInfo::message_level, IndexVacuumInfo::num_heap_tuples, pfree(), pstrdup(), RelationGetRelationName, IndexVacuumInfo::report_progress, restore_vacuum_error_info(), IndexVacuumInfo::strategy, update_vacuum_error_info(), vac_cleanup_one_index(), and VACUUM_ERRCB_PHASE_INDEX_CLEANUP.

Referenced by lazy_cleanup_all_indexes().

◆ lazy_scan_heap()

static void lazy_scan_heap ( LVRelState vacrel)
static

Definition at line 845 of file vacuumlazy.c.

846 {
847  BlockNumber rel_pages = vacrel->rel_pages,
848  blkno,
849  next_unskippable_block,
850  next_failsafe_block = 0,
851  next_fsm_block_to_vacuum = 0;
852  VacDeadItems *dead_items = vacrel->dead_items;
853  Buffer vmbuffer = InvalidBuffer;
854  bool next_unskippable_allvis,
855  skipping_current_range;
856  const int initprog_index[] = {
860  };
861  int64 initprog_val[3];
862 
863  /* Report that we're scanning the heap, advertising total # of blocks */
864  initprog_val[0] = PROGRESS_VACUUM_PHASE_SCAN_HEAP;
865  initprog_val[1] = rel_pages;
866  initprog_val[2] = dead_items->max_items;
867  pgstat_progress_update_multi_param(3, initprog_index, initprog_val);
868 
869  /* Set up an initial range of skippable blocks using the visibility map */
870  next_unskippable_block = lazy_scan_skip(vacrel, &vmbuffer, 0,
871  &next_unskippable_allvis,
872  &skipping_current_range);
873  for (blkno = 0; blkno < rel_pages; blkno++)
874  {
875  Buffer buf;
876  Page page;
877  bool all_visible_according_to_vm;
878  LVPagePruneState prunestate;
879 
880  if (blkno == next_unskippable_block)
881  {
882  /*
883  * Can't skip this page safely. Must scan the page. But
884  * determine the next skippable range after the page first.
885  */
886  all_visible_according_to_vm = next_unskippable_allvis;
887  next_unskippable_block = lazy_scan_skip(vacrel, &vmbuffer,
888  blkno + 1,
889  &next_unskippable_allvis,
890  &skipping_current_range);
891 
892  Assert(next_unskippable_block >= blkno + 1);
893  }
894  else
895  {
896  /* Last page always scanned (may need to set nonempty_pages) */
897  Assert(blkno < rel_pages - 1);
898 
899  if (skipping_current_range)
900  continue;
901 
902  /* Current range is too small to skip -- just scan the page */
903  all_visible_according_to_vm = true;
904  }
905 
906  vacrel->scanned_pages++;
907 
908  /* Report as block scanned, update error traceback information */
911  blkno, InvalidOffsetNumber);
912 
914 
915  /*
916  * Regularly check if wraparound failsafe should trigger.
917  *
918  * There is a similar check inside lazy_vacuum_all_indexes(), but
919  * relfrozenxid might start to look dangerously old before we reach
920  * that point. This check also provides failsafe coverage for the
921  * one-pass strategy, and the two-pass strategy with the index_cleanup
922  * param set to 'off'.
923  */
924  if (blkno - next_failsafe_block >= FAILSAFE_EVERY_PAGES)
925  {
927  next_failsafe_block = blkno;
928  }
929 
930  /*
931  * Consider if we definitely have enough space to process TIDs on page
932  * already. If we are close to overrunning the available space for
933  * dead_items TIDs, pause and do a cycle of vacuuming before we tackle
934  * this page.
935  */
936  Assert(dead_items->max_items >= MaxHeapTuplesPerPage);
937  if (dead_items->max_items - dead_items->num_items < MaxHeapTuplesPerPage)
938  {
939  /*
940  * Before beginning index vacuuming, we release any pin we may
941  * hold on the visibility map page. This isn't necessary for
942  * correctness, but we do it anyway to avoid holding the pin
943  * across a lengthy, unrelated operation.
944  */
945  if (BufferIsValid(vmbuffer))
946  {
947  ReleaseBuffer(vmbuffer);
948  vmbuffer = InvalidBuffer;
949  }
950 
951  /* Perform a round of index and heap vacuuming */
952  vacrel->consider_bypass_optimization = false;
953  lazy_vacuum(vacrel);
954 
955  /*
956  * Vacuum the Free Space Map to make newly-freed space visible on
957  * upper-level FSM pages. Note we have not yet processed blkno.
958  */
959  FreeSpaceMapVacuumRange(vacrel->rel, next_fsm_block_to_vacuum,
960  blkno);
961  next_fsm_block_to_vacuum = blkno;
962 
963  /* Report that we are once again scanning the heap */
966  }
967 
968  /*
969  * Pin the visibility map page in case we need to mark the page
970  * all-visible. In most cases this will be very cheap, because we'll
971  * already have the correct page pinned anyway.
972  */
973  visibilitymap_pin(vacrel->rel, blkno, &vmbuffer);
974 
975  /* Finished preparatory checks. Actually scan the page. */
976  buf = ReadBufferExtended(vacrel->rel, MAIN_FORKNUM, blkno,
977  RBM_NORMAL, vacrel->bstrategy);
978  page = BufferGetPage(buf);
979 
980  /*
981  * We need a buffer cleanup lock to prune HOT chains and defragment
982  * the page in lazy_scan_prune. But when it's not possible to acquire
983  * a cleanup lock right away, we may be able to settle for reduced
984  * processing using lazy_scan_noprune.
985  */
987  {
988  bool hastup,
989  recordfreespace;
990 
992 
993  /* Check for new or empty pages before lazy_scan_noprune call */
994  if (lazy_scan_new_or_empty(vacrel, buf, blkno, page, true,
995  vmbuffer))
996  {
997  /* Processed as new/empty page (lock and pin released) */
998  continue;
999  }
1000 
1001  /* Collect LP_DEAD items in dead_items array, count tuples */
1002  if (lazy_scan_noprune(vacrel, buf, blkno, page, &hastup,
1003  &recordfreespace))
1004  {
1005  Size freespace = 0;
1006 
1007  /*
1008  * Processed page successfully (without cleanup lock) -- just
1009  * need to perform rel truncation and FSM steps, much like the
1010  * lazy_scan_prune case. Don't bother trying to match its
1011  * visibility map setting steps, though.
1012  */
1013  if (hastup)
1014  vacrel->nonempty_pages = blkno + 1;
1015  if (recordfreespace)
1016  freespace = PageGetHeapFreeSpace(page);
1018  if (recordfreespace)
1019  RecordPageWithFreeSpace(vacrel->rel, blkno, freespace);
1020  continue;
1021  }
1022 
1023  /*
1024  * lazy_scan_noprune could not do all required processing. Wait
1025  * for a cleanup lock, and call lazy_scan_prune in the usual way.
1026  */
1027  Assert(vacrel->aggressive);
1030  }
1031 
1032  /* Check for new or empty pages before lazy_scan_prune call */
1033  if (lazy_scan_new_or_empty(vacrel, buf, blkno, page, false, vmbuffer))
1034  {
1035  /* Processed as new/empty page (lock and pin released) */
1036  continue;
1037  }
1038 
1039  /*
1040  * Prune, freeze, and count tuples.
1041  *
1042  * Accumulates details of remaining LP_DEAD line pointers on page in
1043  * dead_items array. This includes LP_DEAD line pointers that we
1044  * pruned ourselves, as well as existing LP_DEAD line pointers that
1045  * were pruned some time earlier. Also considers freezing XIDs in the
1046  * tuple headers of remaining items with storage.
1047  */
1048  lazy_scan_prune(vacrel, buf, blkno, page, &prunestate);
1049 
1050  Assert(!prunestate.all_visible || !prunestate.has_lpdead_items);
1051 
1052  /* Remember the location of the last page with nonremovable tuples */
1053  if (prunestate.hastup)
1054  vacrel->nonempty_pages = blkno + 1;
1055 
1056  if (vacrel->nindexes == 0)
1057  {
1058  /*
1059  * Consider the need to do page-at-a-time heap vacuuming when
1060  * using the one-pass strategy now.
1061  *
1062  * The one-pass strategy will never call lazy_vacuum(). The steps
1063  * performed here can be thought of as the one-pass equivalent of
1064  * a call to lazy_vacuum().
1065  */
1066  if (prunestate.has_lpdead_items)
1067  {
1068  Size freespace;
1069 
1070  lazy_vacuum_heap_page(vacrel, blkno, buf, 0, &vmbuffer);
1071 
1072  /* Forget the LP_DEAD items that we just vacuumed */
1073  dead_items->num_items = 0;
1074 
1075  /*
1076  * Periodically perform FSM vacuuming to make newly-freed
1077  * space visible on upper FSM pages. Note we have not yet
1078  * performed FSM processing for blkno.
1079  */
1080  if (blkno - next_fsm_block_to_vacuum >= VACUUM_FSM_EVERY_PAGES)
1081  {
1082  FreeSpaceMapVacuumRange(vacrel->rel, next_fsm_block_to_vacuum,
1083  blkno);
1084  next_fsm_block_to_vacuum = blkno;
1085  }
1086 
1087  /*
1088  * Now perform FSM processing for blkno, and move on to next
1089  * page.
1090  *
1091  * Our call to lazy_vacuum_heap_page() will have considered if
1092  * it's possible to set all_visible/all_frozen independently
1093  * of lazy_scan_prune(). Note that prunestate was invalidated
1094  * by lazy_vacuum_heap_page() call.
1095  */
1096  freespace = PageGetHeapFreeSpace(page);
1097 
1099  RecordPageWithFreeSpace(vacrel->rel, blkno, freespace);
1100  continue;
1101  }
1102 
1103  /*
1104  * There was no call to lazy_vacuum_heap_page() because pruning
1105  * didn't encounter/create any LP_DEAD items that needed to be
1106  * vacuumed. Prune state has not been invalidated, so proceed
1107  * with prunestate-driven visibility map and FSM steps (just like
1108  * the two-pass strategy).
1109  */
1110  Assert(dead_items->num_items == 0);
1111  }
1112 
1113  /*
1114  * Handle setting visibility map bit based on information from the VM
1115  * (as of last lazy_scan_skip() call), and from prunestate
1116  */
1117  if (!all_visible_according_to_vm && prunestate.all_visible)
1118  {
1120 
1121  if (prunestate.all_frozen)
1122  flags |= VISIBILITYMAP_ALL_FROZEN;
1123 
1124  /*
1125  * It should never be the case that the visibility map page is set
1126  * while the page-level bit is clear, but the reverse is allowed
1127  * (if checksums are not enabled). Regardless, set both bits so
1128  * that we get back in sync.
1129  *
1130  * NB: If the heap page is all-visible but the VM bit is not set,
1131  * we don't need to dirty the heap page. However, if checksums
1132  * are enabled, we do need to make sure that the heap page is
1133  * dirtied before passing it to visibilitymap_set(), because it
1134  * may be logged. Given that this situation should only happen in
1135  * rare cases after a crash, it is not worth optimizing.
1136  */
1137  PageSetAllVisible(page);
1139  visibilitymap_set(vacrel->rel, blkno, buf, InvalidXLogRecPtr,
1140  vmbuffer, prunestate.visibility_cutoff_xid,
1141  flags);
1142  }
1143 
1144  /*
1145  * As of PostgreSQL 9.2, the visibility map bit should never be set if
1146  * the page-level bit is clear. However, it's possible that the bit
1147  * got cleared after lazy_scan_skip() was called, so we must recheck
1148  * with buffer lock before concluding that the VM is corrupt.
1149  */
1150  else if (all_visible_according_to_vm && !PageIsAllVisible(page)
1151  && VM_ALL_VISIBLE(vacrel->rel, blkno, &vmbuffer))
1152  {
1153  elog(WARNING, "page is not marked all-visible but visibility map bit is set in relation \"%s\" page %u",
1154  vacrel->relname, blkno);
1155  visibilitymap_clear(vacrel->rel, blkno, vmbuffer,
1157  }
1158 
1159  /*
1160  * It's possible for the value returned by
1161  * GetOldestNonRemovableTransactionId() to move backwards, so it's not
1162  * wrong for us to see tuples that appear to not be visible to
1163  * everyone yet, while PD_ALL_VISIBLE is already set. The real safe
1164  * xmin value never moves backwards, but
1165  * GetOldestNonRemovableTransactionId() is conservative and sometimes
1166  * returns a value that's unnecessarily small, so if we see that
1167  * contradiction it just means that the tuples that we think are not
1168  * visible to everyone yet actually are, and the PD_ALL_VISIBLE flag
1169  * is correct.
1170  *
1171  * There should never be LP_DEAD items on a page with PD_ALL_VISIBLE
1172  * set, however.
1173  */
1174  else if (prunestate.has_lpdead_items && PageIsAllVisible(page))
1175  {
1176  elog(WARNING, "page containing LP_DEAD items is marked as all-visible in relation \"%s\" page %u",
1177  vacrel->relname, blkno);
1178  PageClearAllVisible(page);
1180  visibilitymap_clear(vacrel->rel, blkno, vmbuffer,
1182  }
1183 
1184  /*
1185  * If the all-visible page is all-frozen but not marked as such yet,
1186  * mark it as all-frozen. Note that all_frozen is only valid if
1187  * all_visible is true, so we must check both prunestate fields.
1188  */
1189  else if (all_visible_according_to_vm && prunestate.all_visible &&
1190  prunestate.all_frozen &&
1191  !VM_ALL_FROZEN(vacrel->rel, blkno, &vmbuffer))
1192  {
1193  /*
1194  * We can pass InvalidTransactionId as the cutoff XID here,
1195  * because setting the all-frozen bit doesn't cause recovery
1196  * conflicts.
1197  */
1198  visibilitymap_set(vacrel->rel, blkno, buf, InvalidXLogRecPtr,
1199  vmbuffer, InvalidTransactionId,
1201  }
1202 
1203  /*
1204  * Final steps for block: drop cleanup lock, record free space in the
1205  * FSM
1206  */
1207  if (prunestate.has_lpdead_items && vacrel->do_index_vacuuming)
1208  {
1209  /*
1210  * Wait until lazy_vacuum_heap_rel() to save free space. This
1211  * doesn't just save us some cycles; it also allows us to record
1212  * any additional free space that lazy_vacuum_heap_page() will
1213  * make available in cases where it's possible to truncate the
1214  * page's line pointer array.
1215  *
1216  * Note: It's not in fact 100% certain that we really will call
1217  * lazy_vacuum_heap_rel() -- lazy_vacuum() might yet opt to skip
1218  * index vacuuming (and so must skip heap vacuuming). This is
1219  * deemed okay because it only happens in emergencies, or when
1220  * there is very little free space anyway. (Besides, we start
1221  * recording free space in the FSM once index vacuuming has been
1222  * abandoned.)
1223  *
1224  * Note: The one-pass (no indexes) case is only supposed to make
1225  * it this far when there were no LP_DEAD items during pruning.
1226  */
1227  Assert(vacrel->nindexes > 0);
1229  }
1230  else
1231  {
1232  Size freespace = PageGetHeapFreeSpace(page);
1233 
1235  RecordPageWithFreeSpace(vacrel->rel, blkno, freespace);
1236  }
1237  }
1238 
1239  vacrel->blkno = InvalidBlockNumber;
1240  if (BufferIsValid(vmbuffer))
1241  ReleaseBuffer(vmbuffer);
1242 
1243  /* report that everything is now scanned */
1245 
1246  /* now we can compute the new value for pg_class.reltuples */
1247  vacrel->new_live_tuples = vac_estimate_reltuples(vacrel->rel, rel_pages,
1248  vacrel->scanned_pages,
1249  vacrel->live_tuples);
1250 
1251  /*
1252  * Also compute the total number of surviving heap entries. In the
1253  * (unlikely) scenario that new_live_tuples is -1, take it as zero.
1254  */
1255  vacrel->new_rel_tuples =
1256  Max(vacrel->new_live_tuples, 0) + vacrel->recently_dead_tuples +
1257  vacrel->missed_dead_tuples;
1258 
1259  /*
1260  * Do index vacuuming (call each index's ambulkdelete routine), then do
1261  * related heap vacuuming
1262  */
1263  if (dead_items->num_items > 0)
1264  lazy_vacuum(vacrel);
1265 
1266  /*
1267  * Vacuum the remainder of the Free Space Map. We must do this whether or
1268  * not there were indexes, and whether or not we bypassed index vacuuming.
1269  */
1270  if (blkno > next_fsm_block_to_vacuum)
1271  FreeSpaceMapVacuumRange(vacrel->rel, next_fsm_block_to_vacuum, blkno);
1272 
1273  /* report all blocks vacuumed */
1275 
1276  /* Do final index cleanup (call each index's amvacuumcleanup routine) */
1277  if (vacrel->nindexes > 0 && vacrel->do_index_cleanup)
1278  lazy_cleanup_all_indexes(vacrel);
1279 }
void pgstat_progress_update_multi_param(int nparam, const int *index, const int64 *val)
#define InvalidBuffer
Definition: buf.h:25
void ReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3915
void MarkBufferDirty(Buffer buffer)
Definition: bufmgr.c:1573
void LockBufferForCleanup(Buffer buffer)
Definition: bufmgr.c:4213
bool ConditionalLockBufferForCleanup(Buffer buffer)
Definition: bufmgr.c:4390
#define BUFFER_LOCK_UNLOCK
Definition: bufmgr.h:96
#define BufferIsValid(bufnum)
Definition: bufmgr.h:123
Size PageGetHeapFreeSpace(Page page)
Definition: bufpage.c:991
#define PageIsAllVisible(page)
Definition: bufpage.h:384
#define PageClearAllVisible(page)
Definition: bufpage.h:388
#define PageSetAllVisible(page)
Definition: bufpage.h:386
unsigned char uint8
Definition: c.h:450
size_t Size
Definition: c.h:551
void FreeSpaceMapVacuumRange(Relation rel, BlockNumber start, BlockNumber end)
Definition: freespace.c:354
void RecordPageWithFreeSpace(Relation rel, BlockNumber heapBlk, Size spaceAvail)
Definition: freespace.c:182
#define PROGRESS_VACUUM_PHASE_SCAN_HEAP
Definition: progress.h:30
#define PROGRESS_VACUUM_TOTAL_HEAP_BLKS
Definition: progress.h:22
#define PROGRESS_VACUUM_HEAP_BLKS_SCANNED
Definition: progress.h:23
#define PROGRESS_VACUUM_MAX_DEAD_TUPLES
Definition: progress.h:26
#define PROGRESS_VACUUM_HEAP_BLKS_VACUUMED
Definition: progress.h:24
TransactionId visibility_cutoff_xid
Definition: vacuumlazy.c:236
BlockNumber blkno
Definition: vacuumlazy.c:185
void vacuum_delay_point(void)
Definition: vacuum.c:2207
double vac_estimate_reltuples(Relation relation, BlockNumber total_pages, BlockNumber scanned_pages, double scanned_tuples)
Definition: vacuum.c:1222
static void lazy_vacuum(LVRelState *vacrel)
Definition: vacuumlazy.c:2176
static void lazy_cleanup_all_indexes(LVRelState *vacrel)
Definition: vacuumlazy.c:2652
static bool lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf, BlockNumber blkno, Page page, bool sharelock, Buffer vmbuffer)
Definition: vacuumlazy.c:1416
static bool lazy_scan_noprune(LVRelState *vacrel, Buffer buf, BlockNumber blkno, Page page, bool *hastup, bool *recordfreespace)
Definition: vacuumlazy.c:1942
static void lazy_scan_prune(LVRelState *vacrel, Buffer buf, BlockNumber blkno, Page page, LVPagePruneState *prunestate)
Definition: vacuumlazy.c:1539
static int lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer, int index, Buffer *vmbuffer)
Definition: vacuumlazy.c:2482
static BlockNumber lazy_scan_skip(LVRelState *vacrel, Buffer *vmbuffer, BlockNumber next_block, bool *next_unskippable_allvis, bool *skipping_current_range)
Definition: vacuumlazy.c:1304
#define FAILSAFE_EVERY_PAGES
Definition: vacuumlazy.c:99
#define VACUUM_FSM_EVERY_PAGES
Definition: vacuumlazy.c:108
bool visibilitymap_clear(Relation rel, BlockNumber heapBlk, Buffer buf, uint8 flags)
void visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, XLogRecPtr recptr, Buffer vmBuf, TransactionId cutoff_xid, uint8 flags)
void visibilitymap_pin(Relation rel, BlockNumber heapBlk, Buffer *buf)
#define VM_ALL_VISIBLE(r, b, v)
Definition: visibilitymap.h:24
#define VM_ALL_FROZEN(r, b, v)
Definition: visibilitymap.h:26
#define VISIBILITYMAP_VALID_BITS
#define VISIBILITYMAP_ALL_FROZEN
#define VISIBILITYMAP_ALL_VISIBLE
#define InvalidXLogRecPtr
Definition: xlogdefs.h:28

References LVRelState::aggressive, LVPagePruneState::all_frozen, LVPagePruneState::all_visible, Assert(), LVRelState::blkno, LVRelState::bstrategy, buf, BUFFER_LOCK_SHARE, BUFFER_LOCK_UNLOCK, BufferGetPage, BufferIsValid, ConditionalLockBufferForCleanup(), LVRelState::consider_bypass_optimization, LVRelState::dead_items, LVRelState::do_index_cleanup, LVRelState::do_index_vacuuming, elog(), FAILSAFE_EVERY_PAGES, FreeSpaceMapVacuumRange(), LVPagePruneState::has_lpdead_items, LVPagePruneState::hastup, InvalidBlockNumber, InvalidBuffer, InvalidOffsetNumber, InvalidTransactionId, InvalidXLogRecPtr, lazy_check_wraparound_failsafe(), lazy_cleanup_all_indexes(), lazy_scan_new_or_empty(), lazy_scan_noprune(), lazy_scan_prune(), lazy_scan_skip(), lazy_vacuum(), lazy_vacuum_heap_page(), LVRelState::live_tuples, LockBuffer(), LockBufferForCleanup(), MAIN_FORKNUM, MarkBufferDirty(), Max, VacDeadItems::max_items, MaxHeapTuplesPerPage, LVRelState::missed_dead_tuples, LVRelState::new_live_tuples, LVRelState::new_rel_tuples, LVRelState::nindexes, LVRelState::nonempty_pages, VacDeadItems::num_items, PageClearAllVisible, PageGetHeapFreeSpace(), PageIsAllVisible, PageSetAllVisible, pgstat_progress_update_multi_param(), pgstat_progress_update_param(), PROGRESS_VACUUM_HEAP_BLKS_SCANNED, PROGRESS_VACUUM_HEAP_BLKS_VACUUMED, PROGRESS_VACUUM_MAX_DEAD_TUPLES, PROGRESS_VACUUM_PHASE, PROGRESS_VACUUM_PHASE_SCAN_HEAP, PROGRESS_VACUUM_TOTAL_HEAP_BLKS, RBM_NORMAL, ReadBufferExtended(), LVRelState::recently_dead_tuples, RecordPageWithFreeSpace(), LVRelState::rel, LVRelState::rel_pages, ReleaseBuffer(), LVRelState::relname, LVRelState::scanned_pages, UnlockReleaseBuffer(), update_vacuum_error_info(), vac_estimate_reltuples(), vacuum_delay_point(), VACUUM_ERRCB_PHASE_SCAN_HEAP, VACUUM_FSM_EVERY_PAGES, LVPagePruneState::visibility_cutoff_xid, VISIBILITYMAP_ALL_FROZEN, VISIBILITYMAP_ALL_VISIBLE, visibilitymap_clear(), visibilitymap_pin(), visibilitymap_set(), VISIBILITYMAP_VALID_BITS, VM_ALL_FROZEN, VM_ALL_VISIBLE, and WARNING.

Referenced by heap_vacuum_rel().

◆ lazy_scan_new_or_empty()

static bool lazy_scan_new_or_empty ( LVRelState vacrel,
Buffer  buf,
BlockNumber  blkno,
Page  page,
bool  sharelock,
Buffer  vmbuffer 
)
static

Definition at line 1416 of file vacuumlazy.c.

1418 {
1419  Size freespace;
1420 
1421  if (PageIsNew(page))
1422  {
1423  /*
1424  * All-zeroes pages can be left over if either a backend extends the
1425  * relation by a single page, but crashes before the newly initialized
1426  * page has been written out, or when bulk-extending the relation
1427  * (which creates a number of empty pages at the tail end of the
1428  * relation), and then enters them into the FSM.
1429  *
1430  * Note we do not enter the page into the visibilitymap. That has the
1431  * downside that we repeatedly visit this page in subsequent vacuums,
1432  * but otherwise we'll never discover the space on a promoted standby.
1433  * The harm of repeated checking ought to normally not be too bad. The
1434  * space usually should be used at some point, otherwise there
1435  * wouldn't be any regular vacuums.
1436  *
1437  * Make sure these pages are in the FSM, to ensure they can be reused.
1438  * Do that by testing if there's any space recorded for the page. If
1439  * not, enter it. We do so after releasing the lock on the heap page,
1440  * the FSM is approximate, after all.
1441  */
1443 
1444  if (GetRecordedFreeSpace(vacrel->rel, blkno) == 0)
1445  {
1446  freespace = BLCKSZ - SizeOfPageHeaderData;
1447 
1448  RecordPageWithFreeSpace(vacrel->rel, blkno, freespace);
1449  }
1450 
1451  return true;
1452  }
1453 
1454  if (PageIsEmpty(page))
1455  {
1456  /*
1457  * It seems likely that caller will always be able to get a cleanup
1458  * lock on an empty page. But don't take any chances -- escalate to
1459  * an exclusive lock (still don't need a cleanup lock, though).
1460  */
1461  if (sharelock)
1462  {
1465 
1466  if (!PageIsEmpty(page))
1467  {
1468  /* page isn't new or empty -- keep lock and pin for now */
1469  return false;
1470  }
1471  }
1472  else
1473  {
1474  /* Already have a full cleanup lock (which is more than enough) */
1475  }
1476 
1477  /*
1478  * Unlike new pages, empty pages are always set all-visible and
1479  * all-frozen.
1480  */
1481  if (!PageIsAllVisible(page))
1482  {
1484 
1485  /* mark buffer dirty before writing a WAL record */
1487 
1488  /*
1489  * It's possible that another backend has extended the heap,
1490  * initialized the page, and then failed to WAL-log the page due
1491  * to an ERROR. Since heap extension is not WAL-logged, recovery
1492  * might try to replay our record setting the page all-visible and
1493  * find that the page isn't initialized, which will cause a PANIC.
1494  * To prevent that, check whether the page has been previously
1495  * WAL-logged, and if not, do that now.
1496  */
1497  if (RelationNeedsWAL(vacrel->rel) &&
1498  PageGetLSN(page) == InvalidXLogRecPtr)
1499  log_newpage_buffer(buf, true);
1500 
1501  PageSetAllVisible(page);
1502  visibilitymap_set(vacrel->rel, blkno, buf, InvalidXLogRecPtr,
1503  vmbuffer, InvalidTransactionId,
1505  END_CRIT_SECTION();
1506  }
1507 
1508  freespace = PageGetHeapFreeSpace(page);
1510  RecordPageWithFreeSpace(vacrel->rel, blkno, freespace);
1511  return true;
1512  }
1513 
1514  /* page isn't new or empty -- keep lock and pin */
1515  return false;
1516 }
#define BUFFER_LOCK_EXCLUSIVE
Definition: bufmgr.h:98
#define SizeOfPageHeaderData
Definition: bufpage.h:215
#define PageGetLSN(page)
Definition: bufpage.h:365
Size GetRecordedFreeSpace(Relation rel, BlockNumber heapBlk)
Definition: freespace.c:232
#define START_CRIT_SECTION()
Definition: miscadmin.h:148
#define END_CRIT_SECTION()
Definition: miscadmin.h:150
#define RelationNeedsWAL(relation)
Definition: rel.h:613
XLogRecPtr log_newpage_buffer(Buffer buffer, bool page_std)
Definition: xloginsert.c:1177

References buf, BUFFER_LOCK_EXCLUSIVE, BUFFER_LOCK_UNLOCK, END_CRIT_SECTION, GetRecordedFreeSpace(), InvalidTransactionId, InvalidXLogRecPtr, LockBuffer(), log_newpage_buffer(), MarkBufferDirty(), PageGetHeapFreeSpace(), PageGetLSN, PageIsAllVisible, PageIsEmpty, PageIsNew, PageSetAllVisible, RecordPageWithFreeSpace(), LVRelState::rel, RelationNeedsWAL, SizeOfPageHeaderData, START_CRIT_SECTION, UnlockReleaseBuffer(), VISIBILITYMAP_ALL_FROZEN, VISIBILITYMAP_ALL_VISIBLE, and visibilitymap_set().

Referenced by lazy_scan_heap().

◆ lazy_scan_noprune()

static bool lazy_scan_noprune ( LVRelState vacrel,
Buffer  buf,
BlockNumber  blkno,
Page  page,
bool hastup,
bool recordfreespace 
)
static

Definition at line 1942 of file vacuumlazy.c.

1948 {
1949  OffsetNumber offnum,
1950  maxoff;
1951  int lpdead_items,
1952  live_tuples,
1953  recently_dead_tuples,
1954  missed_dead_tuples;
1955  HeapTupleHeader tupleheader;
1956  TransactionId NewRelfrozenXid = vacrel->NewRelfrozenXid;
1957  MultiXactId NewRelminMxid = vacrel->NewRelminMxid;
1958  OffsetNumber deadoffsets[MaxHeapTuplesPerPage];
1959 
1960  Assert(BufferGetBlockNumber(buf) == blkno);
1961 
1962  *hastup = false; /* for now */
1963  *recordfreespace = false; /* for now */
1964 
1965  lpdead_items = 0;
1966  live_tuples = 0;
1967  recently_dead_tuples = 0;
1968  missed_dead_tuples = 0;
1969 
1970  maxoff = PageGetMaxOffsetNumber(page);
1971  for (offnum = FirstOffsetNumber;
1972  offnum <= maxoff;
1973  offnum = OffsetNumberNext(offnum))
1974  {
1975  ItemId itemid;
1976  HeapTupleData tuple;
1977 
1978  vacrel->offnum = offnum;
1979  itemid = PageGetItemId(page, offnum);
1980 
1981  if (!ItemIdIsUsed(itemid))
1982  continue;
1983 
1984  if (ItemIdIsRedirected(itemid))
1985  {
1986  *hastup = true;
1987  continue;
1988  }
1989 
1990  if (ItemIdIsDead(itemid))
1991  {
1992  /*
1993  * Deliberately don't set hastup=true here. See same point in
1994  * lazy_scan_prune for an explanation.
1995  */
1996  deadoffsets[lpdead_items++] = offnum;
1997  continue;
1998  }
1999 
2000  *hastup = true; /* page prevents rel truncation */
2001  tupleheader = (HeapTupleHeader) PageGetItem(page, itemid);
2002  if (heap_tuple_would_freeze(tupleheader,
2003  vacrel->FreezeLimit,
2004  vacrel->MultiXactCutoff,
2005  &NewRelfrozenXid, &NewRelminMxid))
2006  {
2007  /* Tuple with XID < FreezeLimit (or MXID < MultiXactCutoff) */
2008  if (vacrel->aggressive)
2009  {
2010  /*
2011  * Aggressive VACUUMs must always be able to advance rel's
2012  * relfrozenxid to a value >= FreezeLimit (and be able to
2013  * advance rel's relminmxid to a value >= MultiXactCutoff).
2014  * The ongoing aggressive VACUUM won't be able to do that
2015  * unless it can freeze an XID (or MXID) from this tuple now.
2016  *
2017  * The only safe option is to have caller perform processing
2018  * of this page using lazy_scan_prune. Caller might have to
2019  * wait a while for a cleanup lock, but it can't be helped.
2020  */
2021  vacrel->offnum = InvalidOffsetNumber;
2022  return false;
2023  }
2024 
2025  /*
2026  * Non-aggressive VACUUMs are under no obligation to advance
2027  * relfrozenxid (even by one XID). We can be much laxer here.
2028  *
2029  * Currently we always just accept an older final relfrozenxid
2030  * and/or relminmxid value. We never make caller wait or work a
2031  * little harder, even when it likely makes sense to do so.
2032  */
2033  }
2034 
2035  ItemPointerSet(&(tuple.t_self), blkno, offnum);
2036  tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
2037  tuple.t_len = ItemIdGetLength(itemid);
2038  tuple.t_tableOid = RelationGetRelid(vacrel->rel);
2039 
2040  switch (HeapTupleSatisfiesVacuum(&tuple, vacrel->OldestXmin, buf))
2041  {
2043  case HEAPTUPLE_LIVE:
2044 
2045  /*
2046  * Count both cases as live, just like lazy_scan_prune
2047  */
2048  live_tuples++;
2049 
2050  break;
2051  case HEAPTUPLE_DEAD:
2052 
2053  /*
2054  * There is some useful work for pruning to do, that won't be
2055  * done due to failure to get a cleanup lock.
2056  */
2057  missed_dead_tuples++;
2058  break;
2060 
2061  /*
2062  * Count in recently_dead_tuples, just like lazy_scan_prune
2063  */
2064  recently_dead_tuples++;
2065  break;
2067 
2068  /*
2069  * Do not count these rows as live, just like lazy_scan_prune
2070  */
2071  break;
2072  default:
2073  elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
2074  break;
2075  }
2076  }
2077 
2078  vacrel->offnum = InvalidOffsetNumber;
2079 
2080  /*
2081  * By here we know for sure that caller can put off freezing and pruning
2082  * this particular page until the next VACUUM. Remember its details now.
2083  * (lazy_scan_prune expects a clean slate, so we have to do this last.)
2084  */
2085  vacrel->NewRelfrozenXid = NewRelfrozenXid;
2086  vacrel->NewRelminMxid = NewRelminMxid;
2087 
2088  /* Save any LP_DEAD items found on the page in dead_items array */
2089  if (vacrel->nindexes == 0)
2090  {
2091  /* Using one-pass strategy (since table has no indexes) */
2092  if (lpdead_items > 0)
2093  {
2094  /*
2095  * Perfunctory handling for the corner case where a single pass
2096  * strategy VACUUM cannot get a cleanup lock, and it turns out
2097  * that there is one or more LP_DEAD items: just count the LP_DEAD
2098  * items as missed_dead_tuples instead. (This is a bit dishonest,
2099  * but it beats having to maintain specialized heap vacuuming code
2100  * forever, for vanishingly little benefit.)
2101  */
2102  *hastup = true;
2103  missed_dead_tuples += lpdead_items;
2104  }
2105 
2106  *recordfreespace = true;
2107  }
2108  else if (lpdead_items == 0)
2109  {
2110  /*
2111  * Won't be vacuuming this page later, so record page's freespace in
2112  * the FSM now
2113  */
2114  *recordfreespace = true;
2115  }
2116  else
2117  {
2118  VacDeadItems *dead_items = vacrel->dead_items;
2119  ItemPointerData tmp;
2120 
2121  /*
2122  * Page has LP_DEAD items, and so any references/TIDs that remain in
2123  * indexes will be deleted during index vacuuming (and then marked
2124  * LP_UNUSED in the heap)
2125  */
2126  vacrel->lpdead_item_pages++;
2127 
2128  ItemPointerSetBlockNumber(&tmp, blkno);
2129 
2130  for (int i = 0; i < lpdead_items; i++)
2131  {
2132  ItemPointerSetOffsetNumber(&tmp, deadoffsets[i]);
2133  dead_items->items[dead_items->num_items++] = tmp;
2134  }
2135 
2136  Assert(dead_items->num_items <= dead_items->max_items);
2138  dead_items->num_items);
2139 
2140  vacrel->lpdead_items += lpdead_items;
2141 
2142  /*
2143  * Assume that we'll go on to vacuum this heap page during final pass
2144  * over the heap. Don't record free space until then.
2145  */
2146  *recordfreespace = false;
2147  }
2148 
2149  /*
2150  * Finally, add relevant page-local counts to whole-VACUUM counts
2151  */
2152  vacrel->live_tuples += live_tuples;
2153  vacrel->recently_dead_tuples += recently_dead_tuples;
2154  vacrel->missed_dead_tuples += missed_dead_tuples;
2155  if (missed_dead_tuples > 0)
2156  vacrel->missed_dead_pages++;
2157 
2158  /* Caller won't need to call lazy_scan_prune with same page */
2159  return true;
2160 }
bool heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid, MultiXactId cutoff_multi, TransactionId *relfrozenxid_out, MultiXactId *relminmxid_out)
Definition: heapam.c:7230
#define ItemPointerSetOffsetNumber(pointer, offsetNumber)
Definition: itemptr.h:148
#define ItemPointerSetBlockNumber(pointer, blockNumber)
Definition: itemptr.h:138
#define PROGRESS_VACUUM_NUM_DEAD_TUPLES
Definition: progress.h:27
ItemPointerData items[FLEXIBLE_ARRAY_MEMBER]
Definition: vacuum.h:247

References LVRelState::aggressive, Assert(), buf, BufferGetBlockNumber(), LVRelState::dead_items, elog(), ERROR, FirstOffsetNumber, LVRelState::FreezeLimit, heap_tuple_would_freeze(), HEAPTUPLE_DEAD, HEAPTUPLE_DELETE_IN_PROGRESS, HEAPTUPLE_INSERT_IN_PROGRESS, HEAPTUPLE_LIVE, HEAPTUPLE_RECENTLY_DEAD, HeapTupleSatisfiesVacuum(), i, InvalidOffsetNumber, ItemIdGetLength, ItemIdIsDead, ItemIdIsRedirected, ItemIdIsUsed, ItemPointerSet, ItemPointerSetBlockNumber, ItemPointerSetOffsetNumber, VacDeadItems::items, LVRelState::live_tuples, LVRelState::lpdead_item_pages, LVRelState::lpdead_items, VacDeadItems::max_items, MaxHeapTuplesPerPage, LVRelState::missed_dead_pages, LVRelState::missed_dead_tuples, LVRelState::MultiXactCutoff, LVRelState::NewRelfrozenXid, LVRelState::NewRelminMxid, LVRelState::nindexes, VacDeadItems::num_items, LVRelState::offnum, OffsetNumberNext, LVRelState::OldestXmin, PageGetItem, PageGetItemId, PageGetMaxOffsetNumber, pgstat_progress_update_param(), PROGRESS_VACUUM_NUM_DEAD_TUPLES, LVRelState::recently_dead_tuples, LVRelState::rel, RelationGetRelid, HeapTupleData::t_data, HeapTupleData::t_len, HeapTupleData::t_self, and HeapTupleData::t_tableOid.

Referenced by lazy_scan_heap().

◆ lazy_scan_prune()

static void lazy_scan_prune ( LVRelState vacrel,
Buffer  buf,
BlockNumber  blkno,
Page  page,
LVPagePruneState prunestate 
)
static

Definition at line 1539 of file vacuumlazy.c.

1544 {
1545  Relation rel = vacrel->rel;
1546  OffsetNumber offnum,
1547  maxoff;
1548  ItemId itemid;
1549  HeapTupleData tuple;
1550  HTSV_Result res;
1551  int tuples_deleted,
1552  lpdead_items,
1553  live_tuples,
1554  recently_dead_tuples;
1555  int nnewlpdead;
1556  int nfrozen;
1557  TransactionId NewRelfrozenXid;
1558  MultiXactId NewRelminMxid;
1559  OffsetNumber deadoffsets[MaxHeapTuplesPerPage];
1561 
1562  Assert(BufferGetBlockNumber(buf) == blkno);
1563 
1564  /*
1565  * maxoff might be reduced following line pointer array truncation in
1566  * heap_page_prune. That's safe for us to ignore, since the reclaimed
1567  * space will continue to look like LP_UNUSED items below.
1568  */
1569  maxoff = PageGetMaxOffsetNumber(page);
1570 
1571 retry:
1572 
1573  /* Initialize (or reset) page-level state */
1574  NewRelfrozenXid = vacrel->NewRelfrozenXid;
1575  NewRelminMxid = vacrel->NewRelminMxid;
1576  tuples_deleted = 0;
1577  lpdead_items = 0;
1578  live_tuples = 0;
1579  recently_dead_tuples = 0;
1580 
1581  /*
1582  * Prune all HOT-update chains in this page.
1583  *
1584  * We count tuples removed by the pruning step as tuples_deleted. Its
1585  * final value can be thought of as the number of tuples that have been
1586  * deleted from the table. It should not be confused with lpdead_items;
1587  * lpdead_items's final value can be thought of as the number of tuples
1588  * that were deleted from indexes.
1589  */
1590  tuples_deleted = heap_page_prune(rel, buf, vacrel->vistest,
1591  InvalidTransactionId, 0, &nnewlpdead,
1592  &vacrel->offnum);
1593 
1594  /*
1595  * Now scan the page to collect LP_DEAD items and check for tuples
1596  * requiring freezing among remaining tuples with storage
1597  */
1598  prunestate->hastup = false;
1599  prunestate->has_lpdead_items = false;
1600  prunestate->all_visible = true;
1601  prunestate->all_frozen = true;
1603  nfrozen = 0;
1604 
1605  for (offnum = FirstOffsetNumber;
1606  offnum <= maxoff;
1607  offnum = OffsetNumberNext(offnum))
1608  {
1609  bool tuple_totally_frozen;
1610 
1611  /*
1612  * Set the offset number so that we can display it along with any
1613  * error that occurred while processing this tuple.
1614  */
1615  vacrel->offnum = offnum;
1616  itemid = PageGetItemId(page, offnum);
1617 
1618  if (!ItemIdIsUsed(itemid))
1619  continue;
1620 
1621  /* Redirect items mustn't be touched */
1622  if (ItemIdIsRedirected(itemid))
1623  {
1624  prunestate->hastup = true; /* page won't be truncatable */
1625  continue;
1626  }
1627 
1628  /*
1629  * LP_DEAD items are processed outside of the loop.
1630  *
1631  * Note that we deliberately don't set hastup=true in the case of an
1632  * LP_DEAD item here, which is not how count_nondeletable_pages() does
1633  * it -- it only considers pages empty/truncatable when they have no
1634  * items at all (except LP_UNUSED items).
1635  *
1636  * Our assumption is that any LP_DEAD items we encounter here will
1637  * become LP_UNUSED inside lazy_vacuum_heap_page() before we actually
1638  * call count_nondeletable_pages(). In any case our opinion of
1639  * whether or not a page 'hastup' (which is how our caller sets its
1640  * vacrel->nonempty_pages value) is inherently race-prone. It must be
1641  * treated as advisory/unreliable, so we might as well be slightly
1642  * optimistic.
1643  */
1644  if (ItemIdIsDead(itemid))
1645  {
1646  deadoffsets[lpdead_items++] = offnum;
1647  prunestate->all_visible = false;
1648  prunestate->has_lpdead_items = true;
1649  continue;
1650  }
1651 
1652  Assert(ItemIdIsNormal(itemid));
1653 
1654  ItemPointerSet(&(tuple.t_self), blkno, offnum);
1655  tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
1656  tuple.t_len = ItemIdGetLength(itemid);
1657  tuple.t_tableOid = RelationGetRelid(rel);
1658 
1659  /*
1660  * DEAD tuples are almost always pruned into LP_DEAD line pointers by
1661  * heap_page_prune(), but it's possible that the tuple state changed
1662  * since heap_page_prune() looked. Handle that here by restarting.
1663  * (See comments at the top of function for a full explanation.)
1664  */
1665  res = HeapTupleSatisfiesVacuum(&tuple, vacrel->OldestXmin, buf);
1666 
1667  if (unlikely(res == HEAPTUPLE_DEAD))
1668  goto retry;
1669 
1670  /*
1671  * The criteria for counting a tuple as live in this block need to
1672  * match what analyze.c's acquire_sample_rows() does, otherwise VACUUM
1673  * and ANALYZE may produce wildly different reltuples values, e.g.
1674  * when there are many recently-dead tuples.
1675  *
1676  * The logic here is a bit simpler than acquire_sample_rows(), as
1677  * VACUUM can't run inside a transaction block, which makes some cases
1678  * impossible (e.g. in-progress insert from the same transaction).
1679  *
1680  * We treat LP_DEAD items (which are the closest thing to DEAD tuples
1681  * that might be seen here) differently, too: we assume that they'll
1682  * become LP_UNUSED before VACUUM finishes. This difference is only
1683  * superficial. VACUUM effectively agrees with ANALYZE about DEAD
1684  * items, in the end. VACUUM won't remember LP_DEAD items, but only
1685  * because they're not supposed to be left behind when it is done.
1686  * (Cases where we bypass index vacuuming will violate this optimistic
1687  * assumption, but the overall impact of that should be negligible.)
1688  */
1689  switch (res)
1690  {
1691  case HEAPTUPLE_LIVE:
1692 
1693  /*
1694  * Count it as live. Not only is this natural, but it's also
1695  * what acquire_sample_rows() does.
1696  */
1697  live_tuples++;
1698 
1699  /*
1700  * Is the tuple definitely visible to all transactions?
1701  *
1702  * NB: Like with per-tuple hint bits, we can't set the
1703  * PD_ALL_VISIBLE flag if the inserter committed
1704  * asynchronously. See SetHintBits for more info. Check that
1705  * the tuple is hinted xmin-committed because of that.
1706  */
1707  if (prunestate->all_visible)
1708  {
1709  TransactionId xmin;
1710 
1712  {
1713  prunestate->all_visible = false;
1714  break;
1715  }
1716 
1717  /*
1718  * The inserter definitely committed. But is it old enough
1719  * that everyone sees it as committed?
1720  */
1721  xmin = HeapTupleHeaderGetXmin(tuple.t_data);
1722  if (!TransactionIdPrecedes(xmin, vacrel->OldestXmin))
1723  {
1724  prunestate->all_visible = false;
1725  break;
1726  }
1727 
1728  /* Track newest xmin on page. */
1729  if (TransactionIdFollows(xmin, prunestate->visibility_cutoff_xid))
1730  prunestate->visibility_cutoff_xid = xmin;
1731  }
1732  break;
1734 
1735  /*
1736  * If tuple is recently dead then we must not remove it from
1737  * the relation. (We only remove items that are LP_DEAD from
1738  * pruning.)
1739  */
1740  recently_dead_tuples++;
1741  prunestate->all_visible = false;
1742  break;
1744 
1745  /*
1746  * We do not count these rows as live, because we expect the
1747  * inserting transaction to update the counters at commit, and
1748  * we assume that will happen only after we report our
1749  * results. This assumption is a bit shaky, but it is what
1750  * acquire_sample_rows() does, so be consistent.
1751  */
1752  prunestate->all_visible = false;
1753  break;
1755  /* This is an expected case during concurrent vacuum */
1756  prunestate->all_visible = false;
1757 
1758  /*
1759  * Count such rows as live. As above, we assume the deleting
1760  * transaction will commit and update the counters after we
1761  * report.
1762  */
1763  live_tuples++;
1764  break;
1765  default:
1766  elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
1767  break;
1768  }
1769 
1770  /*
1771  * Non-removable tuple (i.e. tuple with storage).
1772  *
1773  * Check tuple left behind after pruning to see if needs to be frozen
1774  * now.
1775  */
1776  prunestate->hastup = true; /* page makes rel truncation unsafe */
1778  vacrel->relfrozenxid,
1779  vacrel->relminmxid,
1780  vacrel->FreezeLimit,
1781  vacrel->MultiXactCutoff,
1782  &frozen[nfrozen], &tuple_totally_frozen,
1783  &NewRelfrozenXid, &NewRelminMxid))
1784  {
1785  /* Will execute freeze below */
1786  frozen[nfrozen++].offset = offnum;
1787  }
1788 
1789  /*
1790  * If tuple is not frozen (and not about to become frozen) then caller
1791  * had better not go on to set this page's VM bit
1792  */
1793  if (!tuple_totally_frozen)
1794  prunestate->all_frozen = false;
1795  }
1796 
1797  vacrel->offnum = InvalidOffsetNumber;
1798 
1799  /*
1800  * We have now divided every item on the page into either an LP_DEAD item
1801  * that will need to be vacuumed in indexes later, or a LP_NORMAL tuple
1802  * that remains and needs to be considered for freezing now (LP_UNUSED and
1803  * LP_REDIRECT items also remain, but are of no further interest to us).
1804  */
1805  vacrel->NewRelfrozenXid = NewRelfrozenXid;
1806  vacrel->NewRelminMxid = NewRelminMxid;
1807 
1808  /*
1809  * Consider the need to freeze any items with tuple storage from the page
1810  * first (arbitrary)
1811  */
1812  if (nfrozen > 0)
1813  {
1814  Assert(prunestate->hastup);
1815 
1816  /*
1817  * At least one tuple with storage needs to be frozen -- execute that
1818  * now.
1819  *
1820  * If we need to freeze any tuples we'll mark the buffer dirty, and
1821  * write a WAL record recording the changes. We must log the changes
1822  * to be crash-safe against future truncation of CLOG.
1823  */
1825 
1827 
1828  /* execute collected freezes */
1829  for (int i = 0; i < nfrozen; i++)
1830  {
1831  HeapTupleHeader htup;
1832 
1833  itemid = PageGetItemId(page, frozen[i].offset);
1834  htup = (HeapTupleHeader) PageGetItem(page, itemid);
1835 
1836  heap_execute_freeze_tuple(htup, &frozen[i]);
1837  }
1838 
1839  /* Now WAL-log freezing if necessary */
1840  if (RelationNeedsWAL(vacrel->rel))
1841  {
1842  XLogRecPtr recptr;
1843 
1844  recptr = log_heap_freeze(vacrel->rel, buf, vacrel->FreezeLimit,
1845  frozen, nfrozen);
1846  PageSetLSN(page, recptr);
1847  }
1848 
1849  END_CRIT_SECTION();
1850  }
1851 
1852  /*
1853  * The second pass over the heap can also set visibility map bits, using
1854  * the same approach. This is important when the table frequently has a
1855  * few old LP_DEAD items on each page by the time we get to it (typically
1856  * because past opportunistic pruning operations freed some non-HOT
1857  * tuples).
1858  *
1859  * VACUUM will call heap_page_is_all_visible() during the second pass over
1860  * the heap to determine all_visible and all_frozen for the page -- this
1861  * is a specialized version of the logic from this function. Now that
1862  * we've finished pruning and freezing, make sure that we're in total
1863  * agreement with heap_page_is_all_visible() using an assertion.
1864  */
1865 #ifdef USE_ASSERT_CHECKING
1866  /* Note that all_frozen value does not matter when !all_visible */
1867  if (prunestate->all_visible)
1868  {
1869  TransactionId cutoff;
1870  bool all_frozen;
1871 
1872  if (!heap_page_is_all_visible(vacrel, buf, &cutoff, &all_frozen))
1873  Assert(false);
1874 
1875  Assert(lpdead_items == 0);
1876  Assert(prunestate->all_frozen == all_frozen);
1877 
1878  /*
1879  * It's possible that we froze tuples and made the page's XID cutoff
1880  * (for recovery conflict purposes) FrozenTransactionId. This is okay
1881  * because visibility_cutoff_xid will be logged by our caller in a
1882  * moment.
1883  */
1884  Assert(cutoff == FrozenTransactionId ||
1885  cutoff == prunestate->visibility_cutoff_xid);
1886  }
1887 #endif
1888 
1889  /*
1890  * Now save details of the LP_DEAD items from the page in vacrel
1891  */
1892  if (lpdead_items > 0)
1893  {
1894  VacDeadItems *dead_items = vacrel->dead_items;
1895  ItemPointerData tmp;
1896 
1897  Assert(!prunestate->all_visible);
1898  Assert(prunestate->has_lpdead_items);
1899 
1900  vacrel->lpdead_item_pages++;
1901 
1902  ItemPointerSetBlockNumber(&tmp, blkno);
1903 
1904  for (int i = 0; i < lpdead_items; i++)
1905  {
1906  ItemPointerSetOffsetNumber(&tmp, deadoffsets[i]);
1907  dead_items->items[dead_items->num_items++] = tmp;
1908  }
1909 
1910  Assert(dead_items->num_items <= dead_items->max_items);
1912  dead_items->num_items);
1913  }
1914 
1915  /* Finally, add page-local counts to whole-VACUUM counts */
1916  vacrel->tuples_deleted += tuples_deleted;
1917  vacrel->lpdead_items += lpdead_items;
1918  vacrel->live_tuples += live_tuples;
1919  vacrel->recently_dead_tuples += recently_dead_tuples;
1920 }
#define PageSetLSN(page, lsn)
Definition: bufpage.h:367
void heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
Definition: heapam.c:6760
bool heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId relfrozenxid, TransactionId relminmxid, TransactionId cutoff_xid, TransactionId cutoff_multi, xl_heap_freeze_tuple *frz, bool *totally_frozen, TransactionId *relfrozenxid_out, MultiXactId *relminmxid_out)
Definition: heapam.c:6463
XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer, TransactionId cutoff_xid, xl_heap_freeze_tuple *tuples, int ntuples)
Definition: heapam.c:8151
HTSV_Result
Definition: heapam.h:94
int heap_page_prune(Relation relation, Buffer buffer, GlobalVisState *vistest, TransactionId old_snap_xmin, TimestampTz old_snap_ts, int *nnewlpdead, OffsetNumber *off_loc)
Definition: pruneheap.c:266
OffsetNumber offset
Definition: heapam_xlog.h:327
#define FrozenTransactionId
Definition: transam.h:33
static bool heap_page_is_all_visible(LVRelState *vacrel, Buffer buf, TransactionId *visibility_cutoff_xid, bool *all_frozen)
Definition: vacuumlazy.c:3225
uint64 XLogRecPtr
Definition: xlogdefs.h:21

References LVPagePruneState::all_frozen, LVPagePruneState::all_visible, Assert(), buf, BufferGetBlockNumber(), LVRelState::dead_items, elog(), END_CRIT_SECTION, ERROR, FirstOffsetNumber, LVRelState::FreezeLimit, FrozenTransactionId, LVPagePruneState::has_lpdead_items, LVPagePruneState::hastup, heap_execute_freeze_tuple(), heap_page_is_all_visible(), heap_page_prune(), heap_prepare_freeze_tuple(), HEAPTUPLE_DEAD, HEAPTUPLE_DELETE_IN_PROGRESS, HEAPTUPLE_INSERT_IN_PROGRESS, HEAPTUPLE_LIVE, HEAPTUPLE_RECENTLY_DEAD, HeapTupleHeaderGetXmin, HeapTupleHeaderXminCommitted, HeapTupleSatisfiesVacuum(), i, InvalidOffsetNumber, InvalidTransactionId, ItemIdGetLength, ItemIdIsDead, ItemIdIsNormal, ItemIdIsRedirected, ItemIdIsUsed, ItemPointerSet, ItemPointerSetBlockNumber, ItemPointerSetOffsetNumber, VacDeadItems::items, LVRelState::live_tuples, log_heap_freeze(), LVRelState::lpdead_item_pages, LVRelState::lpdead_items, MarkBufferDirty(), VacDeadItems::max_items, MaxHeapTuplesPerPage, LVRelState::MultiXactCutoff, LVRelState::NewRelfrozenXid, LVRelState::NewRelminMxid, VacDeadItems::num_items, LVRelState::offnum, xl_heap_freeze_tuple::offset, OffsetNumberNext, LVRelState::OldestXmin, PageGetItem, PageGetItemId, PageGetMaxOffsetNumber, PageSetLSN, pgstat_progress_update_param(), PROGRESS_VACUUM_NUM_DEAD_TUPLES, LVRelState::recently_dead_tuples, LVRelState::rel, RelationGetRelid, RelationNeedsWAL, LVRelState::relfrozenxid, LVRelState::relminmxid, res, START_CRIT_SECTION, HeapTupleData::t_data, HeapTupleData::t_len, HeapTupleData::t_self, HeapTupleData::t_tableOid, TransactionIdFollows(), TransactionIdPrecedes(), LVRelState::tuples_deleted, unlikely, LVPagePruneState::visibility_cutoff_xid, and LVRelState::vistest.

Referenced by lazy_scan_heap().

◆ lazy_scan_skip()

static BlockNumber lazy_scan_skip ( LVRelState vacrel,
Buffer vmbuffer,
BlockNumber  next_block,
bool next_unskippable_allvis,
bool skipping_current_range 
)
static

Definition at line 1304 of file vacuumlazy.c.

1306 {
1307  BlockNumber rel_pages = vacrel->rel_pages,
1308  next_unskippable_block = next_block,
1309  nskippable_blocks = 0;
1310  bool skipsallvis = false;
1311 
1312  *next_unskippable_allvis = true;
1313  while (next_unskippable_block < rel_pages)
1314  {
1315  uint8 mapbits = visibilitymap_get_status(vacrel->rel,
1316  next_unskippable_block,
1317  vmbuffer);
1318 
1319  if ((mapbits & VISIBILITYMAP_ALL_VISIBLE) == 0)
1320  {
1321  Assert((mapbits & VISIBILITYMAP_ALL_FROZEN) == 0);
1322  *next_unskippable_allvis = false;
1323  break;
1324  }
1325 
1326  /*
1327  * Caller must scan the last page to determine whether it has tuples
1328  * (caller must have the opportunity to set vacrel->nonempty_pages).
1329  * This rule avoids having lazy_truncate_heap() take access-exclusive
1330  * lock on rel to attempt a truncation that fails anyway, just because
1331  * there are tuples on the last page (it is likely that there will be
1332  * tuples on other nearby pages as well, but those can be skipped).
1333  *
1334  * Implement this by always treating the last block as unsafe to skip.
1335  */
1336  if (next_unskippable_block == rel_pages - 1)
1337  break;
1338 
1339  /* DISABLE_PAGE_SKIPPING makes all skipping unsafe */
1340  if (!vacrel->skipwithvm)
1341  break;
1342 
1343  /*
1344  * Aggressive VACUUM caller can't skip pages just because they are
1345  * all-visible. They may still skip all-frozen pages, which can't
1346  * contain XIDs < OldestXmin (XIDs that aren't already frozen by now).
1347  */
1348  if ((mapbits & VISIBILITYMAP_ALL_FROZEN) == 0)
1349  {
1350  if (vacrel->aggressive)
1351  break;
1352 
1353  /*
1354  * All-visible block is safe to skip in non-aggressive case. But
1355  * remember that the final range contains such a block for later.
1356  */
1357  skipsallvis = true;
1358  }
1359 
1361  next_unskippable_block++;
1362  nskippable_blocks++;
1363  }
1364 
1365  /*
1366  * We only skip a range with at least SKIP_PAGES_THRESHOLD consecutive
1367  * pages. Since we're reading sequentially, the OS should be doing
1368  * readahead for us, so there's no gain in skipping a page now and then.
1369  * Skipping such a range might even discourage sequential detection.
1370  *
1371  * This test also enables more frequent relfrozenxid advancement during
1372  * non-aggressive VACUUMs. If the range has any all-visible pages then
1373  * skipping makes updating relfrozenxid unsafe, which is a real downside.
1374  */
1375  if (nskippable_blocks < SKIP_PAGES_THRESHOLD)
1376  *skipping_current_range = false;
1377  else
1378  {
1379  *skipping_current_range = true;
1380  if (skipsallvis)
1381  vacrel->skippedallvis = true;
1382  }
1383 
1384  return next_unskippable_block;
1385 }
#define SKIP_PAGES_THRESHOLD
Definition: vacuumlazy.c:115
uint8 visibilitymap_get_status(Relation rel, BlockNumber heapBlk, Buffer *buf)

References LVRelState::aggressive, Assert(), LVRelState::rel, LVRelState::rel_pages, SKIP_PAGES_THRESHOLD, LVRelState::skippedallvis, LVRelState::skipwithvm, vacuum_delay_point(), VISIBILITYMAP_ALL_FROZEN, VISIBILITYMAP_ALL_VISIBLE, and visibilitymap_get_status().

Referenced by lazy_scan_heap().

◆ lazy_truncate_heap()

static void lazy_truncate_heap ( LVRelState vacrel)
static

Definition at line 2830 of file vacuumlazy.c.

2831 {
2832  BlockNumber orig_rel_pages = vacrel->rel_pages;
2833  BlockNumber new_rel_pages;
2834  bool lock_waiter_detected;
2835  int lock_retry;
2836 
2837  /* Report that we are now truncating */
2840 
2841  /* Update error traceback information one last time */
2844 
2845  /*
2846  * Loop until no more truncating can be done.
2847  */
2848  do
2849  {
2850  /*
2851  * We need full exclusive lock on the relation in order to do
2852  * truncation. If we can't get it, give up rather than waiting --- we
2853  * don't want to block other backends, and we don't want to deadlock
2854  * (which is quite possible considering we already hold a lower-grade
2855  * lock).
2856  */
2857  lock_waiter_detected = false;
2858  lock_retry = 0;
2859  while (true)
2860  {
2862  break;
2863 
2864  /*
2865  * Check for interrupts while trying to (re-)acquire the exclusive
2866  * lock.
2867  */
2869 
2870  if (++lock_retry > (VACUUM_TRUNCATE_LOCK_TIMEOUT /
2872  {
2873  /*
2874  * We failed to establish the lock in the specified number of
2875  * retries. This means we give up truncating.
2876  */
2877  ereport(vacrel->verbose ? INFO : DEBUG2,
2878  (errmsg("\"%s\": stopping truncate due to conflicting lock request",
2879  vacrel->relname)));
2880  return;
2881  }
2882 
2883  (void) WaitLatch(MyLatch,
2888  }
2889 
2890  /*
2891  * Now that we have exclusive lock, look to see if the rel has grown
2892  * whilst we were vacuuming with non-exclusive lock. If so, give up;
2893  * the newly added pages presumably contain non-deletable tuples.
2894  */
2895  new_rel_pages = RelationGetNumberOfBlocks(vacrel->rel);
2896  if (new_rel_pages != orig_rel_pages)
2897  {
2898  /*
2899  * Note: we intentionally don't update vacrel->rel_pages with the
2900  * new rel size here. If we did, it would amount to assuming that
2901  * the new pages are empty, which is unlikely. Leaving the numbers
2902  * alone amounts to assuming that the new pages have the same
2903  * tuple density as existing ones, which is less unlikely.
2904  */
2906  return;
2907  }
2908 
2909  /*
2910  * Scan backwards from the end to verify that the end pages actually
2911  * contain no tuples. This is *necessary*, not optional, because
2912  * other backends could have added tuples to these pages whilst we
2913  * were vacuuming.
2914  */
2915  new_rel_pages = count_nondeletable_pages(vacrel, &lock_waiter_detected);
2916  vacrel->blkno = new_rel_pages;
2917 
2918  if (new_rel_pages >= orig_rel_pages)
2919  {
2920  /* can't do anything after all */
2922  return;
2923  }
2924 
2925  /*
2926  * Okay to truncate.
2927  */
2928  RelationTruncate(vacrel->rel, new_rel_pages);
2929 
2930  /*
2931  * We can release the exclusive lock as soon as we have truncated.
2932  * Other backends can't safely access the relation until they have
2933  * processed the smgr invalidation that smgrtruncate sent out ... but
2934  * that should happen as part of standard invalidation processing once
2935  * they acquire lock on the relation.
2936  */
2938 
2939  /*
2940  * Update statistics. Here, it *is* correct to adjust rel_pages
2941  * without also touching reltuples, since the tuple count wasn't
2942  * changed by the truncation.
2943  */
2944  vacrel->removed_pages += orig_rel_pages - new_rel_pages;
2945  vacrel->rel_pages = new_rel_pages;
2946 
2947  ereport(vacrel->verbose ? INFO : DEBUG2,
2948  (errmsg("table \"%s\": truncated %u to %u pages",
2949  vacrel->relname,
2950  orig_rel_pages, new_rel_pages)));
2951  orig_rel_pages = new_rel_pages;
2952  } while (new_rel_pages > vacrel->nonempty_pages && lock_waiter_detected);
2953 }
struct Latch * MyLatch
Definition: globals.c:58
void ResetLatch(Latch *latch)
Definition: latch.c:683
int WaitLatch(Latch *latch, int wakeEvents, long timeout, uint32 wait_event_info)
Definition: latch.c:476
#define WL_TIMEOUT
Definition: latch.h:128
#define WL_EXIT_ON_PM_DEATH
Definition: latch.h:130
#define WL_LATCH_SET
Definition: latch.h:125
void UnlockRelation(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:311
bool ConditionalLockRelation(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:276
#define PROGRESS_VACUUM_PHASE_TRUNCATE
Definition: progress.h:34
void RelationTruncate(Relation rel, BlockNumber nblocks)
Definition: storage.c:287
#define VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL
Definition: vacuumlazy.c:87
#define VACUUM_TRUNCATE_LOCK_TIMEOUT
Definition: vacuumlazy.c:88
static BlockNumber count_nondeletable_pages(LVRelState *vacrel, bool *lock_waiter_detected)
Definition: vacuumlazy.c:2961
@ WAIT_EVENT_VACUUM_TRUNCATE
Definition: wait_event.h:149

References AccessExclusiveLock, LVRelState::blkno, CHECK_FOR_INTERRUPTS, ConditionalLockRelation(), count_nondeletable_pages(), DEBUG2, ereport, errmsg(), INFO, InvalidOffsetNumber, MyLatch, LVRelState::nonempty_pages, pgstat_progress_update_param(), PROGRESS_VACUUM_PHASE, PROGRESS_VACUUM_PHASE_TRUNCATE, LVRelState::rel, LVRelState::rel_pages, RelationGetNumberOfBlocks, RelationTruncate(), LVRelState::relname, LVRelState::removed_pages, ResetLatch(), UnlockRelation(), update_vacuum_error_info(), VACUUM_ERRCB_PHASE_TRUNCATE, VACUUM_TRUNCATE_LOCK_TIMEOUT, VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL, LVRelState::verbose, WAIT_EVENT_VACUUM_TRUNCATE, WaitLatch(), WL_EXIT_ON_PM_DEATH, WL_LATCH_SET, and WL_TIMEOUT.

Referenced by heap_vacuum_rel().

◆ lazy_vacuum()

static void lazy_vacuum ( LVRelState vacrel)
static

Definition at line 2176 of file vacuumlazy.c.

2177 {
2178  bool bypass;
2179 
2180  /* Should not end up here with no indexes */
2181  Assert(vacrel->nindexes > 0);
2182  Assert(vacrel->lpdead_item_pages > 0);
2183 
2184  if (!vacrel->do_index_vacuuming)
2185  {
2186  Assert(!vacrel->do_index_cleanup);
2187  vacrel->dead_items->num_items = 0;
2188  return;
2189  }
2190 
2191  /*
2192  * Consider bypassing index vacuuming (and heap vacuuming) entirely.
2193  *
2194  * We currently only do this in cases where the number of LP_DEAD items
2195  * for the entire VACUUM operation is close to zero. This avoids sharp
2196  * discontinuities in the duration and overhead of successive VACUUM
2197  * operations that run against the same table with a fixed workload.
2198  * Ideally, successive VACUUM operations will behave as if there are
2199  * exactly zero LP_DEAD items in cases where there are close to zero.
2200  *
2201  * This is likely to be helpful with a table that is continually affected
2202  * by UPDATEs that can mostly apply the HOT optimization, but occasionally
2203  * have small aberrations that lead to just a few heap pages retaining
2204  * only one or two LP_DEAD items. This is pretty common; even when the
2205  * DBA goes out of their way to make UPDATEs use HOT, it is practically
2206  * impossible to predict whether HOT will be applied in 100% of cases.
2207  * It's far easier to ensure that 99%+ of all UPDATEs against a table use
2208  * HOT through careful tuning.
2209  */
2210  bypass = false;
2211  if (vacrel->consider_bypass_optimization && vacrel->rel_pages > 0)
2212  {
2213  BlockNumber threshold;
2214 
2215  Assert(vacrel->num_index_scans == 0);
2216  Assert(vacrel->lpdead_items == vacrel->dead_items->num_items);
2217  Assert(vacrel->do_index_vacuuming);
2218  Assert(vacrel->do_index_cleanup);
2219 
2220  /*
2221  * This crossover point at which we'll start to do index vacuuming is
2222  * expressed as a percentage of the total number of heap pages in the
2223  * table that are known to have at least one LP_DEAD item. This is
2224  * much more important than the total number of LP_DEAD items, since
2225  * it's a proxy for the number of heap pages whose visibility map bits
2226  * cannot be set on account of bypassing index and heap vacuuming.
2227  *
2228  * We apply one further precautionary test: the space currently used
2229  * to store the TIDs (TIDs that now all point to LP_DEAD items) must
2230  * not exceed 32MB. This limits the risk that we will bypass index
2231  * vacuuming again and again until eventually there is a VACUUM whose
2232  * dead_items space is not CPU cache resident.
2233  *
2234  * We don't take any special steps to remember the LP_DEAD items (such
2235  * as counting them in our final update to the stats system) when the
2236  * optimization is applied. Though the accounting used in analyze.c's
2237  * acquire_sample_rows() will recognize the same LP_DEAD items as dead
2238  * rows in its own stats report, that's okay. The discrepancy should
2239  * be negligible. If this optimization is ever expanded to cover more
2240  * cases then this may need to be reconsidered.
2241  */
2242  threshold = (double) vacrel->rel_pages * BYPASS_THRESHOLD_PAGES;
2243  bypass = (vacrel->lpdead_item_pages < threshold &&
2244  vacrel->lpdead_items < MAXDEADITEMS(32L * 1024L * 1024L));
2245  }
2246 
2247  if (bypass)
2248  {
2249  /*
2250  * There are almost zero TIDs. Behave as if there were precisely
2251  * zero: bypass index vacuuming, but do index cleanup.
2252  *
2253  * We expect that the ongoing VACUUM operation will finish very
2254  * quickly, so there is no point in considering speeding up as a
2255  * failsafe against wraparound failure. (Index cleanup is expected to
2256  * finish very quickly in cases where there were no ambulkdelete()
2257  * calls.)
2258  */
2259  vacrel->do_index_vacuuming = false;
2260  }
2261  else if (lazy_vacuum_all_indexes(vacrel))
2262  {
2263  /*
2264  * We successfully completed a round of index vacuuming. Do related
2265  * heap vacuuming now.
2266  */
2267  lazy_vacuum_heap_rel(vacrel);
2268  }
2269  else
2270  {
2271  /*
2272  * Failsafe case.
2273  *
2274  * We attempted index vacuuming, but didn't finish a full round/full
2275  * index scan. This happens when relfrozenxid or relminmxid is too
2276  * far in the past.
2277  *
2278  * From this point on the VACUUM operation will do no further index
2279  * vacuuming or heap vacuuming. This VACUUM operation won't end up
2280  * back here again.
2281  */
2282  Assert(vacrel->failsafe_active);
2283  }
2284 
2285  /*
2286  * Forget the LP_DEAD items that we just vacuumed (or just decided to not
2287  * vacuum)
2288  */
2289  vacrel->dead_items->num_items = 0;
2290 }
#define BYPASS_THRESHOLD_PAGES
Definition: vacuumlazy.c:94
static bool lazy_vacuum_all_indexes(LVRelState *vacrel)
Definition: vacuumlazy.c:2301
static void lazy_vacuum_heap_rel(LVRelState *vacrel)
Definition: vacuumlazy.c:2396

References Assert(), BYPASS_THRESHOLD_PAGES, LVRelState::consider_bypass_optimization, LVRelState::dead_items, LVRelState::do_index_cleanup, LVRelState::do_index_vacuuming, LVRelState::failsafe_active, lazy_vacuum_all_indexes(), lazy_vacuum_heap_rel(), LVRelState::lpdead_item_pages, LVRelState::lpdead_items, MAXDEADITEMS, LVRelState::nindexes, LVRelState::num_index_scans, VacDeadItems::num_items, and LVRelState::rel_pages.

Referenced by lazy_scan_heap().

◆ lazy_vacuum_all_indexes()

static bool lazy_vacuum_all_indexes ( LVRelState vacrel)
static

Definition at line 2301 of file vacuumlazy.c.

2302 {
2303  bool allindexes = true;
2304 
2305  Assert(vacrel->nindexes > 0);
2306  Assert(vacrel->do_index_vacuuming);
2307  Assert(vacrel->do_index_cleanup);
2308 
2309  /* Precheck for XID wraparound emergencies */
2310  if (lazy_check_wraparound_failsafe(vacrel))
2311  {
2312  /* Wraparound emergency -- don't even start an index scan */
2313  return false;
2314  }
2315 
2316  /* Report that we are now vacuuming indexes */
2319 
2320  if (!ParallelVacuumIsActive(vacrel))
2321  {
2322  for (int idx = 0; idx < vacrel->nindexes; idx++)
2323  {
2324  Relation indrel = vacrel->indrels[idx];
2325  IndexBulkDeleteResult *istat = vacrel->indstats[idx];
2326 
2327  vacrel->indstats[idx] =
2328  lazy_vacuum_one_index(indrel, istat, vacrel->old_live_tuples,
2329  vacrel);
2330 
2331  if (lazy_check_wraparound_failsafe(vacrel))
2332  {
2333  /* Wraparound emergency -- end current index scan */
2334  allindexes = false;
2335  break;
2336  }
2337  }
2338  }
2339  else
2340  {
2341  /* Outsource everything to parallel variant */
2343  vacrel->num_index_scans);
2344 
2345  /*
2346  * Do a postcheck to consider applying wraparound failsafe now. Note
2347  * that parallel VACUUM only gets the precheck and this postcheck.
2348  */
2349  if (lazy_check_wraparound_failsafe(vacrel))
2350  allindexes = false;
2351  }
2352 
2353  /*
2354  * We delete all LP_DEAD items from the first heap pass in all indexes on
2355  * each call here (except calls where we choose to do the failsafe). This
2356  * makes the next call to lazy_vacuum_heap_rel() safe (except in the event
2357  * of the failsafe triggering, which prevents the next call from taking
2358  * place).
2359  */
2360  Assert(vacrel->num_index_scans > 0 ||
2361  vacrel->dead_items->num_items == vacrel->lpdead_items);
2362  Assert(allindexes || vacrel->failsafe_active);
2363 
2364  /*
2365  * Increase and report the number of index scans.
2366  *
2367  * We deliberately include the case where we started a round of bulk
2368  * deletes that we weren't able to finish due to the failsafe triggering.
2369  */
2370  vacrel->num_index_scans++;
2372  vacrel->num_index_scans);
2373 
2374  return allindexes;
2375 }
#define PROGRESS_VACUUM_NUM_INDEX_VACUUMS
Definition: progress.h:25
#define PROGRESS_VACUUM_PHASE_VACUUM_INDEX
Definition: progress.h:31
static IndexBulkDeleteResult * lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat, double reltuples, LVRelState *vacrel)
Definition: vacuumlazy.c:2699
void parallel_vacuum_bulkdel_all_indexes(ParallelVacuumState *pvs, long num_table_tuples, int num_index_scans)

References Assert(), LVRelState::dead_items, LVRelState::do_index_cleanup, LVRelState::do_index_vacuuming, LVRelState::failsafe_active, idx(), LVRelState::indrels, LVRelState::indstats, lazy_check_wraparound_failsafe(), lazy_vacuum_one_index(), LVRelState::lpdead_items, LVRelState::nindexes, LVRelState::num_index_scans, VacDeadItems::num_items, LVRelState::old_live_tuples, parallel_vacuum_bulkdel_all_indexes(), ParallelVacuumIsActive, pgstat_progress_update_param(), PROGRESS_VACUUM_NUM_INDEX_VACUUMS, PROGRESS_VACUUM_PHASE, PROGRESS_VACUUM_PHASE_VACUUM_INDEX, and LVRelState::pvs.

Referenced by lazy_vacuum().

◆ lazy_vacuum_heap_page()

static int lazy_vacuum_heap_page ( LVRelState vacrel,
BlockNumber  blkno,
Buffer  buffer,
int  index,
Buffer vmbuffer 
)
static

Definition at line 2482 of file vacuumlazy.c.

2484 {
2485  VacDeadItems *dead_items = vacrel->dead_items;
2486  Page page = BufferGetPage(buffer);
2488  int uncnt = 0;
2489  TransactionId visibility_cutoff_xid;
2490  bool all_frozen;
2491  LVSavedErrInfo saved_err_info;
2492 
2493  Assert(vacrel->nindexes == 0 || vacrel->do_index_vacuuming);
2494 
2496 
2497  /* Update error traceback information */
2498  update_vacuum_error_info(vacrel, &saved_err_info,
2501 
2503 
2504  for (; index < dead_items->num_items; index++)
2505  {
2506  BlockNumber tblk;
2507  OffsetNumber toff;
2508  ItemId itemid;
2509 
2510  tblk = ItemPointerGetBlockNumber(&dead_items->items[index]);
2511  if (tblk != blkno)
2512  break; /* past end of tuples for this block */
2513  toff = ItemPointerGetOffsetNumber(&dead_items->items[index]);
2514  itemid = PageGetItemId(page, toff);
2515 
2516  Assert(ItemIdIsDead(itemid) && !ItemIdHasStorage(itemid));
2517  ItemIdSetUnused(itemid);
2518  unused[uncnt++] = toff;
2519  }
2520 
2521  Assert(uncnt > 0);
2522 
2523  /* Attempt to truncate line pointer array now */
2525 
2526  /*
2527  * Mark buffer dirty before we write WAL.
2528  */
2529  MarkBufferDirty(buffer);
2530 
2531  /* XLOG stuff */
2532  if (RelationNeedsWAL(vacrel->rel))
2533  {
2534  xl_heap_vacuum xlrec;
2535  XLogRecPtr recptr;
2536 
2537  xlrec.nunused = uncnt;
2538 
2539  XLogBeginInsert();
2540  XLogRegisterData((char *) &xlrec, SizeOfHeapVacuum);
2541 
2542  XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
2543  XLogRegisterBufData(0, (char *) unused, uncnt * sizeof(OffsetNumber));
2544 
2545  recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_VACUUM);
2546 
2547  PageSetLSN(page, recptr);
2548  }
2549 
2550  /*
2551  * End critical section, so we safely can do visibility tests (which
2552  * possibly need to perform IO and allocate memory!). If we crash now the
2553  * page (including the corresponding vm bit) might not be marked all
2554  * visible, but that's fine. A later vacuum will fix that.
2555  */
2556  END_CRIT_SECTION();
2557 
2558  /*
2559  * Now that we have removed the LD_DEAD items from the page, once again
2560  * check if the page has become all-visible. The page is already marked
2561  * dirty, exclusively locked, and, if needed, a full page image has been
2562  * emitted.
2563  */
2564  if (heap_page_is_all_visible(vacrel, buffer, &visibility_cutoff_xid,
2565  &all_frozen))
2566  PageSetAllVisible(page);
2567 
2568  /*
2569  * All the changes to the heap page have been done. If the all-visible
2570  * flag is now set, also set the VM all-visible bit (and, if possible, the
2571  * all-frozen bit) unless this has already been done previously.
2572  */
2573  if (PageIsAllVisible(page))
2574  {
2575  uint8 flags = 0;
2576  uint8 vm_status = visibilitymap_get_status(vacrel->rel,
2577  blkno, vmbuffer);
2578 
2579  /* Set the VM all-frozen bit to flag, if needed */
2580  if ((vm_status & VISIBILITYMAP_ALL_VISIBLE) == 0)
2581  flags |= VISIBILITYMAP_ALL_VISIBLE;
2582  if ((vm_status & VISIBILITYMAP_ALL_FROZEN) == 0 && all_frozen)
2583  flags |= VISIBILITYMAP_ALL_FROZEN;
2584 
2585  Assert(BufferIsValid(*vmbuffer));
2586  if (flags != 0)
2587  visibilitymap_set(vacrel->rel, blkno, buffer, InvalidXLogRecPtr,
2588  *vmbuffer, visibility_cutoff_xid, flags);
2589  }
2590 
2591  /* Revert to the previous phase information for error traceback */
2592  restore_vacuum_error_info(vacrel, &saved_err_info);
2593  return index;
2594 }
void PageTruncateLinePointerArray(Page page)
Definition: bufpage.c:835
#define XLOG_HEAP2_VACUUM
Definition: heapam_xlog.h:55
#define SizeOfHeapVacuum
Definition: heapam_xlog.h:265
#define ItemIdSetUnused(itemId)
Definition: itemid.h:128
#define ItemIdHasStorage(itemId)
Definition: itemid.h:120
#define ItemPointerGetBlockNumber(pointer)
Definition: itemptr.h:98
#define ItemPointerGetOffsetNumber(pointer)
Definition: itemptr.h:117
Definition: type.h:90
XLogRecPtr XLogInsert(RmgrId rmid, uint8 info)
Definition: xloginsert.c:443
void XLogRegisterBufData(uint8 block_id, char *data, int len)
Definition: xloginsert.c:389
void XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
Definition: xloginsert.c:243
void XLogBeginInsert(void)
Definition: xloginsert.c:150
void XLogRegisterData(char *data, int len)
Definition: xloginsert.c:351
#define REGBUF_STANDARD
Definition: xloginsert.h:34

References Assert(), BufferGetPage, BufferIsValid, LVRelState::dead_items, LVRelState::do_index_vacuuming, END_CRIT_SECTION, heap_page_is_all_visible(), InvalidOffsetNumber, InvalidXLogRecPtr, ItemIdHasStorage, ItemIdIsDead, ItemIdSetUnused, ItemPointerGetBlockNumber, ItemPointerGetOffsetNumber, VacDeadItems::items, MarkBufferDirty(), MaxHeapTuplesPerPage, LVRelState::nindexes, VacDeadItems::num_items, xl_heap_vacuum::nunused, PageGetItemId, PageIsAllVisible, PageSetAllVisible, PageSetLSN, PageTruncateLinePointerArray(), pgstat_progress_update_param(), PROGRESS_VACUUM_HEAP_BLKS_VACUUMED, REGBUF_STANDARD, LVRelState::rel, RelationNeedsWAL, restore_vacuum_error_info(), SizeOfHeapVacuum, START_CRIT_SECTION, update_vacuum_error_info(), VACUUM_ERRCB_PHASE_VACUUM_HEAP, VISIBILITYMAP_ALL_FROZEN, VISIBILITYMAP_ALL_VISIBLE, visibilitymap_get_status(), visibilitymap_set(), XLOG_HEAP2_VACUUM, XLogBeginInsert(), XLogInsert(), XLogRegisterBufData(), XLogRegisterBuffer(), and XLogRegisterData().

Referenced by lazy_scan_heap(), and lazy_vacuum_heap_rel().

◆ lazy_vacuum_heap_rel()

static void lazy_vacuum_heap_rel ( LVRelState vacrel)
static

Definition at line 2396 of file vacuumlazy.c.

2397 {
2398  int index;
2399  BlockNumber vacuumed_pages;
2400  Buffer vmbuffer = InvalidBuffer;
2401  LVSavedErrInfo saved_err_info;
2402 
2403  Assert(vacrel->do_index_vacuuming);
2404  Assert(vacrel->do_index_cleanup);
2405  Assert(vacrel->num_index_scans > 0);
2406 
2407  /* Report that we are now vacuuming the heap */
2410 
2411  /* Update error traceback information */
2412  update_vacuum_error_info(vacrel, &saved_err_info,
2415 
2416  vacuumed_pages = 0;
2417 
2418  index = 0;
2419  while (index < vacrel->dead_items->num_items)
2420  {
2421  BlockNumber tblk;
2422  Buffer buf;
2423  Page page;
2424  Size freespace;
2425 
2427 
2428  tblk = ItemPointerGetBlockNumber(&vacrel->dead_items->items[index]);
2429  vacrel->blkno = tblk;
2430  buf = ReadBufferExtended(vacrel->rel, MAIN_FORKNUM, tblk, RBM_NORMAL,
2431  vacrel->bstrategy);
2433  index = lazy_vacuum_heap_page(vacrel, tblk, buf, index, &vmbuffer);
2434 
2435  /* Now that we've vacuumed the page, record its available space */
2436  page = BufferGetPage(buf);
2437  freespace = PageGetHeapFreeSpace(page);
2438 
2440  RecordPageWithFreeSpace(vacrel->rel, tblk, freespace);
2441  vacuumed_pages++;
2442  }
2443 
2444  /* Clear the block number information */
2445  vacrel->blkno = InvalidBlockNumber;
2446 
2447  if (BufferIsValid(vmbuffer))
2448  {
2449  ReleaseBuffer(vmbuffer);
2450  vmbuffer = InvalidBuffer;
2451  }
2452 
2453  /*
2454  * We set all LP_DEAD items from the first heap pass to LP_UNUSED during
2455  * the second heap pass. No more, no less.
2456  */
2457  Assert(index > 0);
2458  Assert(vacrel->num_index_scans > 1 ||
2459  (index == vacrel->lpdead_items &&
2460  vacuumed_pages == vacrel->lpdead_item_pages));
2461 
2462  ereport(DEBUG2,
2463  (errmsg("table \"%s\": removed %lld dead item identifiers in %u pages",
2464  vacrel->relname, (long long) index, vacuumed_pages)));
2465 
2466  /* Revert to the previous phase information for error traceback */
2467  restore_vacuum_error_info(vacrel, &saved_err_info);
2468 }
#define PROGRESS_VACUUM_PHASE_VACUUM_HEAP
Definition: progress.h:32

References Assert(), LVRelState::blkno, LVRelState::bstrategy, buf, BUFFER_LOCK_EXCLUSIVE, BufferGetPage, BufferIsValid, LVRelState::dead_items, DEBUG2, LVRelState::do_index_cleanup, LVRelState::do_index_vacuuming, ereport, errmsg(), InvalidBlockNumber, InvalidBuffer, InvalidOffsetNumber, ItemPointerGetBlockNumber, VacDeadItems::items, lazy_vacuum_heap_page(), LockBuffer(), LVRelState::lpdead_item_pages, LVRelState::lpdead_items, MAIN_FORKNUM, LVRelState::num_index_scans, PageGetHeapFreeSpace(), pgstat_progress_update_param(), PROGRESS_VACUUM_PHASE, PROGRESS_VACUUM_PHASE_VACUUM_HEAP, RBM_NORMAL, ReadBufferExtended(), RecordPageWithFreeSpace(), LVRelState::rel, ReleaseBuffer(), LVRelState::relname, restore_vacuum_error_info(), UnlockReleaseBuffer(), update_vacuum_error_info(), vacuum_delay_point(), and VACUUM_ERRCB_PHASE_VACUUM_HEAP.

Referenced by lazy_vacuum().

◆ lazy_vacuum_one_index()

static IndexBulkDeleteResult * lazy_vacuum_one_index ( Relation  indrel,
IndexBulkDeleteResult istat,
double  reltuples,
LVRelState vacrel 
)
static

Definition at line 2699 of file vacuumlazy.c.

2701 {
2702  IndexVacuumInfo ivinfo;
2703  LVSavedErrInfo saved_err_info;
2704 
2705  ivinfo.index = indrel;
2706  ivinfo.analyze_only = false;
2707  ivinfo.report_progress = false;
2708  ivinfo.estimated_count = true;
2709  ivinfo.message_level = DEBUG2;
2710  ivinfo.num_heap_tuples = reltuples;
2711  ivinfo.strategy = vacrel->bstrategy;
2712 
2713  /*
2714  * Update error traceback information.
2715  *
2716  * The index name is saved during this phase and restored immediately
2717  * after this phase. See vacuum_error_callback.
2718  */
2719  Assert(vacrel->indname == NULL);
2720  vacrel->indname = pstrdup(RelationGetRelationName(indrel));
2721  update_vacuum_error_info(vacrel, &saved_err_info,
2724 
2725  /* Do bulk deletion */
2726  istat = vac_bulkdel_one_index(&ivinfo, istat, (void *) vacrel->dead_items);
2727 
2728  /* Revert to the previous phase information for error traceback */
2729  restore_vacuum_error_info(vacrel, &saved_err_info);
2730  pfree(vacrel->indname);
2731  vacrel->indname = NULL;
2732 
2733  return istat;
2734 }
IndexBulkDeleteResult * vac_bulkdel_one_index(IndexVacuumInfo *ivinfo, IndexBulkDeleteResult *istat, VacDeadItems *dead_items)
Definition: vacuum.c:2326

References IndexVacuumInfo::analyze_only, Assert(), LVRelState::bstrategy, LVRelState::dead_items, DEBUG2, IndexVacuumInfo::estimated_count, IndexVacuumInfo::index, LVRelState::indname, InvalidBlockNumber, InvalidOffsetNumber, IndexVacuumInfo::message_level, IndexVacuumInfo::num_heap_tuples, pfree(), pstrdup(), RelationGetRelationName, IndexVacuumInfo::report_progress, restore_vacuum_error_info(), IndexVacuumInfo::strategy, update_vacuum_error_info(), vac_bulkdel_one_index(), and VACUUM_ERRCB_PHASE_VACUUM_INDEX.

Referenced by lazy_vacuum_all_indexes().

◆ restore_vacuum_error_info()

static void restore_vacuum_error_info ( LVRelState vacrel,
const LVSavedErrInfo saved_vacrel 
)
static

Definition at line 3456 of file vacuumlazy.c.

3458 {
3459  vacrel->blkno = saved_vacrel->blkno;
3460  vacrel->offnum = saved_vacrel->offnum;
3461  vacrel->phase = saved_vacrel->phase;
3462 }
BlockNumber blkno
Definition: vacuumlazy.c:242
VacErrPhase phase
Definition: vacuumlazy.c:244
OffsetNumber offnum
Definition: vacuumlazy.c:243

References LVRelState::blkno, LVSavedErrInfo::blkno, LVRelState::offnum, LVSavedErrInfo::offnum, LVRelState::phase, and LVSavedErrInfo::phase.

Referenced by lazy_cleanup_one_index(), lazy_vacuum_heap_page(), lazy_vacuum_heap_rel(), and lazy_vacuum_one_index().

◆ should_attempt_truncation()

static bool should_attempt_truncation ( LVRelState vacrel)
static

Definition at line 2809 of file vacuumlazy.c.

2810 {
2811  BlockNumber possibly_freeable;
2812 
2813  if (!vacrel->do_rel_truncate || vacrel->failsafe_active ||
2815  return false;
2816 
2817  possibly_freeable = vacrel->rel_pages - vacrel->nonempty_pages;
2818  if (possibly_freeable > 0 &&
2819  (possibly_freeable >= REL_TRUNCATE_MINIMUM ||
2820  possibly_freeable >= vacrel->rel_pages / REL_TRUNCATE_FRACTION))
2821  return true;
2822 
2823  return false;
2824 }
int old_snapshot_threshold
Definition: snapmgr.c:78
#define REL_TRUNCATE_MINIMUM
Definition: vacuumlazy.c:76
#define REL_TRUNCATE_FRACTION
Definition: vacuumlazy.c:77

References LVRelState::do_rel_truncate, LVRelState::failsafe_active, LVRelState::nonempty_pages, old_snapshot_threshold, LVRelState::rel_pages, REL_TRUNCATE_FRACTION, and REL_TRUNCATE_MINIMUM.

Referenced by heap_vacuum_rel().

◆ update_relstats_all_indexes()

static void update_relstats_all_indexes ( LVRelState vacrel)
static

Definition at line 3338 of file vacuumlazy.c.

3339 {
3340  Relation *indrels = vacrel->indrels;
3341  int nindexes = vacrel->nindexes;
3342  IndexBulkDeleteResult **indstats = vacrel->indstats;
3343 
3344  Assert(vacrel->do_index_cleanup);
3345 
3346  for (int idx = 0; idx < nindexes; idx++)
3347  {
3348  Relation indrel = indrels[idx];
3349  IndexBulkDeleteResult *istat = indstats[idx];
3350 
3351  if (istat == NULL || istat->estimated_count)
3352  continue;
3353 
3354  /* Update index statistics */
3355  vac_update_relstats(indrel,
3356  istat->num_pages,
3357  istat->num_index_tuples,
3358  0,
3359  false,
3362  NULL, NULL, false);
3363  }
3364 }
bool estimated_count
Definition: genam.h:77
double num_index_tuples
Definition: genam.h:78

References Assert(), LVRelState::do_index_cleanup, IndexBulkDeleteResult::estimated_count, idx(), LVRelState::indrels, LVRelState::indstats, InvalidMultiXactId, InvalidTransactionId, LVRelState::nindexes, IndexBulkDeleteResult::num_index_tuples, IndexBulkDeleteResult::num_pages, and vac_update_relstats().

Referenced by heap_vacuum_rel().

◆ update_vacuum_error_info()

static void update_vacuum_error_info ( LVRelState vacrel,
LVSavedErrInfo saved_vacrel,
int  phase,
BlockNumber  blkno,
OffsetNumber  offnum 
)
static

Definition at line 3437 of file vacuumlazy.c.

3439 {
3440  if (saved_vacrel)
3441  {
3442  saved_vacrel->offnum = vacrel->offnum;
3443  saved_vacrel->blkno = vacrel->blkno;
3444  saved_vacrel->phase = vacrel->phase;
3445  }
3446 
3447  vacrel->blkno = blkno;
3448  vacrel->offnum = offnum;
3449  vacrel->phase = phase;
3450 }

References LVRelState::blkno, LVSavedErrInfo::blkno, LVRelState::offnum, LVSavedErrInfo::offnum, LVRelState::phase, and LVSavedErrInfo::phase.

Referenced by lazy_cleanup_one_index(), lazy_scan_heap(), lazy_truncate_heap(), lazy_vacuum_heap_page(), lazy_vacuum_heap_rel(), and lazy_vacuum_one_index().

◆ vacuum_error_callback()

static void vacuum_error_callback ( void *  arg)
static

Definition at line 3373 of file vacuumlazy.c.

3374 {
3375  LVRelState *errinfo = arg;
3376 
3377  switch (errinfo->phase)
3378  {
3380  if (BlockNumberIsValid(errinfo->blkno))
3381  {
3382  if (OffsetNumberIsValid(errinfo->offnum))
3383  errcontext("while scanning block %u offset %u of relation \"%s.%s\"",
3384  errinfo->blkno, errinfo->offnum, errinfo->relnamespace, errinfo->relname);
3385  else
3386  errcontext("while scanning block %u of relation \"%s.%s\"",
3387  errinfo->blkno, errinfo->relnamespace, errinfo->relname);
3388  }
3389  else
3390  errcontext("while scanning relation \"%s.%s\"",
3391  errinfo->relnamespace, errinfo->relname);
3392  break;
3393 
3395  if (BlockNumberIsValid(errinfo->blkno))
3396  {
3397  if (OffsetNumberIsValid(errinfo->offnum))
3398  errcontext("while vacuuming block %u offset %u of relation \"%s.%s\"",
3399  errinfo->blkno, errinfo->offnum, errinfo->relnamespace, errinfo->relname);
3400  else
3401  errcontext("while vacuuming block %u of relation \"%s.%s\"",
3402  errinfo->blkno, errinfo->relnamespace, errinfo->relname);
3403  }
3404  else
3405  errcontext("while vacuuming relation \"%s.%s\"",
3406  errinfo->relnamespace, errinfo->relname);
3407  break;
3408 
3410  errcontext("while vacuuming index \"%s\" of relation \"%s.%s\"",
3411  errinfo->indname, errinfo->relnamespace, errinfo->relname);
3412  break;
3413 
3415  errcontext("while cleaning up index \"%s\" of relation \"%s.%s\"",
3416  errinfo->indname, errinfo->relnamespace, errinfo->relname);
3417  break;
3418 
3420  if (BlockNumberIsValid(errinfo->blkno))
3421  errcontext("while truncating relation \"%s.%s\" to %u blocks",
3422  errinfo->relnamespace, errinfo->relname, errinfo->blkno);
3423  break;
3424 
3426  default:
3427  return; /* do nothing; the errinfo may not be
3428  * initialized */
3429  }
3430 }
#define BlockNumberIsValid(blockNumber)
Definition: block.h:70
#define errcontext
Definition: elog.h:190
#define OffsetNumberIsValid(offsetNumber)
Definition: off.h:39
void * arg

References arg, LVRelState::blkno, BlockNumberIsValid, errcontext, LVRelState::indname, LVRelState::offnum, OffsetNumberIsValid, LVRelState::phase, LVRelState::relname, LVRelState::relnamespace, VACUUM_ERRCB_PHASE_INDEX_CLEANUP, VACUUM_ERRCB_PHASE_SCAN_HEAP, VACUUM_ERRCB_PHASE_TRUNCATE, VACUUM_ERRCB_PHASE_UNKNOWN, VACUUM_ERRCB_PHASE_VACUUM_HEAP, and VACUUM_ERRCB_PHASE_VACUUM_INDEX.

Referenced by heap_vacuum_rel().