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