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