1 /*-------------------------------------------------------------------------
4 * Concurrent ("lazy") vacuuming.
6 * The major space usage for vacuuming is storage for the array of dead TIDs
7 * that are to be removed from indexes. We want to ensure we can vacuum even
8 * the very largest relations with finite memory space usage. To do that, we
9 * set upper bounds on the number of TIDs we can keep track of at once.
11 * We are willing to use at most maintenance_work_mem (or perhaps
12 * autovacuum_work_mem) memory space to keep track of dead TIDs. We initially
13 * allocate an array of TIDs of that size, with an upper limit that depends on
14 * table size (this limit ensures we don't allocate a huge area uselessly for
15 * vacuuming small tables). If the array threatens to overflow, we must call
16 * lazy_vacuum to vacuum indexes (and to vacuum the pages that we've pruned).
17 * This frees up the memory space dedicated to storing dead TIDs.
19 * In practice VACUUM will often complete its initial pass over the target
20 * heap relation without ever running out of space to store TIDs. This means
21 * that there only needs to be one call to lazy_vacuum, after the initial pass
24 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
25 * Portions Copyright (c) 1994, Regents of the University of California
29 * src/backend/access/heap/vacuumlazy.c
31 *-------------------------------------------------------------------------
37 #include "access/amapi.h"
38 #include "access/genam.h"
39 #include "access/heapam.h"
40 #include "access/heapam_xlog.h"
41 #include "access/htup_details.h"
42 #include "access/multixact.h"
43 #include "access/transam.h"
44 #include "access/visibilitymap.h"
45 #include "access/xact.h"
46 #include "access/xlog.h"
47 #include "access/xloginsert.h"
48 #include "catalog/index.h"
49 #include "catalog/storage.h"
50 #include "commands/dbcommands.h"
51 #include "commands/progress.h"
52 #include "commands/vacuum.h"
53 #include "executor/instrument.h"
54 #include "miscadmin.h"
55 #include "optimizer/paths.h"
57 #include "portability/instr_time.h"
58 #include "postmaster/autovacuum.h"
59 #include "storage/bufmgr.h"
60 #include "storage/freespace.h"
61 #include "storage/lmgr.h"
62 #include "tcop/tcopprot.h"
63 #include "utils/lsyscache.h"
64 #include "utils/memutils.h"
65 #include "utils/pg_rusage.h"
66 #include "utils/timestamp.h"
70 * Space/time tradeoff parameters: do these need to be user-tunable?
72 * To consider truncating the relation, we want there to be at least
73 * REL_TRUNCATE_MINIMUM or (relsize / REL_TRUNCATE_FRACTION) (whichever
74 * is less) potentially-freeable pages.
76 #define REL_TRUNCATE_MINIMUM 1000
77 #define REL_TRUNCATE_FRACTION 16
80 * Timing parameters for truncate locking heuristics.
82 * These were not exposed as user tunable GUC values because it didn't seem
83 * that the potential for improvement was great enough to merit the cost of
86 #define VACUUM_TRUNCATE_LOCK_CHECK_INTERVAL 20 /* ms */
87 #define VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL 50 /* ms */
88 #define VACUUM_TRUNCATE_LOCK_TIMEOUT 5000 /* ms */
91 * Threshold that controls whether we bypass index vacuuming and heap
92 * vacuuming as an optimization
94 #define BYPASS_THRESHOLD_PAGES 0.02 /* i.e. 2% of rel_pages */
97 * Perform a failsafe check each time we scan another 4GB of pages.
98 * (Note that this is deliberately kept to a power-of-two, usually 2^19.)
100 #define FAILSAFE_EVERY_PAGES \
101 ((BlockNumber) (((uint64) 4 * 1024 * 1024 * 1024) / BLCKSZ))
104 * When a table has no indexes, vacuum the FSM after every 8GB, approximately
105 * (it won't be exact because we only vacuum FSM after processing a heap page
106 * that has some removable tuples). When there are indexes, this is ignored,
107 * and we vacuum FSM after each index/heap cleaning pass.
109 #define VACUUM_FSM_EVERY_PAGES \
110 ((BlockNumber) (((uint64) 8 * 1024 * 1024 * 1024) / BLCKSZ))
113 * Before we consider skipping a page that's marked as clean in
114 * visibility map, we must've seen at least this many clean pages.
116 #define SKIP_PAGES_THRESHOLD ((BlockNumber) 32)
119 * Size of the prefetch window for lazy vacuum backwards truncation scan.
120 * Needs to be a power of 2.
122 #define PREFETCH_SIZE ((BlockNumber) 32)
125 * Macro to check if we are in a parallel vacuum. If true, we are in the
126 * parallel mode and the DSM segment is initialized.
128 #define ParallelVacuumIsActive(vacrel) ((vacrel)->pvs != NULL)
130 /* Phases of vacuum during which we report error context. */
133 VACUUM_ERRCB_PHASE_UNKNOWN
,
134 VACUUM_ERRCB_PHASE_SCAN_HEAP
,
135 VACUUM_ERRCB_PHASE_VACUUM_INDEX
,
136 VACUUM_ERRCB_PHASE_VACUUM_HEAP
,
137 VACUUM_ERRCB_PHASE_INDEX_CLEANUP
,
138 VACUUM_ERRCB_PHASE_TRUNCATE
141 typedef struct LVRelState
143 /* Target heap relation and its indexes */
148 /* Buffer access strategy and parallel vacuum state */
149 BufferAccessStrategy bstrategy
;
150 ParallelVacuumState
*pvs
;
152 /* Aggressive VACUUM? (must set relfrozenxid >= FreezeLimit) */
154 /* Use visibility map to skip? (disabled by DISABLE_PAGE_SKIPPING) */
156 /* Wraparound failsafe has been triggered? */
157 bool failsafe_active
;
158 /* Consider index vacuuming bypass optimization? */
159 bool consider_bypass_optimization
;
161 /* Doing index vacuuming, index cleanup, rel truncation? */
162 bool do_index_vacuuming
;
163 bool do_index_cleanup
;
164 bool do_rel_truncate
;
166 /* VACUUM operation's cutoffs for freezing and pruning */
167 struct VacuumCutoffs cutoffs
;
168 GlobalVisState
*vistest
;
169 /* Tracks oldest extant XID/MXID for setting relfrozenxid/relminmxid */
170 TransactionId NewRelfrozenXid
;
171 MultiXactId NewRelminMxid
;
174 /* Error reporting state */
177 char *indname
; /* Current index name */
178 BlockNumber blkno
; /* used only for heap operations */
179 OffsetNumber offnum
; /* used only for heap operations */
181 bool verbose
; /* VACUUM VERBOSE? */
184 * dead_items stores TIDs whose index tuples are deleted by index
185 * vacuuming. Each TID points to an LP_DEAD line pointer from a heap page
186 * that has been processed by lazy_scan_prune. Also needed by
187 * lazy_vacuum_heap_rel, which marks the same LP_DEAD line pointers as
188 * LP_UNUSED during second heap pass.
190 VacDeadItems
*dead_items
; /* TIDs whose index tuples we'll delete */
191 BlockNumber rel_pages
; /* total number of pages */
192 BlockNumber scanned_pages
; /* # pages examined (not skipped via VM) */
193 BlockNumber removed_pages
; /* # pages removed by relation truncation */
194 BlockNumber frozen_pages
; /* # pages with newly frozen tuples */
195 BlockNumber lpdead_item_pages
; /* # pages with LP_DEAD items */
196 BlockNumber missed_dead_pages
; /* # pages with missed dead tuples */
197 BlockNumber nonempty_pages
; /* actually, last nonempty page + 1 */
199 /* Statistics output by us, for table */
200 double new_rel_tuples
; /* new estimated total # of tuples */
201 double new_live_tuples
; /* new estimated total # of live tuples */
202 /* Statistics output by index AMs */
203 IndexBulkDeleteResult
**indstats
;
205 /* Instrumentation counters */
207 /* Counters that follow are only for scanned_pages */
208 int64 tuples_deleted
; /* # deleted from table */
209 int64 tuples_frozen
; /* # newly frozen */
210 int64 lpdead_items
; /* # deleted from indexes */
211 int64 live_tuples
; /* # live tuples remaining */
212 int64 recently_dead_tuples
; /* # dead, but not yet removable */
213 int64 missed_dead_tuples
; /* # removable, but not removed */
217 * State returned by lazy_scan_prune()
219 typedef struct LVPagePruneState
221 bool hastup
; /* Page prevents rel truncation? */
222 bool has_lpdead_items
; /* includes existing LP_DEAD items */
225 * State describes the proper VM bit states to set for the page following
226 * pruning and freezing. all_visible implies !has_lpdead_items, but don't
227 * trust all_frozen result unless all_visible is also set to true.
229 bool all_visible
; /* Every item visible to all? */
230 bool all_frozen
; /* provided all_visible is also true */
231 TransactionId visibility_cutoff_xid
; /* For recovery conflicts */
234 /* Struct for saving and restoring vacuum error information. */
235 typedef struct LVSavedErrInfo
243 /* non-export function prototypes */
244 static void lazy_scan_heap(LVRelState
*vacrel
);
245 static BlockNumber
lazy_scan_skip(LVRelState
*vacrel
, Buffer
*vmbuffer
,
246 BlockNumber next_block
,
247 bool *next_unskippable_allvis
,
248 bool *skipping_current_range
);
249 static bool lazy_scan_new_or_empty(LVRelState
*vacrel
, Buffer buf
,
250 BlockNumber blkno
, Page page
,
251 bool sharelock
, Buffer vmbuffer
);
252 static void lazy_scan_prune(LVRelState
*vacrel
, Buffer buf
,
253 BlockNumber blkno
, Page page
,
254 LVPagePruneState
*prunestate
);
255 static bool lazy_scan_noprune(LVRelState
*vacrel
, Buffer buf
,
256 BlockNumber blkno
, Page page
,
257 bool *hastup
, bool *recordfreespace
);
258 static void lazy_vacuum(LVRelState
*vacrel
);
259 static bool lazy_vacuum_all_indexes(LVRelState
*vacrel
);
260 static void lazy_vacuum_heap_rel(LVRelState
*vacrel
);
261 static int lazy_vacuum_heap_page(LVRelState
*vacrel
, BlockNumber blkno
,
262 Buffer buffer
, int index
, Buffer
*vmbuffer
);
263 static bool lazy_check_wraparound_failsafe(LVRelState
*vacrel
);
264 static void lazy_cleanup_all_indexes(LVRelState
*vacrel
);
265 static IndexBulkDeleteResult
*lazy_vacuum_one_index(Relation indrel
,
266 IndexBulkDeleteResult
*istat
,
269 static IndexBulkDeleteResult
*lazy_cleanup_one_index(Relation indrel
,
270 IndexBulkDeleteResult
*istat
,
272 bool estimated_count
,
274 static bool should_attempt_truncation(LVRelState
*vacrel
);
275 static void lazy_truncate_heap(LVRelState
*vacrel
);
276 static BlockNumber
count_nondeletable_pages(LVRelState
*vacrel
,
277 bool *lock_waiter_detected
);
278 static void dead_items_alloc(LVRelState
*vacrel
, int nworkers
);
279 static void dead_items_cleanup(LVRelState
*vacrel
);
280 static bool heap_page_is_all_visible(LVRelState
*vacrel
, Buffer buf
,
281 TransactionId
*visibility_cutoff_xid
, bool *all_frozen
);
282 static void update_relstats_all_indexes(LVRelState
*vacrel
);
283 static void vacuum_error_callback(void *arg
);
284 static void update_vacuum_error_info(LVRelState
*vacrel
,
285 LVSavedErrInfo
*saved_vacrel
,
286 int phase
, BlockNumber blkno
,
287 OffsetNumber offnum
);
288 static void restore_vacuum_error_info(LVRelState
*vacrel
,
289 const LVSavedErrInfo
*saved_vacrel
);
293 * heap_vacuum_rel() -- perform VACUUM for one heap relation
295 * This routine sets things up for and then calls lazy_scan_heap, where
296 * almost all work actually takes place. Finalizes everything after call
297 * returns by managing relation truncation and updating rel's pg_class
298 * entry. (Also updates pg_class entries for any indexes that need it.)
300 * At entry, we have already established a transaction and opened
301 * and locked the relation.
304 heap_vacuum_rel(Relation rel
, VacuumParams
*params
,
305 BufferAccessStrategy bstrategy
)
313 BlockNumber orig_rel_pages
,
317 TimestampTz starttime
= 0;
318 PgStat_Counter startreadtime
= 0,
320 WalUsage startwalusage
= pgWalUsage
;
321 int64 StartPageHit
= VacuumPageHit
,
322 StartPageMiss
= VacuumPageMiss
,
323 StartPageDirty
= VacuumPageDirty
;
324 ErrorContextCallback errcallback
;
325 char **indnames
= NULL
;
327 verbose
= (params
->options
& VACOPT_VERBOSE
) != 0;
328 instrument
= (verbose
|| (IsAutoVacuumWorkerProcess() &&
329 params
->log_min_duration
>= 0));
332 pg_rusage_init(&ru0
);
333 starttime
= GetCurrentTimestamp();
336 startreadtime
= pgStatBlockReadTime
;
337 startwritetime
= pgStatBlockWriteTime
;
341 pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM
,
342 RelationGetRelid(rel
));
345 * Setup error traceback support for ereport() first. The idea is to set
346 * up an error context callback to display additional information on any
347 * error during a vacuum. During different phases of vacuum, we update
348 * the state so that the error context callback always display current
351 * Copy the names of heap rel into local memory for error reporting
352 * purposes, too. It isn't always safe to assume that we can get the name
353 * of each rel. It's convenient for code in lazy_scan_heap to always use
356 vacrel
= (LVRelState
*) palloc0(sizeof(LVRelState
));
357 vacrel
->relnamespace
= get_namespace_name(RelationGetNamespace(rel
));
358 vacrel
->relname
= pstrdup(RelationGetRelationName(rel
));
359 vacrel
->indname
= NULL
;
360 vacrel
->phase
= VACUUM_ERRCB_PHASE_UNKNOWN
;
361 vacrel
->verbose
= verbose
;
362 errcallback
.callback
= vacuum_error_callback
;
363 errcallback
.arg
= vacrel
;
364 errcallback
.previous
= error_context_stack
;
365 error_context_stack
= &errcallback
;
367 /* Set up high level stuff about rel and its indexes */
369 vac_open_indexes(vacrel
->rel
, RowExclusiveLock
, &vacrel
->nindexes
,
371 vacrel
->bstrategy
= bstrategy
;
372 if (instrument
&& vacrel
->nindexes
> 0)
374 /* Copy index names used by instrumentation (not error reporting) */
375 indnames
= palloc(sizeof(char *) * vacrel
->nindexes
);
376 for (int i
= 0; i
< vacrel
->nindexes
; i
++)
377 indnames
[i
] = pstrdup(RelationGetRelationName(vacrel
->indrels
[i
]));
381 * The index_cleanup param either disables index vacuuming and cleanup or
382 * forces it to go ahead when we would otherwise apply the index bypass
383 * optimization. The default is 'auto', which leaves the final decision
384 * up to lazy_vacuum().
386 * The truncate param allows user to avoid attempting relation truncation,
387 * though it can't force truncation to happen.
389 Assert(params
->index_cleanup
!= VACOPTVALUE_UNSPECIFIED
);
390 Assert(params
->truncate
!= VACOPTVALUE_UNSPECIFIED
&&
391 params
->truncate
!= VACOPTVALUE_AUTO
);
392 vacrel
->failsafe_active
= false;
393 vacrel
->consider_bypass_optimization
= true;
394 vacrel
->do_index_vacuuming
= true;
395 vacrel
->do_index_cleanup
= true;
396 vacrel
->do_rel_truncate
= (params
->truncate
!= VACOPTVALUE_DISABLED
);
397 if (params
->index_cleanup
== VACOPTVALUE_DISABLED
)
399 /* Force disable index vacuuming up-front */
400 vacrel
->do_index_vacuuming
= false;
401 vacrel
->do_index_cleanup
= false;
403 else if (params
->index_cleanup
== VACOPTVALUE_ENABLED
)
405 /* Force index vacuuming. Note that failsafe can still bypass. */
406 vacrel
->consider_bypass_optimization
= false;
410 /* Default/auto, make all decisions dynamically */
411 Assert(params
->index_cleanup
== VACOPTVALUE_AUTO
);
414 /* Initialize page counters explicitly (be tidy) */
415 vacrel
->scanned_pages
= 0;
416 vacrel
->removed_pages
= 0;
417 vacrel
->frozen_pages
= 0;
418 vacrel
->lpdead_item_pages
= 0;
419 vacrel
->missed_dead_pages
= 0;
420 vacrel
->nonempty_pages
= 0;
421 /* dead_items_alloc allocates vacrel->dead_items later on */
423 /* Allocate/initialize output statistics state */
424 vacrel
->new_rel_tuples
= 0;
425 vacrel
->new_live_tuples
= 0;
426 vacrel
->indstats
= (IndexBulkDeleteResult
**)
427 palloc0(vacrel
->nindexes
* sizeof(IndexBulkDeleteResult
*));
429 /* Initialize remaining counters (be tidy) */
430 vacrel
->num_index_scans
= 0;
431 vacrel
->tuples_deleted
= 0;
432 vacrel
->tuples_frozen
= 0;
433 vacrel
->lpdead_items
= 0;
434 vacrel
->live_tuples
= 0;
435 vacrel
->recently_dead_tuples
= 0;
436 vacrel
->missed_dead_tuples
= 0;
439 * Get cutoffs that determine which deleted tuples are considered DEAD,
440 * not just RECENTLY_DEAD, and which XIDs/MXIDs to freeze. Then determine
441 * the extent of the blocks that we'll scan in lazy_scan_heap. It has to
442 * happen in this order to ensure that the OldestXmin cutoff field works
443 * as an upper bound on the XIDs stored in the pages we'll actually scan
444 * (NewRelfrozenXid tracking must never be allowed to miss unfrozen XIDs).
446 * Next acquire vistest, a related cutoff that's used in heap_page_prune.
447 * We expect vistest will always make heap_page_prune remove any deleted
448 * tuple whose xmax is < OldestXmin. lazy_scan_prune must never become
449 * confused about whether a tuple should be frozen or removed. (In the
450 * future we might want to teach lazy_scan_prune to recompute vistest from
451 * time to time, to increase the number of dead tuples it can prune away.)
453 vacrel
->aggressive
= vacuum_get_cutoffs(rel
, params
, &vacrel
->cutoffs
);
454 vacrel
->rel_pages
= orig_rel_pages
= RelationGetNumberOfBlocks(rel
);
455 vacrel
->vistest
= GlobalVisTestFor(rel
);
456 /* Initialize state used to track oldest extant XID/MXID */
457 vacrel
->NewRelfrozenXid
= vacrel
->cutoffs
.OldestXmin
;
458 vacrel
->NewRelminMxid
= vacrel
->cutoffs
.OldestMxact
;
459 vacrel
->skippedallvis
= false;
461 if (params
->options
& VACOPT_DISABLE_PAGE_SKIPPING
)
464 * Force aggressive mode, and disable skipping blocks using the
465 * visibility map (even those set all-frozen)
467 vacrel
->aggressive
= true;
471 vacrel
->skipwithvm
= skipwithvm
;
475 if (vacrel
->aggressive
)
477 (errmsg("aggressively vacuuming \"%s.%s.%s\"",
478 get_database_name(MyDatabaseId
),
479 vacrel
->relnamespace
, vacrel
->relname
)));
482 (errmsg("vacuuming \"%s.%s.%s\"",
483 get_database_name(MyDatabaseId
),
484 vacrel
->relnamespace
, vacrel
->relname
)));
488 * Allocate dead_items array memory using dead_items_alloc. This handles
489 * parallel VACUUM initialization as part of allocating shared memory
490 * space used for dead_items. (But do a failsafe precheck first, to
491 * ensure that parallel VACUUM won't be attempted at all when relfrozenxid
492 * is already dangerously old.)
494 lazy_check_wraparound_failsafe(vacrel
);
495 dead_items_alloc(vacrel
, params
->nworkers
);
498 * Call lazy_scan_heap to perform all required heap pruning, index
499 * vacuuming, and heap vacuuming (plus related processing)
501 lazy_scan_heap(vacrel
);
504 * Free resources managed by dead_items_alloc. This ends parallel mode in
505 * passing when necessary.
507 dead_items_cleanup(vacrel
);
508 Assert(!IsInParallelMode());
511 * Update pg_class entries for each of rel's indexes where appropriate.
513 * Unlike the later update to rel's pg_class entry, this is not critical.
514 * Maintains relpages/reltuples statistics used by the planner only.
516 if (vacrel
->do_index_cleanup
)
517 update_relstats_all_indexes(vacrel
);
519 /* Done with rel's indexes */
520 vac_close_indexes(vacrel
->nindexes
, vacrel
->indrels
, NoLock
);
522 /* Optionally truncate rel */
523 if (should_attempt_truncation(vacrel
))
524 lazy_truncate_heap(vacrel
);
526 /* Pop the error context stack */
527 error_context_stack
= errcallback
.previous
;
529 /* Report that we are now doing final cleanup */
530 pgstat_progress_update_param(PROGRESS_VACUUM_PHASE
,
531 PROGRESS_VACUUM_PHASE_FINAL_CLEANUP
);
534 * Prepare to update rel's pg_class entry.
536 * Aggressive VACUUMs must always be able to advance relfrozenxid to a
537 * value >= FreezeLimit, and relminmxid to a value >= MultiXactCutoff.
538 * Non-aggressive VACUUMs may advance them by any amount, or not at all.
540 Assert(vacrel
->NewRelfrozenXid
== vacrel
->cutoffs
.OldestXmin
||
541 TransactionIdPrecedesOrEquals(vacrel
->aggressive
? vacrel
->cutoffs
.FreezeLimit
:
542 vacrel
->cutoffs
.relfrozenxid
,
543 vacrel
->NewRelfrozenXid
));
544 Assert(vacrel
->NewRelminMxid
== vacrel
->cutoffs
.OldestMxact
||
545 MultiXactIdPrecedesOrEquals(vacrel
->aggressive
? vacrel
->cutoffs
.MultiXactCutoff
:
546 vacrel
->cutoffs
.relminmxid
,
547 vacrel
->NewRelminMxid
));
548 if (vacrel
->skippedallvis
)
551 * Must keep original relfrozenxid in a non-aggressive VACUUM that
552 * chose to skip an all-visible page range. The state that tracks new
553 * values will have missed unfrozen XIDs from the pages we skipped.
555 Assert(!vacrel
->aggressive
);
556 vacrel
->NewRelfrozenXid
= InvalidTransactionId
;
557 vacrel
->NewRelminMxid
= InvalidMultiXactId
;
561 * For safety, clamp relallvisible to be not more than what we're setting
562 * pg_class.relpages to
564 new_rel_pages
= vacrel
->rel_pages
; /* After possible rel truncation */
565 visibilitymap_count(rel
, &new_rel_allvisible
, NULL
);
566 if (new_rel_allvisible
> new_rel_pages
)
567 new_rel_allvisible
= new_rel_pages
;
570 * Now actually update rel's pg_class entry.
572 * In principle new_live_tuples could be -1 indicating that we (still)
573 * don't know the tuple count. In practice that can't happen, since we
574 * scan every page that isn't skipped using the visibility map.
576 vac_update_relstats(rel
, new_rel_pages
, vacrel
->new_live_tuples
,
577 new_rel_allvisible
, vacrel
->nindexes
> 0,
578 vacrel
->NewRelfrozenXid
, vacrel
->NewRelminMxid
,
579 &frozenxid_updated
, &minmulti_updated
, false);
582 * Report results to the cumulative stats system, too.
584 * Deliberately avoid telling the stats system about LP_DEAD items that
585 * remain in the table due to VACUUM bypassing index and heap vacuuming.
586 * ANALYZE will consider the remaining LP_DEAD items to be dead "tuples".
587 * It seems like a good idea to err on the side of not vacuuming again too
588 * soon in cases where the failsafe prevented significant amounts of heap
591 pgstat_report_vacuum(RelationGetRelid(rel
),
592 rel
->rd_rel
->relisshared
,
593 Max(vacrel
->new_live_tuples
, 0),
594 vacrel
->recently_dead_tuples
+
595 vacrel
->missed_dead_tuples
);
596 pgstat_progress_end_command();
600 TimestampTz endtime
= GetCurrentTimestamp();
602 if (verbose
|| params
->log_min_duration
== 0 ||
603 TimestampDifferenceExceeds(starttime
, endtime
,
604 params
->log_min_duration
))
612 int64 PageHitOp
= VacuumPageHit
- StartPageHit
,
613 PageMissOp
= VacuumPageMiss
- StartPageMiss
,
614 PageDirtyOp
= VacuumPageDirty
- StartPageDirty
;
615 double read_rate
= 0,
618 TimestampDifference(starttime
, endtime
, &secs_dur
, &usecs_dur
);
619 memset(&walusage
, 0, sizeof(WalUsage
));
620 WalUsageAccumDiff(&walusage
, &pgWalUsage
, &startwalusage
);
622 initStringInfo(&buf
);
626 * Aggressiveness already reported earlier, in dedicated
627 * VACUUM VERBOSE ereport
629 Assert(!params
->is_wraparound
);
630 msgfmt
= _("finished vacuuming \"%s.%s.%s\": index scans: %d\n");
632 else if (params
->is_wraparound
)
635 * While it's possible for a VACUUM to be both is_wraparound
636 * and !aggressive, that's just a corner-case -- is_wraparound
637 * implies aggressive. Produce distinct output for the corner
638 * case all the same, just in case.
640 if (vacrel
->aggressive
)
641 msgfmt
= _("automatic aggressive vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n");
643 msgfmt
= _("automatic vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n");
647 if (vacrel
->aggressive
)
648 msgfmt
= _("automatic aggressive vacuum of table \"%s.%s.%s\": index scans: %d\n");
650 msgfmt
= _("automatic vacuum of table \"%s.%s.%s\": index scans: %d\n");
652 appendStringInfo(&buf
, msgfmt
,
653 get_database_name(MyDatabaseId
),
654 vacrel
->relnamespace
,
656 vacrel
->num_index_scans
);
657 appendStringInfo(&buf
, _("pages: %u removed, %u remain, %u scanned (%.2f%% of total)\n"),
658 vacrel
->removed_pages
,
660 vacrel
->scanned_pages
,
661 orig_rel_pages
== 0 ? 100.0 :
662 100.0 * vacrel
->scanned_pages
/ orig_rel_pages
);
663 appendStringInfo(&buf
,
664 _("tuples: %lld removed, %lld remain, %lld are dead but not yet removable\n"),
665 (long long) vacrel
->tuples_deleted
,
666 (long long) vacrel
->new_rel_tuples
,
667 (long long) vacrel
->recently_dead_tuples
);
668 if (vacrel
->missed_dead_tuples
> 0)
669 appendStringInfo(&buf
,
670 _("tuples missed: %lld dead from %u pages not removed due to cleanup lock contention\n"),
671 (long long) vacrel
->missed_dead_tuples
,
672 vacrel
->missed_dead_pages
);
673 diff
= (int32
) (ReadNextTransactionId() -
674 vacrel
->cutoffs
.OldestXmin
);
675 appendStringInfo(&buf
,
676 _("removable cutoff: %u, which was %d XIDs old when operation ended\n"),
677 vacrel
->cutoffs
.OldestXmin
, diff
);
678 if (frozenxid_updated
)
680 diff
= (int32
) (vacrel
->NewRelfrozenXid
-
681 vacrel
->cutoffs
.relfrozenxid
);
682 appendStringInfo(&buf
,
683 _("new relfrozenxid: %u, which is %d XIDs ahead of previous value\n"),
684 vacrel
->NewRelfrozenXid
, diff
);
686 if (minmulti_updated
)
688 diff
= (int32
) (vacrel
->NewRelminMxid
-
689 vacrel
->cutoffs
.relminmxid
);
690 appendStringInfo(&buf
,
691 _("new relminmxid: %u, which is %d MXIDs ahead of previous value\n"),
692 vacrel
->NewRelminMxid
, diff
);
694 appendStringInfo(&buf
, _("frozen: %u pages from table (%.2f%% of total) had %lld tuples frozen\n"),
695 vacrel
->frozen_pages
,
696 orig_rel_pages
== 0 ? 100.0 :
697 100.0 * vacrel
->frozen_pages
/ orig_rel_pages
,
698 (long long) vacrel
->tuples_frozen
);
699 if (vacrel
->do_index_vacuuming
)
701 if (vacrel
->nindexes
== 0 || vacrel
->num_index_scans
== 0)
702 appendStringInfoString(&buf
, _("index scan not needed: "));
704 appendStringInfoString(&buf
, _("index scan needed: "));
706 msgfmt
= _("%u pages from table (%.2f%% of total) had %lld dead item identifiers removed\n");
710 if (!vacrel
->failsafe_active
)
711 appendStringInfoString(&buf
, _("index scan bypassed: "));
713 appendStringInfoString(&buf
, _("index scan bypassed by failsafe: "));
715 msgfmt
= _("%u pages from table (%.2f%% of total) have %lld dead item identifiers\n");
717 appendStringInfo(&buf
, msgfmt
,
718 vacrel
->lpdead_item_pages
,
719 orig_rel_pages
== 0 ? 100.0 :
720 100.0 * vacrel
->lpdead_item_pages
/ orig_rel_pages
,
721 (long long) vacrel
->lpdead_items
);
722 for (int i
= 0; i
< vacrel
->nindexes
; i
++)
724 IndexBulkDeleteResult
*istat
= vacrel
->indstats
[i
];
729 appendStringInfo(&buf
,
730 _("index \"%s\": pages: %u in total, %u newly deleted, %u currently deleted, %u reusable\n"),
733 istat
->pages_newly_deleted
,
734 istat
->pages_deleted
,
739 double read_ms
= (double) (pgStatBlockReadTime
- startreadtime
) / 1000;
740 double write_ms
= (double) (pgStatBlockWriteTime
- startwritetime
) / 1000;
742 appendStringInfo(&buf
, _("I/O timings: read: %.3f ms, write: %.3f ms\n"),
745 if (secs_dur
> 0 || usecs_dur
> 0)
747 read_rate
= (double) BLCKSZ
* PageMissOp
/ (1024 * 1024) /
748 (secs_dur
+ usecs_dur
/ 1000000.0);
749 write_rate
= (double) BLCKSZ
* PageDirtyOp
/ (1024 * 1024) /
750 (secs_dur
+ usecs_dur
/ 1000000.0);
752 appendStringInfo(&buf
, _("avg read rate: %.3f MB/s, avg write rate: %.3f MB/s\n"),
753 read_rate
, write_rate
);
754 appendStringInfo(&buf
,
755 _("buffer usage: %lld hits, %lld misses, %lld dirtied\n"),
756 (long long) PageHitOp
,
757 (long long) PageMissOp
,
758 (long long) PageDirtyOp
);
759 appendStringInfo(&buf
,
760 _("WAL usage: %lld records, %lld full page images, %llu bytes\n"),
761 (long long) walusage
.wal_records
,
762 (long long) walusage
.wal_fpi
,
763 (unsigned long long) walusage
.wal_bytes
);
764 appendStringInfo(&buf
, _("system usage: %s"), pg_rusage_show(&ru0
));
766 ereport(verbose
? INFO
: LOG
,
767 (errmsg_internal("%s", buf
.data
)));
772 /* Cleanup index statistics and index names */
773 for (int i
= 0; i
< vacrel
->nindexes
; i
++)
775 if (vacrel
->indstats
[i
])
776 pfree(vacrel
->indstats
[i
]);
784 * lazy_scan_heap() -- workhorse function for VACUUM
786 * This routine prunes each page in the heap, and considers the need to
787 * freeze remaining tuples with storage (not including pages that can be
788 * skipped using the visibility map). Also performs related maintenance
789 * of the FSM and visibility map. These steps all take place during an
790 * initial pass over the target heap relation.
792 * Also invokes lazy_vacuum_all_indexes to vacuum indexes, which largely
793 * consists of deleting index tuples that point to LP_DEAD items left in
794 * heap pages following pruning. Earlier initial pass over the heap will
795 * have collected the TIDs whose index tuples need to be removed.
797 * Finally, invokes lazy_vacuum_heap_rel to vacuum heap pages, which
798 * largely consists of marking LP_DEAD items (from collected TID array)
799 * as LP_UNUSED. This has to happen in a second, final pass over the
800 * heap, to preserve a basic invariant that all index AMs rely on: no
801 * extant index tuple can ever be allowed to contain a TID that points to
802 * an LP_UNUSED line pointer in the heap. We must disallow premature
803 * recycling of line pointers to avoid index scans that get confused
804 * about which TID points to which tuple immediately after recycling.
805 * (Actually, this isn't a concern when target heap relation happens to
806 * have no indexes, which allows us to safely apply the one-pass strategy
807 * as an optimization).
809 * In practice we often have enough space to fit all TIDs, and so won't
810 * need to call lazy_vacuum more than once, after our initial pass over
811 * the heap has totally finished. Otherwise things are slightly more
812 * complicated: our "initial pass" over the heap applies only to those
813 * pages that were pruned before we needed to call lazy_vacuum, and our
814 * "final pass" over the heap only vacuums these same heap pages.
815 * However, we process indexes in full every time lazy_vacuum is called,
816 * which makes index processing very inefficient when memory is in short
820 lazy_scan_heap(LVRelState
*vacrel
)
822 BlockNumber rel_pages
= vacrel
->rel_pages
,
824 next_unskippable_block
,
825 next_fsm_block_to_vacuum
= 0;
826 VacDeadItems
*dead_items
= vacrel
->dead_items
;
827 Buffer vmbuffer
= InvalidBuffer
;
828 bool next_unskippable_allvis
,
829 skipping_current_range
;
830 const int initprog_index
[] = {
831 PROGRESS_VACUUM_PHASE
,
832 PROGRESS_VACUUM_TOTAL_HEAP_BLKS
,
833 PROGRESS_VACUUM_MAX_DEAD_TUPLES
835 int64 initprog_val
[3];
837 /* Report that we're scanning the heap, advertising total # of blocks */
838 initprog_val
[0] = PROGRESS_VACUUM_PHASE_SCAN_HEAP
;
839 initprog_val
[1] = rel_pages
;
840 initprog_val
[2] = dead_items
->max_items
;
841 pgstat_progress_update_multi_param(3, initprog_index
, initprog_val
);
843 /* Set up an initial range of skippable blocks using the visibility map */
844 next_unskippable_block
= lazy_scan_skip(vacrel
, &vmbuffer
, 0,
845 &next_unskippable_allvis
,
846 &skipping_current_range
);
847 for (blkno
= 0; blkno
< rel_pages
; blkno
++)
851 bool all_visible_according_to_vm
;
852 LVPagePruneState prunestate
;
854 if (blkno
== next_unskippable_block
)
857 * Can't skip this page safely. Must scan the page. But
858 * determine the next skippable range after the page first.
860 all_visible_according_to_vm
= next_unskippable_allvis
;
861 next_unskippable_block
= lazy_scan_skip(vacrel
, &vmbuffer
,
863 &next_unskippable_allvis
,
864 &skipping_current_range
);
866 Assert(next_unskippable_block
>= blkno
+ 1);
870 /* Last page always scanned (may need to set nonempty_pages) */
871 Assert(blkno
< rel_pages
- 1);
873 if (skipping_current_range
)
876 /* Current range is too small to skip -- just scan the page */
877 all_visible_according_to_vm
= true;
880 vacrel
->scanned_pages
++;
882 /* Report as block scanned, update error traceback information */
883 pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED
, blkno
);
884 update_vacuum_error_info(vacrel
, NULL
, VACUUM_ERRCB_PHASE_SCAN_HEAP
,
885 blkno
, InvalidOffsetNumber
);
887 vacuum_delay_point();
890 * Regularly check if wraparound failsafe should trigger.
892 * There is a similar check inside lazy_vacuum_all_indexes(), but
893 * relfrozenxid might start to look dangerously old before we reach
894 * that point. This check also provides failsafe coverage for the
895 * one-pass strategy, and the two-pass strategy with the index_cleanup
896 * param set to 'off'.
898 if (vacrel
->scanned_pages
% FAILSAFE_EVERY_PAGES
== 0)
899 lazy_check_wraparound_failsafe(vacrel
);
902 * Consider if we definitely have enough space to process TIDs on page
903 * already. If we are close to overrunning the available space for
904 * dead_items TIDs, pause and do a cycle of vacuuming before we tackle
907 Assert(dead_items
->max_items
>= MaxHeapTuplesPerPage
);
908 if (dead_items
->max_items
- dead_items
->num_items
< MaxHeapTuplesPerPage
)
911 * Before beginning index vacuuming, we release any pin we may
912 * hold on the visibility map page. This isn't necessary for
913 * correctness, but we do it anyway to avoid holding the pin
914 * across a lengthy, unrelated operation.
916 if (BufferIsValid(vmbuffer
))
918 ReleaseBuffer(vmbuffer
);
919 vmbuffer
= InvalidBuffer
;
922 /* Perform a round of index and heap vacuuming */
923 vacrel
->consider_bypass_optimization
= false;
927 * Vacuum the Free Space Map to make newly-freed space visible on
928 * upper-level FSM pages. Note we have not yet processed blkno.
930 FreeSpaceMapVacuumRange(vacrel
->rel
, next_fsm_block_to_vacuum
,
932 next_fsm_block_to_vacuum
= blkno
;
934 /* Report that we are once again scanning the heap */
935 pgstat_progress_update_param(PROGRESS_VACUUM_PHASE
,
936 PROGRESS_VACUUM_PHASE_SCAN_HEAP
);
940 * Pin the visibility map page in case we need to mark the page
941 * all-visible. In most cases this will be very cheap, because we'll
942 * already have the correct page pinned anyway.
944 visibilitymap_pin(vacrel
->rel
, blkno
, &vmbuffer
);
946 /* Finished preparatory checks. Actually scan the page. */
947 buf
= ReadBufferExtended(vacrel
->rel
, MAIN_FORKNUM
, blkno
,
948 RBM_NORMAL
, vacrel
->bstrategy
);
949 page
= BufferGetPage(buf
);
952 * We need a buffer cleanup lock to prune HOT chains and defragment
953 * the page in lazy_scan_prune. But when it's not possible to acquire
954 * a cleanup lock right away, we may be able to settle for reduced
955 * processing using lazy_scan_noprune.
957 if (!ConditionalLockBufferForCleanup(buf
))
962 LockBuffer(buf
, BUFFER_LOCK_SHARE
);
964 /* Check for new or empty pages before lazy_scan_noprune call */
965 if (lazy_scan_new_or_empty(vacrel
, buf
, blkno
, page
, true,
968 /* Processed as new/empty page (lock and pin released) */
972 /* Collect LP_DEAD items in dead_items array, count tuples */
973 if (lazy_scan_noprune(vacrel
, buf
, blkno
, page
, &hastup
,
979 * Processed page successfully (without cleanup lock) -- just
980 * need to perform rel truncation and FSM steps, much like the
981 * lazy_scan_prune case. Don't bother trying to match its
982 * visibility map setting steps, though.
985 vacrel
->nonempty_pages
= blkno
+ 1;
987 freespace
= PageGetHeapFreeSpace(page
);
988 UnlockReleaseBuffer(buf
);
990 RecordPageWithFreeSpace(vacrel
->rel
, blkno
, freespace
);
995 * lazy_scan_noprune could not do all required processing. Wait
996 * for a cleanup lock, and call lazy_scan_prune in the usual way.
998 Assert(vacrel
->aggressive
);
999 LockBuffer(buf
, BUFFER_LOCK_UNLOCK
);
1000 LockBufferForCleanup(buf
);
1003 /* Check for new or empty pages before lazy_scan_prune call */
1004 if (lazy_scan_new_or_empty(vacrel
, buf
, blkno
, page
, false, vmbuffer
))
1006 /* Processed as new/empty page (lock and pin released) */
1011 * Prune, freeze, and count tuples.
1013 * Accumulates details of remaining LP_DEAD line pointers on page in
1014 * dead_items array. This includes LP_DEAD line pointers that we
1015 * pruned ourselves, as well as existing LP_DEAD line pointers that
1016 * were pruned some time earlier. Also considers freezing XIDs in the
1017 * tuple headers of remaining items with storage.
1019 lazy_scan_prune(vacrel
, buf
, blkno
, page
, &prunestate
);
1021 Assert(!prunestate
.all_visible
|| !prunestate
.has_lpdead_items
);
1023 /* Remember the location of the last page with nonremovable tuples */
1024 if (prunestate
.hastup
)
1025 vacrel
->nonempty_pages
= blkno
+ 1;
1027 if (vacrel
->nindexes
== 0)
1030 * Consider the need to do page-at-a-time heap vacuuming when
1031 * using the one-pass strategy now.
1033 * The one-pass strategy will never call lazy_vacuum(). The steps
1034 * performed here can be thought of as the one-pass equivalent of
1035 * a call to lazy_vacuum().
1037 if (prunestate
.has_lpdead_items
)
1041 lazy_vacuum_heap_page(vacrel
, blkno
, buf
, 0, &vmbuffer
);
1043 /* Forget the LP_DEAD items that we just vacuumed */
1044 dead_items
->num_items
= 0;
1047 * Periodically perform FSM vacuuming to make newly-freed
1048 * space visible on upper FSM pages. Note we have not yet
1049 * performed FSM processing for blkno.
1051 if (blkno
- next_fsm_block_to_vacuum
>= VACUUM_FSM_EVERY_PAGES
)
1053 FreeSpaceMapVacuumRange(vacrel
->rel
, next_fsm_block_to_vacuum
,
1055 next_fsm_block_to_vacuum
= blkno
;
1059 * Now perform FSM processing for blkno, and move on to next
1062 * Our call to lazy_vacuum_heap_page() will have considered if
1063 * it's possible to set all_visible/all_frozen independently
1064 * of lazy_scan_prune(). Note that prunestate was invalidated
1065 * by lazy_vacuum_heap_page() call.
1067 freespace
= PageGetHeapFreeSpace(page
);
1069 UnlockReleaseBuffer(buf
);
1070 RecordPageWithFreeSpace(vacrel
->rel
, blkno
, freespace
);
1075 * There was no call to lazy_vacuum_heap_page() because pruning
1076 * didn't encounter/create any LP_DEAD items that needed to be
1077 * vacuumed. Prune state has not been invalidated, so proceed
1078 * with prunestate-driven visibility map and FSM steps (just like
1079 * the two-pass strategy).
1081 Assert(dead_items
->num_items
== 0);
1085 * Handle setting visibility map bit based on information from the VM
1086 * (as of last lazy_scan_skip() call), and from prunestate
1088 if (!all_visible_according_to_vm
&& prunestate
.all_visible
)
1090 uint8 flags
= VISIBILITYMAP_ALL_VISIBLE
;
1092 if (prunestate
.all_frozen
)
1093 flags
|= VISIBILITYMAP_ALL_FROZEN
;
1096 * It should never be the case that the visibility map page is set
1097 * while the page-level bit is clear, but the reverse is allowed
1098 * (if checksums are not enabled). Regardless, set both bits so
1099 * that we get back in sync.
1101 * NB: If the heap page is all-visible but the VM bit is not set,
1102 * we don't need to dirty the heap page. However, if checksums
1103 * are enabled, we do need to make sure that the heap page is
1104 * dirtied before passing it to visibilitymap_set(), because it
1105 * may be logged. Given that this situation should only happen in
1106 * rare cases after a crash, it is not worth optimizing.
1108 PageSetAllVisible(page
);
1109 MarkBufferDirty(buf
);
1110 visibilitymap_set(vacrel
->rel
, blkno
, buf
, InvalidXLogRecPtr
,
1111 vmbuffer
, prunestate
.visibility_cutoff_xid
,
1116 * As of PostgreSQL 9.2, the visibility map bit should never be set if
1117 * the page-level bit is clear. However, it's possible that the bit
1118 * got cleared after lazy_scan_skip() was called, so we must recheck
1119 * with buffer lock before concluding that the VM is corrupt.
1121 else if (all_visible_according_to_vm
&& !PageIsAllVisible(page
)
1122 && VM_ALL_VISIBLE(vacrel
->rel
, blkno
, &vmbuffer
))
1124 elog(WARNING
, "page is not marked all-visible but visibility map bit is set in relation \"%s\" page %u",
1125 vacrel
->relname
, blkno
);
1126 visibilitymap_clear(vacrel
->rel
, blkno
, vmbuffer
,
1127 VISIBILITYMAP_VALID_BITS
);
1131 * It's possible for the value returned by
1132 * GetOldestNonRemovableTransactionId() to move backwards, so it's not
1133 * wrong for us to see tuples that appear to not be visible to
1134 * everyone yet, while PD_ALL_VISIBLE is already set. The real safe
1135 * xmin value never moves backwards, but
1136 * GetOldestNonRemovableTransactionId() is conservative and sometimes
1137 * returns a value that's unnecessarily small, so if we see that
1138 * contradiction it just means that the tuples that we think are not
1139 * visible to everyone yet actually are, and the PD_ALL_VISIBLE flag
1142 * There should never be LP_DEAD items on a page with PD_ALL_VISIBLE
1145 else if (prunestate
.has_lpdead_items
&& PageIsAllVisible(page
))
1147 elog(WARNING
, "page containing LP_DEAD items is marked as all-visible in relation \"%s\" page %u",
1148 vacrel
->relname
, blkno
);
1149 PageClearAllVisible(page
);
1150 MarkBufferDirty(buf
);
1151 visibilitymap_clear(vacrel
->rel
, blkno
, vmbuffer
,
1152 VISIBILITYMAP_VALID_BITS
);
1156 * If the all-visible page is all-frozen but not marked as such yet,
1157 * mark it as all-frozen. Note that all_frozen is only valid if
1158 * all_visible is true, so we must check both prunestate fields.
1160 else if (all_visible_according_to_vm
&& prunestate
.all_visible
&&
1161 prunestate
.all_frozen
&&
1162 !VM_ALL_FROZEN(vacrel
->rel
, blkno
, &vmbuffer
))
1165 * We can pass InvalidTransactionId as the cutoff XID here,
1166 * because setting the all-frozen bit doesn't cause recovery
1169 visibilitymap_set(vacrel
->rel
, blkno
, buf
, InvalidXLogRecPtr
,
1170 vmbuffer
, InvalidTransactionId
,
1171 VISIBILITYMAP_ALL_FROZEN
);
1175 * Final steps for block: drop cleanup lock, record free space in the
1178 if (prunestate
.has_lpdead_items
&& vacrel
->do_index_vacuuming
)
1181 * Wait until lazy_vacuum_heap_rel() to save free space. This
1182 * doesn't just save us some cycles; it also allows us to record
1183 * any additional free space that lazy_vacuum_heap_page() will
1184 * make available in cases where it's possible to truncate the
1185 * page's line pointer array.
1187 * Note: It's not in fact 100% certain that we really will call
1188 * lazy_vacuum_heap_rel() -- lazy_vacuum() might yet opt to skip
1189 * index vacuuming (and so must skip heap vacuuming). This is
1190 * deemed okay because it only happens in emergencies, or when
1191 * there is very little free space anyway. (Besides, we start
1192 * recording free space in the FSM once index vacuuming has been
1195 * Note: The one-pass (no indexes) case is only supposed to make
1196 * it this far when there were no LP_DEAD items during pruning.
1198 Assert(vacrel
->nindexes
> 0);
1199 UnlockReleaseBuffer(buf
);
1203 Size freespace
= PageGetHeapFreeSpace(page
);
1205 UnlockReleaseBuffer(buf
);
1206 RecordPageWithFreeSpace(vacrel
->rel
, blkno
, freespace
);
1210 vacrel
->blkno
= InvalidBlockNumber
;
1211 if (BufferIsValid(vmbuffer
))
1212 ReleaseBuffer(vmbuffer
);
1214 /* report that everything is now scanned */
1215 pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED
, blkno
);
1217 /* now we can compute the new value for pg_class.reltuples */
1218 vacrel
->new_live_tuples
= vac_estimate_reltuples(vacrel
->rel
, rel_pages
,
1219 vacrel
->scanned_pages
,
1220 vacrel
->live_tuples
);
1223 * Also compute the total number of surviving heap entries. In the
1224 * (unlikely) scenario that new_live_tuples is -1, take it as zero.
1226 vacrel
->new_rel_tuples
=
1227 Max(vacrel
->new_live_tuples
, 0) + vacrel
->recently_dead_tuples
+
1228 vacrel
->missed_dead_tuples
;
1231 * Do index vacuuming (call each index's ambulkdelete routine), then do
1232 * related heap vacuuming
1234 if (dead_items
->num_items
> 0)
1235 lazy_vacuum(vacrel
);
1238 * Vacuum the remainder of the Free Space Map. We must do this whether or
1239 * not there were indexes, and whether or not we bypassed index vacuuming.
1241 if (blkno
> next_fsm_block_to_vacuum
)
1242 FreeSpaceMapVacuumRange(vacrel
->rel
, next_fsm_block_to_vacuum
, blkno
);
1244 /* report all blocks vacuumed */
1245 pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED
, blkno
);
1247 /* Do final index cleanup (call each index's amvacuumcleanup routine) */
1248 if (vacrel
->nindexes
> 0 && vacrel
->do_index_cleanup
)
1249 lazy_cleanup_all_indexes(vacrel
);
1253 * lazy_scan_skip() -- set up range of skippable blocks using visibility map.
1255 * lazy_scan_heap() calls here every time it needs to set up a new range of
1256 * blocks to skip via the visibility map. Caller passes the next block in
1257 * line. We return a next_unskippable_block for this range. When there are
1258 * no skippable blocks we just return caller's next_block. The all-visible
1259 * status of the returned block is set in *next_unskippable_allvis for caller,
1260 * too. Block usually won't be all-visible (since it's unskippable), but it
1261 * can be during aggressive VACUUMs (as well as in certain edge cases).
1263 * Sets *skipping_current_range to indicate if caller should skip this range.
1264 * Costs and benefits drive our decision. Very small ranges won't be skipped.
1266 * Note: our opinion of which blocks can be skipped can go stale immediately.
1267 * It's okay if caller "misses" a page whose all-visible or all-frozen marking
1268 * was concurrently cleared, though. All that matters is that caller scan all
1269 * pages whose tuples might contain XIDs < OldestXmin, or MXIDs < OldestMxact.
1270 * (Actually, non-aggressive VACUUMs can choose to skip all-visible pages with
1271 * older XIDs/MXIDs. The vacrel->skippedallvis flag will be set here when the
1272 * choice to skip such a range is actually made, making everything safe.)
1275 lazy_scan_skip(LVRelState
*vacrel
, Buffer
*vmbuffer
, BlockNumber next_block
,
1276 bool *next_unskippable_allvis
, bool *skipping_current_range
)
1278 BlockNumber rel_pages
= vacrel
->rel_pages
,
1279 next_unskippable_block
= next_block
,
1280 nskippable_blocks
= 0;
1281 bool skipsallvis
= false;
1283 *next_unskippable_allvis
= true;
1284 while (next_unskippable_block
< rel_pages
)
1286 uint8 mapbits
= visibilitymap_get_status(vacrel
->rel
,
1287 next_unskippable_block
,
1290 if ((mapbits
& VISIBILITYMAP_ALL_VISIBLE
) == 0)
1292 Assert((mapbits
& VISIBILITYMAP_ALL_FROZEN
) == 0);
1293 *next_unskippable_allvis
= false;
1298 * Caller must scan the last page to determine whether it has tuples
1299 * (caller must have the opportunity to set vacrel->nonempty_pages).
1300 * This rule avoids having lazy_truncate_heap() take access-exclusive
1301 * lock on rel to attempt a truncation that fails anyway, just because
1302 * there are tuples on the last page (it is likely that there will be
1303 * tuples on other nearby pages as well, but those can be skipped).
1305 * Implement this by always treating the last block as unsafe to skip.
1307 if (next_unskippable_block
== rel_pages
- 1)
1310 /* DISABLE_PAGE_SKIPPING makes all skipping unsafe */
1311 if (!vacrel
->skipwithvm
)
1315 * Aggressive VACUUM caller can't skip pages just because they are
1316 * all-visible. They may still skip all-frozen pages, which can't
1317 * contain XIDs < OldestXmin (XIDs that aren't already frozen by now).
1319 if ((mapbits
& VISIBILITYMAP_ALL_FROZEN
) == 0)
1321 if (vacrel
->aggressive
)
1325 * All-visible block is safe to skip in non-aggressive case. But
1326 * remember that the final range contains such a block for later.
1331 vacuum_delay_point();
1332 next_unskippable_block
++;
1333 nskippable_blocks
++;
1337 * We only skip a range with at least SKIP_PAGES_THRESHOLD consecutive
1338 * pages. Since we're reading sequentially, the OS should be doing
1339 * readahead for us, so there's no gain in skipping a page now and then.
1340 * Skipping such a range might even discourage sequential detection.
1342 * This test also enables more frequent relfrozenxid advancement during
1343 * non-aggressive VACUUMs. If the range has any all-visible pages then
1344 * skipping makes updating relfrozenxid unsafe, which is a real downside.
1346 if (nskippable_blocks
< SKIP_PAGES_THRESHOLD
)
1347 *skipping_current_range
= false;
1350 *skipping_current_range
= true;
1352 vacrel
->skippedallvis
= true;
1355 return next_unskippable_block
;
1359 * lazy_scan_new_or_empty() -- lazy_scan_heap() new/empty page handling.
1361 * Must call here to handle both new and empty pages before calling
1362 * lazy_scan_prune or lazy_scan_noprune, since they're not prepared to deal
1363 * with new or empty pages.
1365 * It's necessary to consider new pages as a special case, since the rules for
1366 * maintaining the visibility map and FSM with empty pages are a little
1367 * different (though new pages can be truncated away during rel truncation).
1369 * Empty pages are not really a special case -- they're just heap pages that
1370 * have no allocated tuples (including even LP_UNUSED items). You might
1371 * wonder why we need to handle them here all the same. It's only necessary
1372 * because of a corner-case involving a hard crash during heap relation
1373 * extension. If we ever make relation-extension crash safe, then it should
1374 * no longer be necessary to deal with empty pages here (or new pages, for
1377 * Caller must hold at least a shared lock. We might need to escalate the
1378 * lock in that case, so the type of lock caller holds needs to be specified
1379 * using 'sharelock' argument.
1381 * Returns false in common case where caller should go on to call
1382 * lazy_scan_prune (or lazy_scan_noprune). Otherwise returns true, indicating
1383 * that lazy_scan_heap is done processing the page, releasing lock on caller's
1387 lazy_scan_new_or_empty(LVRelState
*vacrel
, Buffer buf
, BlockNumber blkno
,
1388 Page page
, bool sharelock
, Buffer vmbuffer
)
1392 if (PageIsNew(page
))
1395 * All-zeroes pages can be left over if either a backend extends the
1396 * relation by a single page, but crashes before the newly initialized
1397 * page has been written out, or when bulk-extending the relation
1398 * (which creates a number of empty pages at the tail end of the
1399 * relation), and then enters them into the FSM.
1401 * Note we do not enter the page into the visibilitymap. That has the
1402 * downside that we repeatedly visit this page in subsequent vacuums,
1403 * but otherwise we'll never discover the space on a promoted standby.
1404 * The harm of repeated checking ought to normally not be too bad. The
1405 * space usually should be used at some point, otherwise there
1406 * wouldn't be any regular vacuums.
1408 * Make sure these pages are in the FSM, to ensure they can be reused.
1409 * Do that by testing if there's any space recorded for the page. If
1410 * not, enter it. We do so after releasing the lock on the heap page,
1411 * the FSM is approximate, after all.
1413 UnlockReleaseBuffer(buf
);
1415 if (GetRecordedFreeSpace(vacrel
->rel
, blkno
) == 0)
1417 freespace
= BLCKSZ
- SizeOfPageHeaderData
;
1419 RecordPageWithFreeSpace(vacrel
->rel
, blkno
, freespace
);
1425 if (PageIsEmpty(page
))
1428 * It seems likely that caller will always be able to get a cleanup
1429 * lock on an empty page. But don't take any chances -- escalate to
1430 * an exclusive lock (still don't need a cleanup lock, though).
1434 LockBuffer(buf
, BUFFER_LOCK_UNLOCK
);
1435 LockBuffer(buf
, BUFFER_LOCK_EXCLUSIVE
);
1437 if (!PageIsEmpty(page
))
1439 /* page isn't new or empty -- keep lock and pin for now */
1445 /* Already have a full cleanup lock (which is more than enough) */
1449 * Unlike new pages, empty pages are always set all-visible and
1452 if (!PageIsAllVisible(page
))
1454 START_CRIT_SECTION();
1456 /* mark buffer dirty before writing a WAL record */
1457 MarkBufferDirty(buf
);
1460 * It's possible that another backend has extended the heap,
1461 * initialized the page, and then failed to WAL-log the page due
1462 * to an ERROR. Since heap extension is not WAL-logged, recovery
1463 * might try to replay our record setting the page all-visible and
1464 * find that the page isn't initialized, which will cause a PANIC.
1465 * To prevent that, check whether the page has been previously
1466 * WAL-logged, and if not, do that now.
1468 if (RelationNeedsWAL(vacrel
->rel
) &&
1469 PageGetLSN(page
) == InvalidXLogRecPtr
)
1470 log_newpage_buffer(buf
, true);
1472 PageSetAllVisible(page
);
1473 visibilitymap_set(vacrel
->rel
, blkno
, buf
, InvalidXLogRecPtr
,
1474 vmbuffer
, InvalidTransactionId
,
1475 VISIBILITYMAP_ALL_VISIBLE
| VISIBILITYMAP_ALL_FROZEN
);
1479 freespace
= PageGetHeapFreeSpace(page
);
1480 UnlockReleaseBuffer(buf
);
1481 RecordPageWithFreeSpace(vacrel
->rel
, blkno
, freespace
);
1485 /* page isn't new or empty -- keep lock and pin */
1490 * lazy_scan_prune() -- lazy_scan_heap() pruning and freezing.
1492 * Caller must hold pin and buffer cleanup lock on the buffer.
1494 * Prior to PostgreSQL 14 there were very rare cases where heap_page_prune()
1495 * was allowed to disagree with our HeapTupleSatisfiesVacuum() call about
1496 * whether or not a tuple should be considered DEAD. This happened when an
1497 * inserting transaction concurrently aborted (after our heap_page_prune()
1498 * call, before our HeapTupleSatisfiesVacuum() call). There was rather a lot
1499 * of complexity just so we could deal with tuples that were DEAD to VACUUM,
1500 * but nevertheless were left with storage after pruning.
1502 * The approach we take now is to restart pruning when the race condition is
1503 * detected. This allows heap_page_prune() to prune the tuples inserted by
1504 * the now-aborted transaction. This is a little crude, but it guarantees
1505 * that any items that make it into the dead_items array are simple LP_DEAD
1506 * line pointers, and that every remaining item with tuple storage is
1507 * considered as a candidate for freezing.
1510 lazy_scan_prune(LVRelState
*vacrel
,
1514 LVPagePruneState
*prunestate
)
1516 Relation rel
= vacrel
->rel
;
1517 OffsetNumber offnum
,
1520 HeapTupleData tuple
;
1526 recently_dead_tuples
;
1528 TransactionId NewRelfrozenXid
;
1529 MultiXactId NewRelminMxid
;
1530 OffsetNumber deadoffsets
[MaxHeapTuplesPerPage
];
1531 HeapTupleFreeze frozen
[MaxHeapTuplesPerPage
];
1533 Assert(BufferGetBlockNumber(buf
) == blkno
);
1536 * maxoff might be reduced following line pointer array truncation in
1537 * heap_page_prune. That's safe for us to ignore, since the reclaimed
1538 * space will continue to look like LP_UNUSED items below.
1540 maxoff
= PageGetMaxOffsetNumber(page
);
1544 /* Initialize (or reset) page-level state */
1545 NewRelfrozenXid
= vacrel
->NewRelfrozenXid
;
1546 NewRelminMxid
= vacrel
->NewRelminMxid
;
1551 recently_dead_tuples
= 0;
1554 * Prune all HOT-update chains in this page.
1556 * We count tuples removed by the pruning step as tuples_deleted. Its
1557 * final value can be thought of as the number of tuples that have been
1558 * deleted from the table. It should not be confused with lpdead_items;
1559 * lpdead_items's final value can be thought of as the number of tuples
1560 * that were deleted from indexes.
1562 tuples_deleted
= heap_page_prune(rel
, buf
, vacrel
->vistest
,
1563 InvalidTransactionId
, 0, &nnewlpdead
,
1567 * Now scan the page to collect LP_DEAD items and check for tuples
1568 * requiring freezing among remaining tuples with storage
1570 prunestate
->hastup
= false;
1571 prunestate
->has_lpdead_items
= false;
1572 prunestate
->all_visible
= true;
1573 prunestate
->all_frozen
= true;
1574 prunestate
->visibility_cutoff_xid
= InvalidTransactionId
;
1576 for (offnum
= FirstOffsetNumber
;
1578 offnum
= OffsetNumberNext(offnum
))
1580 bool totally_frozen
;
1583 * Set the offset number so that we can display it along with any
1584 * error that occurred while processing this tuple.
1586 vacrel
->offnum
= offnum
;
1587 itemid
= PageGetItemId(page
, offnum
);
1589 if (!ItemIdIsUsed(itemid
))
1592 /* Redirect items mustn't be touched */
1593 if (ItemIdIsRedirected(itemid
))
1595 prunestate
->hastup
= true; /* page won't be truncatable */
1600 * LP_DEAD items are processed outside of the loop.
1602 * Note that we deliberately don't set hastup=true in the case of an
1603 * LP_DEAD item here, which is not how count_nondeletable_pages() does
1604 * it -- it only considers pages empty/truncatable when they have no
1605 * items at all (except LP_UNUSED items).
1607 * Our assumption is that any LP_DEAD items we encounter here will
1608 * become LP_UNUSED inside lazy_vacuum_heap_page() before we actually
1609 * call count_nondeletable_pages(). In any case our opinion of
1610 * whether or not a page 'hastup' (which is how our caller sets its
1611 * vacrel->nonempty_pages value) is inherently race-prone. It must be
1612 * treated as advisory/unreliable, so we might as well be slightly
1615 if (ItemIdIsDead(itemid
))
1617 deadoffsets
[lpdead_items
++] = offnum
;
1618 prunestate
->all_visible
= false;
1619 prunestate
->has_lpdead_items
= true;
1623 Assert(ItemIdIsNormal(itemid
));
1625 ItemPointerSet(&(tuple
.t_self
), blkno
, offnum
);
1626 tuple
.t_data
= (HeapTupleHeader
) PageGetItem(page
, itemid
);
1627 tuple
.t_len
= ItemIdGetLength(itemid
);
1628 tuple
.t_tableOid
= RelationGetRelid(rel
);
1631 * DEAD tuples are almost always pruned into LP_DEAD line pointers by
1632 * heap_page_prune(), but it's possible that the tuple state changed
1633 * since heap_page_prune() looked. Handle that here by restarting.
1634 * (See comments at the top of function for a full explanation.)
1636 res
= HeapTupleSatisfiesVacuum(&tuple
, vacrel
->cutoffs
.OldestXmin
,
1639 if (unlikely(res
== HEAPTUPLE_DEAD
))
1643 * The criteria for counting a tuple as live in this block need to
1644 * match what analyze.c's acquire_sample_rows() does, otherwise VACUUM
1645 * and ANALYZE may produce wildly different reltuples values, e.g.
1646 * when there are many recently-dead tuples.
1648 * The logic here is a bit simpler than acquire_sample_rows(), as
1649 * VACUUM can't run inside a transaction block, which makes some cases
1650 * impossible (e.g. in-progress insert from the same transaction).
1652 * We treat LP_DEAD items (which are the closest thing to DEAD tuples
1653 * that might be seen here) differently, too: we assume that they'll
1654 * become LP_UNUSED before VACUUM finishes. This difference is only
1655 * superficial. VACUUM effectively agrees with ANALYZE about DEAD
1656 * items, in the end. VACUUM won't remember LP_DEAD items, but only
1657 * because they're not supposed to be left behind when it is done.
1658 * (Cases where we bypass index vacuuming will violate this optimistic
1659 * assumption, but the overall impact of that should be negligible.)
1663 case HEAPTUPLE_LIVE
:
1666 * Count it as live. Not only is this natural, but it's also
1667 * what acquire_sample_rows() does.
1672 * Is the tuple definitely visible to all transactions?
1674 * NB: Like with per-tuple hint bits, we can't set the
1675 * PD_ALL_VISIBLE flag if the inserter committed
1676 * asynchronously. See SetHintBits for more info. Check that
1677 * the tuple is hinted xmin-committed because of that.
1679 if (prunestate
->all_visible
)
1683 if (!HeapTupleHeaderXminCommitted(tuple
.t_data
))
1685 prunestate
->all_visible
= false;
1690 * The inserter definitely committed. But is it old enough
1691 * that everyone sees it as committed?
1693 xmin
= HeapTupleHeaderGetXmin(tuple
.t_data
);
1694 if (!TransactionIdPrecedes(xmin
,
1695 vacrel
->cutoffs
.OldestXmin
))
1697 prunestate
->all_visible
= false;
1701 /* Track newest xmin on page. */
1702 if (TransactionIdFollows(xmin
, prunestate
->visibility_cutoff_xid
))
1703 prunestate
->visibility_cutoff_xid
= xmin
;
1706 case HEAPTUPLE_RECENTLY_DEAD
:
1709 * If tuple is recently dead then we must not remove it from
1710 * the relation. (We only remove items that are LP_DEAD from
1713 recently_dead_tuples
++;
1714 prunestate
->all_visible
= false;
1716 case HEAPTUPLE_INSERT_IN_PROGRESS
:
1719 * We do not count these rows as live, because we expect the
1720 * inserting transaction to update the counters at commit, and
1721 * we assume that will happen only after we report our
1722 * results. This assumption is a bit shaky, but it is what
1723 * acquire_sample_rows() does, so be consistent.
1725 prunestate
->all_visible
= false;
1727 case HEAPTUPLE_DELETE_IN_PROGRESS
:
1728 /* This is an expected case during concurrent vacuum */
1729 prunestate
->all_visible
= false;
1732 * Count such rows as live. As above, we assume the deleting
1733 * transaction will commit and update the counters after we
1739 elog(ERROR
, "unexpected HeapTupleSatisfiesVacuum result");
1743 prunestate
->hastup
= true; /* page makes rel truncation unsafe */
1745 /* Tuple with storage -- consider need to freeze */
1746 if (heap_prepare_freeze_tuple(tuple
.t_data
, &vacrel
->cutoffs
,
1747 &frozen
[tuples_frozen
], &totally_frozen
,
1748 &NewRelfrozenXid
, &NewRelminMxid
))
1750 /* Save prepared freeze plan for later */
1751 frozen
[tuples_frozen
++].offset
= offnum
;
1755 * If tuple is not frozen (and not about to become frozen) then caller
1756 * had better not go on to set this page's VM bit
1758 if (!totally_frozen
)
1759 prunestate
->all_frozen
= false;
1762 vacrel
->offnum
= InvalidOffsetNumber
;
1765 * We have now divided every item on the page into either an LP_DEAD item
1766 * that will need to be vacuumed in indexes later, or a LP_NORMAL tuple
1767 * that remains and needs to be considered for freezing now (LP_UNUSED and
1768 * LP_REDIRECT items also remain, but are of no further interest to us).
1770 vacrel
->NewRelfrozenXid
= NewRelfrozenXid
;
1771 vacrel
->NewRelminMxid
= NewRelminMxid
;
1774 * Consider the need to freeze any items with tuple storage from the page
1777 if (tuples_frozen
> 0)
1779 Assert(prunestate
->hastup
);
1781 vacrel
->frozen_pages
++;
1783 /* Execute all freeze plans for page as a single atomic action */
1784 heap_freeze_execute_prepared(vacrel
->rel
, buf
,
1785 vacrel
->cutoffs
.FreezeLimit
,
1786 frozen
, tuples_frozen
);
1790 * The second pass over the heap can also set visibility map bits, using
1791 * the same approach. This is important when the table frequently has a
1792 * few old LP_DEAD items on each page by the time we get to it (typically
1793 * because past opportunistic pruning operations freed some non-HOT
1796 * VACUUM will call heap_page_is_all_visible() during the second pass over
1797 * the heap to determine all_visible and all_frozen for the page -- this
1798 * is a specialized version of the logic from this function. Now that
1799 * we've finished pruning and freezing, make sure that we're in total
1800 * agreement with heap_page_is_all_visible() using an assertion.
1802 #ifdef USE_ASSERT_CHECKING
1803 /* Note that all_frozen value does not matter when !all_visible */
1804 if (prunestate
->all_visible
)
1806 TransactionId cutoff
;
1809 if (!heap_page_is_all_visible(vacrel
, buf
, &cutoff
, &all_frozen
))
1812 Assert(lpdead_items
== 0);
1813 Assert(prunestate
->all_frozen
== all_frozen
);
1816 * It's possible that we froze tuples and made the page's XID cutoff
1817 * (for recovery conflict purposes) FrozenTransactionId. This is okay
1818 * because visibility_cutoff_xid will be logged by our caller in a
1821 Assert(cutoff
== FrozenTransactionId
||
1822 cutoff
== prunestate
->visibility_cutoff_xid
);
1827 * Now save details of the LP_DEAD items from the page in vacrel
1829 if (lpdead_items
> 0)
1831 VacDeadItems
*dead_items
= vacrel
->dead_items
;
1832 ItemPointerData tmp
;
1834 Assert(!prunestate
->all_visible
);
1835 Assert(prunestate
->has_lpdead_items
);
1837 vacrel
->lpdead_item_pages
++;
1839 ItemPointerSetBlockNumber(&tmp
, blkno
);
1841 for (int i
= 0; i
< lpdead_items
; i
++)
1843 ItemPointerSetOffsetNumber(&tmp
, deadoffsets
[i
]);
1844 dead_items
->items
[dead_items
->num_items
++] = tmp
;
1847 Assert(dead_items
->num_items
<= dead_items
->max_items
);
1848 pgstat_progress_update_param(PROGRESS_VACUUM_NUM_DEAD_TUPLES
,
1849 dead_items
->num_items
);
1852 /* Finally, add page-local counts to whole-VACUUM counts */
1853 vacrel
->tuples_deleted
+= tuples_deleted
;
1854 vacrel
->tuples_frozen
+= tuples_frozen
;
1855 vacrel
->lpdead_items
+= lpdead_items
;
1856 vacrel
->live_tuples
+= live_tuples
;
1857 vacrel
->recently_dead_tuples
+= recently_dead_tuples
;
1861 * lazy_scan_noprune() -- lazy_scan_prune() without pruning or freezing
1863 * Caller need only hold a pin and share lock on the buffer, unlike
1864 * lazy_scan_prune, which requires a full cleanup lock. While pruning isn't
1865 * performed here, it's quite possible that an earlier opportunistic pruning
1866 * operation left LP_DEAD items behind. We'll at least collect any such items
1867 * in the dead_items array for removal from indexes.
1869 * For aggressive VACUUM callers, we may return false to indicate that a full
1870 * cleanup lock is required for processing by lazy_scan_prune. This is only
1871 * necessary when the aggressive VACUUM needs to freeze some tuple XIDs from
1872 * one or more tuples on the page. We always return true for non-aggressive
1875 * See lazy_scan_prune for an explanation of hastup return flag.
1876 * recordfreespace flag instructs caller on whether or not it should do
1877 * generic FSM processing for page.
1880 lazy_scan_noprune(LVRelState
*vacrel
,
1885 bool *recordfreespace
)
1887 OffsetNumber offnum
,
1891 recently_dead_tuples
,
1893 HeapTupleHeader tupleheader
;
1894 TransactionId NewRelfrozenXid
= vacrel
->NewRelfrozenXid
;
1895 MultiXactId NewRelminMxid
= vacrel
->NewRelminMxid
;
1896 OffsetNumber deadoffsets
[MaxHeapTuplesPerPage
];
1898 Assert(BufferGetBlockNumber(buf
) == blkno
);
1900 *hastup
= false; /* for now */
1901 *recordfreespace
= false; /* for now */
1905 recently_dead_tuples
= 0;
1906 missed_dead_tuples
= 0;
1908 maxoff
= PageGetMaxOffsetNumber(page
);
1909 for (offnum
= FirstOffsetNumber
;
1911 offnum
= OffsetNumberNext(offnum
))
1914 HeapTupleData tuple
;
1916 vacrel
->offnum
= offnum
;
1917 itemid
= PageGetItemId(page
, offnum
);
1919 if (!ItemIdIsUsed(itemid
))
1922 if (ItemIdIsRedirected(itemid
))
1928 if (ItemIdIsDead(itemid
))
1931 * Deliberately don't set hastup=true here. See same point in
1932 * lazy_scan_prune for an explanation.
1934 deadoffsets
[lpdead_items
++] = offnum
;
1938 *hastup
= true; /* page prevents rel truncation */
1939 tupleheader
= (HeapTupleHeader
) PageGetItem(page
, itemid
);
1940 if (heap_tuple_would_freeze(tupleheader
, &vacrel
->cutoffs
,
1941 &NewRelfrozenXid
, &NewRelminMxid
))
1943 /* Tuple with XID < FreezeLimit (or MXID < MultiXactCutoff) */
1944 if (vacrel
->aggressive
)
1947 * Aggressive VACUUMs must always be able to advance rel's
1948 * relfrozenxid to a value >= FreezeLimit (and be able to
1949 * advance rel's relminmxid to a value >= MultiXactCutoff).
1950 * The ongoing aggressive VACUUM won't be able to do that
1951 * unless it can freeze an XID (or MXID) from this tuple now.
1953 * The only safe option is to have caller perform processing
1954 * of this page using lazy_scan_prune. Caller might have to
1955 * wait a while for a cleanup lock, but it can't be helped.
1957 vacrel
->offnum
= InvalidOffsetNumber
;
1962 * Non-aggressive VACUUMs are under no obligation to advance
1963 * relfrozenxid (even by one XID). We can be much laxer here.
1965 * Currently we always just accept an older final relfrozenxid
1966 * and/or relminmxid value. We never make caller wait or work a
1967 * little harder, even when it likely makes sense to do so.
1971 ItemPointerSet(&(tuple
.t_self
), blkno
, offnum
);
1972 tuple
.t_data
= (HeapTupleHeader
) PageGetItem(page
, itemid
);
1973 tuple
.t_len
= ItemIdGetLength(itemid
);
1974 tuple
.t_tableOid
= RelationGetRelid(vacrel
->rel
);
1976 switch (HeapTupleSatisfiesVacuum(&tuple
, vacrel
->cutoffs
.OldestXmin
,
1979 case HEAPTUPLE_DELETE_IN_PROGRESS
:
1980 case HEAPTUPLE_LIVE
:
1983 * Count both cases as live, just like lazy_scan_prune
1988 case HEAPTUPLE_DEAD
:
1991 * There is some useful work for pruning to do, that won't be
1992 * done due to failure to get a cleanup lock.
1994 missed_dead_tuples
++;
1996 case HEAPTUPLE_RECENTLY_DEAD
:
1999 * Count in recently_dead_tuples, just like lazy_scan_prune
2001 recently_dead_tuples
++;
2003 case HEAPTUPLE_INSERT_IN_PROGRESS
:
2006 * Do not count these rows as live, just like lazy_scan_prune
2010 elog(ERROR
, "unexpected HeapTupleSatisfiesVacuum result");
2015 vacrel
->offnum
= InvalidOffsetNumber
;
2018 * By here we know for sure that caller can put off freezing and pruning
2019 * this particular page until the next VACUUM. Remember its details now.
2020 * (lazy_scan_prune expects a clean slate, so we have to do this last.)
2022 vacrel
->NewRelfrozenXid
= NewRelfrozenXid
;
2023 vacrel
->NewRelminMxid
= NewRelminMxid
;
2025 /* Save any LP_DEAD items found on the page in dead_items array */
2026 if (vacrel
->nindexes
== 0)
2028 /* Using one-pass strategy (since table has no indexes) */
2029 if (lpdead_items
> 0)
2032 * Perfunctory handling for the corner case where a single pass
2033 * strategy VACUUM cannot get a cleanup lock, and it turns out
2034 * that there is one or more LP_DEAD items: just count the LP_DEAD
2035 * items as missed_dead_tuples instead. (This is a bit dishonest,
2036 * but it beats having to maintain specialized heap vacuuming code
2037 * forever, for vanishingly little benefit.)
2040 missed_dead_tuples
+= lpdead_items
;
2043 *recordfreespace
= true;
2045 else if (lpdead_items
== 0)
2048 * Won't be vacuuming this page later, so record page's freespace in
2051 *recordfreespace
= true;
2055 VacDeadItems
*dead_items
= vacrel
->dead_items
;
2056 ItemPointerData tmp
;
2059 * Page has LP_DEAD items, and so any references/TIDs that remain in
2060 * indexes will be deleted during index vacuuming (and then marked
2061 * LP_UNUSED in the heap)
2063 vacrel
->lpdead_item_pages
++;
2065 ItemPointerSetBlockNumber(&tmp
, blkno
);
2067 for (int i
= 0; i
< lpdead_items
; i
++)
2069 ItemPointerSetOffsetNumber(&tmp
, deadoffsets
[i
]);
2070 dead_items
->items
[dead_items
->num_items
++] = tmp
;
2073 Assert(dead_items
->num_items
<= dead_items
->max_items
);
2074 pgstat_progress_update_param(PROGRESS_VACUUM_NUM_DEAD_TUPLES
,
2075 dead_items
->num_items
);
2077 vacrel
->lpdead_items
+= lpdead_items
;
2080 * Assume that we'll go on to vacuum this heap page during final pass
2081 * over the heap. Don't record free space until then.
2083 *recordfreespace
= false;
2087 * Finally, add relevant page-local counts to whole-VACUUM counts
2089 vacrel
->live_tuples
+= live_tuples
;
2090 vacrel
->recently_dead_tuples
+= recently_dead_tuples
;
2091 vacrel
->missed_dead_tuples
+= missed_dead_tuples
;
2092 if (missed_dead_tuples
> 0)
2093 vacrel
->missed_dead_pages
++;
2095 /* Caller won't need to call lazy_scan_prune with same page */
2100 * Main entry point for index vacuuming and heap vacuuming.
2102 * Removes items collected in dead_items from table's indexes, then marks the
2103 * same items LP_UNUSED in the heap. See the comments above lazy_scan_heap
2106 * Also empties dead_items, freeing up space for later TIDs.
2108 * We may choose to bypass index vacuuming at this point, though only when the
2109 * ongoing VACUUM operation will definitely only have one index scan/round of
2113 lazy_vacuum(LVRelState
*vacrel
)
2117 /* Should not end up here with no indexes */
2118 Assert(vacrel
->nindexes
> 0);
2119 Assert(vacrel
->lpdead_item_pages
> 0);
2121 if (!vacrel
->do_index_vacuuming
)
2123 Assert(!vacrel
->do_index_cleanup
);
2124 vacrel
->dead_items
->num_items
= 0;
2129 * Consider bypassing index vacuuming (and heap vacuuming) entirely.
2131 * We currently only do this in cases where the number of LP_DEAD items
2132 * for the entire VACUUM operation is close to zero. This avoids sharp
2133 * discontinuities in the duration and overhead of successive VACUUM
2134 * operations that run against the same table with a fixed workload.
2135 * Ideally, successive VACUUM operations will behave as if there are
2136 * exactly zero LP_DEAD items in cases where there are close to zero.
2138 * This is likely to be helpful with a table that is continually affected
2139 * by UPDATEs that can mostly apply the HOT optimization, but occasionally
2140 * have small aberrations that lead to just a few heap pages retaining
2141 * only one or two LP_DEAD items. This is pretty common; even when the
2142 * DBA goes out of their way to make UPDATEs use HOT, it is practically
2143 * impossible to predict whether HOT will be applied in 100% of cases.
2144 * It's far easier to ensure that 99%+ of all UPDATEs against a table use
2145 * HOT through careful tuning.
2148 if (vacrel
->consider_bypass_optimization
&& vacrel
->rel_pages
> 0)
2150 BlockNumber threshold
;
2152 Assert(vacrel
->num_index_scans
== 0);
2153 Assert(vacrel
->lpdead_items
== vacrel
->dead_items
->num_items
);
2154 Assert(vacrel
->do_index_vacuuming
);
2155 Assert(vacrel
->do_index_cleanup
);
2158 * This crossover point at which we'll start to do index vacuuming is
2159 * expressed as a percentage of the total number of heap pages in the
2160 * table that are known to have at least one LP_DEAD item. This is
2161 * much more important than the total number of LP_DEAD items, since
2162 * it's a proxy for the number of heap pages whose visibility map bits
2163 * cannot be set on account of bypassing index and heap vacuuming.
2165 * We apply one further precautionary test: the space currently used
2166 * to store the TIDs (TIDs that now all point to LP_DEAD items) must
2167 * not exceed 32MB. This limits the risk that we will bypass index
2168 * vacuuming again and again until eventually there is a VACUUM whose
2169 * dead_items space is not CPU cache resident.
2171 * We don't take any special steps to remember the LP_DEAD items (such
2172 * as counting them in our final update to the stats system) when the
2173 * optimization is applied. Though the accounting used in analyze.c's
2174 * acquire_sample_rows() will recognize the same LP_DEAD items as dead
2175 * rows in its own stats report, that's okay. The discrepancy should
2176 * be negligible. If this optimization is ever expanded to cover more
2177 * cases then this may need to be reconsidered.
2179 threshold
= (double) vacrel
->rel_pages
* BYPASS_THRESHOLD_PAGES
;
2180 bypass
= (vacrel
->lpdead_item_pages
< threshold
&&
2181 vacrel
->lpdead_items
< MAXDEADITEMS(32L * 1024L * 1024L));
2187 * There are almost zero TIDs. Behave as if there were precisely
2188 * zero: bypass index vacuuming, but do index cleanup.
2190 * We expect that the ongoing VACUUM operation will finish very
2191 * quickly, so there is no point in considering speeding up as a
2192 * failsafe against wraparound failure. (Index cleanup is expected to
2193 * finish very quickly in cases where there were no ambulkdelete()
2196 vacrel
->do_index_vacuuming
= false;
2198 else if (lazy_vacuum_all_indexes(vacrel
))
2201 * We successfully completed a round of index vacuuming. Do related
2202 * heap vacuuming now.
2204 lazy_vacuum_heap_rel(vacrel
);
2211 * We attempted index vacuuming, but didn't finish a full round/full
2212 * index scan. This happens when relfrozenxid or relminmxid is too
2215 * From this point on the VACUUM operation will do no further index
2216 * vacuuming or heap vacuuming. This VACUUM operation won't end up
2219 Assert(vacrel
->failsafe_active
);
2223 * Forget the LP_DEAD items that we just vacuumed (or just decided to not
2226 vacrel
->dead_items
->num_items
= 0;
2230 * lazy_vacuum_all_indexes() -- Main entry for index vacuuming
2232 * Returns true in the common case when all indexes were successfully
2233 * vacuumed. Returns false in rare cases where we determined that the ongoing
2234 * VACUUM operation is at risk of taking too long to finish, leading to
2235 * wraparound failure.
2238 lazy_vacuum_all_indexes(LVRelState
*vacrel
)
2240 bool allindexes
= true;
2241 double old_live_tuples
= vacrel
->rel
->rd_rel
->reltuples
;
2243 Assert(vacrel
->nindexes
> 0);
2244 Assert(vacrel
->do_index_vacuuming
);
2245 Assert(vacrel
->do_index_cleanup
);
2247 /* Precheck for XID wraparound emergencies */
2248 if (lazy_check_wraparound_failsafe(vacrel
))
2250 /* Wraparound emergency -- don't even start an index scan */
2254 /* Report that we are now vacuuming indexes */
2255 pgstat_progress_update_param(PROGRESS_VACUUM_PHASE
,
2256 PROGRESS_VACUUM_PHASE_VACUUM_INDEX
);
2258 if (!ParallelVacuumIsActive(vacrel
))
2260 for (int idx
= 0; idx
< vacrel
->nindexes
; idx
++)
2262 Relation indrel
= vacrel
->indrels
[idx
];
2263 IndexBulkDeleteResult
*istat
= vacrel
->indstats
[idx
];
2265 vacrel
->indstats
[idx
] = lazy_vacuum_one_index(indrel
, istat
,
2269 if (lazy_check_wraparound_failsafe(vacrel
))
2271 /* Wraparound emergency -- end current index scan */
2279 /* Outsource everything to parallel variant */
2280 parallel_vacuum_bulkdel_all_indexes(vacrel
->pvs
, old_live_tuples
,
2281 vacrel
->num_index_scans
);
2284 * Do a postcheck to consider applying wraparound failsafe now. Note
2285 * that parallel VACUUM only gets the precheck and this postcheck.
2287 if (lazy_check_wraparound_failsafe(vacrel
))
2292 * We delete all LP_DEAD items from the first heap pass in all indexes on
2293 * each call here (except calls where we choose to do the failsafe). This
2294 * makes the next call to lazy_vacuum_heap_rel() safe (except in the event
2295 * of the failsafe triggering, which prevents the next call from taking
2298 Assert(vacrel
->num_index_scans
> 0 ||
2299 vacrel
->dead_items
->num_items
== vacrel
->lpdead_items
);
2300 Assert(allindexes
|| vacrel
->failsafe_active
);
2303 * Increase and report the number of index scans.
2305 * We deliberately include the case where we started a round of bulk
2306 * deletes that we weren't able to finish due to the failsafe triggering.
2308 vacrel
->num_index_scans
++;
2309 pgstat_progress_update_param(PROGRESS_VACUUM_NUM_INDEX_VACUUMS
,
2310 vacrel
->num_index_scans
);
2316 * lazy_vacuum_heap_rel() -- second pass over the heap for two pass strategy
2318 * This routine marks LP_DEAD items in vacrel->dead_items array as LP_UNUSED.
2319 * Pages that never had lazy_scan_prune record LP_DEAD items are not visited
2322 * We may also be able to truncate the line pointer array of the heap pages we
2323 * visit. If there is a contiguous group of LP_UNUSED items at the end of the
2324 * array, it can be reclaimed as free space. These LP_UNUSED items usually
2325 * start out as LP_DEAD items recorded by lazy_scan_prune (we set items from
2326 * each page to LP_UNUSED, and then consider if it's possible to truncate the
2327 * page's line pointer array).
2329 * Note: the reason for doing this as a second pass is we cannot remove the
2330 * tuples until we've removed their index entries, and we want to process
2331 * index entry removal in batches as large as possible.
2334 lazy_vacuum_heap_rel(LVRelState
*vacrel
)
2337 BlockNumber vacuumed_pages
;
2338 Buffer vmbuffer
= InvalidBuffer
;
2339 LVSavedErrInfo saved_err_info
;
2341 Assert(vacrel
->do_index_vacuuming
);
2342 Assert(vacrel
->do_index_cleanup
);
2343 Assert(vacrel
->num_index_scans
> 0);
2345 /* Report that we are now vacuuming the heap */
2346 pgstat_progress_update_param(PROGRESS_VACUUM_PHASE
,
2347 PROGRESS_VACUUM_PHASE_VACUUM_HEAP
);
2349 /* Update error traceback information */
2350 update_vacuum_error_info(vacrel
, &saved_err_info
,
2351 VACUUM_ERRCB_PHASE_VACUUM_HEAP
,
2352 InvalidBlockNumber
, InvalidOffsetNumber
);
2357 while (index
< vacrel
->dead_items
->num_items
)
2364 vacuum_delay_point();
2366 tblk
= ItemPointerGetBlockNumber(&vacrel
->dead_items
->items
[index
]);
2367 vacrel
->blkno
= tblk
;
2368 buf
= ReadBufferExtended(vacrel
->rel
, MAIN_FORKNUM
, tblk
, RBM_NORMAL
,
2370 LockBuffer(buf
, BUFFER_LOCK_EXCLUSIVE
);
2371 index
= lazy_vacuum_heap_page(vacrel
, tblk
, buf
, index
, &vmbuffer
);
2373 /* Now that we've vacuumed the page, record its available space */
2374 page
= BufferGetPage(buf
);
2375 freespace
= PageGetHeapFreeSpace(page
);
2377 UnlockReleaseBuffer(buf
);
2378 RecordPageWithFreeSpace(vacrel
->rel
, tblk
, freespace
);
2382 /* Clear the block number information */
2383 vacrel
->blkno
= InvalidBlockNumber
;
2385 if (BufferIsValid(vmbuffer
))
2387 ReleaseBuffer(vmbuffer
);
2388 vmbuffer
= InvalidBuffer
;
2392 * We set all LP_DEAD items from the first heap pass to LP_UNUSED during
2393 * the second heap pass. No more, no less.
2396 Assert(vacrel
->num_index_scans
> 1 ||
2397 (index
== vacrel
->lpdead_items
&&
2398 vacuumed_pages
== vacrel
->lpdead_item_pages
));
2401 (errmsg("table \"%s\": removed %lld dead item identifiers in %u pages",
2402 vacrel
->relname
, (long long) index
, vacuumed_pages
)));
2404 /* Revert to the previous phase information for error traceback */
2405 restore_vacuum_error_info(vacrel
, &saved_err_info
);
2409 * lazy_vacuum_heap_page() -- free page's LP_DEAD items listed in the
2410 * vacrel->dead_items array.
2412 * Caller must have an exclusive buffer lock on the buffer (though a full
2413 * cleanup lock is also acceptable).
2415 * index is an offset into the vacrel->dead_items array for the first listed
2416 * LP_DEAD item on the page. The return value is the first index immediately
2417 * after all LP_DEAD items for the same page in the array.
2420 lazy_vacuum_heap_page(LVRelState
*vacrel
, BlockNumber blkno
, Buffer buffer
,
2421 int index
, Buffer
*vmbuffer
)
2423 VacDeadItems
*dead_items
= vacrel
->dead_items
;
2424 Page page
= BufferGetPage(buffer
);
2425 OffsetNumber unused
[MaxHeapTuplesPerPage
];
2427 TransactionId visibility_cutoff_xid
;
2429 LVSavedErrInfo saved_err_info
;
2431 Assert(vacrel
->nindexes
== 0 || vacrel
->do_index_vacuuming
);
2433 pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED
, blkno
);
2435 /* Update error traceback information */
2436 update_vacuum_error_info(vacrel
, &saved_err_info
,
2437 VACUUM_ERRCB_PHASE_VACUUM_HEAP
, blkno
,
2438 InvalidOffsetNumber
);
2440 START_CRIT_SECTION();
2442 for (; index
< dead_items
->num_items
; index
++)
2448 tblk
= ItemPointerGetBlockNumber(&dead_items
->items
[index
]);
2450 break; /* past end of tuples for this block */
2451 toff
= ItemPointerGetOffsetNumber(&dead_items
->items
[index
]);
2452 itemid
= PageGetItemId(page
, toff
);
2454 Assert(ItemIdIsDead(itemid
) && !ItemIdHasStorage(itemid
));
2455 ItemIdSetUnused(itemid
);
2456 unused
[uncnt
++] = toff
;
2461 /* Attempt to truncate line pointer array now */
2462 PageTruncateLinePointerArray(page
);
2465 * Mark buffer dirty before we write WAL.
2467 MarkBufferDirty(buffer
);
2470 if (RelationNeedsWAL(vacrel
->rel
))
2472 xl_heap_vacuum xlrec
;
2475 xlrec
.nunused
= uncnt
;
2478 XLogRegisterData((char *) &xlrec
, SizeOfHeapVacuum
);
2480 XLogRegisterBuffer(0, buffer
, REGBUF_STANDARD
);
2481 XLogRegisterBufData(0, (char *) unused
, uncnt
* sizeof(OffsetNumber
));
2483 recptr
= XLogInsert(RM_HEAP2_ID
, XLOG_HEAP2_VACUUM
);
2485 PageSetLSN(page
, recptr
);
2489 * End critical section, so we safely can do visibility tests (which
2490 * possibly need to perform IO and allocate memory!). If we crash now the
2491 * page (including the corresponding vm bit) might not be marked all
2492 * visible, but that's fine. A later vacuum will fix that.
2497 * Now that we have removed the LD_DEAD items from the page, once again
2498 * check if the page has become all-visible. The page is already marked
2499 * dirty, exclusively locked, and, if needed, a full page image has been
2502 if (heap_page_is_all_visible(vacrel
, buffer
, &visibility_cutoff_xid
,
2504 PageSetAllVisible(page
);
2507 * All the changes to the heap page have been done. If the all-visible
2508 * flag is now set, also set the VM all-visible bit (and, if possible, the
2509 * all-frozen bit) unless this has already been done previously.
2511 if (PageIsAllVisible(page
))
2514 uint8 vm_status
= visibilitymap_get_status(vacrel
->rel
,
2517 /* Set the VM all-frozen bit to flag, if needed */
2518 if ((vm_status
& VISIBILITYMAP_ALL_VISIBLE
) == 0)
2519 flags
|= VISIBILITYMAP_ALL_VISIBLE
;
2520 if ((vm_status
& VISIBILITYMAP_ALL_FROZEN
) == 0 && all_frozen
)
2521 flags
|= VISIBILITYMAP_ALL_FROZEN
;
2523 Assert(BufferIsValid(*vmbuffer
));
2525 visibilitymap_set(vacrel
->rel
, blkno
, buffer
, InvalidXLogRecPtr
,
2526 *vmbuffer
, visibility_cutoff_xid
, flags
);
2529 /* Revert to the previous phase information for error traceback */
2530 restore_vacuum_error_info(vacrel
, &saved_err_info
);
2535 * Trigger the failsafe to avoid wraparound failure when vacrel table has a
2536 * relfrozenxid and/or relminmxid that is dangerously far in the past.
2537 * Triggering the failsafe makes the ongoing VACUUM bypass any further index
2538 * vacuuming and heap vacuuming. Truncating the heap is also bypassed.
2540 * Any remaining work (work that VACUUM cannot just bypass) is typically sped
2541 * up when the failsafe triggers. VACUUM stops applying any cost-based delay
2542 * that it started out with.
2544 * Returns true when failsafe has been triggered.
2547 lazy_check_wraparound_failsafe(LVRelState
*vacrel
)
2549 /* Don't warn more than once per VACUUM */
2550 if (vacrel
->failsafe_active
)
2553 if (unlikely(vacuum_xid_failsafe_check(&vacrel
->cutoffs
)))
2555 vacrel
->failsafe_active
= true;
2557 /* Disable index vacuuming, index cleanup, and heap rel truncation */
2558 vacrel
->do_index_vacuuming
= false;
2559 vacrel
->do_index_cleanup
= false;
2560 vacrel
->do_rel_truncate
= false;
2563 (errmsg("bypassing nonessential maintenance of table \"%s.%s.%s\" as a failsafe after %d index scans",
2564 get_database_name(MyDatabaseId
),
2565 vacrel
->relnamespace
,
2567 vacrel
->num_index_scans
),
2568 errdetail("The table's relfrozenxid or relminmxid is too far in the past."),
2569 errhint("Consider increasing configuration parameter \"maintenance_work_mem\" or \"autovacuum_work_mem\".\n"
2570 "You might also need to consider other ways for VACUUM to keep up with the allocation of transaction IDs.")));
2572 /* Stop applying cost limits from this point on */
2573 VacuumCostActive
= false;
2574 VacuumCostBalance
= 0;
2583 * lazy_cleanup_all_indexes() -- cleanup all indexes of relation.
2586 lazy_cleanup_all_indexes(LVRelState
*vacrel
)
2588 double reltuples
= vacrel
->new_rel_tuples
;
2589 bool estimated_count
= vacrel
->scanned_pages
< vacrel
->rel_pages
;
2591 Assert(vacrel
->do_index_cleanup
);
2592 Assert(vacrel
->nindexes
> 0);
2594 /* Report that we are now cleaning up indexes */
2595 pgstat_progress_update_param(PROGRESS_VACUUM_PHASE
,
2596 PROGRESS_VACUUM_PHASE_INDEX_CLEANUP
);
2598 if (!ParallelVacuumIsActive(vacrel
))
2600 for (int idx
= 0; idx
< vacrel
->nindexes
; idx
++)
2602 Relation indrel
= vacrel
->indrels
[idx
];
2603 IndexBulkDeleteResult
*istat
= vacrel
->indstats
[idx
];
2605 vacrel
->indstats
[idx
] =
2606 lazy_cleanup_one_index(indrel
, istat
, reltuples
,
2607 estimated_count
, vacrel
);
2612 /* Outsource everything to parallel variant */
2613 parallel_vacuum_cleanup_all_indexes(vacrel
->pvs
, reltuples
,
2614 vacrel
->num_index_scans
,
2620 * lazy_vacuum_one_index() -- vacuum index relation.
2622 * Delete all the index tuples containing a TID collected in
2623 * vacrel->dead_items array. Also update running statistics.
2624 * Exact details depend on index AM's ambulkdelete routine.
2626 * reltuples is the number of heap tuples to be passed to the
2627 * bulkdelete callback. It's always assumed to be estimated.
2628 * See indexam.sgml for more info.
2630 * Returns bulk delete stats derived from input stats
2632 static IndexBulkDeleteResult
*
2633 lazy_vacuum_one_index(Relation indrel
, IndexBulkDeleteResult
*istat
,
2634 double reltuples
, LVRelState
*vacrel
)
2636 IndexVacuumInfo ivinfo
;
2637 LVSavedErrInfo saved_err_info
;
2639 ivinfo
.index
= indrel
;
2640 ivinfo
.analyze_only
= false;
2641 ivinfo
.report_progress
= false;
2642 ivinfo
.estimated_count
= true;
2643 ivinfo
.message_level
= DEBUG2
;
2644 ivinfo
.num_heap_tuples
= reltuples
;
2645 ivinfo
.strategy
= vacrel
->bstrategy
;
2648 * Update error traceback information.
2650 * The index name is saved during this phase and restored immediately
2651 * after this phase. See vacuum_error_callback.
2653 Assert(vacrel
->indname
== NULL
);
2654 vacrel
->indname
= pstrdup(RelationGetRelationName(indrel
));
2655 update_vacuum_error_info(vacrel
, &saved_err_info
,
2656 VACUUM_ERRCB_PHASE_VACUUM_INDEX
,
2657 InvalidBlockNumber
, InvalidOffsetNumber
);
2659 /* Do bulk deletion */
2660 istat
= vac_bulkdel_one_index(&ivinfo
, istat
, (void *) vacrel
->dead_items
);
2662 /* Revert to the previous phase information for error traceback */
2663 restore_vacuum_error_info(vacrel
, &saved_err_info
);
2664 pfree(vacrel
->indname
);
2665 vacrel
->indname
= NULL
;
2671 * lazy_cleanup_one_index() -- do post-vacuum cleanup for index relation.
2673 * Calls index AM's amvacuumcleanup routine. reltuples is the number
2674 * of heap tuples and estimated_count is true if reltuples is an
2675 * estimated value. See indexam.sgml for more info.
2677 * Returns bulk delete stats derived from input stats
2679 static IndexBulkDeleteResult
*
2680 lazy_cleanup_one_index(Relation indrel
, IndexBulkDeleteResult
*istat
,
2681 double reltuples
, bool estimated_count
,
2684 IndexVacuumInfo ivinfo
;
2685 LVSavedErrInfo saved_err_info
;
2687 ivinfo
.index
= indrel
;
2688 ivinfo
.analyze_only
= false;
2689 ivinfo
.report_progress
= false;
2690 ivinfo
.estimated_count
= estimated_count
;
2691 ivinfo
.message_level
= DEBUG2
;
2693 ivinfo
.num_heap_tuples
= reltuples
;
2694 ivinfo
.strategy
= vacrel
->bstrategy
;
2697 * Update error traceback information.
2699 * The index name is saved during this phase and restored immediately
2700 * after this phase. See vacuum_error_callback.
2702 Assert(vacrel
->indname
== NULL
);
2703 vacrel
->indname
= pstrdup(RelationGetRelationName(indrel
));
2704 update_vacuum_error_info(vacrel
, &saved_err_info
,
2705 VACUUM_ERRCB_PHASE_INDEX_CLEANUP
,
2706 InvalidBlockNumber
, InvalidOffsetNumber
);
2708 istat
= vac_cleanup_one_index(&ivinfo
, istat
);
2710 /* Revert to the previous phase information for error traceback */
2711 restore_vacuum_error_info(vacrel
, &saved_err_info
);
2712 pfree(vacrel
->indname
);
2713 vacrel
->indname
= NULL
;
2719 * should_attempt_truncation - should we attempt to truncate the heap?
2721 * Don't even think about it unless we have a shot at releasing a goodly
2722 * number of pages. Otherwise, the time taken isn't worth it, mainly because
2723 * an AccessExclusive lock must be replayed on any hot standby, where it can
2724 * be particularly disruptive.
2726 * Also don't attempt it if wraparound failsafe is in effect. The entire
2727 * system might be refusing to allocate new XIDs at this point. The system
2728 * definitely won't return to normal unless and until VACUUM actually advances
2729 * the oldest relfrozenxid -- which hasn't happened for target rel just yet.
2730 * If lazy_truncate_heap attempted to acquire an AccessExclusiveLock to
2731 * truncate the table under these circumstances, an XID exhaustion error might
2732 * make it impossible for VACUUM to fix the underlying XID exhaustion problem.
2733 * There is very little chance of truncation working out when the failsafe is
2734 * in effect in any case. lazy_scan_prune makes the optimistic assumption
2735 * that any LP_DEAD items it encounters will always be LP_UNUSED by the time
2738 * Also don't attempt it if we are doing early pruning/vacuuming, because a
2739 * scan which cannot find a truncated heap page cannot determine that the
2740 * snapshot is too old to read that page.
2743 should_attempt_truncation(LVRelState
*vacrel
)
2745 BlockNumber possibly_freeable
;
2747 if (!vacrel
->do_rel_truncate
|| vacrel
->failsafe_active
||
2748 old_snapshot_threshold
>= 0)
2751 possibly_freeable
= vacrel
->rel_pages
- vacrel
->nonempty_pages
;
2752 if (possibly_freeable
> 0 &&
2753 (possibly_freeable
>= REL_TRUNCATE_MINIMUM
||
2754 possibly_freeable
>= vacrel
->rel_pages
/ REL_TRUNCATE_FRACTION
))
2761 * lazy_truncate_heap - try to truncate off any empty pages at the end
2764 lazy_truncate_heap(LVRelState
*vacrel
)
2766 BlockNumber orig_rel_pages
= vacrel
->rel_pages
;
2767 BlockNumber new_rel_pages
;
2768 bool lock_waiter_detected
;
2771 /* Report that we are now truncating */
2772 pgstat_progress_update_param(PROGRESS_VACUUM_PHASE
,
2773 PROGRESS_VACUUM_PHASE_TRUNCATE
);
2775 /* Update error traceback information one last time */
2776 update_vacuum_error_info(vacrel
, NULL
, VACUUM_ERRCB_PHASE_TRUNCATE
,
2777 vacrel
->nonempty_pages
, InvalidOffsetNumber
);
2780 * Loop until no more truncating can be done.
2785 * We need full exclusive lock on the relation in order to do
2786 * truncation. If we can't get it, give up rather than waiting --- we
2787 * don't want to block other backends, and we don't want to deadlock
2788 * (which is quite possible considering we already hold a lower-grade
2791 lock_waiter_detected
= false;
2795 if (ConditionalLockRelation(vacrel
->rel
, AccessExclusiveLock
))
2799 * Check for interrupts while trying to (re-)acquire the exclusive
2802 CHECK_FOR_INTERRUPTS();
2804 if (++lock_retry
> (VACUUM_TRUNCATE_LOCK_TIMEOUT
/
2805 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL
))
2808 * We failed to establish the lock in the specified number of
2809 * retries. This means we give up truncating.
2811 ereport(vacrel
->verbose
? INFO
: DEBUG2
,
2812 (errmsg("\"%s\": stopping truncate due to conflicting lock request",
2817 (void) WaitLatch(MyLatch
,
2818 WL_LATCH_SET
| WL_TIMEOUT
| WL_EXIT_ON_PM_DEATH
,
2819 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL
,
2820 WAIT_EVENT_VACUUM_TRUNCATE
);
2821 ResetLatch(MyLatch
);
2825 * Now that we have exclusive lock, look to see if the rel has grown
2826 * whilst we were vacuuming with non-exclusive lock. If so, give up;
2827 * the newly added pages presumably contain non-deletable tuples.
2829 new_rel_pages
= RelationGetNumberOfBlocks(vacrel
->rel
);
2830 if (new_rel_pages
!= orig_rel_pages
)
2833 * Note: we intentionally don't update vacrel->rel_pages with the
2834 * new rel size here. If we did, it would amount to assuming that
2835 * the new pages are empty, which is unlikely. Leaving the numbers
2836 * alone amounts to assuming that the new pages have the same
2837 * tuple density as existing ones, which is less unlikely.
2839 UnlockRelation(vacrel
->rel
, AccessExclusiveLock
);
2844 * Scan backwards from the end to verify that the end pages actually
2845 * contain no tuples. This is *necessary*, not optional, because
2846 * other backends could have added tuples to these pages whilst we
2849 new_rel_pages
= count_nondeletable_pages(vacrel
, &lock_waiter_detected
);
2850 vacrel
->blkno
= new_rel_pages
;
2852 if (new_rel_pages
>= orig_rel_pages
)
2854 /* can't do anything after all */
2855 UnlockRelation(vacrel
->rel
, AccessExclusiveLock
);
2862 RelationTruncate(vacrel
->rel
, new_rel_pages
);
2865 * We can release the exclusive lock as soon as we have truncated.
2866 * Other backends can't safely access the relation until they have
2867 * processed the smgr invalidation that smgrtruncate sent out ... but
2868 * that should happen as part of standard invalidation processing once
2869 * they acquire lock on the relation.
2871 UnlockRelation(vacrel
->rel
, AccessExclusiveLock
);
2874 * Update statistics. Here, it *is* correct to adjust rel_pages
2875 * without also touching reltuples, since the tuple count wasn't
2876 * changed by the truncation.
2878 vacrel
->removed_pages
+= orig_rel_pages
- new_rel_pages
;
2879 vacrel
->rel_pages
= new_rel_pages
;
2881 ereport(vacrel
->verbose
? INFO
: DEBUG2
,
2882 (errmsg("table \"%s\": truncated %u to %u pages",
2884 orig_rel_pages
, new_rel_pages
)));
2885 orig_rel_pages
= new_rel_pages
;
2886 } while (new_rel_pages
> vacrel
->nonempty_pages
&& lock_waiter_detected
);
2890 * Rescan end pages to verify that they are (still) empty of tuples.
2892 * Returns number of nondeletable pages (last nonempty page + 1).
2895 count_nondeletable_pages(LVRelState
*vacrel
, bool *lock_waiter_detected
)
2898 BlockNumber prefetchedUntil
;
2899 instr_time starttime
;
2901 /* Initialize the starttime if we check for conflicting lock requests */
2902 INSTR_TIME_SET_CURRENT(starttime
);
2905 * Start checking blocks at what we believe relation end to be and move
2906 * backwards. (Strange coding of loop control is needed because blkno is
2907 * unsigned.) To make the scan faster, we prefetch a few blocks at a time
2908 * in forward direction, so that OS-level readahead can kick in.
2910 blkno
= vacrel
->rel_pages
;
2911 StaticAssertStmt((PREFETCH_SIZE
& (PREFETCH_SIZE
- 1)) == 0,
2912 "prefetch size must be power of 2");
2913 prefetchedUntil
= InvalidBlockNumber
;
2914 while (blkno
> vacrel
->nonempty_pages
)
2918 OffsetNumber offnum
,
2923 * Check if another process requests a lock on our relation. We are
2924 * holding an AccessExclusiveLock here, so they will be waiting. We
2925 * only do this once per VACUUM_TRUNCATE_LOCK_CHECK_INTERVAL, and we
2926 * only check if that interval has elapsed once every 32 blocks to
2927 * keep the number of system calls and actual shared lock table
2928 * lookups to a minimum.
2930 if ((blkno
% 32) == 0)
2932 instr_time currenttime
;
2935 INSTR_TIME_SET_CURRENT(currenttime
);
2936 elapsed
= currenttime
;
2937 INSTR_TIME_SUBTRACT(elapsed
, starttime
);
2938 if ((INSTR_TIME_GET_MICROSEC(elapsed
) / 1000)
2939 >= VACUUM_TRUNCATE_LOCK_CHECK_INTERVAL
)
2941 if (LockHasWaitersRelation(vacrel
->rel
, AccessExclusiveLock
))
2943 ereport(vacrel
->verbose
? INFO
: DEBUG2
,
2944 (errmsg("table \"%s\": suspending truncate due to conflicting lock request",
2947 *lock_waiter_detected
= true;
2950 starttime
= currenttime
;
2955 * We don't insert a vacuum delay point here, because we have an
2956 * exclusive lock on the table which we want to hold for as short a
2957 * time as possible. We still need to check for interrupts however.
2959 CHECK_FOR_INTERRUPTS();
2963 /* If we haven't prefetched this lot yet, do so now. */
2964 if (prefetchedUntil
> blkno
)
2966 BlockNumber prefetchStart
;
2969 prefetchStart
= blkno
& ~(PREFETCH_SIZE
- 1);
2970 for (pblkno
= prefetchStart
; pblkno
<= blkno
; pblkno
++)
2972 PrefetchBuffer(vacrel
->rel
, MAIN_FORKNUM
, pblkno
);
2973 CHECK_FOR_INTERRUPTS();
2975 prefetchedUntil
= prefetchStart
;
2978 buf
= ReadBufferExtended(vacrel
->rel
, MAIN_FORKNUM
, blkno
, RBM_NORMAL
,
2981 /* In this phase we only need shared access to the buffer */
2982 LockBuffer(buf
, BUFFER_LOCK_SHARE
);
2984 page
= BufferGetPage(buf
);
2986 if (PageIsNew(page
) || PageIsEmpty(page
))
2988 UnlockReleaseBuffer(buf
);
2993 maxoff
= PageGetMaxOffsetNumber(page
);
2994 for (offnum
= FirstOffsetNumber
;
2996 offnum
= OffsetNumberNext(offnum
))
3000 itemid
= PageGetItemId(page
, offnum
);
3003 * Note: any non-unused item should be taken as a reason to keep
3004 * this page. Even an LP_DEAD item makes truncation unsafe, since
3005 * we must not have cleaned out its index entries.
3007 if (ItemIdIsUsed(itemid
))
3010 break; /* can stop scanning */
3012 } /* scan along page */
3014 UnlockReleaseBuffer(buf
);
3016 /* Done scanning if we found a tuple here */
3022 * If we fall out of the loop, all the previously-thought-to-be-empty
3023 * pages still are; we need not bother to look at the last known-nonempty
3026 return vacrel
->nonempty_pages
;
3030 * Returns the number of dead TIDs that VACUUM should allocate space to
3031 * store, given a heap rel of size vacrel->rel_pages, and given current
3032 * maintenance_work_mem setting (or current autovacuum_work_mem setting,
3035 * See the comments at the head of this file for rationale.
3038 dead_items_max_items(LVRelState
*vacrel
)
3041 int vac_work_mem
= IsAutoVacuumWorkerProcess() &&
3042 autovacuum_work_mem
!= -1 ?
3043 autovacuum_work_mem
: maintenance_work_mem
;
3045 if (vacrel
->nindexes
> 0)
3047 BlockNumber rel_pages
= vacrel
->rel_pages
;
3049 max_items
= MAXDEADITEMS(vac_work_mem
* 1024L);
3050 max_items
= Min(max_items
, INT_MAX
);
3051 max_items
= Min(max_items
, MAXDEADITEMS(MaxAllocSize
));
3053 /* curious coding here to ensure the multiplication can't overflow */
3054 if ((BlockNumber
) (max_items
/ MaxHeapTuplesPerPage
) > rel_pages
)
3055 max_items
= rel_pages
* MaxHeapTuplesPerPage
;
3057 /* stay sane if small maintenance_work_mem */
3058 max_items
= Max(max_items
, MaxHeapTuplesPerPage
);
3062 /* One-pass case only stores a single heap page's TIDs at a time */
3063 max_items
= MaxHeapTuplesPerPage
;
3066 return (int) max_items
;
3070 * Allocate dead_items (either using palloc, or in dynamic shared memory).
3071 * Sets dead_items in vacrel for caller.
3073 * Also handles parallel initialization as part of allocating dead_items in
3074 * DSM when required.
3077 dead_items_alloc(LVRelState
*vacrel
, int nworkers
)
3079 VacDeadItems
*dead_items
;
3082 max_items
= dead_items_max_items(vacrel
);
3083 Assert(max_items
>= MaxHeapTuplesPerPage
);
3086 * Initialize state for a parallel vacuum. As of now, only one worker can
3087 * be used for an index, so we invoke parallelism only if there are at
3088 * least two indexes on a table.
3090 if (nworkers
>= 0 && vacrel
->nindexes
> 1 && vacrel
->do_index_vacuuming
)
3093 * Since parallel workers cannot access data in temporary tables, we
3094 * can't perform parallel vacuum on them.
3096 if (RelationUsesLocalBuffers(vacrel
->rel
))
3099 * Give warning only if the user explicitly tries to perform a
3100 * parallel vacuum on the temporary table.
3104 (errmsg("disabling parallel option of vacuum on \"%s\" --- cannot vacuum temporary tables in parallel",
3108 vacrel
->pvs
= parallel_vacuum_init(vacrel
->rel
, vacrel
->indrels
,
3109 vacrel
->nindexes
, nworkers
,
3111 vacrel
->verbose
? INFO
: DEBUG2
,
3114 /* If parallel mode started, dead_items space is allocated in DSM */
3115 if (ParallelVacuumIsActive(vacrel
))
3117 vacrel
->dead_items
= parallel_vacuum_get_dead_items(vacrel
->pvs
);
3122 /* Serial VACUUM case */
3123 dead_items
= (VacDeadItems
*) palloc(vac_max_items_to_alloc_size(max_items
));
3124 dead_items
->max_items
= max_items
;
3125 dead_items
->num_items
= 0;
3127 vacrel
->dead_items
= dead_items
;
3131 * Perform cleanup for resources allocated in dead_items_alloc
3134 dead_items_cleanup(LVRelState
*vacrel
)
3136 if (!ParallelVacuumIsActive(vacrel
))
3138 /* Don't bother with pfree here */
3142 /* End parallel mode */
3143 parallel_vacuum_end(vacrel
->pvs
, vacrel
->indstats
);
3148 * Check if every tuple in the given page is visible to all current and future
3149 * transactions. Also return the visibility_cutoff_xid which is the highest
3150 * xmin amongst the visible tuples. Set *all_frozen to true if every tuple
3151 * on this page is frozen.
3153 * This is a stripped down version of lazy_scan_prune(). If you change
3154 * anything here, make sure that everything stays in sync. Note that an
3155 * assertion calls us to verify that everybody still agrees. Be sure to avoid
3156 * introducing new side-effects here.
3159 heap_page_is_all_visible(LVRelState
*vacrel
, Buffer buf
,
3160 TransactionId
*visibility_cutoff_xid
,
3163 Page page
= BufferGetPage(buf
);
3164 BlockNumber blockno
= BufferGetBlockNumber(buf
);
3165 OffsetNumber offnum
,
3167 bool all_visible
= true;
3169 *visibility_cutoff_xid
= InvalidTransactionId
;
3172 maxoff
= PageGetMaxOffsetNumber(page
);
3173 for (offnum
= FirstOffsetNumber
;
3174 offnum
<= maxoff
&& all_visible
;
3175 offnum
= OffsetNumberNext(offnum
))
3178 HeapTupleData tuple
;
3181 * Set the offset number so that we can display it along with any
3182 * error that occurred while processing this tuple.
3184 vacrel
->offnum
= offnum
;
3185 itemid
= PageGetItemId(page
, offnum
);
3187 /* Unused or redirect line pointers are of no interest */
3188 if (!ItemIdIsUsed(itemid
) || ItemIdIsRedirected(itemid
))
3191 ItemPointerSet(&(tuple
.t_self
), blockno
, offnum
);
3194 * Dead line pointers can have index pointers pointing to them. So
3195 * they can't be treated as visible
3197 if (ItemIdIsDead(itemid
))
3199 all_visible
= false;
3200 *all_frozen
= false;
3204 Assert(ItemIdIsNormal(itemid
));
3206 tuple
.t_data
= (HeapTupleHeader
) PageGetItem(page
, itemid
);
3207 tuple
.t_len
= ItemIdGetLength(itemid
);
3208 tuple
.t_tableOid
= RelationGetRelid(vacrel
->rel
);
3210 switch (HeapTupleSatisfiesVacuum(&tuple
, vacrel
->cutoffs
.OldestXmin
,
3213 case HEAPTUPLE_LIVE
:
3217 /* Check comments in lazy_scan_prune. */
3218 if (!HeapTupleHeaderXminCommitted(tuple
.t_data
))
3220 all_visible
= false;
3221 *all_frozen
= false;
3226 * The inserter definitely committed. But is it old enough
3227 * that everyone sees it as committed?
3229 xmin
= HeapTupleHeaderGetXmin(tuple
.t_data
);
3230 if (!TransactionIdPrecedes(xmin
,
3231 vacrel
->cutoffs
.OldestXmin
))
3233 all_visible
= false;
3234 *all_frozen
= false;
3238 /* Track newest xmin on page. */
3239 if (TransactionIdFollows(xmin
, *visibility_cutoff_xid
))
3240 *visibility_cutoff_xid
= xmin
;
3242 /* Check whether this tuple is already frozen or not */
3243 if (all_visible
&& *all_frozen
&&
3244 heap_tuple_needs_eventual_freeze(tuple
.t_data
))
3245 *all_frozen
= false;
3249 case HEAPTUPLE_DEAD
:
3250 case HEAPTUPLE_RECENTLY_DEAD
:
3251 case HEAPTUPLE_INSERT_IN_PROGRESS
:
3252 case HEAPTUPLE_DELETE_IN_PROGRESS
:
3254 all_visible
= false;
3255 *all_frozen
= false;
3259 elog(ERROR
, "unexpected HeapTupleSatisfiesVacuum result");
3262 } /* scan along page */
3264 /* Clear the offset information once we have processed the given page. */
3265 vacrel
->offnum
= InvalidOffsetNumber
;
3271 * Update index statistics in pg_class if the statistics are accurate.
3274 update_relstats_all_indexes(LVRelState
*vacrel
)
3276 Relation
*indrels
= vacrel
->indrels
;
3277 int nindexes
= vacrel
->nindexes
;
3278 IndexBulkDeleteResult
**indstats
= vacrel
->indstats
;
3280 Assert(vacrel
->do_index_cleanup
);
3282 for (int idx
= 0; idx
< nindexes
; idx
++)
3284 Relation indrel
= indrels
[idx
];
3285 IndexBulkDeleteResult
*istat
= indstats
[idx
];
3287 if (istat
== NULL
|| istat
->estimated_count
)
3290 /* Update index statistics */
3291 vac_update_relstats(indrel
,
3293 istat
->num_index_tuples
,
3296 InvalidTransactionId
,
3303 * Error context callback for errors occurring during vacuum. The error
3304 * context messages for index phases should match the messages set in parallel
3305 * vacuum. If you change this function for those phases, change
3306 * parallel_vacuum_error_callback() as well.
3309 vacuum_error_callback(void *arg
)
3311 LVRelState
*errinfo
= arg
;
3313 switch (errinfo
->phase
)
3315 case VACUUM_ERRCB_PHASE_SCAN_HEAP
:
3316 if (BlockNumberIsValid(errinfo
->blkno
))
3318 if (OffsetNumberIsValid(errinfo
->offnum
))
3319 errcontext("while scanning block %u offset %u of relation \"%s.%s\"",
3320 errinfo
->blkno
, errinfo
->offnum
, errinfo
->relnamespace
, errinfo
->relname
);
3322 errcontext("while scanning block %u of relation \"%s.%s\"",
3323 errinfo
->blkno
, errinfo
->relnamespace
, errinfo
->relname
);
3326 errcontext("while scanning relation \"%s.%s\"",
3327 errinfo
->relnamespace
, errinfo
->relname
);
3330 case VACUUM_ERRCB_PHASE_VACUUM_HEAP
:
3331 if (BlockNumberIsValid(errinfo
->blkno
))
3333 if (OffsetNumberIsValid(errinfo
->offnum
))
3334 errcontext("while vacuuming block %u offset %u of relation \"%s.%s\"",
3335 errinfo
->blkno
, errinfo
->offnum
, errinfo
->relnamespace
, errinfo
->relname
);
3337 errcontext("while vacuuming block %u of relation \"%s.%s\"",
3338 errinfo
->blkno
, errinfo
->relnamespace
, errinfo
->relname
);
3341 errcontext("while vacuuming relation \"%s.%s\"",
3342 errinfo
->relnamespace
, errinfo
->relname
);
3345 case VACUUM_ERRCB_PHASE_VACUUM_INDEX
:
3346 errcontext("while vacuuming index \"%s\" of relation \"%s.%s\"",
3347 errinfo
->indname
, errinfo
->relnamespace
, errinfo
->relname
);
3350 case VACUUM_ERRCB_PHASE_INDEX_CLEANUP
:
3351 errcontext("while cleaning up index \"%s\" of relation \"%s.%s\"",
3352 errinfo
->indname
, errinfo
->relnamespace
, errinfo
->relname
);
3355 case VACUUM_ERRCB_PHASE_TRUNCATE
:
3356 if (BlockNumberIsValid(errinfo
->blkno
))
3357 errcontext("while truncating relation \"%s.%s\" to %u blocks",
3358 errinfo
->relnamespace
, errinfo
->relname
, errinfo
->blkno
);
3361 case VACUUM_ERRCB_PHASE_UNKNOWN
:
3363 return; /* do nothing; the errinfo may not be
3369 * Updates the information required for vacuum error callback. This also saves
3370 * the current information which can be later restored via restore_vacuum_error_info.
3373 update_vacuum_error_info(LVRelState
*vacrel
, LVSavedErrInfo
*saved_vacrel
,
3374 int phase
, BlockNumber blkno
, OffsetNumber offnum
)
3378 saved_vacrel
->offnum
= vacrel
->offnum
;
3379 saved_vacrel
->blkno
= vacrel
->blkno
;
3380 saved_vacrel
->phase
= vacrel
->phase
;
3383 vacrel
->blkno
= blkno
;
3384 vacrel
->offnum
= offnum
;
3385 vacrel
->phase
= phase
;
3389 * Restores the vacuum information saved via a prior call to update_vacuum_error_info.
3392 restore_vacuum_error_info(LVRelState
*vacrel
,
3393 const LVSavedErrInfo
*saved_vacrel
)
3395 vacrel
->blkno
= saved_vacrel
->blkno
;
3396 vacrel
->offnum
= saved_vacrel
->offnum
;
3397 vacrel
->phase
= saved_vacrel
->phase
;