Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * vacuumlazy.c
4 : : * Concurrent ("lazy") vacuuming.
5 : : *
6 : : * Heap relations are vacuumed in three main phases. In phase I, vacuum scans
7 : : * relation pages, pruning and freezing tuples and saving dead tuples' TIDs in
8 : : * a TID store. If that TID store fills up or vacuum finishes scanning the
9 : : * relation, it progresses to phase II: index vacuuming. Index vacuuming
10 : : * deletes the dead index entries referenced in the TID store. In phase III,
11 : : * vacuum scans the blocks of the relation referred to by the TIDs in the TID
12 : : * store and reaps the corresponding dead items, freeing that space for future
13 : : * tuples.
14 : : *
15 : : * If there are no indexes or index scanning is disabled, phase II may be
16 : : * skipped. If phase I identified very few dead index entries or if vacuum's
17 : : * failsafe mechanism has triggered (to avoid transaction ID wraparound),
18 : : * vacuum may skip phases II and III.
19 : : *
20 : : * If the TID store fills up in phase I, vacuum suspends phase I and proceeds
21 : : * to phases II and III, cleaning up the dead tuples referenced in the current
22 : : * TID store. This empties the TID store, allowing vacuum to resume phase I.
23 : : *
24 : : * In a way, the phases are more like states in a state machine, but they have
25 : : * been referred to colloquially as phases for so long that they are referred
26 : : * to as such here.
27 : : *
28 : : * Manually invoked VACUUMs may scan indexes during phase II in parallel. For
29 : : * more information on this, see the comment at the top of vacuumparallel.c.
30 : : *
31 : : * In between phases, vacuum updates the freespace map (every
32 : : * VACUUM_FSM_EVERY_PAGES).
33 : : *
34 : : * After completing all three phases, vacuum may truncate the relation if it
35 : : * has emptied pages at the end. Finally, vacuum updates relation statistics
36 : : * in pg_class and the cumulative statistics subsystem.
37 : : *
38 : : * Relation Scanning:
39 : : *
40 : : * Vacuum scans the heap relation, starting at the beginning and progressing
41 : : * to the end, skipping pages as permitted by their visibility status, vacuum
42 : : * options, and various other requirements.
43 : : *
44 : : * Vacuums are either aggressive or normal. Aggressive vacuums must scan every
45 : : * unfrozen tuple in order to advance relfrozenxid and avoid transaction ID
46 : : * wraparound. Normal vacuums may scan otherwise skippable pages for one of
47 : : * two reasons:
48 : : *
49 : : * When page skipping is not disabled, a normal vacuum may scan pages that are
50 : : * marked all-visible (and even all-frozen) in the visibility map if the range
51 : : * of skippable pages is below SKIP_PAGES_THRESHOLD. This is primarily for the
52 : : * benefit of kernel readahead (see comment in heap_vac_scan_next_block()).
53 : : *
54 : : * A normal vacuum may also scan skippable pages in an effort to freeze them
55 : : * and decrease the backlog of all-visible but not all-frozen pages that have
56 : : * to be processed by the next aggressive vacuum. These are referred to as
57 : : * eagerly scanned pages. Pages scanned due to SKIP_PAGES_THRESHOLD do not
58 : : * count as eagerly scanned pages.
59 : : *
60 : : * Eagerly scanned pages that are set all-frozen in the VM are successful
61 : : * eager freezes and those not set all-frozen in the VM are failed eager
62 : : * freezes.
63 : : *
64 : : * Because we want to amortize the overhead of freezing pages over multiple
65 : : * vacuums, normal vacuums cap the number of successful eager freezes to
66 : : * MAX_EAGER_FREEZE_SUCCESS_RATE of the number of all-visible but not
67 : : * all-frozen pages at the beginning of the vacuum. Since eagerly frozen pages
68 : : * may be unfrozen before the next aggressive vacuum, capping the number of
69 : : * successful eager freezes also caps the downside of eager freezing:
70 : : * potentially wasted work.
71 : : *
72 : : * Once the success cap has been hit, eager scanning is disabled for the
73 : : * remainder of the vacuum of the relation.
74 : : *
75 : : * Success is capped globally because we don't want to limit our successes if
76 : : * old data happens to be concentrated in a particular part of the table. This
77 : : * is especially likely to happen for append-mostly workloads where the oldest
78 : : * data is at the beginning of the unfrozen portion of the relation.
79 : : *
80 : : * On the assumption that different regions of the table are likely to contain
81 : : * similarly aged data, normal vacuums use a localized eager freeze failure
82 : : * cap. The failure count is reset for each region of the table -- comprised
83 : : * of EAGER_SCAN_REGION_SIZE blocks. In each region, we tolerate
84 : : * vacuum_max_eager_freeze_failure_rate of EAGER_SCAN_REGION_SIZE failures
85 : : * before suspending eager scanning until the end of the region.
86 : : * vacuum_max_eager_freeze_failure_rate is configurable both globally and per
87 : : * table.
88 : : *
89 : : * Aggressive vacuums must examine every unfrozen tuple and thus are not
90 : : * subject to any of the limits imposed by the eager scanning algorithm.
91 : : *
92 : : * Once vacuum has decided to scan a given block, it must read the block and
93 : : * obtain a cleanup lock to prune tuples on the page. A non-aggressive vacuum
94 : : * may choose to skip pruning and freezing if it cannot acquire a cleanup lock
95 : : * on the buffer right away. In this case, it may miss cleaning up dead tuples
96 : : * and their associated index entries (though it is free to reap any existing
97 : : * dead items on the page).
98 : : *
99 : : * After pruning and freezing, pages that are newly all-visible and all-frozen
100 : : * are marked as such in the visibility map.
101 : : *
102 : : * Dead TID Storage:
103 : : *
104 : : * The major space usage for vacuuming is storage for the dead tuple IDs that
105 : : * are to be removed from indexes. We want to ensure we can vacuum even the
106 : : * very largest relations with finite memory space usage. To do that, we set
107 : : * upper bounds on the memory that can be used for keeping track of dead TIDs
108 : : * at once.
109 : : *
110 : : * We are willing to use at most maintenance_work_mem (or perhaps
111 : : * autovacuum_work_mem) memory space to keep track of dead TIDs. If the
112 : : * TID store is full, we must call lazy_vacuum to vacuum indexes (and to vacuum
113 : : * the pages that we've pruned). This frees up the memory space dedicated to
114 : : * store dead TIDs.
115 : : *
116 : : * In practice VACUUM will often complete its initial pass over the target
117 : : * heap relation without ever running out of space to store TIDs. This means
118 : : * that there only needs to be one call to lazy_vacuum, after the initial pass
119 : : * completes.
120 : : *
121 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
122 : : * Portions Copyright (c) 1994, Regents of the University of California
123 : : *
124 : : *
125 : : * IDENTIFICATION
126 : : * src/backend/access/heap/vacuumlazy.c
127 : : *
128 : : *-------------------------------------------------------------------------
129 : : */
130 : : #include "postgres.h"
131 : :
132 : : #include <math.h>
133 : :
134 : : #include "access/genam.h"
135 : : #include "access/heapam.h"
136 : : #include "access/htup_details.h"
137 : : #include "access/multixact.h"
138 : : #include "access/tidstore.h"
139 : : #include "access/transam.h"
140 : : #include "access/visibilitymap.h"
141 : : #include "access/xloginsert.h"
142 : : #include "catalog/storage.h"
143 : : #include "commands/progress.h"
144 : : #include "commands/vacuum.h"
145 : : #include "common/int.h"
146 : : #include "common/pg_prng.h"
147 : : #include "executor/instrument.h"
148 : : #include "miscadmin.h"
149 : : #include "pgstat.h"
150 : : #include "portability/instr_time.h"
151 : : #include "postmaster/autovacuum.h"
152 : : #include "storage/bufmgr.h"
153 : : #include "storage/freespace.h"
154 : : #include "storage/lmgr.h"
155 : : #include "storage/read_stream.h"
156 : : #include "utils/lsyscache.h"
157 : : #include "utils/pg_rusage.h"
158 : : #include "utils/timestamp.h"
159 : :
160 : :
161 : : /*
162 : : * Space/time tradeoff parameters: do these need to be user-tunable?
163 : : *
164 : : * To consider truncating the relation, we want there to be at least
165 : : * REL_TRUNCATE_MINIMUM or (relsize / REL_TRUNCATE_FRACTION) (whichever
166 : : * is less) potentially-freeable pages.
167 : : */
168 : : #define REL_TRUNCATE_MINIMUM 1000
169 : : #define REL_TRUNCATE_FRACTION 16
170 : :
171 : : /*
172 : : * Timing parameters for truncate locking heuristics.
173 : : *
174 : : * These were not exposed as user tunable GUC values because it didn't seem
175 : : * that the potential for improvement was great enough to merit the cost of
176 : : * supporting them.
177 : : */
178 : : #define VACUUM_TRUNCATE_LOCK_CHECK_INTERVAL 20 /* ms */
179 : : #define VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL 50 /* ms */
180 : : #define VACUUM_TRUNCATE_LOCK_TIMEOUT 5000 /* ms */
181 : :
182 : : /*
183 : : * Threshold that controls whether we bypass index vacuuming and heap
184 : : * vacuuming as an optimization
185 : : */
186 : : #define BYPASS_THRESHOLD_PAGES 0.02 /* i.e. 2% of rel_pages */
187 : :
188 : : /*
189 : : * Perform a failsafe check each time we scan another 4GB of pages.
190 : : * (Note that this is deliberately kept to a power-of-two, usually 2^19.)
191 : : */
192 : : #define FAILSAFE_EVERY_PAGES \
193 : : ((BlockNumber) (((uint64) 4 * 1024 * 1024 * 1024) / BLCKSZ))
194 : :
195 : : /*
196 : : * When a table has no indexes, vacuum the FSM after every 8GB, approximately
197 : : * (it won't be exact because we only vacuum FSM after processing a heap page
198 : : * that has some removable tuples). When there are indexes, this is ignored,
199 : : * and we vacuum FSM after each index/heap cleaning pass.
200 : : */
201 : : #define VACUUM_FSM_EVERY_PAGES \
202 : : ((BlockNumber) (((uint64) 8 * 1024 * 1024 * 1024) / BLCKSZ))
203 : :
204 : : /*
205 : : * Before we consider skipping a page that's marked as clean in
206 : : * visibility map, we must've seen at least this many clean pages.
207 : : */
208 : : #define SKIP_PAGES_THRESHOLD ((BlockNumber) 32)
209 : :
210 : : /*
211 : : * Size of the prefetch window for lazy vacuum backwards truncation scan.
212 : : * Needs to be a power of 2.
213 : : */
214 : : #define PREFETCH_SIZE ((BlockNumber) 32)
215 : :
216 : : /*
217 : : * Macro to check if we are in a parallel vacuum. If true, we are in the
218 : : * parallel mode and the DSM segment is initialized.
219 : : */
220 : : #define ParallelVacuumIsActive(vacrel) ((vacrel)->pvs != NULL)
221 : :
222 : : /* Phases of vacuum during which we report error context. */
223 : : typedef enum
224 : : {
225 : : VACUUM_ERRCB_PHASE_UNKNOWN,
226 : : VACUUM_ERRCB_PHASE_SCAN_HEAP,
227 : : VACUUM_ERRCB_PHASE_VACUUM_INDEX,
228 : : VACUUM_ERRCB_PHASE_VACUUM_HEAP,
229 : : VACUUM_ERRCB_PHASE_INDEX_CLEANUP,
230 : : VACUUM_ERRCB_PHASE_TRUNCATE,
231 : : } VacErrPhase;
232 : :
233 : : /*
234 : : * An eager scan of a page that is set all-frozen in the VM is considered
235 : : * "successful". To spread out freezing overhead across multiple normal
236 : : * vacuums, we limit the number of successful eager page freezes. The maximum
237 : : * number of eager page freezes is calculated as a ratio of the all-visible
238 : : * but not all-frozen pages at the beginning of the vacuum.
239 : : */
240 : : #define MAX_EAGER_FREEZE_SUCCESS_RATE 0.2
241 : :
242 : : /*
243 : : * On the assumption that different regions of the table tend to have
244 : : * similarly aged data, once vacuum fails to freeze
245 : : * vacuum_max_eager_freeze_failure_rate of the blocks in a region of size
246 : : * EAGER_SCAN_REGION_SIZE, it suspends eager scanning until it has progressed
247 : : * to another region of the table with potentially older data.
248 : : */
249 : : #define EAGER_SCAN_REGION_SIZE 4096
250 : :
251 : : /*
252 : : * heap_vac_scan_next_block() sets these flags to communicate information
253 : : * about the block it read to the caller.
254 : : */
255 : : #define VAC_BLK_WAS_EAGER_SCANNED (1 << 0)
256 : : #define VAC_BLK_ALL_VISIBLE_ACCORDING_TO_VM (1 << 1)
257 : :
258 : : typedef struct LVRelState
259 : : {
260 : : /* Target heap relation and its indexes */
261 : : Relation rel;
262 : : Relation *indrels;
263 : : int nindexes;
264 : :
265 : : /* Buffer access strategy and parallel vacuum state */
266 : : BufferAccessStrategy bstrategy;
267 : : ParallelVacuumState *pvs;
268 : :
269 : : /* Aggressive VACUUM? (must set relfrozenxid >= FreezeLimit) */
270 : : bool aggressive;
271 : : /* Use visibility map to skip? (disabled by DISABLE_PAGE_SKIPPING) */
272 : : bool skipwithvm;
273 : : /* Consider index vacuuming bypass optimization? */
274 : : bool consider_bypass_optimization;
275 : :
276 : : /* Doing index vacuuming, index cleanup, rel truncation? */
277 : : bool do_index_vacuuming;
278 : : bool do_index_cleanup;
279 : : bool do_rel_truncate;
280 : :
281 : : /* VACUUM operation's cutoffs for freezing and pruning */
282 : : struct VacuumCutoffs cutoffs;
283 : : GlobalVisState *vistest;
284 : : /* Tracks oldest extant XID/MXID for setting relfrozenxid/relminmxid */
285 : : TransactionId NewRelfrozenXid;
286 : : MultiXactId NewRelminMxid;
287 : : bool skippedallvis;
288 : :
289 : : /* Error reporting state */
290 : : char *dbname;
291 : : char *relnamespace;
292 : : char *relname;
293 : : char *indname; /* Current index name */
294 : : BlockNumber blkno; /* used only for heap operations */
295 : : OffsetNumber offnum; /* used only for heap operations */
296 : : VacErrPhase phase;
297 : : bool verbose; /* VACUUM VERBOSE? */
298 : :
299 : : /*
300 : : * dead_items stores TIDs whose index tuples are deleted by index
301 : : * vacuuming. Each TID points to an LP_DEAD line pointer from a heap page
302 : : * that has been processed by lazy_scan_prune. Also needed by
303 : : * lazy_vacuum_heap_rel, which marks the same LP_DEAD line pointers as
304 : : * LP_UNUSED during second heap pass.
305 : : *
306 : : * Both dead_items and dead_items_info are allocated in shared memory in
307 : : * parallel vacuum cases.
308 : : */
309 : : TidStore *dead_items; /* TIDs whose index tuples we'll delete */
310 : : VacDeadItemsInfo *dead_items_info;
311 : :
312 : : BlockNumber rel_pages; /* total number of pages */
313 : : BlockNumber scanned_pages; /* # pages examined (not skipped via VM) */
314 : :
315 : : /*
316 : : * Count of all-visible blocks eagerly scanned (for logging only). This
317 : : * does not include skippable blocks scanned due to SKIP_PAGES_THRESHOLD.
318 : : */
319 : : BlockNumber eager_scanned_pages;
320 : :
321 : : BlockNumber removed_pages; /* # pages removed by relation truncation */
322 : : BlockNumber new_frozen_tuple_pages; /* # pages with newly frozen tuples */
323 : :
324 : : /* # pages newly set all-visible in the VM */
325 : : BlockNumber vm_new_visible_pages;
326 : :
327 : : /*
328 : : * # pages newly set all-visible and all-frozen in the VM. This is a
329 : : * subset of vm_new_visible_pages. That is, vm_new_visible_pages includes
330 : : * all pages set all-visible, but vm_new_visible_frozen_pages includes
331 : : * only those which were also set all-frozen.
332 : : */
333 : : BlockNumber vm_new_visible_frozen_pages;
334 : :
335 : : /* # all-visible pages newly set all-frozen in the VM */
336 : : BlockNumber vm_new_frozen_pages;
337 : :
338 : : BlockNumber lpdead_item_pages; /* # pages with LP_DEAD items */
339 : : BlockNumber missed_dead_pages; /* # pages with missed dead tuples */
340 : : BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */
341 : :
342 : : /* Statistics output by us, for table */
343 : : double new_rel_tuples; /* new estimated total # of tuples */
344 : : double new_live_tuples; /* new estimated total # of live tuples */
345 : : /* Statistics output by index AMs */
346 : : IndexBulkDeleteResult **indstats;
347 : :
348 : : /* Instrumentation counters */
349 : : int num_index_scans;
350 : : /* Counters that follow are only for scanned_pages */
351 : : int64 tuples_deleted; /* # deleted from table */
352 : : int64 tuples_frozen; /* # newly frozen */
353 : : int64 lpdead_items; /* # deleted from indexes */
354 : : int64 live_tuples; /* # live tuples remaining */
355 : : int64 recently_dead_tuples; /* # dead, but not yet removable */
356 : : int64 missed_dead_tuples; /* # removable, but not removed */
357 : :
358 : : /* State maintained by heap_vac_scan_next_block() */
359 : : BlockNumber current_block; /* last block returned */
360 : : BlockNumber next_unskippable_block; /* next unskippable block */
361 : : bool next_unskippable_allvis; /* its visibility status */
362 : : bool next_unskippable_eager_scanned; /* if it was eagerly scanned */
363 : : Buffer next_unskippable_vmbuffer; /* buffer containing its VM bit */
364 : :
365 : : /* State related to managing eager scanning of all-visible pages */
366 : :
367 : : /*
368 : : * A normal vacuum that has failed to freeze too many eagerly scanned
369 : : * blocks in a region suspends eager scanning.
370 : : * next_eager_scan_region_start is the block number of the first block
371 : : * eligible for resumed eager scanning.
372 : : *
373 : : * When eager scanning is permanently disabled, either initially
374 : : * (including for aggressive vacuum) or due to hitting the success cap,
375 : : * this is set to InvalidBlockNumber.
376 : : */
377 : : BlockNumber next_eager_scan_region_start;
378 : :
379 : : /*
380 : : * The remaining number of blocks a normal vacuum will consider eager
381 : : * scanning when it is successful. When eager scanning is enabled, this is
382 : : * initialized to MAX_EAGER_FREEZE_SUCCESS_RATE of the total number of
383 : : * all-visible but not all-frozen pages. For each eager freeze success,
384 : : * this is decremented. Once it hits 0, eager scanning is permanently
385 : : * disabled. It is initialized to 0 if eager scanning starts out disabled
386 : : * (including for aggressive vacuum).
387 : : */
388 : : BlockNumber eager_scan_remaining_successes;
389 : :
390 : : /*
391 : : * The maximum number of blocks which may be eagerly scanned and not
392 : : * frozen before eager scanning is temporarily suspended. This is
393 : : * configurable both globally, via the
394 : : * vacuum_max_eager_freeze_failure_rate GUC, and per table, with a table
395 : : * storage parameter of the same name. It is calculated as
396 : : * vacuum_max_eager_freeze_failure_rate of EAGER_SCAN_REGION_SIZE blocks.
397 : : * It is 0 when eager scanning is disabled.
398 : : */
399 : : BlockNumber eager_scan_max_fails_per_region;
400 : :
401 : : /*
402 : : * The number of eagerly scanned blocks vacuum failed to freeze (due to
403 : : * age) in the current eager scan region. Vacuum resets it to
404 : : * eager_scan_max_fails_per_region each time it enters a new region of the
405 : : * relation. If eager_scan_remaining_fails hits 0, eager scanning is
406 : : * suspended until the next region. It is also 0 if eager scanning has
407 : : * been permanently disabled.
408 : : */
409 : : BlockNumber eager_scan_remaining_fails;
410 : : } LVRelState;
411 : :
412 : :
413 : : /* Struct for saving and restoring vacuum error information. */
414 : : typedef struct LVSavedErrInfo
415 : : {
416 : : BlockNumber blkno;
417 : : OffsetNumber offnum;
418 : : VacErrPhase phase;
419 : : } LVSavedErrInfo;
420 : :
421 : :
422 : : /* non-export function prototypes */
423 : : static void lazy_scan_heap(LVRelState *vacrel);
424 : : static void heap_vacuum_eager_scan_setup(LVRelState *vacrel,
425 : : const VacuumParams params);
426 : : static BlockNumber heap_vac_scan_next_block(ReadStream *stream,
427 : : void *callback_private_data,
428 : : void *per_buffer_data);
429 : : static void find_next_unskippable_block(LVRelState *vacrel, bool *skipsallvis);
430 : : static bool lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf,
431 : : BlockNumber blkno, Page page,
432 : : bool sharelock, Buffer vmbuffer);
433 : : static int lazy_scan_prune(LVRelState *vacrel, Buffer buf,
434 : : BlockNumber blkno, Page page,
435 : : Buffer vmbuffer, bool all_visible_according_to_vm,
436 : : bool *has_lpdead_items, bool *vm_page_frozen);
437 : : static bool lazy_scan_noprune(LVRelState *vacrel, Buffer buf,
438 : : BlockNumber blkno, Page page,
439 : : bool *has_lpdead_items);
440 : : static void lazy_vacuum(LVRelState *vacrel);
441 : : static bool lazy_vacuum_all_indexes(LVRelState *vacrel);
442 : : static void lazy_vacuum_heap_rel(LVRelState *vacrel);
443 : : static void lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno,
444 : : Buffer buffer, OffsetNumber *deadoffsets,
445 : : int num_offsets, Buffer vmbuffer);
446 : : static bool lazy_check_wraparound_failsafe(LVRelState *vacrel);
447 : : static void lazy_cleanup_all_indexes(LVRelState *vacrel);
448 : : static IndexBulkDeleteResult *lazy_vacuum_one_index(Relation indrel,
449 : : IndexBulkDeleteResult *istat,
450 : : double reltuples,
451 : : LVRelState *vacrel);
452 : : static IndexBulkDeleteResult *lazy_cleanup_one_index(Relation indrel,
453 : : IndexBulkDeleteResult *istat,
454 : : double reltuples,
455 : : bool estimated_count,
456 : : LVRelState *vacrel);
457 : : static bool should_attempt_truncation(LVRelState *vacrel);
458 : : static void lazy_truncate_heap(LVRelState *vacrel);
459 : : static BlockNumber count_nondeletable_pages(LVRelState *vacrel,
460 : : bool *lock_waiter_detected);
461 : : static void dead_items_alloc(LVRelState *vacrel, int nworkers);
462 : : static void dead_items_add(LVRelState *vacrel, BlockNumber blkno, OffsetNumber *offsets,
463 : : int num_offsets);
464 : : static void dead_items_reset(LVRelState *vacrel);
465 : : static void dead_items_cleanup(LVRelState *vacrel);
466 : :
467 : : #ifdef USE_ASSERT_CHECKING
468 : : static bool heap_page_is_all_visible(Relation rel, Buffer buf,
469 : : TransactionId OldestXmin,
470 : : bool *all_frozen,
471 : : TransactionId *visibility_cutoff_xid,
472 : : OffsetNumber *logging_offnum);
473 : : #endif
474 : : static bool heap_page_would_be_all_visible(Relation rel, Buffer buf,
475 : : TransactionId OldestXmin,
476 : : OffsetNumber *deadoffsets,
477 : : int ndeadoffsets,
478 : : bool *all_frozen,
479 : : TransactionId *visibility_cutoff_xid,
480 : : OffsetNumber *logging_offnum);
481 : : static void update_relstats_all_indexes(LVRelState *vacrel);
482 : : static void vacuum_error_callback(void *arg);
483 : : static void update_vacuum_error_info(LVRelState *vacrel,
484 : : LVSavedErrInfo *saved_vacrel,
485 : : int phase, BlockNumber blkno,
486 : : OffsetNumber offnum);
487 : : static void restore_vacuum_error_info(LVRelState *vacrel,
488 : : const LVSavedErrInfo *saved_vacrel);
489 : :
490 : :
491 : :
492 : : /*
493 : : * Helper to set up the eager scanning state for vacuuming a single relation.
494 : : * Initializes the eager scan management related members of the LVRelState.
495 : : *
496 : : * Caller provides whether or not an aggressive vacuum is required due to
497 : : * vacuum options or for relfrozenxid/relminmxid advancement.
498 : : */
499 : : static void
120 michael@paquier.xyz 500 :GNC 13024 : heap_vacuum_eager_scan_setup(LVRelState *vacrel, const VacuumParams params)
501 : : {
502 : : uint32 randseed;
503 : : BlockNumber allvisible;
504 : : BlockNumber allfrozen;
505 : : float first_region_ratio;
259 melanieplageman@gmai 506 :CBC 13024 : bool oldest_unfrozen_before_cutoff = false;
507 : :
508 : : /*
509 : : * Initialize eager scan management fields to their disabled values.
510 : : * Aggressive vacuums, normal vacuums of small tables, and normal vacuums
511 : : * of tables without sufficiently old tuples disable eager scanning.
512 : : */
513 : 13024 : vacrel->next_eager_scan_region_start = InvalidBlockNumber;
514 : 13024 : vacrel->eager_scan_max_fails_per_region = 0;
515 : 13024 : vacrel->eager_scan_remaining_fails = 0;
516 : 13024 : vacrel->eager_scan_remaining_successes = 0;
517 : :
518 : : /* If eager scanning is explicitly disabled, just return. */
120 michael@paquier.xyz 519 [ - + ]:GNC 13024 : if (params.max_eager_freeze_failure_rate == 0)
259 melanieplageman@gmai 520 :CBC 13024 : return;
521 : :
522 : : /*
523 : : * The caller will have determined whether or not an aggressive vacuum is
524 : : * required by either the vacuum parameters or the relative age of the
525 : : * oldest unfrozen transaction IDs. An aggressive vacuum must scan every
526 : : * all-visible page to safely advance the relfrozenxid and/or relminmxid,
527 : : * so scans of all-visible pages are not considered eager.
528 : : */
529 [ + + ]: 13024 : if (vacrel->aggressive)
530 : 7636 : return;
531 : :
532 : : /*
533 : : * Aggressively vacuuming a small relation shouldn't take long, so it
534 : : * isn't worth amortizing. We use two times the region size as the size
535 : : * cutoff because the eager scan start block is a random spot somewhere in
536 : : * the first region, making the second region the first to be eager
537 : : * scanned normally.
538 : : */
539 [ + - ]: 5388 : if (vacrel->rel_pages < 2 * EAGER_SCAN_REGION_SIZE)
540 : 5388 : return;
541 : :
542 : : /*
543 : : * We only want to enable eager scanning if we are likely to be able to
544 : : * freeze some of the pages in the relation.
545 : : *
546 : : * Tuples with XIDs older than OldestXmin or MXIDs older than OldestMxact
547 : : * are technically freezable, but we won't freeze them unless the criteria
548 : : * for opportunistic freezing is met. Only tuples with XIDs/MXIDs older
549 : : * than the FreezeLimit/MultiXactCutoff are frozen in the common case.
550 : : *
551 : : * So, as a heuristic, we wait until the FreezeLimit has advanced past the
552 : : * relfrozenxid or the MultiXactCutoff has advanced past the relminmxid to
553 : : * enable eager scanning.
554 : : */
259 melanieplageman@gmai 555 [ # # # # ]:UBC 0 : if (TransactionIdIsNormal(vacrel->cutoffs.relfrozenxid) &&
556 : 0 : TransactionIdPrecedes(vacrel->cutoffs.relfrozenxid,
557 : : vacrel->cutoffs.FreezeLimit))
558 : 0 : oldest_unfrozen_before_cutoff = true;
559 : :
560 [ # # ]: 0 : if (!oldest_unfrozen_before_cutoff &&
561 [ # # # # ]: 0 : MultiXactIdIsValid(vacrel->cutoffs.relminmxid) &&
562 : 0 : MultiXactIdPrecedes(vacrel->cutoffs.relminmxid,
563 : : vacrel->cutoffs.MultiXactCutoff))
564 : 0 : oldest_unfrozen_before_cutoff = true;
565 : :
566 [ # # ]: 0 : if (!oldest_unfrozen_before_cutoff)
567 : 0 : return;
568 : :
569 : : /* We have met the criteria to eagerly scan some pages. */
570 : :
571 : : /*
572 : : * Our success cap is MAX_EAGER_FREEZE_SUCCESS_RATE of the number of
573 : : * all-visible but not all-frozen blocks in the relation.
574 : : */
575 : 0 : visibilitymap_count(vacrel->rel, &allvisible, &allfrozen);
576 : :
577 : 0 : vacrel->eager_scan_remaining_successes =
578 : 0 : (BlockNumber) (MAX_EAGER_FREEZE_SUCCESS_RATE *
579 : 0 : (allvisible - allfrozen));
580 : :
581 : : /* If every all-visible page is frozen, eager scanning is disabled. */
582 [ # # ]: 0 : if (vacrel->eager_scan_remaining_successes == 0)
583 : 0 : return;
584 : :
585 : : /*
586 : : * Now calculate the bounds of the first eager scan region. Its end block
587 : : * will be a random spot somewhere in the first EAGER_SCAN_REGION_SIZE
588 : : * blocks. This affects the bounds of all subsequent regions and avoids
589 : : * eager scanning and failing to freeze the same blocks each vacuum of the
590 : : * relation.
591 : : */
592 : 0 : randseed = pg_prng_uint32(&pg_global_prng_state);
593 : :
594 : 0 : vacrel->next_eager_scan_region_start = randseed % EAGER_SCAN_REGION_SIZE;
595 : :
120 michael@paquier.xyz 596 [ # # # # ]:UNC 0 : Assert(params.max_eager_freeze_failure_rate > 0 &&
597 : : params.max_eager_freeze_failure_rate <= 1);
598 : :
259 melanieplageman@gmai 599 :UBC 0 : vacrel->eager_scan_max_fails_per_region =
120 michael@paquier.xyz 600 :UNC 0 : params.max_eager_freeze_failure_rate *
601 : : EAGER_SCAN_REGION_SIZE;
602 : :
603 : : /*
604 : : * The first region will be smaller than subsequent regions. As such,
605 : : * adjust the eager freeze failures tolerated for this region.
606 : : */
259 melanieplageman@gmai 607 :UBC 0 : first_region_ratio = 1 - (float) vacrel->next_eager_scan_region_start /
608 : : EAGER_SCAN_REGION_SIZE;
609 : :
610 : 0 : vacrel->eager_scan_remaining_fails =
611 : 0 : vacrel->eager_scan_max_fails_per_region *
612 : : first_region_ratio;
613 : : }
614 : :
615 : : /*
616 : : * heap_vacuum_rel() -- perform VACUUM for one heap relation
617 : : *
618 : : * This routine sets things up for and then calls lazy_scan_heap, where
619 : : * almost all work actually takes place. Finalizes everything after call
620 : : * returns by managing relation truncation and updating rel's pg_class
621 : : * entry. (Also updates pg_class entries for any indexes that need it.)
622 : : *
623 : : * At entry, we have already established a transaction and opened
624 : : * and locked the relation.
625 : : */
626 : : void
120 michael@paquier.xyz 627 :GNC 13024 : heap_vacuum_rel(Relation rel, const VacuumParams params,
628 : : BufferAccessStrategy bstrategy)
629 : : {
630 : : LVRelState *vacrel;
631 : : bool verbose,
632 : : instrument,
633 : : skipwithvm,
634 : : frozenxid_updated,
635 : : minmulti_updated;
636 : : BlockNumber orig_rel_pages,
637 : : new_rel_pages,
638 : : new_rel_allvisible,
639 : : new_rel_allfrozen;
640 : : PGRUsage ru0;
6557 bruce@momjian.us 641 :CBC 13024 : TimestampTz starttime = 0;
1292 pg@bowt.ie 642 : 13024 : PgStat_Counter startreadtime = 0,
1265 tgl@sss.pgh.pa.us 643 : 13024 : startwritetime = 0;
1292 pg@bowt.ie 644 : 13024 : WalUsage startwalusage = pgWalUsage;
545 msawada@postgresql.o 645 : 13024 : BufferUsage startbufferusage = pgBufferUsage;
646 : : ErrorContextCallback errcallback;
1305 pg@bowt.ie 647 : 13024 : char **indnames = NULL;
648 : :
120 michael@paquier.xyz 649 :GNC 13024 : verbose = (params.options & VACOPT_VERBOSE) != 0;
603 heikki.linnakangas@i 650 [ + + + + ]:CBC 13192 : instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
13 peter@eisentraut.org 651 [ + - ]:GNC 168 : params.log_vacuum_min_duration >= 0));
1383 pg@bowt.ie 652 [ + + ]:CBC 13024 : if (instrument)
653 : : {
5185 rhaas@postgresql.org 654 : 175 : pg_rusage_init(&ru0);
1687 sfrost@snowman.net 655 [ - + ]: 175 : if (track_io_timing)
656 : : {
1687 sfrost@snowman.net 657 :UBC 0 : startreadtime = pgStatBlockReadTime;
658 : 0 : startwritetime = pgStatBlockWriteTime;
659 : : }
660 : : }
661 : :
662 : : /* Used for instrumentation and stats report */
273 michael@paquier.xyz 663 :CBC 13024 : starttime = GetCurrentTimestamp();
664 : :
3520 rhaas@postgresql.org 665 : 13024 : pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM,
666 : : RelationGetRelid(rel));
667 : :
668 : : /*
669 : : * Setup error traceback support for ereport() first. The idea is to set
670 : : * up an error context callback to display additional information on any
671 : : * error during a vacuum. During different phases of vacuum, we update
672 : : * the state so that the error context callback always display current
673 : : * information.
674 : : *
675 : : * Copy the names of heap rel into local memory for error reporting
676 : : * purposes, too. It isn't always safe to assume that we can get the name
677 : : * of each rel. It's convenient for code in lazy_scan_heap to always use
678 : : * these temp copies.
679 : : */
1667 pg@bowt.ie 680 : 13024 : vacrel = (LVRelState *) palloc0(sizeof(LVRelState));
1029 681 : 13024 : vacrel->dbname = get_database_name(MyDatabaseId);
1383 682 : 13024 : vacrel->relnamespace = get_namespace_name(RelationGetNamespace(rel));
683 : 13024 : vacrel->relname = pstrdup(RelationGetRelationName(rel));
684 : 13024 : vacrel->indname = NULL;
685 : 13024 : vacrel->phase = VACUUM_ERRCB_PHASE_UNKNOWN;
686 : 13024 : vacrel->verbose = verbose;
687 : 13024 : errcallback.callback = vacuum_error_callback;
688 : 13024 : errcallback.arg = vacrel;
689 : 13024 : errcallback.previous = error_context_stack;
690 : 13024 : error_context_stack = &errcallback;
691 : :
692 : : /* Set up high level stuff about rel and its indexes */
1667 693 : 13024 : vacrel->rel = rel;
694 : 13024 : vac_open_indexes(vacrel->rel, RowExclusiveLock, &vacrel->nindexes,
695 : : &vacrel->indrels);
1041 696 : 13024 : vacrel->bstrategy = bstrategy;
1383 697 [ + + + + ]: 13024 : if (instrument && vacrel->nindexes > 0)
698 : : {
699 : : /* Copy index names used by instrumentation (not error reporting) */
700 : 165 : indnames = palloc(sizeof(char *) * vacrel->nindexes);
701 [ + + ]: 478 : for (int i = 0; i < vacrel->nindexes; i++)
702 : 313 : indnames[i] = pstrdup(RelationGetRelationName(vacrel->indrels[i]));
703 : : }
704 : :
705 : : /*
706 : : * The index_cleanup param either disables index vacuuming and cleanup or
707 : : * forces it to go ahead when we would otherwise apply the index bypass
708 : : * optimization. The default is 'auto', which leaves the final decision
709 : : * up to lazy_vacuum().
710 : : *
711 : : * The truncate param allows user to avoid attempting relation truncation,
712 : : * though it can't force truncation to happen.
713 : : */
120 michael@paquier.xyz 714 [ - + ]:GNC 13024 : Assert(params.index_cleanup != VACOPTVALUE_UNSPECIFIED);
715 [ + - - + ]: 13024 : Assert(params.truncate != VACOPTVALUE_UNSPECIFIED &&
716 : : params.truncate != VACOPTVALUE_AUTO);
717 : :
718 : : /*
719 : : * While VacuumFailSafeActive is reset to false before calling this, we
720 : : * still need to reset it here due to recursive calls.
721 : : */
914 dgustafsson@postgres 722 :CBC 13024 : VacuumFailsafeActive = false;
1383 pg@bowt.ie 723 : 13024 : vacrel->consider_bypass_optimization = true;
1666 724 : 13024 : vacrel->do_index_vacuuming = true;
725 : 13024 : vacrel->do_index_cleanup = true;
120 michael@paquier.xyz 726 :GNC 13024 : vacrel->do_rel_truncate = (params.truncate != VACOPTVALUE_DISABLED);
727 [ + + ]: 13024 : if (params.index_cleanup == VACOPTVALUE_DISABLED)
728 : : {
729 : : /* Force disable index vacuuming up-front */
1666 pg@bowt.ie 730 :CBC 128 : vacrel->do_index_vacuuming = false;
731 : 128 : vacrel->do_index_cleanup = false;
732 : : }
120 michael@paquier.xyz 733 [ + + ]:GNC 12896 : else if (params.index_cleanup == VACOPTVALUE_ENABLED)
734 : : {
735 : : /* Force index vacuuming. Note that failsafe can still bypass. */
1593 pg@bowt.ie 736 :CBC 13 : vacrel->consider_bypass_optimization = false;
737 : : }
738 : : else
739 : : {
740 : : /* Default/auto, make all decisions dynamically */
120 michael@paquier.xyz 741 [ - + ]:GNC 12883 : Assert(params.index_cleanup == VACOPTVALUE_AUTO);
742 : : }
743 : :
744 : : /* Initialize page counters explicitly (be tidy) */
1326 pg@bowt.ie 745 :CBC 13024 : vacrel->scanned_pages = 0;
259 melanieplageman@gmai 746 : 13024 : vacrel->eager_scanned_pages = 0;
1326 pg@bowt.ie 747 : 13024 : vacrel->removed_pages = 0;
315 melanieplageman@gmai 748 : 13024 : vacrel->new_frozen_tuple_pages = 0;
1326 pg@bowt.ie 749 : 13024 : vacrel->lpdead_item_pages = 0;
750 : 13024 : vacrel->missed_dead_pages = 0;
751 : 13024 : vacrel->nonempty_pages = 0;
752 : : /* dead_items_alloc allocates vacrel->dead_items later on */
753 : :
754 : : /* Allocate/initialize output statistics state */
755 : 13024 : vacrel->new_rel_tuples = 0;
756 : 13024 : vacrel->new_live_tuples = 0;
757 : 13024 : vacrel->indstats = (IndexBulkDeleteResult **)
758 : 13024 : palloc0(vacrel->nindexes * sizeof(IndexBulkDeleteResult *));
759 : :
760 : : /* Initialize remaining counters (be tidy) */
761 : 13024 : vacrel->num_index_scans = 0;
762 : 13024 : vacrel->tuples_deleted = 0;
1146 763 : 13024 : vacrel->tuples_frozen = 0;
1326 764 : 13024 : vacrel->lpdead_items = 0;
765 : 13024 : vacrel->live_tuples = 0;
766 : 13024 : vacrel->recently_dead_tuples = 0;
767 : 13024 : vacrel->missed_dead_tuples = 0;
768 : :
315 melanieplageman@gmai 769 : 13024 : vacrel->vm_new_visible_pages = 0;
770 : 13024 : vacrel->vm_new_visible_frozen_pages = 0;
771 : 13024 : vacrel->vm_new_frozen_pages = 0;
772 : :
773 : : /*
774 : : * Get cutoffs that determine which deleted tuples are considered DEAD,
775 : : * not just RECENTLY_DEAD, and which XIDs/MXIDs to freeze. Then determine
776 : : * the extent of the blocks that we'll scan in lazy_scan_heap. It has to
777 : : * happen in this order to ensure that the OldestXmin cutoff field works
778 : : * as an upper bound on the XIDs stored in the pages we'll actually scan
779 : : * (NewRelfrozenXid tracking must never be allowed to miss unfrozen XIDs).
780 : : *
781 : : * Next acquire vistest, a related cutoff that's used in pruning. We use
782 : : * vistest in combination with OldestXmin to ensure that
783 : : * heap_page_prune_and_freeze() always removes any deleted tuple whose
784 : : * xmax is < OldestXmin. lazy_scan_prune must never become confused about
785 : : * whether a tuple should be frozen or removed. (In the future we might
786 : : * want to teach lazy_scan_prune to recompute vistest from time to time,
787 : : * to increase the number of dead tuples it can prune away.)
788 : : */
1041 pg@bowt.ie 789 : 13024 : vacrel->aggressive = vacuum_get_cutoffs(rel, params, &vacrel->cutoffs);
148 melanieplageman@gmai 790 : 13024 : vacrel->rel_pages = orig_rel_pages = RelationGetNumberOfBlocks(rel);
1326 pg@bowt.ie 791 : 13024 : vacrel->vistest = GlobalVisTestFor(rel);
792 : :
793 : : /* Initialize state used to track oldest extant XID/MXID */
1041 794 : 13024 : vacrel->NewRelfrozenXid = vacrel->cutoffs.OldestXmin;
795 : 13024 : vacrel->NewRelminMxid = vacrel->cutoffs.OldestMxact;
796 : :
797 : : /*
798 : : * Initialize state related to tracking all-visible page skipping. This is
799 : : * very important to determine whether or not it is safe to advance the
800 : : * relfrozenxid/relminmxid.
801 : : */
1304 802 : 13024 : vacrel->skippedallvis = false;
1041 803 : 13024 : skipwithvm = true;
120 michael@paquier.xyz 804 [ + + ]:GNC 13024 : if (params.options & VACOPT_DISABLE_PAGE_SKIPPING)
805 : : {
806 : : /*
807 : : * Force aggressive mode, and disable skipping blocks using the
808 : : * visibility map (even those set all-frozen)
809 : : */
1041 pg@bowt.ie 810 :CBC 166 : vacrel->aggressive = true;
811 : 166 : skipwithvm = false;
812 : : }
813 : :
814 : 13024 : vacrel->skipwithvm = skipwithvm;
815 : :
816 : : /*
817 : : * Set up eager scan tracking state. This must happen after determining
818 : : * whether or not the vacuum must be aggressive, because only normal
819 : : * vacuums use the eager scan algorithm.
820 : : */
259 melanieplageman@gmai 821 : 13024 : heap_vacuum_eager_scan_setup(vacrel, params);
822 : :
1041 pg@bowt.ie 823 [ + + ]: 13024 : if (verbose)
824 : : {
825 [ + + ]: 7 : if (vacrel->aggressive)
826 [ + - ]: 1 : ereport(INFO,
827 : : (errmsg("aggressively vacuuming \"%s.%s.%s\"",
828 : : vacrel->dbname, vacrel->relnamespace,
829 : : vacrel->relname)));
830 : : else
831 [ + - ]: 6 : ereport(INFO,
832 : : (errmsg("vacuuming \"%s.%s.%s\"",
833 : : vacrel->dbname, vacrel->relnamespace,
834 : : vacrel->relname)));
835 : : }
836 : :
837 : : /*
838 : : * Allocate dead_items memory using dead_items_alloc. This handles
839 : : * parallel VACUUM initialization as part of allocating shared memory
840 : : * space used for dead_items. (But do a failsafe precheck first, to
841 : : * ensure that parallel VACUUM won't be attempted at all when relfrozenxid
842 : : * is already dangerously old.)
843 : : */
1303 844 : 13024 : lazy_check_wraparound_failsafe(vacrel);
120 michael@paquier.xyz 845 :GNC 13024 : dead_items_alloc(vacrel, params.nworkers);
846 : :
847 : : /*
848 : : * Call lazy_scan_heap to perform all required heap pruning, index
849 : : * vacuuming, and heap vacuuming (plus related processing)
850 : : */
1303 pg@bowt.ie 851 :CBC 13024 : lazy_scan_heap(vacrel);
852 : :
853 : : /*
854 : : * Free resources managed by dead_items_alloc. This ends parallel mode in
855 : : * passing when necessary.
856 : : */
857 : 13024 : dead_items_cleanup(vacrel);
858 [ - + ]: 13024 : Assert(!IsInParallelMode());
859 : :
860 : : /*
861 : : * Update pg_class entries for each of rel's indexes where appropriate.
862 : : *
863 : : * Unlike the later update to rel's pg_class entry, this is not critical.
864 : : * Maintains relpages/reltuples statistics used by the planner only.
865 : : */
1326 866 [ + + ]: 13024 : if (vacrel->do_index_cleanup)
867 : 12896 : update_relstats_all_indexes(vacrel);
868 : :
869 : : /* Done with rel's indexes */
870 : 13024 : vac_close_indexes(vacrel->nindexes, vacrel->indrels, NoLock);
871 : :
872 : : /* Optionally truncate rel */
1593 873 [ + + ]: 13024 : if (should_attempt_truncation(vacrel))
1667 874 : 174 : lazy_truncate_heap(vacrel);
875 : :
876 : : /* Pop the error context stack */
2038 akapila@postgresql.o 877 : 13024 : error_context_stack = errcallback.previous;
878 : :
879 : : /* Report that we are now doing final cleanup */
3514 rhaas@postgresql.org 880 : 13024 : pgstat_progress_update_param(PROGRESS_VACUUM_PHASE,
881 : : PROGRESS_VACUUM_PHASE_FINAL_CLEANUP);
882 : :
883 : : /*
884 : : * Prepare to update rel's pg_class entry.
885 : : *
886 : : * Aggressive VACUUMs must always be able to advance relfrozenxid to a
887 : : * value >= FreezeLimit, and relminmxid to a value >= MultiXactCutoff.
888 : : * Non-aggressive VACUUMs may advance them by any amount, or not at all.
889 : : */
1041 pg@bowt.ie 890 [ + + + + : 13024 : Assert(vacrel->NewRelfrozenXid == vacrel->cutoffs.OldestXmin ||
- + ]
891 : : TransactionIdPrecedesOrEquals(vacrel->aggressive ? vacrel->cutoffs.FreezeLimit :
892 : : vacrel->cutoffs.relfrozenxid,
893 : : vacrel->NewRelfrozenXid));
894 [ + + - + : 13024 : Assert(vacrel->NewRelminMxid == vacrel->cutoffs.OldestMxact ||
- + ]
895 : : MultiXactIdPrecedesOrEquals(vacrel->aggressive ? vacrel->cutoffs.MultiXactCutoff :
896 : : vacrel->cutoffs.relminmxid,
897 : : vacrel->NewRelminMxid));
1304 898 [ + + ]: 13024 : if (vacrel->skippedallvis)
899 : : {
900 : : /*
901 : : * Must keep original relfrozenxid in a non-aggressive VACUUM that
902 : : * chose to skip an all-visible page range. The state that tracks new
903 : : * values will have missed unfrozen XIDs from the pages we skipped.
904 : : */
1041 905 [ - + ]: 32 : Assert(!vacrel->aggressive);
1304 906 : 32 : vacrel->NewRelfrozenXid = InvalidTransactionId;
907 : 32 : vacrel->NewRelminMxid = InvalidMultiXactId;
908 : : }
909 : :
910 : : /*
911 : : * For safety, clamp relallvisible to be not more than what we're setting
912 : : * pg_class.relpages to
913 : : */
1355 914 : 13024 : new_rel_pages = vacrel->rel_pages; /* After possible rel truncation */
239 melanieplageman@gmai 915 : 13024 : visibilitymap_count(rel, &new_rel_allvisible, &new_rel_allfrozen);
5128 tgl@sss.pgh.pa.us 916 [ - + ]: 13024 : if (new_rel_allvisible > new_rel_pages)
5128 tgl@sss.pgh.pa.us 917 :UBC 0 : new_rel_allvisible = new_rel_pages;
918 : :
919 : : /*
920 : : * An all-frozen block _must_ be all-visible. As such, clamp the count of
921 : : * all-frozen blocks to the count of all-visible blocks. This matches the
922 : : * clamping of relallvisible above.
923 : : */
239 melanieplageman@gmai 924 [ - + ]:CBC 13024 : if (new_rel_allfrozen > new_rel_allvisible)
239 melanieplageman@gmai 925 :UBC 0 : new_rel_allfrozen = new_rel_allvisible;
926 : :
927 : : /*
928 : : * Now actually update rel's pg_class entry.
929 : : *
930 : : * In principle new_live_tuples could be -1 indicating that we (still)
931 : : * don't know the tuple count. In practice that can't happen, since we
932 : : * scan every page that isn't skipped using the visibility map.
933 : : */
1304 pg@bowt.ie 934 :CBC 13024 : vac_update_relstats(rel, new_rel_pages, vacrel->new_live_tuples,
935 : : new_rel_allvisible, new_rel_allfrozen,
239 melanieplageman@gmai 936 : 13024 : vacrel->nindexes > 0,
937 : : vacrel->NewRelfrozenXid, vacrel->NewRelminMxid,
938 : : &frozenxid_updated, &minmulti_updated, false);
939 : :
940 : : /*
941 : : * Report results to the cumulative stats system, too.
942 : : *
943 : : * Deliberately avoid telling the stats system about LP_DEAD items that
944 : : * remain in the table due to VACUUM bypassing index and heap vacuuming.
945 : : * ANALYZE will consider the remaining LP_DEAD items to be dead "tuples".
946 : : * It seems like a good idea to err on the side of not vacuuming again too
947 : : * soon in cases where the failsafe prevented significant amounts of heap
948 : : * vacuuming.
949 : : */
1667 pg@bowt.ie 950 : 7558 : pgstat_report_vacuum(RelationGetRelid(rel),
951 : 13024 : rel->rd_rel->relisshared,
1305 952 : 5466 : Max(vacrel->new_live_tuples, 0),
1355 953 : 13024 : vacrel->recently_dead_tuples +
273 michael@paquier.xyz 954 [ + + ]: 13024 : vacrel->missed_dead_tuples,
955 : : starttime);
3520 rhaas@postgresql.org 956 : 13024 : pgstat_progress_end_command();
957 : :
1383 pg@bowt.ie 958 [ + + ]: 13024 : if (instrument)
959 : : {
4888 bruce@momjian.us 960 : 175 : TimestampTz endtime = GetCurrentTimestamp();
961 : :
13 peter@eisentraut.org 962 [ + + + + :GNC 284 : if (verbose || params.log_vacuum_min_duration == 0 ||
- + ]
5086 alvherre@alvh.no-ip. 963 :CBC 109 : TimestampDifferenceExceeds(starttime, endtime,
13 peter@eisentraut.org 964 :GNC 109 : params.log_vacuum_min_duration))
965 : : {
966 : : long secs_dur;
967 : : int usecs_dur;
968 : : WalUsage walusage;
969 : : BufferUsage bufferusage;
970 : : StringInfoData buf;
971 : : char *msgfmt;
972 : : int32 diff;
1292 pg@bowt.ie 973 :CBC 66 : double read_rate = 0,
974 : 66 : write_rate = 0;
975 : : int64 total_blks_hit;
976 : : int64 total_blks_read;
977 : : int64 total_blks_dirtied;
978 : :
979 : 66 : TimestampDifference(starttime, endtime, &secs_dur, &usecs_dur);
2031 akapila@postgresql.o 980 : 66 : memset(&walusage, 0, sizeof(WalUsage));
1292 pg@bowt.ie 981 : 66 : WalUsageAccumDiff(&walusage, &pgWalUsage, &startwalusage);
545 msawada@postgresql.o 982 : 66 : memset(&bufferusage, 0, sizeof(BufferUsage));
983 : 66 : BufferUsageAccumDiff(&bufferusage, &pgBufferUsage, &startbufferusage);
984 : :
441 985 : 66 : total_blks_hit = bufferusage.shared_blks_hit +
986 : 66 : bufferusage.local_blks_hit;
987 : 66 : total_blks_read = bufferusage.shared_blks_read +
988 : 66 : bufferusage.local_blks_read;
989 : 66 : total_blks_dirtied = bufferusage.shared_blks_dirtied +
990 : 66 : bufferusage.local_blks_dirtied;
991 : :
3967 alvherre@alvh.no-ip. 992 : 66 : initStringInfo(&buf);
1383 pg@bowt.ie 993 [ + + ]: 66 : if (verbose)
994 : : {
995 : : /*
996 : : * Aggressiveness already reported earlier, in dedicated
997 : : * VACUUM VERBOSE ereport
998 : : */
120 michael@paquier.xyz 999 [ - + ]:GNC 7 : Assert(!params.is_wraparound);
1383 pg@bowt.ie 1000 :CBC 7 : msgfmt = _("finished vacuuming \"%s.%s.%s\": index scans: %d\n");
1001 : : }
120 michael@paquier.xyz 1002 [ - + ]:GNC 59 : else if (params.is_wraparound)
1003 : : {
1004 : : /*
1005 : : * While it's possible for a VACUUM to be both is_wraparound
1006 : : * and !aggressive, that's just a corner-case -- is_wraparound
1007 : : * implies aggressive. Produce distinct output for the corner
1008 : : * case all the same, just in case.
1009 : : */
1041 pg@bowt.ie 1010 [ # # ]:UBC 0 : if (vacrel->aggressive)
2037 michael@paquier.xyz 1011 : 0 : msgfmt = _("automatic aggressive vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n");
1012 : : else
1013 : 0 : msgfmt = _("automatic vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n");
1014 : : }
1015 : : else
1016 : : {
1041 pg@bowt.ie 1017 [ + + ]:CBC 59 : if (vacrel->aggressive)
2601 michael@paquier.xyz 1018 : 4 : msgfmt = _("automatic aggressive vacuum of table \"%s.%s.%s\": index scans: %d\n");
1019 : : else
1020 : 55 : msgfmt = _("automatic vacuum of table \"%s.%s.%s\": index scans: %d\n");
1021 : : }
2924 rhaas@postgresql.org 1022 : 66 : appendStringInfo(&buf, msgfmt,
1023 : : vacrel->dbname,
1024 : : vacrel->relnamespace,
1025 : : vacrel->relname,
1026 : : vacrel->num_index_scans);
259 melanieplageman@gmai 1027 [ + - ]: 132 : appendStringInfo(&buf, _("pages: %u removed, %u remain, %u scanned (%.2f%% of total), %u eagerly scanned\n"),
1028 : : vacrel->removed_pages,
1029 : : new_rel_pages,
1030 : : vacrel->scanned_pages,
1031 : : orig_rel_pages == 0 ? 100.0 :
1032 : 66 : 100.0 * vacrel->scanned_pages /
1033 : : orig_rel_pages,
1034 : : vacrel->eager_scanned_pages);
3967 alvherre@alvh.no-ip. 1035 : 66 : appendStringInfo(&buf,
213 peter@eisentraut.org 1036 : 66 : _("tuples: %" PRId64 " removed, %" PRId64 " remain, %" PRId64 " are dead but not yet removable\n"),
1037 : : vacrel->tuples_deleted,
1038 : 66 : (int64) vacrel->new_rel_tuples,
1039 : : vacrel->recently_dead_tuples);
1355 pg@bowt.ie 1040 [ - + ]: 66 : if (vacrel->missed_dead_tuples > 0)
1355 pg@bowt.ie 1041 :UBC 0 : appendStringInfo(&buf,
213 peter@eisentraut.org 1042 : 0 : _("tuples missed: %" PRId64 " dead from %u pages not removed due to cleanup lock contention\n"),
1043 : : vacrel->missed_dead_tuples,
1044 : : vacrel->missed_dead_pages);
1041 pg@bowt.ie 1045 :CBC 66 : diff = (int32) (ReadNextTransactionId() -
1046 : 66 : vacrel->cutoffs.OldestXmin);
1355 1047 : 66 : appendStringInfo(&buf,
1292 1048 : 66 : _("removable cutoff: %u, which was %d XIDs old when operation ended\n"),
1049 : : vacrel->cutoffs.OldestXmin, diff);
1355 1050 [ + + ]: 66 : if (frozenxid_updated)
1051 : : {
1041 1052 : 46 : diff = (int32) (vacrel->NewRelfrozenXid -
1053 : 46 : vacrel->cutoffs.relfrozenxid);
1355 1054 : 46 : appendStringInfo(&buf,
1292 1055 : 46 : _("new relfrozenxid: %u, which is %d XIDs ahead of previous value\n"),
1056 : : vacrel->NewRelfrozenXid, diff);
1057 : : }
1355 1058 [ + + ]: 66 : if (minmulti_updated)
1059 : : {
1041 1060 : 13 : diff = (int32) (vacrel->NewRelminMxid -
1061 : 13 : vacrel->cutoffs.relminmxid);
1355 1062 : 13 : appendStringInfo(&buf,
1292 1063 : 13 : _("new relminmxid: %u, which is %d MXIDs ahead of previous value\n"),
1064 : : vacrel->NewRelminMxid, diff);
1065 : : }
213 peter@eisentraut.org 1066 [ + - ]: 132 : appendStringInfo(&buf, _("frozen: %u pages from table (%.2f%% of total) had %" PRId64 " tuples frozen\n"),
1067 : : vacrel->new_frozen_tuple_pages,
1068 : : orig_rel_pages == 0 ? 100.0 :
315 melanieplageman@gmai 1069 : 66 : 100.0 * vacrel->new_frozen_tuple_pages /
1070 : : orig_rel_pages,
1071 : : vacrel->tuples_frozen);
1072 : :
1073 : 66 : appendStringInfo(&buf,
1074 : 66 : _("visibility map: %u pages set all-visible, %u pages set all-frozen (%u were all-visible)\n"),
1075 : : vacrel->vm_new_visible_pages,
1076 : 66 : vacrel->vm_new_visible_frozen_pages +
1077 : 66 : vacrel->vm_new_frozen_pages,
1078 : : vacrel->vm_new_frozen_pages);
1292 pg@bowt.ie 1079 [ + + ]: 66 : if (vacrel->do_index_vacuuming)
1080 : : {
1081 [ + + + + ]: 65 : if (vacrel->nindexes == 0 || vacrel->num_index_scans == 0)
1082 : 19 : appendStringInfoString(&buf, _("index scan not needed: "));
1083 : : else
1084 : 46 : appendStringInfoString(&buf, _("index scan needed: "));
1085 : :
213 peter@eisentraut.org 1086 : 65 : msgfmt = _("%u pages from table (%.2f%% of total) had %" PRId64 " dead item identifiers removed\n");
1087 : : }
1088 : : else
1089 : : {
935 dgustafsson@postgres 1090 [ + - ]: 1 : if (!VacuumFailsafeActive)
1292 pg@bowt.ie 1091 : 1 : appendStringInfoString(&buf, _("index scan bypassed: "));
1092 : : else
1292 pg@bowt.ie 1093 :UBC 0 : appendStringInfoString(&buf, _("index scan bypassed by failsafe: "));
1094 : :
213 peter@eisentraut.org 1095 :CBC 1 : msgfmt = _("%u pages from table (%.2f%% of total) have %" PRId64 " dead item identifiers\n");
1096 : : }
1292 pg@bowt.ie 1097 [ + - ]: 132 : appendStringInfo(&buf, msgfmt,
1098 : : vacrel->lpdead_item_pages,
1099 : : orig_rel_pages == 0 ? 100.0 :
1100 : 66 : 100.0 * vacrel->lpdead_item_pages / orig_rel_pages,
1101 : : vacrel->lpdead_items);
1667 1102 [ + + ]: 179 : for (int i = 0; i < vacrel->nindexes; i++)
1103 : : {
1104 : 113 : IndexBulkDeleteResult *istat = vacrel->indstats[i];
1105 : :
1106 [ + + ]: 113 : if (!istat)
1680 michael@paquier.xyz 1107 : 6 : continue;
1108 : :
1109 : 107 : appendStringInfo(&buf,
1679 1110 : 107 : _("index \"%s\": pages: %u in total, %u newly deleted, %u currently deleted, %u reusable\n"),
1680 1111 : 107 : indnames[i],
1112 : : istat->num_pages,
1113 : : istat->pages_newly_deleted,
1114 : : istat->pages_deleted,
1115 : : istat->pages_free);
1116 : : }
256 nathan@postgresql.or 1117 [ - + ]: 66 : if (track_cost_delay_timing)
1118 : : {
1119 : : /*
1120 : : * We bypass the changecount mechanism because this value is
1121 : : * only updated by the calling process. We also rely on the
1122 : : * above call to pgstat_progress_end_command() to not clear
1123 : : * the st_progress_param array.
1124 : : */
256 nathan@postgresql.or 1125 :UBC 0 : appendStringInfo(&buf, _("delay time: %.3f ms\n"),
1126 : 0 : (double) MyBEEntry->st_progress_param[PROGRESS_VACUUM_DELAY_TIME] / 1000000.0);
1127 : : }
1687 sfrost@snowman.net 1128 [ - + ]:CBC 66 : if (track_io_timing)
1129 : : {
1523 pg@bowt.ie 1130 :UBC 0 : double read_ms = (double) (pgStatBlockReadTime - startreadtime) / 1000;
1131 : 0 : double write_ms = (double) (pgStatBlockWriteTime - startwritetime) / 1000;
1132 : :
1133 : 0 : appendStringInfo(&buf, _("I/O timings: read: %.3f ms, write: %.3f ms\n"),
1134 : : read_ms, write_ms);
1135 : : }
1292 pg@bowt.ie 1136 [ + + + - ]:CBC 66 : if (secs_dur > 0 || usecs_dur > 0)
1137 : : {
441 msawada@postgresql.o 1138 : 66 : read_rate = (double) BLCKSZ * total_blks_read /
545 1139 : 66 : (1024 * 1024) / (secs_dur + usecs_dur / 1000000.0);
441 1140 : 66 : write_rate = (double) BLCKSZ * total_blks_dirtied /
545 1141 : 66 : (1024 * 1024) / (secs_dur + usecs_dur / 1000000.0);
1142 : : }
1523 pg@bowt.ie 1143 : 66 : appendStringInfo(&buf, _("avg read rate: %.3f MB/s, avg write rate: %.3f MB/s\n"),
1144 : : read_rate, write_rate);
1145 : 66 : appendStringInfo(&buf,
213 peter@eisentraut.org 1146 : 66 : _("buffer usage: %" PRId64 " hits, %" PRId64 " reads, %" PRId64 " dirtied\n"),
1147 : : total_blks_hit,
1148 : : total_blks_read,
1149 : : total_blks_dirtied);
2031 akapila@postgresql.o 1150 : 66 : appendStringInfo(&buf,
213 peter@eisentraut.org 1151 : 66 : _("WAL usage: %" PRId64 " records, %" PRId64 " full page images, %" PRIu64 " bytes, %" PRId64 " buffers full\n"),
1152 : : walusage.wal_records,
1153 : : walusage.wal_fpi,
1154 : : walusage.wal_bytes,
1155 : : walusage.wal_buffers_full);
1523 pg@bowt.ie 1156 : 66 : appendStringInfo(&buf, _("system usage: %s"), pg_rusage_show(&ru0));
1157 : :
1383 1158 [ + + + - ]: 66 : ereport(verbose ? INFO : LOG,
1159 : : (errmsg_internal("%s", buf.data)));
3967 alvherre@alvh.no-ip. 1160 : 66 : pfree(buf.data);
1161 : : }
1162 : : }
1163 : :
1164 : : /* Cleanup index statistics and index names */
1667 pg@bowt.ie 1165 [ + + ]: 31736 : for (int i = 0; i < vacrel->nindexes; i++)
1166 : : {
1167 [ + + ]: 18712 : if (vacrel->indstats[i])
1168 : 1436 : pfree(vacrel->indstats[i]);
1169 : :
1383 1170 [ + + ]: 18712 : if (instrument)
1680 michael@paquier.xyz 1171 : 313 : pfree(indnames[i]);
1172 : : }
8873 tgl@sss.pgh.pa.us 1173 : 13024 : }
1174 : :
1175 : : /*
1176 : : * lazy_scan_heap() -- workhorse function for VACUUM
1177 : : *
1178 : : * This routine prunes each page in the heap, and considers the need to
1179 : : * freeze remaining tuples with storage (not including pages that can be
1180 : : * skipped using the visibility map). Also performs related maintenance
1181 : : * of the FSM and visibility map. These steps all take place during an
1182 : : * initial pass over the target heap relation.
1183 : : *
1184 : : * Also invokes lazy_vacuum_all_indexes to vacuum indexes, which largely
1185 : : * consists of deleting index tuples that point to LP_DEAD items left in
1186 : : * heap pages following pruning. Earlier initial pass over the heap will
1187 : : * have collected the TIDs whose index tuples need to be removed.
1188 : : *
1189 : : * Finally, invokes lazy_vacuum_heap_rel to vacuum heap pages, which
1190 : : * largely consists of marking LP_DEAD items (from vacrel->dead_items)
1191 : : * as LP_UNUSED. This has to happen in a second, final pass over the
1192 : : * heap, to preserve a basic invariant that all index AMs rely on: no
1193 : : * extant index tuple can ever be allowed to contain a TID that points to
1194 : : * an LP_UNUSED line pointer in the heap. We must disallow premature
1195 : : * recycling of line pointers to avoid index scans that get confused
1196 : : * about which TID points to which tuple immediately after recycling.
1197 : : * (Actually, this isn't a concern when target heap relation happens to
1198 : : * have no indexes, which allows us to safely apply the one-pass strategy
1199 : : * as an optimization).
1200 : : *
1201 : : * In practice we often have enough space to fit all TIDs, and so won't
1202 : : * need to call lazy_vacuum more than once, after our initial pass over
1203 : : * the heap has totally finished. Otherwise things are slightly more
1204 : : * complicated: our "initial pass" over the heap applies only to those
1205 : : * pages that were pruned before we needed to call lazy_vacuum, and our
1206 : : * "final pass" over the heap only vacuums these same heap pages.
1207 : : * However, we process indexes in full every time lazy_vacuum is called,
1208 : : * which makes index processing very inefficient when memory is in short
1209 : : * supply.
1210 : : */
1211 : : static void
1303 pg@bowt.ie 1212 : 13024 : lazy_scan_heap(LVRelState *vacrel)
1213 : : {
1214 : : ReadStream *stream;
1326 1215 : 13024 : BlockNumber rel_pages = vacrel->rel_pages,
256 melanieplageman@gmai 1216 : 13024 : blkno = 0,
1303 pg@bowt.ie 1217 : 13024 : next_fsm_block_to_vacuum = 0;
259 melanieplageman@gmai 1218 : 13024 : BlockNumber orig_eager_scan_success_limit =
1219 : : vacrel->eager_scan_remaining_successes; /* for logging */
6173 heikki.linnakangas@i 1220 : 13024 : Buffer vmbuffer = InvalidBuffer;
3514 rhaas@postgresql.org 1221 : 13024 : const int initprog_index[] = {
1222 : : PROGRESS_VACUUM_PHASE,
1223 : : PROGRESS_VACUUM_TOTAL_HEAP_BLKS,
1224 : : PROGRESS_VACUUM_MAX_DEAD_TUPLE_BYTES
1225 : : };
1226 : : int64 initprog_val[3];
1227 : :
1228 : : /* Report that we're scanning the heap, advertising total # of blocks */
1229 : 13024 : initprog_val[0] = PROGRESS_VACUUM_PHASE_SCAN_HEAP;
1326 pg@bowt.ie 1230 : 13024 : initprog_val[1] = rel_pages;
328 john.naylor@postgres 1231 : 13024 : initprog_val[2] = vacrel->dead_items_info->max_bytes;
3514 rhaas@postgresql.org 1232 : 13024 : pgstat_progress_update_multi_param(3, initprog_index, initprog_val);
1233 : :
1234 : : /* Initialize for the first heap_vac_scan_next_block() call */
596 heikki.linnakangas@i 1235 : 13024 : vacrel->current_block = InvalidBlockNumber;
1236 : 13024 : vacrel->next_unskippable_block = InvalidBlockNumber;
1237 : 13024 : vacrel->next_unskippable_allvis = false;
259 melanieplageman@gmai 1238 : 13024 : vacrel->next_unskippable_eager_scanned = false;
596 heikki.linnakangas@i 1239 : 13024 : vacrel->next_unskippable_vmbuffer = InvalidBuffer;
1240 : :
1241 : : /*
1242 : : * Set up the read stream for vacuum's first pass through the heap.
1243 : : *
1244 : : * This could be made safe for READ_STREAM_USE_BATCHING, but only with
1245 : : * explicit work in heap_vac_scan_next_block.
1246 : : */
256 melanieplageman@gmai 1247 : 13024 : stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE,
1248 : : vacrel->bstrategy,
1249 : : vacrel->rel,
1250 : : MAIN_FORKNUM,
1251 : : heap_vac_scan_next_block,
1252 : : vacrel,
1253 : : sizeof(uint8));
1254 : :
1255 : : while (true)
8873 tgl@sss.pgh.pa.us 1256 : 70730 : {
1257 : : Buffer buf;
1258 : : Page page;
256 melanieplageman@gmai 1259 : 83754 : uint8 blk_info = 0;
119 msawada@postgresql.o 1260 : 83754 : int ndeleted = 0;
1261 : : bool has_lpdead_items;
252 melanieplageman@gmai 1262 : 83754 : void *per_buffer_data = NULL;
259 1263 : 83754 : bool vm_page_frozen = false;
641 rhaas@postgresql.org 1264 : 83754 : bool got_cleanup_lock = false;
1265 : :
259 nathan@postgresql.or 1266 : 83754 : vacuum_delay_point(false);
1267 : :
1268 : : /*
1269 : : * Regularly check if wraparound failsafe should trigger.
1270 : : *
1271 : : * There is a similar check inside lazy_vacuum_all_indexes(), but
1272 : : * relfrozenxid might start to look dangerously old before we reach
1273 : : * that point. This check also provides failsafe coverage for the
1274 : : * one-pass strategy, and the two-pass strategy with the index_cleanup
1275 : : * param set to 'off'.
1276 : : */
256 melanieplageman@gmai 1277 [ + + ]: 83754 : if (vacrel->scanned_pages > 0 &&
1278 [ - + ]: 70730 : vacrel->scanned_pages % FAILSAFE_EVERY_PAGES == 0)
1618 pg@bowt.ie 1279 :UBC 0 : lazy_check_wraparound_failsafe(vacrel);
1280 : :
1281 : : /*
1282 : : * Consider if we definitely have enough space to process TIDs on page
1283 : : * already. If we are close to overrunning the available space for
1284 : : * dead_items TIDs, pause and do a cycle of vacuuming before we tackle
1285 : : * this page. However, let's force at least one page-worth of tuples
1286 : : * to be stored as to ensure we do at least some work when the memory
1287 : : * configured is so low that we run out before storing anything.
1288 : : */
224 msawada@postgresql.o 1289 [ + + ]:CBC 83754 : if (vacrel->dead_items_info->num_items > 0 &&
1290 [ + + ]: 25203 : TidStoreMemoryUsage(vacrel->dead_items) > vacrel->dead_items_info->max_bytes)
1291 : : {
1292 : : /*
1293 : : * Before beginning index vacuuming, we release any pin we may
1294 : : * hold on the visibility map page. This isn't necessary for
1295 : : * correctness, but we do it anyway to avoid holding the pin
1296 : : * across a lengthy, unrelated operation.
1297 : : */
4936 rhaas@postgresql.org 1298 [ + - ]: 17 : if (BufferIsValid(vmbuffer))
1299 : : {
1300 : 17 : ReleaseBuffer(vmbuffer);
1301 : 17 : vmbuffer = InvalidBuffer;
1302 : : }
1303 : :
1304 : : /* Perform a round of index and heap vacuuming */
1593 pg@bowt.ie 1305 : 17 : vacrel->consider_bypass_optimization = false;
1306 : 17 : lazy_vacuum(vacrel);
1307 : :
1308 : : /*
1309 : : * Vacuum the Free Space Map to make newly-freed space visible on
1310 : : * upper-level FSM pages. Note that blkno is the previously
1311 : : * processed block.
1312 : : */
1667 1313 : 17 : FreeSpaceMapVacuumRange(vacrel->rel, next_fsm_block_to_vacuum,
1314 : : blkno + 1);
2770 tgl@sss.pgh.pa.us 1315 : 17 : next_fsm_block_to_vacuum = blkno;
1316 : :
1317 : : /* Report that we are once again scanning the heap */
3514 rhaas@postgresql.org 1318 : 17 : pgstat_progress_update_param(PROGRESS_VACUUM_PHASE,
1319 : : PROGRESS_VACUUM_PHASE_SCAN_HEAP);
1320 : : }
1321 : :
256 melanieplageman@gmai 1322 : 83754 : buf = read_stream_next_buffer(stream, &per_buffer_data);
1323 : :
1324 : : /* The relation is exhausted. */
1325 [ + + ]: 83754 : if (!BufferIsValid(buf))
1326 : 13024 : break;
1327 : :
1328 : 70730 : blk_info = *((uint8 *) per_buffer_data);
1329 : 70730 : CheckBufferIsPinnedOnce(buf);
1330 : 70730 : page = BufferGetPage(buf);
1331 : 70730 : blkno = BufferGetBlockNumber(buf);
1332 : :
1333 : 70730 : vacrel->scanned_pages++;
1334 [ - + ]: 70730 : if (blk_info & VAC_BLK_WAS_EAGER_SCANNED)
256 melanieplageman@gmai 1335 :UBC 0 : vacrel->eager_scanned_pages++;
1336 : :
1337 : : /* Report as block scanned, update error traceback information */
256 melanieplageman@gmai 1338 :CBC 70730 : pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno);
1339 : 70730 : update_vacuum_error_info(vacrel, NULL, VACUUM_ERRCB_PHASE_SCAN_HEAP,
1340 : : blkno, InvalidOffsetNumber);
1341 : :
1342 : : /*
1343 : : * Pin the visibility map page in case we need to mark the page
1344 : : * all-visible. In most cases this will be very cheap, because we'll
1345 : : * already have the correct page pinned anyway.
1346 : : */
1667 pg@bowt.ie 1347 : 70730 : visibilitymap_pin(vacrel->rel, blkno, &vmbuffer);
1348 : :
1349 : : /*
1350 : : * We need a buffer cleanup lock to prune HOT chains and defragment
1351 : : * the page in lazy_scan_prune. But when it's not possible to acquire
1352 : : * a cleanup lock right away, we may be able to settle for reduced
1353 : : * processing using lazy_scan_noprune.
1354 : : */
641 rhaas@postgresql.org 1355 : 70730 : got_cleanup_lock = ConditionalLockBufferForCleanup(buf);
1356 : :
1357 [ + + ]: 70730 : if (!got_cleanup_lock)
1358 : 92 : LockBuffer(buf, BUFFER_LOCK_SHARE);
1359 : :
1360 : : /* Check for new or empty pages before lazy_scan_[no]prune call */
1361 [ + + ]: 70730 : if (lazy_scan_new_or_empty(vacrel, buf, blkno, page, !got_cleanup_lock,
1362 : 70730 : vmbuffer))
1363 : : {
1364 : : /* Processed as new/empty page (lock and pin released) */
1365 : 1348 : continue;
1366 : : }
1367 : :
1368 : : /*
1369 : : * If we didn't get the cleanup lock, we can still collect LP_DEAD
1370 : : * items in the dead_items area for later vacuuming, count live and
1371 : : * recently dead tuples for vacuum logging, and determine if this
1372 : : * block could later be truncated. If we encounter any xid/mxids that
1373 : : * require advancing the relfrozenxid/relminxid, we'll have to wait
1374 : : * for a cleanup lock and call lazy_scan_prune().
1375 : : */
1376 [ + + ]: 69382 : if (!got_cleanup_lock &&
1377 [ + + ]: 92 : !lazy_scan_noprune(vacrel, buf, blkno, page, &has_lpdead_items))
1378 : : {
1379 : : /*
1380 : : * lazy_scan_noprune could not do all required processing. Wait
1381 : : * for a cleanup lock, and call lazy_scan_prune in the usual way.
1382 : : */
1355 pg@bowt.ie 1383 [ - + ]: 79 : Assert(vacrel->aggressive);
1384 : 79 : LockBuffer(buf, BUFFER_LOCK_UNLOCK);
1385 : 79 : LockBufferForCleanup(buf);
641 rhaas@postgresql.org 1386 : 79 : got_cleanup_lock = true;
1387 : : }
1388 : :
1389 : : /*
1390 : : * If we have a cleanup lock, we must now prune, freeze, and count
1391 : : * tuples. We may have acquired the cleanup lock originally, or we may
1392 : : * have gone back and acquired it after lazy_scan_noprune() returned
1393 : : * false. Either way, the page hasn't been processed yet.
1394 : : *
1395 : : * Like lazy_scan_noprune(), lazy_scan_prune() will count
1396 : : * recently_dead_tuples and live tuples for vacuum logging, determine
1397 : : * if the block can later be truncated, and accumulate the details of
1398 : : * remaining LP_DEAD line pointers on the page into dead_items. These
1399 : : * dead items include those pruned by lazy_scan_prune() as well as
1400 : : * line pointers previously marked LP_DEAD.
1401 : : */
1402 [ + + ]: 69382 : if (got_cleanup_lock)
119 msawada@postgresql.o 1403 : 69369 : ndeleted = lazy_scan_prune(vacrel, buf, blkno, page,
1404 : : vmbuffer,
1405 : 69369 : blk_info & VAC_BLK_ALL_VISIBLE_ACCORDING_TO_VM,
1406 : : &has_lpdead_items, &vm_page_frozen);
1407 : :
1408 : : /*
1409 : : * Count an eagerly scanned page as a failure or a success.
1410 : : *
1411 : : * Only lazy_scan_prune() freezes pages, so if we didn't get the
1412 : : * cleanup lock, we won't have frozen the page. However, we only count
1413 : : * pages that were too new to require freezing as eager freeze
1414 : : * failures.
1415 : : *
1416 : : * We could gather more information from lazy_scan_noprune() about
1417 : : * whether or not there were tuples with XIDs or MXIDs older than the
1418 : : * FreezeLimit or MultiXactCutoff. However, for simplicity, we simply
1419 : : * exclude pages skipped due to cleanup lock contention from eager
1420 : : * freeze algorithm caps.
1421 : : */
256 melanieplageman@gmai 1422 [ + + ]: 69382 : if (got_cleanup_lock &&
1423 [ - + ]: 69369 : (blk_info & VAC_BLK_WAS_EAGER_SCANNED))
1424 : : {
1425 : : /* Aggressive vacuums do not eager scan. */
259 melanieplageman@gmai 1426 [ # # ]:UBC 0 : Assert(!vacrel->aggressive);
1427 : :
1428 [ # # ]: 0 : if (vm_page_frozen)
1429 : : {
154 msawada@postgresql.o 1430 [ # # ]: 0 : if (vacrel->eager_scan_remaining_successes > 0)
1431 : 0 : vacrel->eager_scan_remaining_successes--;
1432 : :
259 melanieplageman@gmai 1433 [ # # ]: 0 : if (vacrel->eager_scan_remaining_successes == 0)
1434 : : {
1435 : : /*
1436 : : * Report only once that we disabled eager scanning. We
1437 : : * may eagerly read ahead blocks in excess of the success
1438 : : * or failure caps before attempting to freeze them, so we
1439 : : * could reach here even after disabling additional eager
1440 : : * scanning.
1441 : : */
154 msawada@postgresql.o 1442 [ # # ]: 0 : if (vacrel->eager_scan_max_fails_per_region > 0)
1443 [ # # # # ]: 0 : ereport(vacrel->verbose ? INFO : DEBUG2,
1444 : : (errmsg("disabling eager scanning after freezing %u eagerly scanned blocks of relation \"%s.%s.%s\"",
1445 : : orig_eager_scan_success_limit,
1446 : : vacrel->dbname, vacrel->relnamespace,
1447 : : vacrel->relname)));
1448 : :
1449 : : /*
1450 : : * If we hit our success cap, permanently disable eager
1451 : : * scanning by setting the other eager scan management
1452 : : * fields to their disabled values.
1453 : : */
259 melanieplageman@gmai 1454 : 0 : vacrel->eager_scan_remaining_fails = 0;
1455 : 0 : vacrel->next_eager_scan_region_start = InvalidBlockNumber;
1456 : 0 : vacrel->eager_scan_max_fails_per_region = 0;
1457 : : }
1458 : : }
154 msawada@postgresql.o 1459 [ # # ]: 0 : else if (vacrel->eager_scan_remaining_fails > 0)
259 melanieplageman@gmai 1460 : 0 : vacrel->eager_scan_remaining_fails--;
1461 : : }
1462 : :
1463 : : /*
1464 : : * Now drop the buffer lock and, potentially, update the FSM.
1465 : : *
1466 : : * Our goal is to update the freespace map the last time we touch the
1467 : : * page. If we'll process a block in the second pass, we may free up
1468 : : * additional space on the page, so it is better to update the FSM
1469 : : * after the second pass. If the relation has no indexes, or if index
1470 : : * vacuuming is disabled, there will be no second heap pass; if this
1471 : : * particular page has no dead items, the second heap pass will not
1472 : : * touch this page. So, in those cases, update the FSM now.
1473 : : *
1474 : : * Note: In corner cases, it's possible to miss updating the FSM
1475 : : * entirely. If index vacuuming is currently enabled, we'll skip the
1476 : : * FSM update now. But if failsafe mode is later activated, or there
1477 : : * are so few dead tuples that index vacuuming is bypassed, there will
1478 : : * also be no opportunity to update the FSM later, because we'll never
1479 : : * revisit this page. Since updating the FSM is desirable but not
1480 : : * absolutely required, that's OK.
1481 : : */
649 rhaas@postgresql.org 1482 [ + + ]:CBC 69382 : if (vacrel->nindexes == 0
1483 [ + + ]: 63562 : || !vacrel->do_index_vacuuming
1484 [ + + ]: 63209 : || !has_lpdead_items)
1666 pg@bowt.ie 1485 : 54000 : {
1486 : 54000 : Size freespace = PageGetHeapFreeSpace(page);
1487 : :
1488 : 54000 : UnlockReleaseBuffer(buf);
1667 1489 : 54000 : RecordPageWithFreeSpace(vacrel->rel, blkno, freespace);
1490 : :
1491 : : /*
1492 : : * Periodically perform FSM vacuuming to make newly-freed space
1493 : : * visible on upper FSM pages. This is done after vacuuming if the
1494 : : * table has indexes. There will only be newly-freed space if we
1495 : : * held the cleanup lock and lazy_scan_prune() was called.
1496 : : */
119 msawada@postgresql.o 1497 [ + + + + : 54000 : if (got_cleanup_lock && vacrel->nindexes == 0 && ndeleted > 0 &&
+ + ]
649 rhaas@postgresql.org 1498 [ - + ]: 447 : blkno - next_fsm_block_to_vacuum >= VACUUM_FSM_EVERY_PAGES)
1499 : : {
649 rhaas@postgresql.org 1500 :UBC 0 : FreeSpaceMapVacuumRange(vacrel->rel, next_fsm_block_to_vacuum,
1501 : : blkno);
1502 : 0 : next_fsm_block_to_vacuum = blkno;
1503 : : }
1504 : : }
1505 : : else
649 rhaas@postgresql.org 1506 :CBC 15382 : UnlockReleaseBuffer(buf);
1507 : : }
1508 : :
1303 pg@bowt.ie 1509 : 13024 : vacrel->blkno = InvalidBlockNumber;
1510 [ + + ]: 13024 : if (BufferIsValid(vmbuffer))
1511 : 5541 : ReleaseBuffer(vmbuffer);
1512 : :
1513 : : /*
1514 : : * Report that everything is now scanned. We never skip scanning the last
1515 : : * block in the relation, so we can pass rel_pages here.
1516 : : */
256 melanieplageman@gmai 1517 : 13024 : pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED,
1518 : : rel_pages);
1519 : :
1520 : : /* now we can compute the new value for pg_class.reltuples */
1326 pg@bowt.ie 1521 : 26048 : vacrel->new_live_tuples = vac_estimate_reltuples(vacrel->rel, rel_pages,
1522 : : vacrel->scanned_pages,
1666 1523 : 13024 : vacrel->live_tuples);
1524 : :
1525 : : /*
1526 : : * Also compute the total number of surviving heap entries. In the
1527 : : * (unlikely) scenario that new_live_tuples is -1, take it as zero.
1528 : : */
1667 1529 : 13024 : vacrel->new_rel_tuples =
1355 1530 [ + + ]: 13024 : Max(vacrel->new_live_tuples, 0) + vacrel->recently_dead_tuples +
1531 : 13024 : vacrel->missed_dead_tuples;
1532 : :
256 melanieplageman@gmai 1533 : 13024 : read_stream_end(stream);
1534 : :
1535 : : /*
1536 : : * Do index vacuuming (call each index's ambulkdelete routine), then do
1537 : : * related heap vacuuming
1538 : : */
328 john.naylor@postgres 1539 [ + + ]: 13024 : if (vacrel->dead_items_info->num_items > 0)
1593 pg@bowt.ie 1540 : 673 : lazy_vacuum(vacrel);
1541 : :
1542 : : /*
1543 : : * Vacuum the remainder of the Free Space Map. We must do this whether or
1544 : : * not there were indexes, and whether or not we bypassed index vacuuming.
1545 : : * We can pass rel_pages here because we never skip scanning the last
1546 : : * block of the relation.
1547 : : */
256 melanieplageman@gmai 1548 [ + + ]: 13024 : if (rel_pages > next_fsm_block_to_vacuum)
1549 : 5544 : FreeSpaceMapVacuumRange(vacrel->rel, next_fsm_block_to_vacuum, rel_pages);
1550 : :
1551 : : /* report all blocks vacuumed */
1552 : 13024 : pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED, rel_pages);
1553 : :
1554 : : /* Do final index cleanup (call each index's amvacuumcleanup routine) */
1666 pg@bowt.ie 1555 [ + + + + ]: 13024 : if (vacrel->nindexes > 0 && vacrel->do_index_cleanup)
1667 1556 : 11847 : lazy_cleanup_all_indexes(vacrel);
8873 tgl@sss.pgh.pa.us 1557 : 13024 : }
1558 : :
1559 : : /*
1560 : : * heap_vac_scan_next_block() -- read stream callback to get the next block
1561 : : * for vacuum to process
1562 : : *
1563 : : * Every time lazy_scan_heap() needs a new block to process during its first
1564 : : * phase, it invokes read_stream_next_buffer() with a stream set up to call
1565 : : * heap_vac_scan_next_block() to get the next block.
1566 : : *
1567 : : * heap_vac_scan_next_block() uses the visibility map, vacuum options, and
1568 : : * various thresholds to skip blocks which do not need to be processed and
1569 : : * returns the next block to process or InvalidBlockNumber if there are no
1570 : : * remaining blocks.
1571 : : *
1572 : : * The visibility status of the next block to process and whether or not it
1573 : : * was eager scanned is set in the per_buffer_data.
1574 : : *
1575 : : * callback_private_data contains a reference to the LVRelState, passed to the
1576 : : * read stream API during stream setup. The LVRelState is an in/out parameter
1577 : : * here (locally named `vacrel`). Vacuum options and information about the
1578 : : * relation are read from it. vacrel->skippedallvis is set if we skip a block
1579 : : * that's all-visible but not all-frozen (to ensure that we don't update
1580 : : * relfrozenxid in that case). vacrel also holds information about the next
1581 : : * unskippable block -- as bookkeeping for this function.
1582 : : */
1583 : : static BlockNumber
256 melanieplageman@gmai 1584 : 83754 : heap_vac_scan_next_block(ReadStream *stream,
1585 : : void *callback_private_data,
1586 : : void *per_buffer_data)
1587 : : {
1588 : : BlockNumber next_block;
1589 : 83754 : LVRelState *vacrel = callback_private_data;
1590 : 83754 : uint8 blk_info = 0;
1591 : :
1592 : : /* relies on InvalidBlockNumber + 1 overflowing to 0 on first call */
596 heikki.linnakangas@i 1593 : 83754 : next_block = vacrel->current_block + 1;
1594 : :
1595 : : /* Have we reached the end of the relation? */
1596 [ + + ]: 83754 : if (next_block >= vacrel->rel_pages)
1597 : : {
1598 [ + + ]: 13024 : if (BufferIsValid(vacrel->next_unskippable_vmbuffer))
1599 : : {
1600 : 4093 : ReleaseBuffer(vacrel->next_unskippable_vmbuffer);
1601 : 4093 : vacrel->next_unskippable_vmbuffer = InvalidBuffer;
1602 : : }
256 melanieplageman@gmai 1603 : 13024 : return InvalidBlockNumber;
1604 : : }
1605 : :
1606 : : /*
1607 : : * We must be in one of the three following states:
1608 : : */
596 heikki.linnakangas@i 1609 [ + + ]: 70730 : if (next_block > vacrel->next_unskippable_block ||
1610 [ + + ]: 14729 : vacrel->next_unskippable_block == InvalidBlockNumber)
1611 : : {
1612 : : /*
1613 : : * 1. We have just processed an unskippable block (or we're at the
1614 : : * beginning of the scan). Find the next unskippable block using the
1615 : : * visibility map.
1616 : : */
1617 : : bool skipsallvis;
1618 : :
1619 : 61545 : find_next_unskippable_block(vacrel, &skipsallvis);
1620 : :
1621 : : /*
1622 : : * We now know the next block that we must process. It can be the
1623 : : * next block after the one we just processed, or something further
1624 : : * ahead. If it's further ahead, we can jump to it, but we choose to
1625 : : * do so only if we can skip at least SKIP_PAGES_THRESHOLD consecutive
1626 : : * pages. Since we're reading sequentially, the OS should be doing
1627 : : * readahead for us, so there's no gain in skipping a page now and
1628 : : * then. Skipping such a range might even discourage sequential
1629 : : * detection.
1630 : : *
1631 : : * This test also enables more frequent relfrozenxid advancement
1632 : : * during non-aggressive VACUUMs. If the range has any all-visible
1633 : : * pages then skipping makes updating relfrozenxid unsafe, which is a
1634 : : * real downside.
1635 : : */
1636 [ + + ]: 61545 : if (vacrel->next_unskippable_block - next_block >= SKIP_PAGES_THRESHOLD)
1637 : : {
1638 : 256 : next_block = vacrel->next_unskippable_block;
1639 [ + + ]: 256 : if (skipsallvis)
1640 : 32 : vacrel->skippedallvis = true;
1641 : : }
1642 : : }
1643 : :
1644 : : /* Now we must be in one of the two remaining states: */
1645 [ + + ]: 70730 : if (next_block < vacrel->next_unskippable_block)
1646 : : {
1647 : : /*
1648 : : * 2. We are processing a range of blocks that we could have skipped
1649 : : * but chose not to. We know that they are all-visible in the VM,
1650 : : * otherwise they would've been unskippable.
1651 : : */
256 melanieplageman@gmai 1652 : 9185 : vacrel->current_block = next_block;
1653 : 9185 : blk_info |= VAC_BLK_ALL_VISIBLE_ACCORDING_TO_VM;
1654 : 9185 : *((uint8 *) per_buffer_data) = blk_info;
1655 : 9185 : return vacrel->current_block;
1656 : : }
1657 : : else
1658 : : {
1659 : : /*
1660 : : * 3. We reached the next unskippable block. Process it. On next
1661 : : * iteration, we will be back in state 1.
1662 : : */
596 heikki.linnakangas@i 1663 [ - + ]: 61545 : Assert(next_block == vacrel->next_unskippable_block);
1664 : :
256 melanieplageman@gmai 1665 : 61545 : vacrel->current_block = next_block;
1666 [ + + ]: 61545 : if (vacrel->next_unskippable_allvis)
1667 : 2742 : blk_info |= VAC_BLK_ALL_VISIBLE_ACCORDING_TO_VM;
1668 [ - + ]: 61545 : if (vacrel->next_unskippable_eager_scanned)
256 melanieplageman@gmai 1669 :UBC 0 : blk_info |= VAC_BLK_WAS_EAGER_SCANNED;
256 melanieplageman@gmai 1670 :CBC 61545 : *((uint8 *) per_buffer_data) = blk_info;
1671 : 61545 : return vacrel->current_block;
1672 : : }
1673 : : }
1674 : :
1675 : : /*
1676 : : * Find the next unskippable block in a vacuum scan using the visibility map.
1677 : : * The next unskippable block and its visibility information is updated in
1678 : : * vacrel.
1679 : : *
1680 : : * Note: our opinion of which blocks can be skipped can go stale immediately.
1681 : : * It's okay if caller "misses" a page whose all-visible or all-frozen marking
1682 : : * was concurrently cleared, though. All that matters is that caller scan all
1683 : : * pages whose tuples might contain XIDs < OldestXmin, or MXIDs < OldestMxact.
1684 : : * (Actually, non-aggressive VACUUMs can choose to skip all-visible pages with
1685 : : * older XIDs/MXIDs. The *skippedallvis flag will be set here when the choice
1686 : : * to skip such a range is actually made, making everything safe.)
1687 : : */
1688 : : static void
596 heikki.linnakangas@i 1689 : 61545 : find_next_unskippable_block(LVRelState *vacrel, bool *skipsallvis)
1690 : : {
1691 : 61545 : BlockNumber rel_pages = vacrel->rel_pages;
1692 : 61545 : BlockNumber next_unskippable_block = vacrel->next_unskippable_block + 1;
1693 : 61545 : Buffer next_unskippable_vmbuffer = vacrel->next_unskippable_vmbuffer;
259 melanieplageman@gmai 1694 : 61545 : bool next_unskippable_eager_scanned = false;
1695 : : bool next_unskippable_allvis;
1696 : :
596 heikki.linnakangas@i 1697 : 61545 : *skipsallvis = false;
1698 : :
259 melanieplageman@gmai 1699 : 29418 : for (;; next_unskippable_block++)
1304 pg@bowt.ie 1700 : 29418 : {
1701 : 90963 : uint8 mapbits = visibilitymap_get_status(vacrel->rel,
1702 : : next_unskippable_block,
1703 : : &next_unskippable_vmbuffer);
1704 : :
596 heikki.linnakangas@i 1705 : 90963 : next_unskippable_allvis = (mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0;
1706 : :
1707 : : /*
1708 : : * At the start of each eager scan region, normal vacuums with eager
1709 : : * scanning enabled reset the failure counter, allowing vacuum to
1710 : : * resume eager scanning if it had been suspended in the previous
1711 : : * region.
1712 : : */
259 melanieplageman@gmai 1713 [ - + ]: 90963 : if (next_unskippable_block >= vacrel->next_eager_scan_region_start)
1714 : : {
259 melanieplageman@gmai 1715 :UBC 0 : vacrel->eager_scan_remaining_fails =
1716 : 0 : vacrel->eager_scan_max_fails_per_region;
1717 : 0 : vacrel->next_eager_scan_region_start += EAGER_SCAN_REGION_SIZE;
1718 : : }
1719 : :
1720 : : /*
1721 : : * A block is unskippable if it is not all visible according to the
1722 : : * visibility map.
1723 : : */
596 heikki.linnakangas@i 1724 [ + + ]:CBC 90963 : if (!next_unskippable_allvis)
1725 : : {
1304 pg@bowt.ie 1726 [ - + ]: 58803 : Assert((mapbits & VISIBILITYMAP_ALL_FROZEN) == 0);
1727 : 58803 : break;
1728 : : }
1729 : :
1730 : : /*
1731 : : * Caller must scan the last page to determine whether it has tuples
1732 : : * (caller must have the opportunity to set vacrel->nonempty_pages).
1733 : : * This rule avoids having lazy_truncate_heap() take access-exclusive
1734 : : * lock on rel to attempt a truncation that fails anyway, just because
1735 : : * there are tuples on the last page (it is likely that there will be
1736 : : * tuples on other nearby pages as well, but those can be skipped).
1737 : : *
1738 : : * Implement this by always treating the last block as unsafe to skip.
1739 : : */
1740 [ + + ]: 32160 : if (next_unskippable_block == rel_pages - 1)
1741 : 2352 : break;
1742 : :
1743 : : /* DISABLE_PAGE_SKIPPING makes all skipping unsafe */
1744 [ + + ]: 29808 : if (!vacrel->skipwithvm)
1745 : 390 : break;
1746 : :
1747 : : /*
1748 : : * All-frozen pages cannot contain XIDs < OldestXmin (XIDs that aren't
1749 : : * already frozen by now), so this page can be skipped.
1750 : : */
259 melanieplageman@gmai 1751 [ + + ]: 29418 : if ((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0)
1752 : 26618 : continue;
1753 : :
1754 : : /*
1755 : : * Aggressive vacuums cannot skip any all-visible pages that are not
1756 : : * also all-frozen.
1757 : : */
1758 [ - + ]: 2800 : if (vacrel->aggressive)
259 melanieplageman@gmai 1759 :UBC 0 : break;
1760 : :
1761 : : /*
1762 : : * Normal vacuums with eager scanning enabled only skip all-visible
1763 : : * but not all-frozen pages if they have hit the failure limit for the
1764 : : * current eager scan region.
1765 : : */
259 melanieplageman@gmai 1766 [ - + ]:CBC 2800 : if (vacrel->eager_scan_remaining_fails > 0)
1767 : : {
259 melanieplageman@gmai 1768 :UBC 0 : next_unskippable_eager_scanned = true;
1769 : 0 : break;
1770 : : }
1771 : :
1772 : : /*
1773 : : * All-visible blocks are safe to skip in a normal vacuum. But
1774 : : * remember that the final range contains such a block for later.
1775 : : */
259 melanieplageman@gmai 1776 :CBC 2800 : *skipsallvis = true;
1777 : : }
1778 : :
1779 : : /* write the local variables back to vacrel */
596 heikki.linnakangas@i 1780 : 61545 : vacrel->next_unskippable_block = next_unskippable_block;
1781 : 61545 : vacrel->next_unskippable_allvis = next_unskippable_allvis;
259 melanieplageman@gmai 1782 : 61545 : vacrel->next_unskippable_eager_scanned = next_unskippable_eager_scanned;
596 heikki.linnakangas@i 1783 : 61545 : vacrel->next_unskippable_vmbuffer = next_unskippable_vmbuffer;
1304 pg@bowt.ie 1784 : 61545 : }
1785 : :
1786 : : /*
1787 : : * lazy_scan_new_or_empty() -- lazy_scan_heap() new/empty page handling.
1788 : : *
1789 : : * Must call here to handle both new and empty pages before calling
1790 : : * lazy_scan_prune or lazy_scan_noprune, since they're not prepared to deal
1791 : : * with new or empty pages.
1792 : : *
1793 : : * It's necessary to consider new pages as a special case, since the rules for
1794 : : * maintaining the visibility map and FSM with empty pages are a little
1795 : : * different (though new pages can be truncated away during rel truncation).
1796 : : *
1797 : : * Empty pages are not really a special case -- they're just heap pages that
1798 : : * have no allocated tuples (including even LP_UNUSED items). You might
1799 : : * wonder why we need to handle them here all the same. It's only necessary
1800 : : * because of a corner-case involving a hard crash during heap relation
1801 : : * extension. If we ever make relation-extension crash safe, then it should
1802 : : * no longer be necessary to deal with empty pages here (or new pages, for
1803 : : * that matter).
1804 : : *
1805 : : * Caller must hold at least a shared lock. We might need to escalate the
1806 : : * lock in that case, so the type of lock caller holds needs to be specified
1807 : : * using 'sharelock' argument.
1808 : : *
1809 : : * Returns false in common case where caller should go on to call
1810 : : * lazy_scan_prune (or lazy_scan_noprune). Otherwise returns true, indicating
1811 : : * that lazy_scan_heap is done processing the page, releasing lock on caller's
1812 : : * behalf.
1813 : : *
1814 : : * No vm_page_frozen output parameter (like that passed to lazy_scan_prune())
1815 : : * is passed here because neither empty nor new pages can be eagerly frozen.
1816 : : * New pages are never frozen. Empty pages are always set frozen in the VM at
1817 : : * the same time that they are set all-visible, and we don't eagerly scan
1818 : : * frozen pages.
1819 : : */
1820 : : static bool
1355 1821 : 70730 : lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf, BlockNumber blkno,
1822 : : Page page, bool sharelock, Buffer vmbuffer)
1823 : : {
1824 : : Size freespace;
1825 : :
1826 [ + + ]: 70730 : if (PageIsNew(page))
1827 : : {
1828 : : /*
1829 : : * All-zeroes pages can be left over if either a backend extends the
1830 : : * relation by a single page, but crashes before the newly initialized
1831 : : * page has been written out, or when bulk-extending the relation
1832 : : * (which creates a number of empty pages at the tail end of the
1833 : : * relation), and then enters them into the FSM.
1834 : : *
1835 : : * Note we do not enter the page into the visibilitymap. That has the
1836 : : * downside that we repeatedly visit this page in subsequent vacuums,
1837 : : * but otherwise we'll never discover the space on a promoted standby.
1838 : : * The harm of repeated checking ought to normally not be too bad. The
1839 : : * space usually should be used at some point, otherwise there
1840 : : * wouldn't be any regular vacuums.
1841 : : *
1842 : : * Make sure these pages are in the FSM, to ensure they can be reused.
1843 : : * Do that by testing if there's any space recorded for the page. If
1844 : : * not, enter it. We do so after releasing the lock on the heap page,
1845 : : * the FSM is approximate, after all.
1846 : : */
1847 : 1321 : UnlockReleaseBuffer(buf);
1848 : :
1849 [ + + ]: 1321 : if (GetRecordedFreeSpace(vacrel->rel, blkno) == 0)
1850 : : {
1851 : 521 : freespace = BLCKSZ - SizeOfPageHeaderData;
1852 : :
1853 : 521 : RecordPageWithFreeSpace(vacrel->rel, blkno, freespace);
1854 : : }
1855 : :
1856 : 1321 : return true;
1857 : : }
1858 : :
1859 [ + + ]: 69409 : if (PageIsEmpty(page))
1860 : : {
1861 : : /*
1862 : : * It seems likely that caller will always be able to get a cleanup
1863 : : * lock on an empty page. But don't take any chances -- escalate to
1864 : : * an exclusive lock (still don't need a cleanup lock, though).
1865 : : */
1866 [ - + ]: 27 : if (sharelock)
1867 : : {
1355 pg@bowt.ie 1868 :UBC 0 : LockBuffer(buf, BUFFER_LOCK_UNLOCK);
1869 : 0 : LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
1870 : :
1871 [ # # ]: 0 : if (!PageIsEmpty(page))
1872 : : {
1873 : : /* page isn't new or empty -- keep lock and pin for now */
1874 : 0 : return false;
1875 : : }
1876 : : }
1877 : : else
1878 : : {
1879 : : /* Already have a full cleanup lock (which is more than enough) */
1880 : : }
1881 : :
1882 : : /*
1883 : : * Unlike new pages, empty pages are always set all-visible and
1884 : : * all-frozen.
1885 : : */
1355 pg@bowt.ie 1886 [ - + ]:CBC 27 : if (!PageIsAllVisible(page))
1887 : : {
1355 pg@bowt.ie 1888 :UBC 0 : START_CRIT_SECTION();
1889 : :
1890 : : /* mark buffer dirty before writing a WAL record */
1891 : 0 : MarkBufferDirty(buf);
1892 : :
1893 : : /*
1894 : : * It's possible that another backend has extended the heap,
1895 : : * initialized the page, and then failed to WAL-log the page due
1896 : : * to an ERROR. Since heap extension is not WAL-logged, recovery
1897 : : * might try to replay our record setting the page all-visible and
1898 : : * find that the page isn't initialized, which will cause a PANIC.
1899 : : * To prevent that, check whether the page has been previously
1900 : : * WAL-logged, and if not, do that now.
1901 : : */
1902 [ # # # # : 0 : if (RelationNeedsWAL(vacrel->rel) &&
# # # # #
# ]
1903 : 0 : PageGetLSN(page) == InvalidXLogRecPtr)
1904 : 0 : log_newpage_buffer(buf, true);
1905 : :
1906 : 0 : PageSetAllVisible(page);
124 melanieplageman@gmai 1907 : 0 : visibilitymap_set(vacrel->rel, blkno, buf,
1908 : : InvalidXLogRecPtr,
1909 : : vmbuffer, InvalidTransactionId,
1910 : : VISIBILITYMAP_ALL_VISIBLE |
1911 : : VISIBILITYMAP_ALL_FROZEN);
1355 pg@bowt.ie 1912 [ # # ]: 0 : END_CRIT_SECTION();
1913 : :
1914 : : /* Count the newly all-frozen pages for logging */
124 melanieplageman@gmai 1915 : 0 : vacrel->vm_new_visible_pages++;
1916 : 0 : vacrel->vm_new_visible_frozen_pages++;
1917 : : }
1918 : :
1355 pg@bowt.ie 1919 :CBC 27 : freespace = PageGetHeapFreeSpace(page);
1920 : 27 : UnlockReleaseBuffer(buf);
1921 : 27 : RecordPageWithFreeSpace(vacrel->rel, blkno, freespace);
1922 : 27 : return true;
1923 : : }
1924 : :
1925 : : /* page isn't new or empty -- keep lock and pin */
1926 : 69382 : return false;
1927 : : }
1928 : :
1929 : : /* qsort comparator for sorting OffsetNumbers */
1930 : : static int
573 heikki.linnakangas@i 1931 : 3582566 : cmpOffsetNumbers(const void *a, const void *b)
1932 : : {
1933 : 3582566 : return pg_cmp_u16(*(const OffsetNumber *) a, *(const OffsetNumber *) b);
1934 : : }
1935 : :
1936 : : /*
1937 : : * lazy_scan_prune() -- lazy_scan_heap() pruning and freezing.
1938 : : *
1939 : : * Caller must hold pin and buffer cleanup lock on the buffer.
1940 : : *
1941 : : * vmbuffer is the buffer containing the VM block with visibility information
1942 : : * for the heap block, blkno. all_visible_according_to_vm is the saved
1943 : : * visibility status of the heap block looked up earlier by the caller. We
1944 : : * won't rely entirely on this status, as it may be out of date.
1945 : : *
1946 : : * *has_lpdead_items is set to true or false depending on whether, upon return
1947 : : * from this function, any LP_DEAD items are still present on the page.
1948 : : *
1949 : : * *vm_page_frozen is set to true if the page is newly set all-frozen in the
1950 : : * VM. The caller currently only uses this for determining whether an eagerly
1951 : : * scanned page was successfully set all-frozen.
1952 : : *
1953 : : * Returns the number of tuples deleted from the page during HOT pruning.
1954 : : */
1955 : : static int
1666 pg@bowt.ie 1956 : 69369 : lazy_scan_prune(LVRelState *vacrel,
1957 : : Buffer buf,
1958 : : BlockNumber blkno,
1959 : : Page page,
1960 : : Buffer vmbuffer,
1961 : : bool all_visible_according_to_vm,
1962 : : bool *has_lpdead_items,
1963 : : bool *vm_page_frozen)
1964 : : {
1965 : 69369 : Relation rel = vacrel->rel;
1966 : : PruneFreezeResult presult;
574 heikki.linnakangas@i 1967 : 69369 : int prune_options = 0;
1968 : :
1355 pg@bowt.ie 1969 [ - + ]: 69369 : Assert(BufferGetBlockNumber(buf) == blkno);
1970 : :
1971 : : /*
1972 : : * Prune all HOT-update chains and potentially freeze tuples on this page.
1973 : : *
1974 : : * If the relation has no indexes, we can immediately mark would-be dead
1975 : : * items LP_UNUSED.
1976 : : *
1977 : : * The number of tuples removed from the page is returned in
1978 : : * presult.ndeleted. It should not be confused with presult.lpdead_items;
1979 : : * presult.lpdead_items's final value can be thought of as the number of
1980 : : * tuples that were deleted from indexes.
1981 : : *
1982 : : * We will update the VM after collecting LP_DEAD items and freezing
1983 : : * tuples. Pruning will have determined whether or not the page is
1984 : : * all-visible.
1985 : : */
573 heikki.linnakangas@i 1986 : 69369 : prune_options = HEAP_PAGE_PRUNE_FREEZE;
1987 [ + + ]: 69369 : if (vacrel->nindexes == 0)
1988 : 5820 : prune_options |= HEAP_PAGE_PRUNE_MARK_UNUSED_NOW;
1989 : :
1990 : 69369 : heap_page_prune_and_freeze(rel, buf, vacrel->vistest, prune_options,
1991 : : &vacrel->cutoffs, &presult, PRUNE_VACUUM_SCAN,
1992 : : &vacrel->offnum,
1993 : : &vacrel->NewRelfrozenXid, &vacrel->NewRelminMxid);
1994 : :
1995 [ - + ]: 69369 : Assert(MultiXactIdIsValid(vacrel->NewRelminMxid));
1996 [ - + ]: 69369 : Assert(TransactionIdIsValid(vacrel->NewRelfrozenXid));
1997 : :
1998 [ + + ]: 69369 : if (presult.nfrozen > 0)
1999 : : {
2000 : : /*
2001 : : * We don't increment the new_frozen_tuple_pages instrumentation
2002 : : * counter when nfrozen == 0, since it only counts pages with newly
2003 : : * frozen tuples (don't confuse that with pages newly set all-frozen
2004 : : * in VM).
2005 : : */
315 melanieplageman@gmai 2006 : 20951 : vacrel->new_frozen_tuple_pages++;
2007 : : }
2008 : :
2009 : : /*
2010 : : * VACUUM will call heap_page_is_all_visible() during the second pass over
2011 : : * the heap to determine all_visible and all_frozen for the page -- this
2012 : : * is a specialized version of the logic from this function. Now that
2013 : : * we've finished pruning and freezing, make sure that we're in total
2014 : : * agreement with heap_page_is_all_visible() using an assertion.
2015 : : */
2016 : : #ifdef USE_ASSERT_CHECKING
2017 : : /* Note that all_frozen value does not matter when !all_visible */
573 heikki.linnakangas@i 2018 [ + + ]: 69369 : if (presult.all_visible)
2019 : : {
2020 : : TransactionId debug_cutoff;
2021 : : bool debug_all_frozen;
2022 : :
2023 [ - + ]: 49579 : Assert(presult.lpdead_items == 0);
2024 : :
14 melanieplageman@gmai 2025 [ - + ]:GNC 49579 : if (!heap_page_is_all_visible(vacrel->rel, buf,
2026 : : vacrel->cutoffs.OldestXmin, &debug_all_frozen,
2027 : : &debug_cutoff, &vacrel->offnum))
1666 pg@bowt.ie 2028 :UBC 0 : Assert(false);
2029 : :
573 heikki.linnakangas@i 2030 [ - + ]:CBC 49579 : Assert(presult.all_frozen == debug_all_frozen);
2031 : :
649 rhaas@postgresql.org 2032 [ + + - + ]: 49579 : Assert(!TransactionIdIsValid(debug_cutoff) ||
2033 : : debug_cutoff == presult.vm_conflict_horizon);
2034 : : }
2035 : : #endif
2036 : :
2037 : : /*
2038 : : * Now save details of the LP_DEAD items from the page in vacrel
2039 : : */
573 heikki.linnakangas@i 2040 [ + + ]: 69369 : if (presult.lpdead_items > 0)
2041 : : {
1666 pg@bowt.ie 2042 : 15424 : vacrel->lpdead_item_pages++;
2043 : :
2044 : : /*
2045 : : * deadoffsets are collected incrementally in
2046 : : * heap_page_prune_and_freeze() as each dead line pointer is recorded,
2047 : : * with an indeterminate order, but dead_items_add requires them to be
2048 : : * sorted.
2049 : : */
573 heikki.linnakangas@i 2050 : 15424 : qsort(presult.deadoffsets, presult.lpdead_items, sizeof(OffsetNumber),
2051 : : cmpOffsetNumbers);
2052 : :
2053 : 15424 : dead_items_add(vacrel, blkno, presult.deadoffsets, presult.lpdead_items);
2054 : : }
2055 : :
2056 : : /* Finally, add page-local counts to whole-VACUUM counts */
761 rhaas@postgresql.org 2057 : 69369 : vacrel->tuples_deleted += presult.ndeleted;
573 heikki.linnakangas@i 2058 : 69369 : vacrel->tuples_frozen += presult.nfrozen;
2059 : 69369 : vacrel->lpdead_items += presult.lpdead_items;
2060 : 69369 : vacrel->live_tuples += presult.live_tuples;
2061 : 69369 : vacrel->recently_dead_tuples += presult.recently_dead_tuples;
2062 : :
2063 : : /* Can't truncate this page */
2064 [ + + ]: 69369 : if (presult.hastup)
656 rhaas@postgresql.org 2065 : 61507 : vacrel->nonempty_pages = blkno + 1;
2066 : :
2067 : : /* Did we find LP_DEAD items? */
573 heikki.linnakangas@i 2068 : 69369 : *has_lpdead_items = (presult.lpdead_items > 0);
2069 : :
2070 [ + + - + ]: 69369 : Assert(!presult.all_visible || !(*has_lpdead_items));
2071 : :
2072 : : /*
2073 : : * Handle setting visibility map bit based on information from the VM (as
2074 : : * of last heap_vac_scan_next_block() call), and from all_visible and
2075 : : * all_frozen variables
2076 : : */
2077 [ + + + + ]: 69369 : if (!all_visible_according_to_vm && presult.all_visible)
649 rhaas@postgresql.org 2078 : 37680 : {
2079 : : uint8 old_vmbits;
2080 : 37680 : uint8 flags = VISIBILITYMAP_ALL_VISIBLE;
2081 : :
573 heikki.linnakangas@i 2082 [ + + ]: 37680 : if (presult.all_frozen)
2083 : : {
2084 [ - + ]: 26896 : Assert(!TransactionIdIsValid(presult.vm_conflict_horizon));
649 rhaas@postgresql.org 2085 : 26896 : flags |= VISIBILITYMAP_ALL_FROZEN;
2086 : : }
2087 : :
2088 : : /*
2089 : : * It should never be the case that the visibility map page is set
2090 : : * while the page-level bit is clear, but the reverse is allowed (if
2091 : : * checksums are not enabled). Regardless, set both bits so that we
2092 : : * get back in sync.
2093 : : *
2094 : : * NB: If the heap page is all-visible but the VM bit is not set, we
2095 : : * don't need to dirty the heap page. However, if checksums are
2096 : : * enabled, we do need to make sure that the heap page is dirtied
2097 : : * before passing it to visibilitymap_set(), because it may be logged.
2098 : : * Given that this situation should only happen in rare cases after a
2099 : : * crash, it is not worth optimizing.
2100 : : */
2101 : 37680 : PageSetAllVisible(page);
2102 : 37680 : MarkBufferDirty(buf);
315 melanieplageman@gmai 2103 : 37680 : old_vmbits = visibilitymap_set(vacrel->rel, blkno, buf,
2104 : : InvalidXLogRecPtr,
2105 : : vmbuffer, presult.vm_conflict_horizon,
2106 : : flags);
2107 : :
2108 : : /*
2109 : : * If the page wasn't already set all-visible and/or all-frozen in the
2110 : : * VM, count it as newly set for logging.
2111 : : */
2112 [ + - ]: 37680 : if ((old_vmbits & VISIBILITYMAP_ALL_VISIBLE) == 0)
2113 : : {
2114 : 37680 : vacrel->vm_new_visible_pages++;
2115 [ + + ]: 37680 : if (presult.all_frozen)
2116 : : {
2117 : 26896 : vacrel->vm_new_visible_frozen_pages++;
259 2118 : 26896 : *vm_page_frozen = true;
2119 : : }
2120 : : }
315 melanieplageman@gmai 2121 [ # # ]:UBC 0 : else if ((old_vmbits & VISIBILITYMAP_ALL_FROZEN) == 0 &&
2122 [ # # ]: 0 : presult.all_frozen)
2123 : : {
2124 : 0 : vacrel->vm_new_frozen_pages++;
259 2125 : 0 : *vm_page_frozen = true;
2126 : : }
2127 : : }
2128 : :
2129 : : /*
2130 : : * As of PostgreSQL 9.2, the visibility map bit should never be set if the
2131 : : * page-level bit is clear. However, it's possible that the bit got
2132 : : * cleared after heap_vac_scan_next_block() was called, so we must recheck
2133 : : * with buffer lock before concluding that the VM is corrupt.
2134 : : */
649 rhaas@postgresql.org 2135 [ + + - + :CBC 31689 : else if (all_visible_according_to_vm && !PageIsAllVisible(page) &&
- - ]
649 rhaas@postgresql.org 2136 :UBC 0 : visibilitymap_get_status(vacrel->rel, blkno, &vmbuffer) != 0)
2137 : : {
50 melanieplageman@gmai 2138 [ # # ]:UNC 0 : ereport(WARNING,
2139 : : (errcode(ERRCODE_DATA_CORRUPTED),
2140 : : errmsg("page is not marked all-visible but visibility map bit is set in relation \"%s\" page %u",
2141 : : vacrel->relname, blkno)));
2142 : :
649 rhaas@postgresql.org 2143 :UBC 0 : visibilitymap_clear(vacrel->rel, blkno, vmbuffer,
2144 : : VISIBILITYMAP_VALID_BITS);
2145 : : }
2146 : :
2147 : : /*
2148 : : * It's possible for the value returned by
2149 : : * GetOldestNonRemovableTransactionId() to move backwards, so it's not
2150 : : * wrong for us to see tuples that appear to not be visible to everyone
2151 : : * yet, while PD_ALL_VISIBLE is already set. The real safe xmin value
2152 : : * never moves backwards, but GetOldestNonRemovableTransactionId() is
2153 : : * conservative and sometimes returns a value that's unnecessarily small,
2154 : : * so if we see that contradiction it just means that the tuples that we
2155 : : * think are not visible to everyone yet actually are, and the
2156 : : * PD_ALL_VISIBLE flag is correct.
2157 : : *
2158 : : * There should never be LP_DEAD items on a page with PD_ALL_VISIBLE set,
2159 : : * however.
2160 : : */
573 heikki.linnakangas@i 2161 [ + + - + ]:CBC 31689 : else if (presult.lpdead_items > 0 && PageIsAllVisible(page))
2162 : : {
50 melanieplageman@gmai 2163 [ # # ]:UNC 0 : ereport(WARNING,
2164 : : (errcode(ERRCODE_DATA_CORRUPTED),
2165 : : errmsg("page containing LP_DEAD items is marked as all-visible in relation \"%s\" page %u",
2166 : : vacrel->relname, blkno)));
2167 : :
649 rhaas@postgresql.org 2168 :UBC 0 : PageClearAllVisible(page);
2169 : 0 : MarkBufferDirty(buf);
2170 : 0 : visibilitymap_clear(vacrel->rel, blkno, vmbuffer,
2171 : : VISIBILITYMAP_VALID_BITS);
2172 : : }
2173 : :
2174 : : /*
2175 : : * If the all-visible page is all-frozen but not marked as such yet, mark
2176 : : * it as all-frozen. Note that all_frozen is only valid if all_visible is
2177 : : * true, so we must check both all_visible and all_frozen.
2178 : : */
573 heikki.linnakangas@i 2179 [ + + + - ]:CBC 31689 : else if (all_visible_according_to_vm && presult.all_visible &&
2180 [ + + + + ]: 11899 : presult.all_frozen && !VM_ALL_FROZEN(vacrel->rel, blkno, &vmbuffer))
2181 : : {
2182 : : uint8 old_vmbits;
2183 : :
2184 : : /*
2185 : : * Avoid relying on all_visible_according_to_vm as a proxy for the
2186 : : * page-level PD_ALL_VISIBLE bit being set, since it might have become
2187 : : * stale -- even when all_visible is set
2188 : : */
649 rhaas@postgresql.org 2189 [ - + ]: 21 : if (!PageIsAllVisible(page))
2190 : : {
649 rhaas@postgresql.org 2191 :UBC 0 : PageSetAllVisible(page);
2192 : 0 : MarkBufferDirty(buf);
2193 : : }
2194 : :
2195 : : /*
2196 : : * Set the page all-frozen (and all-visible) in the VM.
2197 : : *
2198 : : * We can pass InvalidTransactionId as our cutoff_xid, since a
2199 : : * snapshotConflictHorizon sufficient to make everything safe for REDO
2200 : : * was logged when the page's tuples were frozen.
2201 : : */
573 heikki.linnakangas@i 2202 [ - + ]:CBC 21 : Assert(!TransactionIdIsValid(presult.vm_conflict_horizon));
315 melanieplageman@gmai 2203 : 21 : old_vmbits = visibilitymap_set(vacrel->rel, blkno, buf,
2204 : : InvalidXLogRecPtr,
2205 : : vmbuffer, InvalidTransactionId,
2206 : : VISIBILITYMAP_ALL_VISIBLE |
2207 : : VISIBILITYMAP_ALL_FROZEN);
2208 : :
2209 : : /*
2210 : : * The page was likely already set all-visible in the VM. However,
2211 : : * there is a small chance that it was modified sometime between
2212 : : * setting all_visible_according_to_vm and checking the visibility
2213 : : * during pruning. Check the return value of old_vmbits anyway to
2214 : : * ensure the visibility map counters used for logging are accurate.
2215 : : */
2216 [ - + ]: 21 : if ((old_vmbits & VISIBILITYMAP_ALL_VISIBLE) == 0)
2217 : : {
315 melanieplageman@gmai 2218 :UBC 0 : vacrel->vm_new_visible_pages++;
2219 : 0 : vacrel->vm_new_visible_frozen_pages++;
259 2220 : 0 : *vm_page_frozen = true;
2221 : : }
2222 : :
2223 : : /*
2224 : : * We already checked that the page was not set all-frozen in the VM
2225 : : * above, so we don't need to test the value of old_vmbits.
2226 : : */
2227 : : else
2228 : : {
315 melanieplageman@gmai 2229 :CBC 21 : vacrel->vm_new_frozen_pages++;
259 2230 : 21 : *vm_page_frozen = true;
2231 : : }
2232 : : }
2233 : :
119 msawada@postgresql.o 2234 : 69369 : return presult.ndeleted;
2235 : : }
2236 : :
2237 : : /*
2238 : : * lazy_scan_noprune() -- lazy_scan_prune() without pruning or freezing
2239 : : *
2240 : : * Caller need only hold a pin and share lock on the buffer, unlike
2241 : : * lazy_scan_prune, which requires a full cleanup lock. While pruning isn't
2242 : : * performed here, it's quite possible that an earlier opportunistic pruning
2243 : : * operation left LP_DEAD items behind. We'll at least collect any such items
2244 : : * in dead_items for removal from indexes.
2245 : : *
2246 : : * For aggressive VACUUM callers, we may return false to indicate that a full
2247 : : * cleanup lock is required for processing by lazy_scan_prune. This is only
2248 : : * necessary when the aggressive VACUUM needs to freeze some tuple XIDs from
2249 : : * one or more tuples on the page. We always return true for non-aggressive
2250 : : * callers.
2251 : : *
2252 : : * If this function returns true, *has_lpdead_items gets set to true or false
2253 : : * depending on whether, upon return from this function, any LP_DEAD items are
2254 : : * present on the page. If this function returns false, *has_lpdead_items
2255 : : * is not updated.
2256 : : */
2257 : : static bool
1355 pg@bowt.ie 2258 : 92 : lazy_scan_noprune(LVRelState *vacrel,
2259 : : Buffer buf,
2260 : : BlockNumber blkno,
2261 : : Page page,
2262 : : bool *has_lpdead_items)
2263 : : {
2264 : : OffsetNumber offnum,
2265 : : maxoff;
2266 : : int lpdead_items,
2267 : : live_tuples,
2268 : : recently_dead_tuples,
2269 : : missed_dead_tuples;
2270 : : bool hastup;
2271 : : HeapTupleHeader tupleheader;
1035 2272 : 92 : TransactionId NoFreezePageRelfrozenXid = vacrel->NewRelfrozenXid;
2273 : 92 : MultiXactId NoFreezePageRelminMxid = vacrel->NewRelminMxid;
2274 : : OffsetNumber deadoffsets[MaxHeapTuplesPerPage];
2275 : :
1355 2276 [ - + ]: 92 : Assert(BufferGetBlockNumber(buf) == blkno);
2277 : :
656 rhaas@postgresql.org 2278 : 92 : hastup = false; /* for now */
2279 : :
1355 pg@bowt.ie 2280 : 92 : lpdead_items = 0;
2281 : 92 : live_tuples = 0;
2282 : 92 : recently_dead_tuples = 0;
2283 : 92 : missed_dead_tuples = 0;
2284 : :
2285 : 92 : maxoff = PageGetMaxOffsetNumber(page);
2286 : 92 : for (offnum = FirstOffsetNumber;
2287 [ + + ]: 880 : offnum <= maxoff;
2288 : 788 : offnum = OffsetNumberNext(offnum))
2289 : : {
2290 : : ItemId itemid;
2291 : : HeapTupleData tuple;
2292 : :
2293 : 867 : vacrel->offnum = offnum;
2294 : 867 : itemid = PageGetItemId(page, offnum);
2295 : :
2296 [ + + ]: 867 : if (!ItemIdIsUsed(itemid))
1355 pg@bowt.ie 2297 :GBC 447 : continue;
2298 : :
1355 pg@bowt.ie 2299 [ + + ]:CBC 662 : if (ItemIdIsRedirected(itemid))
2300 : : {
656 rhaas@postgresql.org 2301 :GBC 17 : hastup = true;
1355 pg@bowt.ie 2302 : 17 : continue;
2303 : : }
2304 : :
1355 pg@bowt.ie 2305 [ + + ]:CBC 645 : if (ItemIdIsDead(itemid))
2306 : : {
2307 : : /*
2308 : : * Deliberately don't set hastup=true here. See same point in
2309 : : * lazy_scan_prune for an explanation.
2310 : : */
1355 pg@bowt.ie 2311 :GBC 225 : deadoffsets[lpdead_items++] = offnum;
2312 : 225 : continue;
2313 : : }
2314 : :
656 rhaas@postgresql.org 2315 :CBC 420 : hastup = true; /* page prevents rel truncation */
1355 pg@bowt.ie 2316 : 420 : tupleheader = (HeapTupleHeader) PageGetItem(page, itemid);
1035 2317 [ + + ]: 420 : if (heap_tuple_should_freeze(tupleheader, &vacrel->cutoffs,
2318 : : &NoFreezePageRelfrozenXid,
2319 : : &NoFreezePageRelminMxid))
2320 : : {
2321 : : /* Tuple with XID < FreezeLimit (or MXID < MultiXactCutoff) */
1355 2322 [ + + ]: 143 : if (vacrel->aggressive)
2323 : : {
2324 : : /*
2325 : : * Aggressive VACUUMs must always be able to advance rel's
2326 : : * relfrozenxid to a value >= FreezeLimit (and be able to
2327 : : * advance rel's relminmxid to a value >= MultiXactCutoff).
2328 : : * The ongoing aggressive VACUUM won't be able to do that
2329 : : * unless it can freeze an XID (or MXID) from this tuple now.
2330 : : *
2331 : : * The only safe option is to have caller perform processing
2332 : : * of this page using lazy_scan_prune. Caller might have to
2333 : : * wait a while for a cleanup lock, but it can't be helped.
2334 : : */
2335 : 79 : vacrel->offnum = InvalidOffsetNumber;
2336 : 79 : return false;
2337 : : }
2338 : :
2339 : : /*
2340 : : * Non-aggressive VACUUMs are under no obligation to advance
2341 : : * relfrozenxid (even by one XID). We can be much laxer here.
2342 : : *
2343 : : * Currently we always just accept an older final relfrozenxid
2344 : : * and/or relminmxid value. We never make caller wait or work a
2345 : : * little harder, even when it likely makes sense to do so.
2346 : : */
2347 : : }
2348 : :
2349 : 341 : ItemPointerSet(&(tuple.t_self), blkno, offnum);
2350 : 341 : tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
2351 : 341 : tuple.t_len = ItemIdGetLength(itemid);
2352 : 341 : tuple.t_tableOid = RelationGetRelid(vacrel->rel);
2353 : :
1041 2354 [ + + + + : 341 : switch (HeapTupleSatisfiesVacuum(&tuple, vacrel->cutoffs.OldestXmin,
- ]
2355 : : buf))
2356 : : {
1355 2357 : 274 : case HEAPTUPLE_DELETE_IN_PROGRESS:
2358 : : case HEAPTUPLE_LIVE:
2359 : :
2360 : : /*
2361 : : * Count both cases as live, just like lazy_scan_prune
2362 : : */
2363 : 274 : live_tuples++;
2364 : :
2365 : 274 : break;
2366 : 14 : case HEAPTUPLE_DEAD:
2367 : :
2368 : : /*
2369 : : * There is some useful work for pruning to do, that won't be
2370 : : * done due to failure to get a cleanup lock.
2371 : : */
2372 : 14 : missed_dead_tuples++;
2373 : 14 : break;
2374 : 50 : case HEAPTUPLE_RECENTLY_DEAD:
2375 : :
2376 : : /*
2377 : : * Count in recently_dead_tuples, just like lazy_scan_prune
2378 : : */
2379 : 50 : recently_dead_tuples++;
2380 : 50 : break;
1355 pg@bowt.ie 2381 :GBC 3 : case HEAPTUPLE_INSERT_IN_PROGRESS:
2382 : :
2383 : : /*
2384 : : * Do not count these rows as live, just like lazy_scan_prune
2385 : : */
2386 : 3 : break;
1355 pg@bowt.ie 2387 :UBC 0 : default:
2388 [ # # ]: 0 : elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
2389 : : break;
2390 : : }
2391 : : }
2392 : :
1355 pg@bowt.ie 2393 :CBC 13 : vacrel->offnum = InvalidOffsetNumber;
2394 : :
2395 : : /*
2396 : : * By here we know for sure that caller can put off freezing and pruning
2397 : : * this particular page until the next VACUUM. Remember its details now.
2398 : : * (lazy_scan_prune expects a clean slate, so we have to do this last.)
2399 : : */
1035 2400 : 13 : vacrel->NewRelfrozenXid = NoFreezePageRelfrozenXid;
2401 : 13 : vacrel->NewRelminMxid = NoFreezePageRelminMxid;
2402 : :
2403 : : /* Save any LP_DEAD items found on the page in dead_items */
1355 2404 [ - + ]: 13 : if (vacrel->nindexes == 0)
2405 : : {
2406 : : /* Using one-pass strategy (since table has no indexes) */
1355 pg@bowt.ie 2407 [ # # ]:LBC (1) : if (lpdead_items > 0)
2408 : : {
2409 : : /*
2410 : : * Perfunctory handling for the corner case where a single pass
2411 : : * strategy VACUUM cannot get a cleanup lock, and it turns out
2412 : : * that there is one or more LP_DEAD items: just count the LP_DEAD
2413 : : * items as missed_dead_tuples instead. (This is a bit dishonest,
2414 : : * but it beats having to maintain specialized heap vacuuming code
2415 : : * forever, for vanishingly little benefit.)
2416 : : */
656 rhaas@postgresql.org 2417 :UBC 0 : hastup = true;
1355 pg@bowt.ie 2418 : 0 : missed_dead_tuples += lpdead_items;
2419 : : }
2420 : : }
651 rhaas@postgresql.org 2421 [ + + ]:CBC 13 : else if (lpdead_items > 0)
2422 : : {
2423 : : /*
2424 : : * Page has LP_DEAD items, and so any references/TIDs that remain in
2425 : : * indexes will be deleted during index vacuuming (and then marked
2426 : : * LP_UNUSED in the heap)
2427 : : */
1355 pg@bowt.ie 2428 :GBC 5 : vacrel->lpdead_item_pages++;
2429 : :
574 msawada@postgresql.o 2430 : 5 : dead_items_add(vacrel, blkno, deadoffsets, lpdead_items);
2431 : :
1355 pg@bowt.ie 2432 : 5 : vacrel->lpdead_items += lpdead_items;
2433 : : }
2434 : :
2435 : : /*
2436 : : * Finally, add relevant page-local counts to whole-VACUUM counts
2437 : : */
1342 pg@bowt.ie 2438 :CBC 13 : vacrel->live_tuples += live_tuples;
1355 2439 : 13 : vacrel->recently_dead_tuples += recently_dead_tuples;
2440 : 13 : vacrel->missed_dead_tuples += missed_dead_tuples;
2441 [ + + ]: 13 : if (missed_dead_tuples > 0)
2442 : 4 : vacrel->missed_dead_pages++;
2443 : :
2444 : : /* Can't truncate this page */
656 rhaas@postgresql.org 2445 [ + - ]: 13 : if (hastup)
2446 : 13 : vacrel->nonempty_pages = blkno + 1;
2447 : :
2448 : : /* Did we find LP_DEAD items? */
651 2449 : 13 : *has_lpdead_items = (lpdead_items > 0);
2450 : :
2451 : : /* Caller won't need to call lazy_scan_prune with same page */
1355 pg@bowt.ie 2452 : 13 : return true;
2453 : : }
2454 : :
2455 : : /*
2456 : : * Main entry point for index vacuuming and heap vacuuming.
2457 : : *
2458 : : * Removes items collected in dead_items from table's indexes, then marks the
2459 : : * same items LP_UNUSED in the heap. See the comments above lazy_scan_heap
2460 : : * for full details.
2461 : : *
2462 : : * Also empties dead_items, freeing up space for later TIDs.
2463 : : *
2464 : : * We may choose to bypass index vacuuming at this point, though only when the
2465 : : * ongoing VACUUM operation will definitely only have one index scan/round of
2466 : : * index vacuuming.
2467 : : */
2468 : : static void
1593 2469 : 690 : lazy_vacuum(LVRelState *vacrel)
2470 : : {
2471 : : bool bypass;
2472 : :
2473 : : /* Should not end up here with no indexes */
1666 2474 [ - + ]: 690 : Assert(vacrel->nindexes > 0);
2475 [ - + ]: 690 : Assert(vacrel->lpdead_item_pages > 0);
2476 : :
2477 [ + + ]: 690 : if (!vacrel->do_index_vacuuming)
2478 : : {
2479 [ - + ]: 7 : Assert(!vacrel->do_index_cleanup);
574 msawada@postgresql.o 2480 : 7 : dead_items_reset(vacrel);
1666 pg@bowt.ie 2481 : 7 : return;
2482 : : }
2483 : :
2484 : : /*
2485 : : * Consider bypassing index vacuuming (and heap vacuuming) entirely.
2486 : : *
2487 : : * We currently only do this in cases where the number of LP_DEAD items
2488 : : * for the entire VACUUM operation is close to zero. This avoids sharp
2489 : : * discontinuities in the duration and overhead of successive VACUUM
2490 : : * operations that run against the same table with a fixed workload.
2491 : : * Ideally, successive VACUUM operations will behave as if there are
2492 : : * exactly zero LP_DEAD items in cases where there are close to zero.
2493 : : *
2494 : : * This is likely to be helpful with a table that is continually affected
2495 : : * by UPDATEs that can mostly apply the HOT optimization, but occasionally
2496 : : * have small aberrations that lead to just a few heap pages retaining
2497 : : * only one or two LP_DEAD items. This is pretty common; even when the
2498 : : * DBA goes out of their way to make UPDATEs use HOT, it is practically
2499 : : * impossible to predict whether HOT will be applied in 100% of cases.
2500 : : * It's far easier to ensure that 99%+ of all UPDATEs against a table use
2501 : : * HOT through careful tuning.
2502 : : */
1593 2503 : 683 : bypass = false;
2504 [ + + + - ]: 683 : if (vacrel->consider_bypass_optimization && vacrel->rel_pages > 0)
2505 : : {
2506 : : BlockNumber threshold;
2507 : :
1665 2508 [ - + ]: 653 : Assert(vacrel->num_index_scans == 0);
574 msawada@postgresql.o 2509 [ - + ]: 653 : Assert(vacrel->lpdead_items == vacrel->dead_items_info->num_items);
1665 pg@bowt.ie 2510 [ - + ]: 653 : Assert(vacrel->do_index_vacuuming);
2511 [ - + ]: 653 : Assert(vacrel->do_index_cleanup);
2512 : :
2513 : : /*
2514 : : * This crossover point at which we'll start to do index vacuuming is
2515 : : * expressed as a percentage of the total number of heap pages in the
2516 : : * table that are known to have at least one LP_DEAD item. This is
2517 : : * much more important than the total number of LP_DEAD items, since
2518 : : * it's a proxy for the number of heap pages whose visibility map bits
2519 : : * cannot be set on account of bypassing index and heap vacuuming.
2520 : : *
2521 : : * We apply one further precautionary test: the space currently used
2522 : : * to store the TIDs (TIDs that now all point to LP_DEAD items) must
2523 : : * not exceed 32MB. This limits the risk that we will bypass index
2524 : : * vacuuming again and again until eventually there is a VACUUM whose
2525 : : * dead_items space is not CPU cache resident.
2526 : : *
2527 : : * We don't take any special steps to remember the LP_DEAD items (such
2528 : : * as counting them in our final update to the stats system) when the
2529 : : * optimization is applied. Though the accounting used in analyze.c's
2530 : : * acquire_sample_rows() will recognize the same LP_DEAD items as dead
2531 : : * rows in its own stats report, that's okay. The discrepancy should
2532 : : * be negligible. If this optimization is ever expanded to cover more
2533 : : * cases then this may need to be reconsidered.
2534 : : */
2535 : 653 : threshold = (double) vacrel->rel_pages * BYPASS_THRESHOLD_PAGES;
1593 2536 [ + + + - ]: 658 : bypass = (vacrel->lpdead_item_pages < threshold &&
270 tgl@sss.pgh.pa.us 2537 : 5 : TidStoreMemoryUsage(vacrel->dead_items) < 32 * 1024 * 1024);
2538 : : }
2539 : :
1593 pg@bowt.ie 2540 [ + + ]: 683 : if (bypass)
2541 : : {
2542 : : /*
2543 : : * There are almost zero TIDs. Behave as if there were precisely
2544 : : * zero: bypass index vacuuming, but do index cleanup.
2545 : : *
2546 : : * We expect that the ongoing VACUUM operation will finish very
2547 : : * quickly, so there is no point in considering speeding up as a
2548 : : * failsafe against wraparound failure. (Index cleanup is expected to
2549 : : * finish very quickly in cases where there were no ambulkdelete()
2550 : : * calls.)
2551 : : */
1665 2552 : 5 : vacrel->do_index_vacuuming = false;
2553 : : }
2554 [ + - ]: 678 : else if (lazy_vacuum_all_indexes(vacrel))
2555 : : {
2556 : : /*
2557 : : * We successfully completed a round of index vacuuming. Do related
2558 : : * heap vacuuming now.
2559 : : */
2560 : 678 : lazy_vacuum_heap_rel(vacrel);
2561 : : }
2562 : : else
2563 : : {
2564 : : /*
2565 : : * Failsafe case.
2566 : : *
2567 : : * We attempted index vacuuming, but didn't finish a full round/full
2568 : : * index scan. This happens when relfrozenxid or relminmxid is too
2569 : : * far in the past.
2570 : : *
2571 : : * From this point on the VACUUM operation will do no further index
2572 : : * vacuuming or heap vacuuming. This VACUUM operation won't end up
2573 : : * back here again.
2574 : : */
935 dgustafsson@postgres 2575 [ # # ]:UBC 0 : Assert(VacuumFailsafeActive);
2576 : : }
2577 : :
2578 : : /*
2579 : : * Forget the LP_DEAD items that we just vacuumed (or just decided to not
2580 : : * vacuum)
2581 : : */
574 msawada@postgresql.o 2582 :CBC 683 : dead_items_reset(vacrel);
2583 : : }
2584 : :
2585 : : /*
2586 : : * lazy_vacuum_all_indexes() -- Main entry for index vacuuming
2587 : : *
2588 : : * Returns true in the common case when all indexes were successfully
2589 : : * vacuumed. Returns false in rare cases where we determined that the ongoing
2590 : : * VACUUM operation is at risk of taking too long to finish, leading to
2591 : : * wraparound failure.
2592 : : */
2593 : : static bool
1667 pg@bowt.ie 2594 : 678 : lazy_vacuum_all_indexes(LVRelState *vacrel)
2595 : : {
1665 2596 : 678 : bool allindexes = true;
1041 2597 : 678 : double old_live_tuples = vacrel->rel->rd_rel->reltuples;
840 msawada@postgresql.o 2598 : 678 : const int progress_start_index[] = {
2599 : : PROGRESS_VACUUM_PHASE,
2600 : : PROGRESS_VACUUM_INDEXES_TOTAL
2601 : : };
2602 : 678 : const int progress_end_index[] = {
2603 : : PROGRESS_VACUUM_INDEXES_TOTAL,
2604 : : PROGRESS_VACUUM_INDEXES_PROCESSED,
2605 : : PROGRESS_VACUUM_NUM_INDEX_VACUUMS
2606 : : };
2607 : : int64 progress_start_val[2];
2608 : : int64 progress_end_val[3];
2609 : :
1667 pg@bowt.ie 2610 [ - + ]: 678 : Assert(vacrel->nindexes > 0);
1666 2611 [ - + ]: 678 : Assert(vacrel->do_index_vacuuming);
2612 [ - + ]: 678 : Assert(vacrel->do_index_cleanup);
2613 : :
2614 : : /* Precheck for XID wraparound emergencies */
1665 2615 [ - + ]: 678 : if (lazy_check_wraparound_failsafe(vacrel))
2616 : : {
2617 : : /* Wraparound emergency -- don't even start an index scan */
1665 pg@bowt.ie 2618 :UBC 0 : return false;
2619 : : }
2620 : :
2621 : : /*
2622 : : * Report that we are now vacuuming indexes and the number of indexes to
2623 : : * vacuum.
2624 : : */
840 msawada@postgresql.o 2625 :CBC 678 : progress_start_val[0] = PROGRESS_VACUUM_PHASE_VACUUM_INDEX;
2626 : 678 : progress_start_val[1] = vacrel->nindexes;
2627 : 678 : pgstat_progress_update_multi_param(2, progress_start_index, progress_start_val);
2628 : :
1667 pg@bowt.ie 2629 [ + + ]: 678 : if (!ParallelVacuumIsActive(vacrel))
2630 : : {
2631 [ + + ]: 1921 : for (int idx = 0; idx < vacrel->nindexes; idx++)
2632 : : {
2633 : 1264 : Relation indrel = vacrel->indrels[idx];
2634 : 1264 : IndexBulkDeleteResult *istat = vacrel->indstats[idx];
2635 : :
1041 2636 : 1264 : vacrel->indstats[idx] = lazy_vacuum_one_index(indrel, istat,
2637 : : old_live_tuples,
2638 : : vacrel);
2639 : :
2640 : : /* Report the number of indexes vacuumed */
840 msawada@postgresql.o 2641 : 1264 : pgstat_progress_update_param(PROGRESS_VACUUM_INDEXES_PROCESSED,
2642 : 1264 : idx + 1);
2643 : :
1665 pg@bowt.ie 2644 [ - + ]: 1264 : if (lazy_check_wraparound_failsafe(vacrel))
2645 : : {
2646 : : /* Wraparound emergency -- end current index scan */
1665 pg@bowt.ie 2647 :UBC 0 : allindexes = false;
2648 : 0 : break;
2649 : : }
2650 : : }
2651 : : }
2652 : : else
2653 : : {
2654 : : /* Outsource everything to parallel variant */
1041 pg@bowt.ie 2655 :CBC 21 : parallel_vacuum_bulkdel_all_indexes(vacrel->pvs, old_live_tuples,
2656 : : vacrel->num_index_scans);
2657 : :
2658 : : /*
2659 : : * Do a postcheck to consider applying wraparound failsafe now. Note
2660 : : * that parallel VACUUM only gets the precheck and this postcheck.
2661 : : */
1665 2662 [ - + ]: 21 : if (lazy_check_wraparound_failsafe(vacrel))
1665 pg@bowt.ie 2663 :UBC 0 : allindexes = false;
2664 : : }
2665 : :
2666 : : /*
2667 : : * We delete all LP_DEAD items from the first heap pass in all indexes on
2668 : : * each call here (except calls where we choose to do the failsafe). This
2669 : : * makes the next call to lazy_vacuum_heap_rel() safe (except in the event
2670 : : * of the failsafe triggering, which prevents the next call from taking
2671 : : * place).
2672 : : */
1666 pg@bowt.ie 2673 [ + + - + ]:CBC 678 : Assert(vacrel->num_index_scans > 0 ||
2674 : : vacrel->dead_items_info->num_items == vacrel->lpdead_items);
935 dgustafsson@postgres 2675 [ - + - - ]: 678 : Assert(allindexes || VacuumFailsafeActive);
2676 : :
2677 : : /*
2678 : : * Increase and report the number of index scans. Also, we reset
2679 : : * PROGRESS_VACUUM_INDEXES_TOTAL and PROGRESS_VACUUM_INDEXES_PROCESSED.
2680 : : *
2681 : : * We deliberately include the case where we started a round of bulk
2682 : : * deletes that we weren't able to finish due to the failsafe triggering.
2683 : : */
1667 pg@bowt.ie 2684 : 678 : vacrel->num_index_scans++;
840 msawada@postgresql.o 2685 : 678 : progress_end_val[0] = 0;
2686 : 678 : progress_end_val[1] = 0;
2687 : 678 : progress_end_val[2] = vacrel->num_index_scans;
2688 : 678 : pgstat_progress_update_multi_param(3, progress_end_index, progress_end_val);
2689 : :
1665 pg@bowt.ie 2690 : 678 : return allindexes;
2691 : : }
2692 : :
2693 : : /*
2694 : : * Read stream callback for vacuum's third phase (second pass over the heap).
2695 : : * Gets the next block from the TID store and returns it or InvalidBlockNumber
2696 : : * if there are no further blocks to vacuum.
2697 : : *
2698 : : * NB: Assumed to be safe to use with READ_STREAM_USE_BATCHING.
2699 : : */
2700 : : static BlockNumber
256 melanieplageman@gmai 2701 : 16055 : vacuum_reap_lp_read_stream_next(ReadStream *stream,
2702 : : void *callback_private_data,
2703 : : void *per_buffer_data)
2704 : : {
2705 : 16055 : TidStoreIter *iter = callback_private_data;
2706 : : TidStoreIterResult *iter_result;
2707 : :
2708 : 16055 : iter_result = TidStoreIterateNext(iter);
2709 [ + + ]: 16055 : if (iter_result == NULL)
2710 : 678 : return InvalidBlockNumber;
2711 : :
2712 : : /*
2713 : : * Save the TidStoreIterResult for later, so we can extract the offsets.
2714 : : * It is safe to copy the result, according to TidStoreIterateNext().
2715 : : */
2716 : 15377 : memcpy(per_buffer_data, iter_result, sizeof(*iter_result));
2717 : :
2718 : 15377 : return iter_result->blkno;
2719 : : }
2720 : :
2721 : : /*
2722 : : * lazy_vacuum_heap_rel() -- second pass over the heap for two pass strategy
2723 : : *
2724 : : * This routine marks LP_DEAD items in vacrel->dead_items as LP_UNUSED. Pages
2725 : : * that never had lazy_scan_prune record LP_DEAD items are not visited at all.
2726 : : *
2727 : : * We may also be able to truncate the line pointer array of the heap pages we
2728 : : * visit. If there is a contiguous group of LP_UNUSED items at the end of the
2729 : : * array, it can be reclaimed as free space. These LP_UNUSED items usually
2730 : : * start out as LP_DEAD items recorded by lazy_scan_prune (we set items from
2731 : : * each page to LP_UNUSED, and then consider if it's possible to truncate the
2732 : : * page's line pointer array).
2733 : : *
2734 : : * Note: the reason for doing this as a second pass is we cannot remove the
2735 : : * tuples until we've removed their index entries, and we want to process
2736 : : * index entry removal in batches as large as possible.
2737 : : */
2738 : : static void
1667 pg@bowt.ie 2739 : 678 : lazy_vacuum_heap_rel(LVRelState *vacrel)
2740 : : {
2741 : : ReadStream *stream;
1021 2742 : 678 : BlockNumber vacuumed_pages = 0;
4640 heikki.linnakangas@i 2743 : 678 : Buffer vmbuffer = InvalidBuffer;
2744 : : LVSavedErrInfo saved_err_info;
2745 : : TidStoreIter *iter;
2746 : :
1666 pg@bowt.ie 2747 [ - + ]: 678 : Assert(vacrel->do_index_vacuuming);
2748 [ - + ]: 678 : Assert(vacrel->do_index_cleanup);
2749 [ - + ]: 678 : Assert(vacrel->num_index_scans > 0);
2750 : :
2751 : : /* Report that we are now vacuuming the heap */
2133 michael@paquier.xyz 2752 : 678 : pgstat_progress_update_param(PROGRESS_VACUUM_PHASE,
2753 : : PROGRESS_VACUUM_PHASE_VACUUM_HEAP);
2754 : :
2755 : : /* Update error traceback information */
1667 pg@bowt.ie 2756 : 678 : update_vacuum_error_info(vacrel, &saved_err_info,
2757 : : VACUUM_ERRCB_PHASE_VACUUM_HEAP,
2758 : : InvalidBlockNumber, InvalidOffsetNumber);
2759 : :
574 msawada@postgresql.o 2760 : 678 : iter = TidStoreBeginIterate(vacrel->dead_items);
2761 : :
2762 : : /*
2763 : : * Set up the read stream for vacuum's second pass through the heap.
2764 : : *
2765 : : * It is safe to use batchmode, as vacuum_reap_lp_read_stream_next() does
2766 : : * not need to wait for IO and does not perform locking. Once we support
2767 : : * parallelism it should still be fine, as presumably the holder of locks
2768 : : * would never be blocked by IO while holding the lock.
2769 : : */
212 andres@anarazel.de 2770 : 678 : stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
2771 : : READ_STREAM_USE_BATCHING,
2772 : : vacrel->bstrategy,
2773 : : vacrel->rel,
2774 : : MAIN_FORKNUM,
2775 : : vacuum_reap_lp_read_stream_next,
2776 : : iter,
2777 : : sizeof(TidStoreIterResult));
2778 : :
2779 : : while (true)
8873 tgl@sss.pgh.pa.us 2780 : 15377 : {
2781 : : BlockNumber blkno;
2782 : : Buffer buf;
2783 : : Page page;
2784 : : TidStoreIterResult *iter_result;
2785 : : Size freespace;
2786 : : OffsetNumber offsets[MaxOffsetNumber];
2787 : : int num_offsets;
2788 : :
259 nathan@postgresql.or 2789 : 16055 : vacuum_delay_point(false);
2790 : :
256 melanieplageman@gmai 2791 : 16055 : buf = read_stream_next_buffer(stream, (void **) &iter_result);
2792 : :
2793 : : /* The relation is exhausted */
2794 [ + + ]: 16055 : if (!BufferIsValid(buf))
2795 : 678 : break;
2796 : :
2797 : 15377 : vacrel->blkno = blkno = BufferGetBlockNumber(buf);
2798 : :
2799 [ - + ]: 15377 : Assert(iter_result);
461 tmunro@postgresql.or 2800 : 15377 : num_offsets = TidStoreGetBlockOffsets(iter_result, offsets, lengthof(offsets));
2801 [ - + ]: 15377 : Assert(num_offsets <= lengthof(offsets));
2802 : :
2803 : : /*
2804 : : * Pin the visibility map page in case we need to mark the page
2805 : : * all-visible. In most cases this will be very cheap, because we'll
2806 : : * already have the correct page pinned anyway.
2807 : : */
1016 pg@bowt.ie 2808 : 15377 : visibilitymap_pin(vacrel->rel, blkno, &vmbuffer);
2809 : :
2810 : : /* We need a non-cleanup exclusive lock to mark dead_items unused */
1666 2811 : 15377 : LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
461 tmunro@postgresql.or 2812 : 15377 : lazy_vacuum_heap_page(vacrel, blkno, buf, offsets,
2813 : : num_offsets, vmbuffer);
2814 : :
2815 : : /* Now that we've vacuumed the page, record its available space */
3478 kgrittn@postgresql.o 2816 : 15377 : page = BufferGetPage(buf);
6237 heikki.linnakangas@i 2817 : 15377 : freespace = PageGetHeapFreeSpace(page);
2818 : :
7151 tgl@sss.pgh.pa.us 2819 : 15377 : UnlockReleaseBuffer(buf);
1021 pg@bowt.ie 2820 : 15377 : RecordPageWithFreeSpace(vacrel->rel, blkno, freespace);
1667 2821 : 15377 : vacuumed_pages++;
2822 : : }
2823 : :
256 melanieplageman@gmai 2824 : 678 : read_stream_end(stream);
574 msawada@postgresql.o 2825 : 678 : TidStoreEndIterate(iter);
2826 : :
1667 pg@bowt.ie 2827 : 678 : vacrel->blkno = InvalidBlockNumber;
4640 heikki.linnakangas@i 2828 [ + - ]: 678 : if (BufferIsValid(vmbuffer))
2829 : 678 : ReleaseBuffer(vmbuffer);
2830 : :
2831 : : /*
2832 : : * We set all LP_DEAD items from the first heap pass to LP_UNUSED during
2833 : : * the second heap pass. No more, no less.
2834 : : */
1666 pg@bowt.ie 2835 [ + + + - : 678 : Assert(vacrel->num_index_scans > 1 ||
- + ]
2836 : : (vacrel->dead_items_info->num_items == vacrel->lpdead_items &&
2837 : : vacuumed_pages == vacrel->lpdead_item_pages));
2838 : :
1383 2839 [ - + ]: 678 : ereport(DEBUG2,
2840 : : (errmsg("table \"%s\": removed %" PRId64 " dead item identifiers in %u pages",
2841 : : vacrel->relname, vacrel->dead_items_info->num_items,
2842 : : vacuumed_pages)));
2843 : :
2844 : : /* Revert to the previous phase information for error traceback */
1667 2845 : 678 : restore_vacuum_error_info(vacrel, &saved_err_info);
8873 tgl@sss.pgh.pa.us 2846 : 678 : }
2847 : :
2848 : : /*
2849 : : * lazy_vacuum_heap_page() -- free page's LP_DEAD items listed in the
2850 : : * vacrel->dead_items store.
2851 : : *
2852 : : * Caller must have an exclusive buffer lock on the buffer (though a full
2853 : : * cleanup lock is also acceptable). vmbuffer must be valid and already have
2854 : : * a pin on blkno's visibility map page.
2855 : : */
2856 : : static void
1667 pg@bowt.ie 2857 : 15377 : lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
2858 : : OffsetNumber *deadoffsets, int num_offsets,
2859 : : Buffer vmbuffer)
2860 : : {
3478 kgrittn@postgresql.o 2861 : 15377 : Page page = BufferGetPage(buffer);
2862 : : OffsetNumber unused[MaxHeapTuplesPerPage];
1021 pg@bowt.ie 2863 : 15377 : int nunused = 0;
2864 : : TransactionId visibility_cutoff_xid;
15 melanieplageman@gmai 2865 :GNC 15377 : TransactionId conflict_xid = InvalidTransactionId;
2866 : : bool all_frozen;
2867 : : LVSavedErrInfo saved_err_info;
2868 : 15377 : uint8 vmflags = 0;
2869 : :
649 rhaas@postgresql.org 2870 [ - + ]:CBC 15377 : Assert(vacrel->do_index_vacuuming);
2871 : :
3514 2872 : 15377 : pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED, blkno);
2873 : :
2874 : : /* Update error traceback information */
1667 pg@bowt.ie 2875 : 15377 : update_vacuum_error_info(vacrel, &saved_err_info,
2876 : : VACUUM_ERRCB_PHASE_VACUUM_HEAP, blkno,
2877 : : InvalidOffsetNumber);
2878 : :
2879 : : /*
2880 : : * Before marking dead items unused, check whether the page will become
2881 : : * all-visible once that change is applied. This lets us reap the tuples
2882 : : * and mark the page all-visible within the same critical section,
2883 : : * enabling both changes to be emitted in a single WAL record. Since the
2884 : : * visibility checks may perform I/O and allocate memory, they must be
2885 : : * done outside the critical section.
2886 : : */
14 melanieplageman@gmai 2887 [ + + ]:GNC 15377 : if (heap_page_would_be_all_visible(vacrel->rel, buffer,
2888 : : vacrel->cutoffs.OldestXmin,
2889 : : deadoffsets, num_offsets,
2890 : : &all_frozen, &visibility_cutoff_xid,
2891 : : &vacrel->offnum))
2892 : : {
15 2893 : 15207 : vmflags |= VISIBILITYMAP_ALL_VISIBLE;
2894 [ + + ]: 15207 : if (all_frozen)
2895 : : {
2896 : 11757 : vmflags |= VISIBILITYMAP_ALL_FROZEN;
2897 [ - + ]: 11757 : Assert(!TransactionIdIsValid(visibility_cutoff_xid));
2898 : : }
2899 : :
2900 : : /*
2901 : : * Take the lock on the vmbuffer before entering a critical section.
2902 : : * The heap page lock must also be held while updating the VM to
2903 : : * ensure consistency.
2904 : : */
2905 : 15207 : LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE);
2906 : : }
2907 : :
8873 tgl@sss.pgh.pa.us 2908 :CBC 15377 : START_CRIT_SECTION();
2909 : :
574 msawada@postgresql.o 2910 [ + + ]: 964089 : for (int i = 0; i < num_offsets; i++)
2911 : : {
2912 : : ItemId itemid;
2913 : 948712 : OffsetNumber toff = deadoffsets[i];
2914 : :
8873 tgl@sss.pgh.pa.us 2915 : 948712 : itemid = PageGetItemId(page, toff);
2916 : :
1666 pg@bowt.ie 2917 [ + - - + ]: 948712 : Assert(ItemIdIsDead(itemid) && !ItemIdHasStorage(itemid));
6621 tgl@sss.pgh.pa.us 2918 : 948712 : ItemIdSetUnused(itemid);
1021 pg@bowt.ie 2919 : 948712 : unused[nunused++] = toff;
2920 : : }
2921 : :
2922 [ - + ]: 15377 : Assert(nunused > 0);
2923 : :
2924 : : /* Attempt to truncate line pointer array now */
1665 2925 : 15377 : PageTruncateLinePointerArray(page);
2926 : :
15 melanieplageman@gmai 2927 [ + + ]:GNC 15377 : if ((vmflags & VISIBILITYMAP_VALID_BITS) != 0)
2928 : : {
2929 : : /*
2930 : : * The page is guaranteed to have had dead line pointers, so we always
2931 : : * set PD_ALL_VISIBLE.
2932 : : */
2933 : 15207 : PageSetAllVisible(page);
2934 : 15207 : visibilitymap_set_vmbits(blkno,
2935 : : vmbuffer, vmflags,
2936 : 15207 : vacrel->rel->rd_locator);
2937 : 15207 : conflict_xid = visibility_cutoff_xid;
2938 : : }
2939 : :
2940 : : /*
2941 : : * Mark buffer dirty before we write WAL.
2942 : : */
4564 simon@2ndQuadrant.co 2943 :CBC 15377 : MarkBufferDirty(buffer);
2944 : :
2945 : : /* XLOG stuff */
1667 pg@bowt.ie 2946 [ + + + + : 15377 : if (RelationNeedsWAL(vacrel->rel))
+ - + - ]
2947 : : {
582 heikki.linnakangas@i 2948 [ + + ]: 14527 : log_heap_prune_and_freeze(vacrel->rel, buffer,
2949 : : vmflags != 0 ? vmbuffer : InvalidBuffer,
2950 : : vmflags,
2951 : : conflict_xid,
2952 : : false, /* no cleanup lock required */
2953 : : PRUNE_VACUUM_CLEANUP,
2954 : : NULL, 0, /* frozen */
2955 : : NULL, 0, /* redirected */
2956 : : NULL, 0, /* dead */
2957 : : unused, nunused);
2958 : : }
2959 : :
4148 andres@anarazel.de 2960 [ - + ]: 15377 : END_CRIT_SECTION();
2961 : :
15 melanieplageman@gmai 2962 [ + + ]:GNC 15377 : if ((vmflags & VISIBILITYMAP_ALL_VISIBLE) != 0)
2963 : : {
2964 : : /* Count the newly set VM page for logging */
2965 : 15207 : LockBuffer(vmbuffer, BUFFER_LOCK_UNLOCK);
124 melanieplageman@gmai 2966 :CBC 15207 : vacrel->vm_new_visible_pages++;
2967 [ + + ]: 15207 : if (all_frozen)
2968 : 11757 : vacrel->vm_new_visible_frozen_pages++;
2969 : : }
2970 : :
2971 : : /* Revert to the previous phase information for error traceback */
1667 pg@bowt.ie 2972 : 15377 : restore_vacuum_error_info(vacrel, &saved_err_info);
8873 tgl@sss.pgh.pa.us 2973 : 15377 : }
2974 : :
2975 : : /*
2976 : : * Trigger the failsafe to avoid wraparound failure when vacrel table has a
2977 : : * relfrozenxid and/or relminmxid that is dangerously far in the past.
2978 : : * Triggering the failsafe makes the ongoing VACUUM bypass any further index
2979 : : * vacuuming and heap vacuuming. Truncating the heap is also bypassed.
2980 : : *
2981 : : * Any remaining work (work that VACUUM cannot just bypass) is typically sped
2982 : : * up when the failsafe triggers. VACUUM stops applying any cost-based delay
2983 : : * that it started out with.
2984 : : *
2985 : : * Returns true when failsafe has been triggered.
2986 : : */
2987 : : static bool
1665 pg@bowt.ie 2988 : 14987 : lazy_check_wraparound_failsafe(LVRelState *vacrel)
2989 : : {
2990 : : /* Don't warn more than once per VACUUM */
935 dgustafsson@postgres 2991 [ - + ]: 14987 : if (VacuumFailsafeActive)
1665 pg@bowt.ie 2992 :UBC 0 : return true;
2993 : :
1041 pg@bowt.ie 2994 [ - + ]:CBC 14987 : if (unlikely(vacuum_xid_failsafe_check(&vacrel->cutoffs)))
2995 : : {
840 msawada@postgresql.o 2996 :UBC 0 : const int progress_index[] = {
2997 : : PROGRESS_VACUUM_INDEXES_TOTAL,
2998 : : PROGRESS_VACUUM_INDEXES_PROCESSED
2999 : : };
3000 : 0 : int64 progress_val[2] = {0, 0};
3001 : :
935 dgustafsson@postgres 3002 : 0 : VacuumFailsafeActive = true;
3003 : :
3004 : : /*
3005 : : * Abandon use of a buffer access strategy to allow use of all of
3006 : : * shared buffers. We assume the caller who allocated the memory for
3007 : : * the BufferAccessStrategy will free it.
3008 : : */
939 drowley@postgresql.o 3009 : 0 : vacrel->bstrategy = NULL;
3010 : :
3011 : : /* Disable index vacuuming, index cleanup, and heap rel truncation */
1665 pg@bowt.ie 3012 : 0 : vacrel->do_index_vacuuming = false;
3013 : 0 : vacrel->do_index_cleanup = false;
1593 3014 : 0 : vacrel->do_rel_truncate = false;
3015 : :
3016 : : /* Reset the progress counters */
840 msawada@postgresql.o 3017 : 0 : pgstat_progress_update_multi_param(2, progress_index, progress_val);
3018 : :
1665 pg@bowt.ie 3019 [ # # ]: 0 : ereport(WARNING,
3020 : : (errmsg("bypassing nonessential maintenance of table \"%s.%s.%s\" as a failsafe after %d index scans",
3021 : : vacrel->dbname, vacrel->relnamespace, vacrel->relname,
3022 : : vacrel->num_index_scans),
3023 : : errdetail("The table's relfrozenxid or relminmxid is too far in the past."),
3024 : : errhint("Consider increasing configuration parameter \"maintenance_work_mem\" or \"autovacuum_work_mem\".\n"
3025 : : "You might also need to consider other ways for VACUUM to keep up with the allocation of transaction IDs.")));
3026 : :
3027 : : /* Stop applying cost limits from this point on */
3028 : 0 : VacuumCostActive = false;
3029 : 0 : VacuumCostBalance = 0;
3030 : :
3031 : 0 : return true;
3032 : : }
3033 : :
1665 pg@bowt.ie 3034 :CBC 14987 : return false;
3035 : : }
3036 : :
3037 : : /*
3038 : : * lazy_cleanup_all_indexes() -- cleanup all indexes of relation.
3039 : : */
3040 : : static void
1667 3041 : 11847 : lazy_cleanup_all_indexes(LVRelState *vacrel)
3042 : : {
1326 3043 : 11847 : double reltuples = vacrel->new_rel_tuples;
3044 : 11847 : bool estimated_count = vacrel->scanned_pages < vacrel->rel_pages;
840 msawada@postgresql.o 3045 : 11847 : const int progress_start_index[] = {
3046 : : PROGRESS_VACUUM_PHASE,
3047 : : PROGRESS_VACUUM_INDEXES_TOTAL
3048 : : };
3049 : 11847 : const int progress_end_index[] = {
3050 : : PROGRESS_VACUUM_INDEXES_TOTAL,
3051 : : PROGRESS_VACUUM_INDEXES_PROCESSED
3052 : : };
3053 : : int64 progress_start_val[2];
3054 : 11847 : int64 progress_end_val[2] = {0, 0};
3055 : :
1326 pg@bowt.ie 3056 [ - + ]: 11847 : Assert(vacrel->do_index_cleanup);
1667 3057 [ - + ]: 11847 : Assert(vacrel->nindexes > 0);
3058 : :
3059 : : /*
3060 : : * Report that we are now cleaning up indexes and the number of indexes to
3061 : : * cleanup.
3062 : : */
840 msawada@postgresql.o 3063 : 11847 : progress_start_val[0] = PROGRESS_VACUUM_PHASE_INDEX_CLEANUP;
3064 : 11847 : progress_start_val[1] = vacrel->nindexes;
3065 : 11847 : pgstat_progress_update_multi_param(2, progress_start_index, progress_start_val);
3066 : :
1667 pg@bowt.ie 3067 [ + + ]: 11847 : if (!ParallelVacuumIsActive(vacrel))
3068 : : {
3069 [ + + ]: 30270 : for (int idx = 0; idx < vacrel->nindexes; idx++)
3070 : : {
3071 : 18440 : Relation indrel = vacrel->indrels[idx];
3072 : 18440 : IndexBulkDeleteResult *istat = vacrel->indstats[idx];
3073 : :
3074 : 36880 : vacrel->indstats[idx] =
3075 : 18440 : lazy_cleanup_one_index(indrel, istat, reltuples,
3076 : : estimated_count, vacrel);
3077 : :
3078 : : /* Report the number of indexes cleaned up */
840 msawada@postgresql.o 3079 : 18440 : pgstat_progress_update_param(PROGRESS_VACUUM_INDEXES_PROCESSED,
3080 : 18440 : idx + 1);
3081 : : }
3082 : : }
3083 : : else
3084 : : {
3085 : : /* Outsource everything to parallel variant */
1326 pg@bowt.ie 3086 : 17 : parallel_vacuum_cleanup_all_indexes(vacrel->pvs, reltuples,
3087 : : vacrel->num_index_scans,
3088 : : estimated_count);
3089 : : }
3090 : :
3091 : : /* Reset the progress counters */
840 msawada@postgresql.o 3092 : 11847 : pgstat_progress_update_multi_param(2, progress_end_index, progress_end_val);
2108 akapila@postgresql.o 3093 : 11847 : }
3094 : :
3095 : : /*
3096 : : * lazy_vacuum_one_index() -- vacuum index relation.
3097 : : *
3098 : : * Delete all the index tuples containing a TID collected in
3099 : : * vacrel->dead_items. Also update running statistics. Exact
3100 : : * details depend on index AM's ambulkdelete routine.
3101 : : *
3102 : : * reltuples is the number of heap tuples to be passed to the
3103 : : * bulkdelete callback. It's always assumed to be estimated.
3104 : : * See indexam.sgml for more info.
3105 : : *
3106 : : * Returns bulk delete stats derived from input stats
3107 : : */
3108 : : static IndexBulkDeleteResult *
1667 pg@bowt.ie 3109 : 1264 : lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
3110 : : double reltuples, LVRelState *vacrel)
3111 : : {
3112 : : IndexVacuumInfo ivinfo;
3113 : : LVSavedErrInfo saved_err_info;
3114 : :
7119 tgl@sss.pgh.pa.us 3115 : 1264 : ivinfo.index = indrel;
939 pg@bowt.ie 3116 : 1264 : ivinfo.heaprel = vacrel->rel;
6062 tgl@sss.pgh.pa.us 3117 : 1264 : ivinfo.analyze_only = false;
2400 alvherre@alvh.no-ip. 3118 : 1264 : ivinfo.report_progress = false;
5988 tgl@sss.pgh.pa.us 3119 : 1264 : ivinfo.estimated_count = true;
1383 pg@bowt.ie 3120 : 1264 : ivinfo.message_level = DEBUG2;
2108 akapila@postgresql.o 3121 : 1264 : ivinfo.num_heap_tuples = reltuples;
1667 pg@bowt.ie 3122 : 1264 : ivinfo.strategy = vacrel->bstrategy;
3123 : :
3124 : : /*
3125 : : * Update error traceback information.
3126 : : *
3127 : : * The index name is saved during this phase and restored immediately
3128 : : * after this phase. See vacuum_error_callback.
3129 : : */
3130 [ - + ]: 1264 : Assert(vacrel->indname == NULL);
3131 : 1264 : vacrel->indname = pstrdup(RelationGetRelationName(indrel));
3132 : 1264 : update_vacuum_error_info(vacrel, &saved_err_info,
3133 : : VACUUM_ERRCB_PHASE_VACUUM_INDEX,
3134 : : InvalidBlockNumber, InvalidOffsetNumber);
3135 : :
3136 : : /* Do bulk deletion */
334 peter@eisentraut.org 3137 : 1264 : istat = vac_bulkdel_one_index(&ivinfo, istat, vacrel->dead_items,
3138 : : vacrel->dead_items_info);
3139 : :
3140 : : /* Revert to the previous phase information for error traceback */
1667 pg@bowt.ie 3141 : 1264 : restore_vacuum_error_info(vacrel, &saved_err_info);
3142 : 1264 : pfree(vacrel->indname);
3143 : 1264 : vacrel->indname = NULL;
3144 : :
3145 : 1264 : return istat;
3146 : : }
3147 : :
3148 : : /*
3149 : : * lazy_cleanup_one_index() -- do post-vacuum cleanup for index relation.
3150 : : *
3151 : : * Calls index AM's amvacuumcleanup routine. reltuples is the number
3152 : : * of heap tuples and estimated_count is true if reltuples is an
3153 : : * estimated value. See indexam.sgml for more info.
3154 : : *
3155 : : * Returns bulk delete stats derived from input stats
3156 : : */
3157 : : static IndexBulkDeleteResult *
3158 : 18440 : lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
3159 : : double reltuples, bool estimated_count,
3160 : : LVRelState *vacrel)
3161 : : {
3162 : : IndexVacuumInfo ivinfo;
3163 : : LVSavedErrInfo saved_err_info;
3164 : :
7119 tgl@sss.pgh.pa.us 3165 : 18440 : ivinfo.index = indrel;
939 pg@bowt.ie 3166 : 18440 : ivinfo.heaprel = vacrel->rel;
6062 tgl@sss.pgh.pa.us 3167 : 18440 : ivinfo.analyze_only = false;
2400 alvherre@alvh.no-ip. 3168 : 18440 : ivinfo.report_progress = false;
2108 akapila@postgresql.o 3169 : 18440 : ivinfo.estimated_count = estimated_count;
1383 pg@bowt.ie 3170 : 18440 : ivinfo.message_level = DEBUG2;
3171 : :
2108 akapila@postgresql.o 3172 : 18440 : ivinfo.num_heap_tuples = reltuples;
1667 pg@bowt.ie 3173 : 18440 : ivinfo.strategy = vacrel->bstrategy;
3174 : :
3175 : : /*
3176 : : * Update error traceback information.
3177 : : *
3178 : : * The index name is saved during this phase and restored immediately
3179 : : * after this phase. See vacuum_error_callback.
3180 : : */
3181 [ - + ]: 18440 : Assert(vacrel->indname == NULL);
3182 : 18440 : vacrel->indname = pstrdup(RelationGetRelationName(indrel));
3183 : 18440 : update_vacuum_error_info(vacrel, &saved_err_info,
3184 : : VACUUM_ERRCB_PHASE_INDEX_CLEANUP,
3185 : : InvalidBlockNumber, InvalidOffsetNumber);
3186 : :
1406 akapila@postgresql.o 3187 : 18440 : istat = vac_cleanup_one_index(&ivinfo, istat);
3188 : :
3189 : : /* Revert to the previous phase information for error traceback */
1667 pg@bowt.ie 3190 : 18440 : restore_vacuum_error_info(vacrel, &saved_err_info);
3191 : 18440 : pfree(vacrel->indname);
3192 : 18440 : vacrel->indname = NULL;
3193 : :
3194 : 18440 : return istat;
3195 : : }
3196 : :
3197 : : /*
3198 : : * should_attempt_truncation - should we attempt to truncate the heap?
3199 : : *
3200 : : * Don't even think about it unless we have a shot at releasing a goodly
3201 : : * number of pages. Otherwise, the time taken isn't worth it, mainly because
3202 : : * an AccessExclusive lock must be replayed on any hot standby, where it can
3203 : : * be particularly disruptive.
3204 : : *
3205 : : * Also don't attempt it if wraparound failsafe is in effect. The entire
3206 : : * system might be refusing to allocate new XIDs at this point. The system
3207 : : * definitely won't return to normal unless and until VACUUM actually advances
3208 : : * the oldest relfrozenxid -- which hasn't happened for target rel just yet.
3209 : : * If lazy_truncate_heap attempted to acquire an AccessExclusiveLock to
3210 : : * truncate the table under these circumstances, an XID exhaustion error might
3211 : : * make it impossible for VACUUM to fix the underlying XID exhaustion problem.
3212 : : * There is very little chance of truncation working out when the failsafe is
3213 : : * in effect in any case. lazy_scan_prune makes the optimistic assumption
3214 : : * that any LP_DEAD items it encounters will always be LP_UNUSED by the time
3215 : : * we're called.
3216 : : */
3217 : : static bool
1593 3218 : 13024 : should_attempt_truncation(LVRelState *vacrel)
3219 : : {
3220 : : BlockNumber possibly_freeable;
3221 : :
784 tmunro@postgresql.or 3222 [ + + - + ]: 13024 : if (!vacrel->do_rel_truncate || VacuumFailsafeActive)
1659 pg@bowt.ie 3223 : 141 : return false;
3224 : :
1667 3225 : 12883 : possibly_freeable = vacrel->rel_pages - vacrel->nonempty_pages;
3590 tgl@sss.pgh.pa.us 3226 [ + + + + ]: 12883 : if (possibly_freeable > 0 &&
3227 : 185 : (possibly_freeable >= REL_TRUNCATE_MINIMUM ||
1326 pg@bowt.ie 3228 [ + + ]: 185 : possibly_freeable >= vacrel->rel_pages / REL_TRUNCATE_FRACTION))
3590 tgl@sss.pgh.pa.us 3229 : 174 : return true;
3230 : :
1326 pg@bowt.ie 3231 : 12709 : return false;
3232 : : }
3233 : :
3234 : : /*
3235 : : * lazy_truncate_heap - try to truncate off any empty pages at the end
3236 : : */
3237 : : static void
1667 3238 : 174 : lazy_truncate_heap(LVRelState *vacrel)
3239 : : {
1519 3240 : 174 : BlockNumber orig_rel_pages = vacrel->rel_pages;
3241 : : BlockNumber new_rel_pages;
3242 : : bool lock_waiter_detected;
3243 : : int lock_retry;
3244 : :
3245 : : /* Report that we are now truncating */
3514 rhaas@postgresql.org 3246 : 174 : pgstat_progress_update_param(PROGRESS_VACUUM_PHASE,
3247 : : PROGRESS_VACUUM_PHASE_TRUNCATE);
3248 : :
3249 : : /* Update error traceback information one last time */
1326 pg@bowt.ie 3250 : 174 : update_vacuum_error_info(vacrel, NULL, VACUUM_ERRCB_PHASE_TRUNCATE,
3251 : : vacrel->nonempty_pages, InvalidOffsetNumber);
3252 : :
3253 : : /*
3254 : : * Loop until no more truncating can be done.
3255 : : */
3256 : : do
3257 : : {
3258 : : /*
3259 : : * We need full exclusive lock on the relation in order to do
3260 : : * truncation. If we can't get it, give up rather than waiting --- we
3261 : : * don't want to block other backends, and we don't want to deadlock
3262 : : * (which is quite possible considering we already hold a lower-grade
3263 : : * lock).
3264 : : */
1596 3265 : 174 : lock_waiter_detected = false;
4704 kgrittn@postgresql.o 3266 : 174 : lock_retry = 0;
3267 : : while (true)
3268 : : {
1667 pg@bowt.ie 3269 [ + + ]: 374 : if (ConditionalLockRelation(vacrel->rel, AccessExclusiveLock))
4704 kgrittn@postgresql.o 3270 : 172 : break;
3271 : :
3272 : : /*
3273 : : * Check for interrupts while trying to (re-)acquire the exclusive
3274 : : * lock.
3275 : : */
3276 [ - + ]: 202 : CHECK_FOR_INTERRUPTS();
3277 : :
4565 3278 [ + + ]: 202 : if (++lock_retry > (VACUUM_TRUNCATE_LOCK_TIMEOUT /
3279 : : VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL))
3280 : : {
3281 : : /*
3282 : : * We failed to establish the lock in the specified number of
3283 : : * retries. This means we give up truncating.
3284 : : */
1383 pg@bowt.ie 3285 [ + - + - ]: 2 : ereport(vacrel->verbose ? INFO : DEBUG2,
3286 : : (errmsg("\"%s\": stopping truncate due to conflicting lock request",
3287 : : vacrel->relname)));
4704 kgrittn@postgresql.o 3288 : 3 : return;
3289 : : }
3290 : :
1579 michael@paquier.xyz 3291 : 200 : (void) WaitLatch(MyLatch,
3292 : : WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
3293 : : VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
3294 : : WAIT_EVENT_VACUUM_TRUNCATE);
3295 : 200 : ResetLatch(MyLatch);
3296 : : }
3297 : :
3298 : : /*
3299 : : * Now that we have exclusive lock, look to see if the rel has grown
3300 : : * whilst we were vacuuming with non-exclusive lock. If so, give up;
3301 : : * the newly added pages presumably contain non-deletable tuples.
3302 : : */
1667 pg@bowt.ie 3303 : 172 : new_rel_pages = RelationGetNumberOfBlocks(vacrel->rel);
1519 3304 [ - + ]: 172 : if (new_rel_pages != orig_rel_pages)
3305 : : {
3306 : : /*
3307 : : * Note: we intentionally don't update vacrel->rel_pages with the
3308 : : * new rel size here. If we did, it would amount to assuming that
3309 : : * the new pages are empty, which is unlikely. Leaving the numbers
3310 : : * alone amounts to assuming that the new pages have the same
3311 : : * tuple density as existing ones, which is less unlikely.
3312 : : */
1667 pg@bowt.ie 3313 :UBC 0 : UnlockRelation(vacrel->rel, AccessExclusiveLock);
4704 kgrittn@postgresql.o 3314 : 0 : return;
3315 : : }
3316 : :
3317 : : /*
3318 : : * Scan backwards from the end to verify that the end pages actually
3319 : : * contain no tuples. This is *necessary*, not optional, because
3320 : : * other backends could have added tuples to these pages whilst we
3321 : : * were vacuuming.
3322 : : */
1596 pg@bowt.ie 3323 :CBC 172 : new_rel_pages = count_nondeletable_pages(vacrel, &lock_waiter_detected);
1667 3324 : 172 : vacrel->blkno = new_rel_pages;
3325 : :
1519 3326 [ + + ]: 172 : if (new_rel_pages >= orig_rel_pages)
3327 : : {
3328 : : /* can't do anything after all */
1667 3329 : 1 : UnlockRelation(vacrel->rel, AccessExclusiveLock);
4704 kgrittn@postgresql.o 3330 : 1 : return;
3331 : : }
3332 : :
3333 : : /*
3334 : : * Okay to truncate.
3335 : : */
1667 pg@bowt.ie 3336 : 171 : RelationTruncate(vacrel->rel, new_rel_pages);
3337 : :
3338 : : /*
3339 : : * We can release the exclusive lock as soon as we have truncated.
3340 : : * Other backends can't safely access the relation until they have
3341 : : * processed the smgr invalidation that smgrtruncate sent out ... but
3342 : : * that should happen as part of standard invalidation processing once
3343 : : * they acquire lock on the relation.
3344 : : */
3345 : 171 : UnlockRelation(vacrel->rel, AccessExclusiveLock);
3346 : :
3347 : : /*
3348 : : * Update statistics. Here, it *is* correct to adjust rel_pages
3349 : : * without also touching reltuples, since the tuple count wasn't
3350 : : * changed by the truncation.
3351 : : */
1369 3352 : 171 : vacrel->removed_pages += orig_rel_pages - new_rel_pages;
1667 3353 : 171 : vacrel->rel_pages = new_rel_pages;
3354 : :
1383 3355 [ + + + + ]: 171 : ereport(vacrel->verbose ? INFO : DEBUG2,
3356 : : (errmsg("table \"%s\": truncated %u to %u pages",
3357 : : vacrel->relname,
3358 : : orig_rel_pages, new_rel_pages)));
1519 3359 : 171 : orig_rel_pages = new_rel_pages;
1596 3360 [ + + - + ]: 171 : } while (new_rel_pages > vacrel->nonempty_pages && lock_waiter_detected);
3361 : : }
3362 : :
3363 : : /*
3364 : : * Rescan end pages to verify that they are (still) empty of tuples.
3365 : : *
3366 : : * Returns number of nondeletable pages (last nonempty page + 1).
3367 : : */
3368 : : static BlockNumber
3369 : 172 : count_nondeletable_pages(LVRelState *vacrel, bool *lock_waiter_detected)
3370 : : {
3371 : : BlockNumber blkno;
3372 : : BlockNumber prefetchedUntil;
3373 : : instr_time starttime;
3374 : :
3375 : : /* Initialize the starttime if we check for conflicting lock requests */
4704 kgrittn@postgresql.o 3376 : 172 : INSTR_TIME_SET_CURRENT(starttime);
3377 : :
3378 : : /*
3379 : : * Start checking blocks at what we believe relation end to be and move
3380 : : * backwards. (Strange coding of loop control is needed because blkno is
3381 : : * unsigned.) To make the scan faster, we prefetch a few blocks at a time
3382 : : * in forward direction, so that OS-level readahead can kick in.
3383 : : */
1667 pg@bowt.ie 3384 : 172 : blkno = vacrel->rel_pages;
3385 : : StaticAssertStmt((PREFETCH_SIZE & (PREFETCH_SIZE - 1)) == 0,
3386 : : "prefetch size must be power of 2");
3200 alvherre@alvh.no-ip. 3387 : 172 : prefetchedUntil = InvalidBlockNumber;
1667 pg@bowt.ie 3388 [ + + ]: 3115 : while (blkno > vacrel->nonempty_pages)
3389 : : {
3390 : : Buffer buf;
3391 : : Page page;
3392 : : OffsetNumber offnum,
3393 : : maxoff;
3394 : : bool hastup;
3395 : :
3396 : : /*
3397 : : * Check if another process requests a lock on our relation. We are
3398 : : * holding an AccessExclusiveLock here, so they will be waiting. We
3399 : : * only do this once per VACUUM_TRUNCATE_LOCK_CHECK_INTERVAL, and we
3400 : : * only check if that interval has elapsed once every 32 blocks to
3401 : : * keep the number of system calls and actual shared lock table
3402 : : * lookups to a minimum.
3403 : : */
4704 kgrittn@postgresql.o 3404 [ + + ]: 2946 : if ((blkno % 32) == 0)
3405 : : {
3406 : : instr_time currenttime;
3407 : : instr_time elapsed;
3408 : :
3409 : 101 : INSTR_TIME_SET_CURRENT(currenttime);
3410 : 101 : elapsed = currenttime;
3411 : 101 : INSTR_TIME_SUBTRACT(elapsed, starttime);
3412 [ - + ]: 101 : if ((INSTR_TIME_GET_MICROSEC(elapsed) / 1000)
3413 : : >= VACUUM_TRUNCATE_LOCK_CHECK_INTERVAL)
3414 : : {
1667 pg@bowt.ie 3415 [ # # ]:UBC 0 : if (LockHasWaitersRelation(vacrel->rel, AccessExclusiveLock))
3416 : : {
1383 3417 [ # # # # ]: 0 : ereport(vacrel->verbose ? INFO : DEBUG2,
3418 : : (errmsg("table \"%s\": suspending truncate due to conflicting lock request",
3419 : : vacrel->relname)));
3420 : :
1596 3421 : 0 : *lock_waiter_detected = true;
4704 kgrittn@postgresql.o 3422 : 0 : return blkno;
3423 : : }
3424 : 0 : starttime = currenttime;
3425 : : }
3426 : : }
3427 : :
3428 : : /*
3429 : : * We don't insert a vacuum delay point here, because we have an
3430 : : * exclusive lock on the table which we want to hold for as short a
3431 : : * time as possible. We still need to check for interrupts however.
3432 : : */
6621 alvherre@alvh.no-ip. 3433 [ - + ]:CBC 2946 : CHECK_FOR_INTERRUPTS();
3434 : :
8873 tgl@sss.pgh.pa.us 3435 : 2946 : blkno--;
3436 : :
3437 : : /* If we haven't prefetched this lot yet, do so now. */
3200 alvherre@alvh.no-ip. 3438 [ + + ]: 2946 : if (prefetchedUntil > blkno)
3439 : : {
3440 : : BlockNumber prefetchStart;
3441 : : BlockNumber pblkno;
3442 : :
3443 : 243 : prefetchStart = blkno & ~(PREFETCH_SIZE - 1);
3444 [ + + ]: 4364 : for (pblkno = prefetchStart; pblkno <= blkno; pblkno++)
3445 : : {
1667 pg@bowt.ie 3446 : 4121 : PrefetchBuffer(vacrel->rel, MAIN_FORKNUM, pblkno);
3200 alvherre@alvh.no-ip. 3447 [ - + ]: 4121 : CHECK_FOR_INTERRUPTS();
3448 : : }
3449 : 243 : prefetchedUntil = prefetchStart;
3450 : : }
3451 : :
1667 pg@bowt.ie 3452 : 2946 : buf = ReadBufferExtended(vacrel->rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
3453 : : vacrel->bstrategy);
3454 : :
3455 : : /* In this phase we only need shared access to the buffer */
8873 tgl@sss.pgh.pa.us 3456 : 2946 : LockBuffer(buf, BUFFER_LOCK_SHARE);
3457 : :
3478 kgrittn@postgresql.o 3458 : 2946 : page = BufferGetPage(buf);
3459 : :
8873 tgl@sss.pgh.pa.us 3460 [ + + + + ]: 2946 : if (PageIsNew(page) || PageIsEmpty(page))
3461 : : {
7151 3462 : 1560 : UnlockReleaseBuffer(buf);
8873 3463 : 1560 : continue;
3464 : : }
3465 : :
3466 : 1386 : hastup = false;
3467 : 1386 : maxoff = PageGetMaxOffsetNumber(page);
3468 : 1386 : for (offnum = FirstOffsetNumber;
3469 [ + + ]: 2772 : offnum <= maxoff;
3470 : 1386 : offnum = OffsetNumberNext(offnum))
3471 : : {
3472 : : ItemId itemid;
3473 : :
3474 : 1389 : itemid = PageGetItemId(page, offnum);
3475 : :
3476 : : /*
3477 : : * Note: any non-unused item should be taken as a reason to keep
3478 : : * this page. Even an LP_DEAD item makes truncation unsafe, since
3479 : : * we must not have cleaned out its index entries.
3480 : : */
6617 3481 [ + + ]: 1389 : if (ItemIdIsUsed(itemid))
3482 : : {
8873 3483 : 3 : hastup = true;
3484 : 3 : break; /* can stop scanning */
3485 : : }
3486 : : } /* scan along page */
3487 : :
7151 3488 : 1386 : UnlockReleaseBuffer(buf);
3489 : :
3490 : : /* Done scanning if we found a tuple here */
8873 3491 [ + + ]: 1386 : if (hastup)
3492 : 3 : return blkno + 1;
3493 : : }
3494 : :
3495 : : /*
3496 : : * If we fall out of the loop, all the previously-thought-to-be-empty
3497 : : * pages still are; we need not bother to look at the last known-nonempty
3498 : : * page.
3499 : : */
1667 pg@bowt.ie 3500 : 169 : return vacrel->nonempty_pages;
3501 : : }
3502 : :
3503 : : /*
3504 : : * Allocate dead_items and dead_items_info (either using palloc, or in dynamic
3505 : : * shared memory). Sets both in vacrel for caller.
3506 : : *
3507 : : * Also handles parallel initialization as part of allocating dead_items in
3508 : : * DSM when required.
3509 : : */
3510 : : static void
1429 3511 : 13024 : dead_items_alloc(LVRelState *vacrel, int nworkers)
3512 : : {
3513 : : VacDeadItemsInfo *dead_items_info;
574 msawada@postgresql.o 3514 : 26216 : int vac_work_mem = AmAutoVacuumWorkerProcess() &&
3515 [ - + ]: 168 : autovacuum_work_mem != -1 ?
3516 [ + + ]: 13192 : autovacuum_work_mem : maintenance_work_mem;
3517 : :
3518 : : /*
3519 : : * Initialize state for a parallel vacuum. As of now, only one worker can
3520 : : * be used for an index, so we invoke parallelism only if there are at
3521 : : * least two indexes on a table.
3522 : : */
1665 pg@bowt.ie 3523 [ + + + + : 13024 : if (nworkers >= 0 && vacrel->nindexes > 1 && vacrel->do_index_vacuuming)
+ + ]
3524 : : {
3525 : : /*
3526 : : * Since parallel workers cannot access data in temporary tables, we
3527 : : * can't perform parallel vacuum on them.
3528 : : */
1667 3529 [ + + ]: 5134 : if (RelationUsesLocalBuffers(vacrel->rel))
3530 : : {
3531 : : /*
3532 : : * Give warning only if the user explicitly tries to perform a
3533 : : * parallel vacuum on the temporary table.
3534 : : */
3535 [ + - ]: 3 : if (nworkers > 0)
3536 [ + - ]: 3 : ereport(WARNING,
3537 : : (errmsg("disabling parallel option of vacuum on \"%s\" --- cannot vacuum temporary tables in parallel",
3538 : : vacrel->relname)));
3539 : : }
3540 : : else
1405 akapila@postgresql.o 3541 : 5131 : vacrel->pvs = parallel_vacuum_init(vacrel->rel, vacrel->indrels,
3542 : : vacrel->nindexes, nworkers,
3543 : : vac_work_mem,
1383 pg@bowt.ie 3544 [ - + ]: 5131 : vacrel->verbose ? INFO : DEBUG2,
3545 : : vacrel->bstrategy);
3546 : :
3547 : : /*
3548 : : * If parallel mode started, dead_items and dead_items_info spaces are
3549 : : * allocated in DSM.
3550 : : */
1667 3551 [ + + ]: 5134 : if (ParallelVacuumIsActive(vacrel))
3552 : : {
574 msawada@postgresql.o 3553 : 17 : vacrel->dead_items = parallel_vacuum_get_dead_items(vacrel->pvs,
3554 : : &vacrel->dead_items_info);
1667 pg@bowt.ie 3555 : 17 : return;
3556 : : }
3557 : : }
3558 : :
3559 : : /*
3560 : : * Serial VACUUM case. Allocate both dead_items and dead_items_info
3561 : : * locally.
3562 : : */
3563 : :
574 msawada@postgresql.o 3564 : 13007 : dead_items_info = (VacDeadItemsInfo *) palloc(sizeof(VacDeadItemsInfo));
270 tgl@sss.pgh.pa.us 3565 : 13007 : dead_items_info->max_bytes = vac_work_mem * (Size) 1024;
574 msawada@postgresql.o 3566 : 13007 : dead_items_info->num_items = 0;
3567 : 13007 : vacrel->dead_items_info = dead_items_info;
3568 : :
569 john.naylor@postgres 3569 : 13007 : vacrel->dead_items = TidStoreCreateLocal(dead_items_info->max_bytes, true);
3570 : : }
3571 : :
3572 : : /*
3573 : : * Add the given block number and offset numbers to dead_items.
3574 : : */
3575 : : static void
574 msawada@postgresql.o 3576 : 15429 : dead_items_add(LVRelState *vacrel, BlockNumber blkno, OffsetNumber *offsets,
3577 : : int num_offsets)
3578 : : {
501 3579 : 15429 : const int prog_index[2] = {
3580 : : PROGRESS_VACUUM_NUM_DEAD_ITEM_IDS,
3581 : : PROGRESS_VACUUM_DEAD_TUPLE_BYTES
3582 : : };
3583 : : int64 prog_val[2];
3584 : :
328 john.naylor@postgres 3585 : 15429 : TidStoreSetBlockOffsets(vacrel->dead_items, blkno, offsets, num_offsets);
574 msawada@postgresql.o 3586 : 15429 : vacrel->dead_items_info->num_items += num_offsets;
3587 : :
3588 : : /* update the progress information */
501 3589 : 15429 : prog_val[0] = vacrel->dead_items_info->num_items;
328 john.naylor@postgres 3590 : 15429 : prog_val[1] = TidStoreMemoryUsage(vacrel->dead_items);
501 msawada@postgresql.o 3591 : 15429 : pgstat_progress_update_multi_param(2, prog_index, prog_val);
574 3592 : 15429 : }
3593 : :
3594 : : /*
3595 : : * Forget all collected dead items.
3596 : : */
3597 : : static void
3598 : 690 : dead_items_reset(LVRelState *vacrel)
3599 : : {
3600 [ + + ]: 690 : if (ParallelVacuumIsActive(vacrel))
3601 : : {
3602 : 21 : parallel_vacuum_reset_dead_items(vacrel->pvs);
25 john.naylor@postgres 3603 : 21 : vacrel->dead_items = parallel_vacuum_get_dead_items(vacrel->pvs,
3604 : : &vacrel->dead_items_info);
574 msawada@postgresql.o 3605 : 21 : return;
3606 : : }
3607 : :
3608 : : /* Recreate the tidstore with the same max_bytes limitation */
328 john.naylor@postgres 3609 : 669 : TidStoreDestroy(vacrel->dead_items);
569 3610 : 669 : vacrel->dead_items = TidStoreCreateLocal(vacrel->dead_items_info->max_bytes, true);
3611 : :
3612 : : /* Reset the counter */
574 msawada@postgresql.o 3613 : 669 : vacrel->dead_items_info->num_items = 0;
3614 : : }
3615 : :
3616 : : /*
3617 : : * Perform cleanup for resources allocated in dead_items_alloc
3618 : : */
3619 : : static void
1429 pg@bowt.ie 3620 : 13024 : dead_items_cleanup(LVRelState *vacrel)
3621 : : {
1667 3622 [ + + ]: 13024 : if (!ParallelVacuumIsActive(vacrel))
3623 : : {
3624 : : /* Don't bother with pfree here */
3625 : 13007 : return;
3626 : : }
3627 : :
3628 : : /* End parallel mode */
1405 akapila@postgresql.o 3629 : 17 : parallel_vacuum_end(vacrel->pvs, vacrel->indstats);
3630 : 17 : vacrel->pvs = NULL;
3631 : : }
3632 : :
3633 : : #ifdef USE_ASSERT_CHECKING
3634 : :
3635 : : /*
3636 : : * Wrapper for heap_page_would_be_all_visible() which can be used for callers
3637 : : * that expect no LP_DEAD on the page. Currently assert-only, but there is no
3638 : : * reason not to use it outside of asserts.
3639 : : */
3640 : : static bool
14 melanieplageman@gmai 3641 :GNC 49579 : heap_page_is_all_visible(Relation rel, Buffer buf,
3642 : : TransactionId OldestXmin,
3643 : : bool *all_frozen,
3644 : : TransactionId *visibility_cutoff_xid,
3645 : : OffsetNumber *logging_offnum)
3646 : : {
3647 : :
3648 : 49579 : return heap_page_would_be_all_visible(rel, buf,
3649 : : OldestXmin,
3650 : : NULL, 0,
3651 : : all_frozen,
3652 : : visibility_cutoff_xid,
3653 : : logging_offnum);
3654 : : }
3655 : : #endif
3656 : :
3657 : : /*
3658 : : * Check whether the heap page in buf is all-visible except for the dead
3659 : : * tuples referenced in the deadoffsets array.
3660 : : *
3661 : : * Vacuum uses this to check if a page would become all-visible after reaping
3662 : : * known dead tuples. This function does not remove the dead items.
3663 : : *
3664 : : * This cannot be called in a critical section, as the visibility checks may
3665 : : * perform IO and allocate memory.
3666 : : *
3667 : : * Returns true if the page is all-visible other than the provided
3668 : : * deadoffsets and false otherwise.
3669 : : *
3670 : : * OldestXmin is used to determine visibility.
3671 : : *
3672 : : * Output parameters:
3673 : : *
3674 : : * - *all_frozen: true if every tuple on the page is frozen
3675 : : * - *visibility_cutoff_xid: newest xmin; valid only if page is all-visible
3676 : : * - *logging_offnum: OffsetNumber of current tuple being processed;
3677 : : * used by vacuum's error callback system.
3678 : : *
3679 : : * Callers looking to verify that the page is already all-visible can call
3680 : : * heap_page_is_all_visible().
3681 : : *
3682 : : * This logic is closely related to heap_prune_record_unchanged_lp_normal().
3683 : : * If you modify this function, ensure consistency with that code. An
3684 : : * assertion cross-checks that both remain in agreement. Do not introduce new
3685 : : * side-effects.
3686 : : */
3687 : : static bool
3688 : 64956 : heap_page_would_be_all_visible(Relation rel, Buffer buf,
3689 : : TransactionId OldestXmin,
3690 : : OffsetNumber *deadoffsets,
3691 : : int ndeadoffsets,
3692 : : bool *all_frozen,
3693 : : TransactionId *visibility_cutoff_xid,
3694 : : OffsetNumber *logging_offnum)
3695 : : {
3478 kgrittn@postgresql.o 3696 :CBC 64956 : Page page = BufferGetPage(buf);
3811 bruce@momjian.us 3697 : 64956 : BlockNumber blockno = BufferGetBlockNumber(buf);
3698 : : OffsetNumber offnum,
3699 : : maxoff;
4535 3700 : 64956 : bool all_visible = true;
15 melanieplageman@gmai 3701 :GNC 64956 : int matched_dead_count = 0;
3702 : :
4640 heikki.linnakangas@i 3703 :CBC 64956 : *visibility_cutoff_xid = InvalidTransactionId;
3528 rhaas@postgresql.org 3704 : 64956 : *all_frozen = true;
3705 : :
15 melanieplageman@gmai 3706 [ + + - + ]:GNC 64956 : Assert(ndeadoffsets == 0 || deadoffsets);
3707 : :
3708 : : #ifdef USE_ASSERT_CHECKING
3709 : : /* Confirm input deadoffsets[] is strictly sorted */
3710 [ + + ]: 64956 : if (ndeadoffsets > 1)
3711 : : {
3712 [ + + ]: 947284 : for (int i = 1; i < ndeadoffsets; i++)
3713 [ - + ]: 933335 : Assert(deadoffsets[i - 1] < deadoffsets[i]);
3714 : : }
3715 : : #endif
3716 : :
4640 heikki.linnakangas@i 3717 :CBC 64956 : maxoff = PageGetMaxOffsetNumber(page);
3718 : 64956 : for (offnum = FirstOffsetNumber;
4535 bruce@momjian.us 3719 [ + + + + ]: 4633710 : offnum <= maxoff && all_visible;
3720 : 4568754 : offnum = OffsetNumberNext(offnum))
3721 : : {
3722 : : ItemId itemid;
3723 : : HeapTupleData tuple;
3724 : :
3725 : : /*
3726 : : * Set the offset number so that we can display it along with any
3727 : : * error that occurred while processing this tuple.
3728 : : */
14 melanieplageman@gmai 3729 :GNC 4568758 : *logging_offnum = offnum;
4640 heikki.linnakangas@i 3730 :CBC 4568758 : itemid = PageGetItemId(page, offnum);
3731 : :
3732 : : /* Unused or redirect line pointers are of no interest */
3733 [ + + + + ]: 4568758 : if (!ItemIdIsUsed(itemid) || ItemIdIsRedirected(itemid))
3734 : 1021972 : continue;
3735 : :
4001 andres@anarazel.de 3736 : 4492630 : ItemPointerSet(&(tuple.t_self), blockno, offnum);
3737 : :
3738 : : /*
3739 : : * Dead line pointers can have index pointers pointing to them. So
3740 : : * they can't be treated as visible
3741 : : */
4640 heikki.linnakangas@i 3742 [ + + ]: 4492630 : if (ItemIdIsDead(itemid))
3743 : : {
15 melanieplageman@gmai 3744 [ + - + - ]:GNC 945848 : if (!deadoffsets ||
3745 : 945848 : matched_dead_count >= ndeadoffsets ||
3746 [ + + ]: 945848 : deadoffsets[matched_dead_count] != offnum)
3747 : : {
3748 : 4 : *all_frozen = all_visible = false;
3749 : 4 : break;
3750 : : }
3751 : 945844 : matched_dead_count++;
3752 : 945844 : continue;
3753 : : }
3754 : :
4640 heikki.linnakangas@i 3755 [ - + ]:CBC 3546782 : Assert(ItemIdIsNormal(itemid));
3756 : :
3757 : 3546782 : tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
4481 rhaas@postgresql.org 3758 : 3546782 : tuple.t_len = ItemIdGetLength(itemid);
14 melanieplageman@gmai 3759 :GNC 3546782 : tuple.t_tableOid = RelationGetRelid(rel);
3760 : :
3761 : : /* Visibility checks may do IO or allocate memory */
15 3762 [ - + ]: 3546782 : Assert(CritSectionCount == 0);
14 3763 [ + + - ]: 3546782 : switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
3764 : : {
4640 heikki.linnakangas@i 3765 :CBC 3546663 : case HEAPTUPLE_LIVE:
3766 : : {
3767 : : TransactionId xmin;
3768 : :
3769 : : /* Check comments in lazy_scan_prune. */
4328 rhaas@postgresql.org 3770 [ - + ]: 3546663 : if (!HeapTupleHeaderXminCommitted(tuple.t_data))
3771 : : {
4640 heikki.linnakangas@i 3772 :UBC 0 : all_visible = false;
3434 rhaas@postgresql.org 3773 : 0 : *all_frozen = false;
4640 heikki.linnakangas@i 3774 : 0 : break;
3775 : : }
3776 : :
3777 : : /*
3778 : : * The inserter definitely committed. But is it old enough
3779 : : * that everyone sees it as committed?
3780 : : */
4640 heikki.linnakangas@i 3781 :CBC 3546663 : xmin = HeapTupleHeaderGetXmin(tuple.t_data);
14 melanieplageman@gmai 3782 [ + + ]:GNC 3546663 : if (!TransactionIdPrecedes(xmin, OldestXmin))
3783 : : {
4640 heikki.linnakangas@i 3784 :CBC 47 : all_visible = false;
3434 rhaas@postgresql.org 3785 : 47 : *all_frozen = false;
4640 heikki.linnakangas@i 3786 : 47 : break;
3787 : : }
3788 : :
3789 : : /* Track newest xmin on page. */
1030 pg@bowt.ie 3790 [ + + + + ]: 3546616 : if (TransactionIdFollows(xmin, *visibility_cutoff_xid) &&
3791 : : TransactionIdIsNormal(xmin))
4640 heikki.linnakangas@i 3792 : 28170 : *visibility_cutoff_xid = xmin;
3793 : :
3794 : : /* Check whether this tuple is already frozen or not */
3528 rhaas@postgresql.org 3795 [ + - + + : 5758643 : if (all_visible && *all_frozen &&
+ + ]
3796 : 2212027 : heap_tuple_needs_eventual_freeze(tuple.t_data))
3797 : 14935 : *all_frozen = false;
3798 : : }
4640 heikki.linnakangas@i 3799 : 3546616 : break;
3800 : :
3801 : 119 : case HEAPTUPLE_DEAD:
3802 : : case HEAPTUPLE_RECENTLY_DEAD:
3803 : : case HEAPTUPLE_INSERT_IN_PROGRESS:
3804 : : case HEAPTUPLE_DELETE_IN_PROGRESS:
3805 : : {
3434 rhaas@postgresql.org 3806 : 119 : all_visible = false;
3807 : 119 : *all_frozen = false;
3808 : 119 : break;
3809 : : }
4640 heikki.linnakangas@i 3810 :UBC 0 : default:
3811 [ # # ]: 0 : elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
3812 : : break;
3813 : : }
3814 : : } /* scan along page */
3815 : :
3816 : : /* Clear the offset information once we have processed the given page. */
14 melanieplageman@gmai 3817 :GNC 64956 : *logging_offnum = InvalidOffsetNumber;
3818 : :
4640 heikki.linnakangas@i 3819 :CBC 64956 : return all_visible;
3820 : : }
3821 : :
3822 : : /*
3823 : : * Update index statistics in pg_class if the statistics are accurate.
3824 : : */
3825 : : static void
1326 pg@bowt.ie 3826 : 12896 : update_relstats_all_indexes(LVRelState *vacrel)
3827 : : {
1667 3828 : 12896 : Relation *indrels = vacrel->indrels;
3829 : 12896 : int nindexes = vacrel->nindexes;
3830 : 12896 : IndexBulkDeleteResult **indstats = vacrel->indstats;
3831 : :
1326 3832 [ - + ]: 12896 : Assert(vacrel->do_index_cleanup);
3833 : :
1667 3834 [ + + ]: 31397 : for (int idx = 0; idx < nindexes; idx++)
3835 : : {
3836 : 18501 : Relation indrel = indrels[idx];
3837 : 18501 : IndexBulkDeleteResult *istat = indstats[idx];
3838 : :
3839 [ + + + + ]: 18501 : if (istat == NULL || istat->estimated_count)
2108 akapila@postgresql.o 3840 : 17073 : continue;
3841 : :
3842 : : /* Update index statistics */
1667 pg@bowt.ie 3843 : 1428 : vac_update_relstats(indrel,
3844 : : istat->num_pages,
3845 : : istat->num_index_tuples,
3846 : : 0, 0,
3847 : : false,
3848 : : InvalidTransactionId,
3849 : : InvalidMultiXactId,
3850 : : NULL, NULL, false);
3851 : : }
2108 akapila@postgresql.o 3852 : 12896 : }
3853 : :
3854 : : /*
3855 : : * Error context callback for errors occurring during vacuum. The error
3856 : : * context messages for index phases should match the messages set in parallel
3857 : : * vacuum. If you change this function for those phases, change
3858 : : * parallel_vacuum_error_callback() as well.
3859 : : */
3860 : : static void
2038 3861 : 13 : vacuum_error_callback(void *arg)
3862 : : {
1667 pg@bowt.ie 3863 : 13 : LVRelState *errinfo = arg;
3864 : :
2038 akapila@postgresql.o 3865 [ - - - - : 13 : switch (errinfo->phase)
+ + ]
3866 : : {
2038 akapila@postgresql.o 3867 :UBC 0 : case VACUUM_ERRCB_PHASE_SCAN_HEAP:
3868 [ # # ]: 0 : if (BlockNumberIsValid(errinfo->blkno))
3869 : : {
1889 3870 [ # # # # : 0 : if (OffsetNumberIsValid(errinfo->offnum))
# # ]
1543 peter@eisentraut.org 3871 : 0 : errcontext("while scanning block %u offset %u of relation \"%s.%s\"",
1889 akapila@postgresql.o 3872 : 0 : errinfo->blkno, errinfo->offnum, errinfo->relnamespace, errinfo->relname);
3873 : : else
3874 : 0 : errcontext("while scanning block %u of relation \"%s.%s\"",
3875 : : errinfo->blkno, errinfo->relnamespace, errinfo->relname);
3876 : : }
3877 : : else
1891 3878 : 0 : errcontext("while scanning relation \"%s.%s\"",
3879 : : errinfo->relnamespace, errinfo->relname);
2038 3880 : 0 : break;
3881 : :
3882 : 0 : case VACUUM_ERRCB_PHASE_VACUUM_HEAP:
3883 [ # # ]: 0 : if (BlockNumberIsValid(errinfo->blkno))
3884 : : {
1889 3885 [ # # # # : 0 : if (OffsetNumberIsValid(errinfo->offnum))
# # ]
1543 peter@eisentraut.org 3886 : 0 : errcontext("while vacuuming block %u offset %u of relation \"%s.%s\"",
1889 akapila@postgresql.o 3887 : 0 : errinfo->blkno, errinfo->offnum, errinfo->relnamespace, errinfo->relname);
3888 : : else
3889 : 0 : errcontext("while vacuuming block %u of relation \"%s.%s\"",
3890 : : errinfo->blkno, errinfo->relnamespace, errinfo->relname);
3891 : : }
3892 : : else
1891 3893 : 0 : errcontext("while vacuuming relation \"%s.%s\"",
3894 : : errinfo->relnamespace, errinfo->relname);
2038 3895 : 0 : break;
3896 : :
3897 : 0 : case VACUUM_ERRCB_PHASE_VACUUM_INDEX:
3898 : 0 : errcontext("while vacuuming index \"%s\" of relation \"%s.%s\"",
3899 : : errinfo->indname, errinfo->relnamespace, errinfo->relname);
3900 : 0 : break;
3901 : :
3902 : 0 : case VACUUM_ERRCB_PHASE_INDEX_CLEANUP:
3903 : 0 : errcontext("while cleaning up index \"%s\" of relation \"%s.%s\"",
3904 : : errinfo->indname, errinfo->relnamespace, errinfo->relname);
3905 : 0 : break;
3906 : :
2038 akapila@postgresql.o 3907 :CBC 3 : case VACUUM_ERRCB_PHASE_TRUNCATE:
3908 [ + - ]: 3 : if (BlockNumberIsValid(errinfo->blkno))
3909 : 3 : errcontext("while truncating relation \"%s.%s\" to %u blocks",
3910 : : errinfo->relnamespace, errinfo->relname, errinfo->blkno);
3911 : 3 : break;
3912 : :
3913 : 10 : case VACUUM_ERRCB_PHASE_UNKNOWN:
3914 : : default:
3915 : 10 : return; /* do nothing; the errinfo may not be
3916 : : * initialized */
3917 : : }
3918 : : }
3919 : :
3920 : : /*
3921 : : * Updates the information required for vacuum error callback. This also saves
3922 : : * the current information which can be later restored via restore_vacuum_error_info.
3923 : : */
3924 : : static void
1667 pg@bowt.ie 3925 : 106663 : update_vacuum_error_info(LVRelState *vacrel, LVSavedErrInfo *saved_vacrel,
3926 : : int phase, BlockNumber blkno, OffsetNumber offnum)
3927 : : {
3928 [ + + ]: 106663 : if (saved_vacrel)
3929 : : {
3930 : 35759 : saved_vacrel->offnum = vacrel->offnum;
3931 : 35759 : saved_vacrel->blkno = vacrel->blkno;
3932 : 35759 : saved_vacrel->phase = vacrel->phase;
3933 : : }
3934 : :
3935 : 106663 : vacrel->blkno = blkno;
3936 : 106663 : vacrel->offnum = offnum;
3937 : 106663 : vacrel->phase = phase;
1945 akapila@postgresql.o 3938 : 106663 : }
3939 : :
3940 : : /*
3941 : : * Restores the vacuum information saved via a prior call to update_vacuum_error_info.
3942 : : */
3943 : : static void
1667 pg@bowt.ie 3944 : 35759 : restore_vacuum_error_info(LVRelState *vacrel,
3945 : : const LVSavedErrInfo *saved_vacrel)
3946 : : {
3947 : 35759 : vacrel->blkno = saved_vacrel->blkno;
3948 : 35759 : vacrel->offnum = saved_vacrel->offnum;
3949 : 35759 : vacrel->phase = saved_vacrel->phase;
2038 akapila@postgresql.o 3950 : 35759 : }
|