PostgreSQL Source Code git master
Loading...
Searching...
No Matches
pruneheap.c File Reference
#include "postgres.h"
#include "access/heapam.h"
#include "access/heapam_xlog.h"
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/transam.h"
#include "access/visibilitymap.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
#include "commands/vacuum.h"
#include "executor/instrument.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
Include dependency graph for pruneheap.c:

Go to the source code of this file.

Data Structures

struct  PruneState
 

Typedefs

typedef enum VMCorruptionType VMCorruptionType
 

Enumerations

enum  VMCorruptionType { VM_CORRUPT_MISSING_PAGE_HINT , VM_CORRUPT_LPDEAD , VM_CORRUPT_TUPLE_VISIBILITY }
 

Functions

static void prune_freeze_setup (PruneFreezeParams *params, TransactionId *new_relfrozen_xid, MultiXactId *new_relmin_mxid, PruneFreezeResult *presult, PruneState *prstate)
 
static void heap_page_fix_vm_corruption (PruneState *prstate, OffsetNumber offnum, VMCorruptionType ctype)
 
static void prune_freeze_fast_path (PruneState *prstate, PruneFreezeResult *presult)
 
static void prune_freeze_plan (PruneState *prstate, OffsetNumber *off_loc)
 
static HTSV_Result heap_prune_satisfies_vacuum (PruneState *prstate, HeapTuple tup)
 
static HTSV_Result htsv_get_valid_status (int status)
 
static void heap_prune_chain (OffsetNumber maxoff, OffsetNumber rootoffnum, PruneState *prstate)
 
static void heap_prune_record_prunable (PruneState *prstate, TransactionId xid, OffsetNumber offnum)
 
static void heap_prune_record_redirect (PruneState *prstate, OffsetNumber offnum, OffsetNumber rdoffnum, bool was_normal)
 
static void heap_prune_record_dead (PruneState *prstate, OffsetNumber offnum, bool was_normal)
 
static void heap_prune_record_dead_or_unused (PruneState *prstate, OffsetNumber offnum, bool was_normal)
 
static void heap_prune_record_unused (PruneState *prstate, OffsetNumber offnum, bool was_normal)
 
static void heap_prune_record_unchanged_lp_unused (PruneState *prstate, OffsetNumber offnum)
 
static void heap_prune_record_unchanged_lp_normal (PruneState *prstate, OffsetNumber offnum)
 
static void heap_prune_record_unchanged_lp_dead (PruneState *prstate, OffsetNumber offnum)
 
static void heap_prune_record_unchanged_lp_redirect (PruneState *prstate, OffsetNumber offnum)
 
static void page_verify_redirects (Page page)
 
static bool heap_page_will_freeze (bool did_tuple_hint_fpi, bool do_prune, bool do_hint_prune, PruneState *prstate)
 
static bool heap_page_will_set_vm (PruneState *prstate, PruneReason reason, bool do_prune, bool do_freeze)
 
void heap_page_prune_opt (Relation relation, Buffer buffer, Buffer *vmbuffer, bool rel_read_only)
 
void heap_page_prune_and_freeze (PruneFreezeParams *params, PruneFreezeResult *presult, OffsetNumber *off_loc, TransactionId *new_relfrozen_xid, MultiXactId *new_relmin_mxid)
 
void heap_page_prune_execute (Buffer buffer, bool lp_truncate_only, OffsetNumber *redirected, int nredirected, OffsetNumber *nowdead, int ndead, OffsetNumber *nowunused, int nunused)
 
void heap_get_root_tuples (Page page, OffsetNumber *root_offsets)
 
static bool heap_log_freeze_eq (xlhp_freeze_plan *plan, HeapTupleFreeze *frz)
 
static int heap_log_freeze_cmp (const void *arg1, const void *arg2)
 
static void heap_log_freeze_new_plan (xlhp_freeze_plan *plan, HeapTupleFreeze *frz)
 
static int heap_log_freeze_plan (HeapTupleFreeze *tuples, int ntuples, xlhp_freeze_plan *plans_out, OffsetNumber *offsets_out)
 
void log_heap_prune_and_freeze (Relation relation, Buffer buffer, Buffer vmbuffer, uint8 vmflags, TransactionId conflict_xid, bool cleanup_lock, PruneReason reason, HeapTupleFreeze *frozen, int nfrozen, OffsetNumber *redirected, int nredirected, OffsetNumber *dead, int ndead, OffsetNumber *unused, int nunused)
 

Typedef Documentation

◆ VMCorruptionType

Enumeration Type Documentation

◆ VMCorruptionType

Enumerator
VM_CORRUPT_MISSING_PAGE_HINT 
VM_CORRUPT_LPDEAD 
VM_CORRUPT_TUPLE_VISIBILITY 

Definition at line 190 of file pruneheap.c.

191{
192 /* VM bits are set but the heap page-level PD_ALL_VISIBLE flag is not */
194 /* LP_DEAD line pointers found on a page marked all-visible */
196 /* Tuple not visible to all transactions on a page marked all-visible */
VMCorruptionType
Definition pruneheap.c:191
@ VM_CORRUPT_MISSING_PAGE_HINT
Definition pruneheap.c:193
@ VM_CORRUPT_LPDEAD
Definition pruneheap.c:195
@ VM_CORRUPT_TUPLE_VISIBILITY
Definition pruneheap.c:197

Function Documentation

◆ heap_get_root_tuples()

void heap_get_root_tuples ( Page  page,
OffsetNumber root_offsets 
)

Definition at line 2289 of file pruneheap.c.

2290{
2291 OffsetNumber offnum,
2292 maxoff;
2293
2296
2297 maxoff = PageGetMaxOffsetNumber(page);
2298 for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum))
2299 {
2300 ItemId lp = PageGetItemId(page, offnum);
2301 HeapTupleHeader htup;
2304
2305 /* skip unused and dead items */
2306 if (!ItemIdIsUsed(lp) || ItemIdIsDead(lp))
2307 continue;
2308
2309 if (ItemIdIsNormal(lp))
2310 {
2311 htup = (HeapTupleHeader) PageGetItem(page, lp);
2312
2313 /*
2314 * Check if this tuple is part of a HOT-chain rooted at some other
2315 * tuple. If so, skip it for now; we'll process it when we find
2316 * its root.
2317 */
2318 if (HeapTupleHeaderIsHeapOnly(htup))
2319 continue;
2320
2321 /*
2322 * This is either a plain tuple or the root of a HOT-chain.
2323 * Remember it in the mapping.
2324 */
2325 root_offsets[offnum - 1] = offnum;
2326
2327 /* If it's not the start of a HOT-chain, we're done with it */
2328 if (!HeapTupleHeaderIsHotUpdated(htup))
2329 continue;
2330
2331 /* Set up to scan the HOT-chain */
2334 }
2335 else
2336 {
2337 /* Must be a redirect item. We do not set its root_offsets entry */
2339 /* Set up to scan the HOT-chain */
2342 }
2343
2344 /*
2345 * Now follow the HOT-chain and collect other tuples in the chain.
2346 *
2347 * Note: Even though this is a nested loop, the complexity of the
2348 * function is O(N) because a tuple in the page should be visited not
2349 * more than twice, once in the outer loop and once in HOT-chain
2350 * chases.
2351 */
2352 for (;;)
2353 {
2354 /* Sanity check (pure paranoia) */
2355 if (offnum < FirstOffsetNumber)
2356 break;
2357
2358 /*
2359 * An offset past the end of page's line pointer array is possible
2360 * when the array was truncated
2361 */
2362 if (offnum > maxoff)
2363 break;
2364
2365 lp = PageGetItemId(page, nextoffnum);
2366
2367 /* Check for broken chains */
2368 if (!ItemIdIsNormal(lp))
2369 break;
2370
2371 htup = (HeapTupleHeader) PageGetItem(page, lp);
2372
2375 break;
2376
2377 /* Remember the root line pointer for this item */
2378 root_offsets[nextoffnum - 1] = offnum;
2379
2380 /* Advance to next chain member, if any */
2381 if (!HeapTupleHeaderIsHotUpdated(htup))
2382 break;
2383
2384 /* HOT implies it can't have moved to different partition */
2386
2389 }
2390 }
2391}
static ItemId PageGetItemId(Page page, OffsetNumber offsetNumber)
Definition bufpage.h:268
static void * PageGetItem(PageData *page, const ItemIdData *itemId)
Definition bufpage.h:378
static OffsetNumber PageGetMaxOffsetNumber(const PageData *page)
Definition bufpage.h:396
#define Assert(condition)
Definition c.h:943
#define MemSet(start, val, len)
Definition c.h:1107
uint32 TransactionId
Definition c.h:736
HeapTupleHeaderData * HeapTupleHeader
Definition htup.h:23
static bool HeapTupleHeaderIsHeapOnly(const HeapTupleHeaderData *tup)
static TransactionId HeapTupleHeaderGetXmin(const HeapTupleHeaderData *tup)
static bool HeapTupleHeaderIndicatesMovedPartitions(const HeapTupleHeaderData *tup)
static bool HeapTupleHeaderIsHotUpdated(const HeapTupleHeaderData *tup)
static TransactionId HeapTupleHeaderGetUpdateXid(const HeapTupleHeaderData *tup)
#define MaxHeapTuplesPerPage
#define ItemIdIsNormal(itemId)
Definition itemid.h:99
#define ItemIdGetRedirect(itemId)
Definition itemid.h:78
#define ItemIdIsDead(itemId)
Definition itemid.h:113
#define ItemIdIsUsed(itemId)
Definition itemid.h:92
#define ItemIdIsRedirected(itemId)
Definition itemid.h:106
static OffsetNumber ItemPointerGetOffsetNumber(const ItemPointerData *pointer)
Definition itemptr.h:124
#define InvalidOffsetNumber
Definition off.h:26
#define OffsetNumberNext(offsetNumber)
Definition off.h:52
uint16 OffsetNumber
Definition off.h:24
#define FirstOffsetNumber
Definition off.h:27
static int fb(int x)
ItemPointerData t_ctid
#define InvalidTransactionId
Definition transam.h:31
#define TransactionIdEquals(id1, id2)
Definition transam.h:43
#define TransactionIdIsValid(xid)
Definition transam.h:41

References Assert, fb(), FirstOffsetNumber, HeapTupleHeaderGetUpdateXid(), HeapTupleHeaderGetXmin(), HeapTupleHeaderIndicatesMovedPartitions(), HeapTupleHeaderIsHeapOnly(), HeapTupleHeaderIsHotUpdated(), InvalidOffsetNumber, InvalidTransactionId, ItemIdGetRedirect, ItemIdIsDead, ItemIdIsNormal, ItemIdIsRedirected, ItemIdIsUsed, ItemPointerGetOffsetNumber(), MaxHeapTuplesPerPage, MemSet, OffsetNumberNext, PageGetItem(), PageGetItemId(), PageGetMaxOffsetNumber(), HeapTupleHeaderData::t_ctid, TransactionIdEquals, and TransactionIdIsValid.

Referenced by heapam_index_build_range_scan(), and heapam_index_validate_scan().

◆ heap_log_freeze_cmp()

static int heap_log_freeze_cmp ( const void arg1,
const void arg2 
)
static

Definition at line 2416 of file pruneheap.c.

2417{
2418 const HeapTupleFreeze *frz1 = arg1;
2419 const HeapTupleFreeze *frz2 = arg2;
2420
2421 if (frz1->xmax < frz2->xmax)
2422 return -1;
2423 else if (frz1->xmax > frz2->xmax)
2424 return 1;
2425
2426 if (frz1->t_infomask2 < frz2->t_infomask2)
2427 return -1;
2428 else if (frz1->t_infomask2 > frz2->t_infomask2)
2429 return 1;
2430
2431 if (frz1->t_infomask < frz2->t_infomask)
2432 return -1;
2433 else if (frz1->t_infomask > frz2->t_infomask)
2434 return 1;
2435
2436 if (frz1->frzflags < frz2->frzflags)
2437 return -1;
2438 else if (frz1->frzflags > frz2->frzflags)
2439 return 1;
2440
2441 /*
2442 * heap_log_freeze_eq would consider these tuple-wise plans to be equal.
2443 * (So the tuples will share a single canonical freeze plan.)
2444 *
2445 * We tiebreak on page offset number to keep each freeze plan's page
2446 * offset number array individually sorted. (Unnecessary, but be tidy.)
2447 */
2448 if (frz1->offset < frz2->offset)
2449 return -1;
2450 else if (frz1->offset > frz2->offset)
2451 return 1;
2452
2453 Assert(false);
2454 return 0;
2455}

References Assert, and fb().

Referenced by heap_log_freeze_plan().

◆ heap_log_freeze_eq()

static bool heap_log_freeze_eq ( xlhp_freeze_plan plan,
HeapTupleFreeze frz 
)
inlinestatic

Definition at line 2400 of file pruneheap.c.

2401{
2402 if (plan->xmax == frz->xmax &&
2403 plan->t_infomask2 == frz->t_infomask2 &&
2404 plan->t_infomask == frz->t_infomask &&
2405 plan->frzflags == frz->frzflags)
2406 return true;
2407
2408 /* Caller must call heap_log_freeze_new_plan again for frz */
2409 return false;
2410}
#define plan(x)
Definition pg_regress.c:164

References fb(), and plan.

Referenced by heap_log_freeze_plan().

◆ heap_log_freeze_new_plan()

static void heap_log_freeze_new_plan ( xlhp_freeze_plan plan,
HeapTupleFreeze frz 
)
inlinestatic

Definition at line 2462 of file pruneheap.c.

2463{
2464 plan->xmax = frz->xmax;
2465 plan->t_infomask2 = frz->t_infomask2;
2466 plan->t_infomask = frz->t_infomask;
2467 plan->frzflags = frz->frzflags;
2468 plan->ntuples = 1; /* for now */
2469}

References fb(), and plan.

Referenced by heap_log_freeze_plan().

◆ heap_log_freeze_plan()

static int heap_log_freeze_plan ( HeapTupleFreeze tuples,
int  ntuples,
xlhp_freeze_plan plans_out,
OffsetNumber offsets_out 
)
static

Definition at line 2482 of file pruneheap.c.

2485{
2486 int nplans = 0;
2487
2488 /* Sort tuple-based freeze plans in the order required to deduplicate */
2489 qsort(tuples, ntuples, sizeof(HeapTupleFreeze), heap_log_freeze_cmp);
2490
2491 for (int i = 0; i < ntuples; i++)
2492 {
2493 HeapTupleFreeze *frz = tuples + i;
2494
2495 if (i == 0)
2496 {
2497 /* New canonical freeze plan starting with first tup */
2499 nplans++;
2500 }
2501 else if (heap_log_freeze_eq(plans_out, frz))
2502 {
2503 /* tup matches open canonical plan -- include tup in it */
2504 Assert(offsets_out[i - 1] < frz->offset);
2505 plans_out->ntuples++;
2506 }
2507 else
2508 {
2509 /* Tup doesn't match current plan -- done with it now */
2510 plans_out++;
2511
2512 /* New canonical freeze plan starting with this tup */
2514 nplans++;
2515 }
2516
2517 /*
2518 * Save page offset number in dedicated buffer in passing.
2519 *
2520 * REDO routine relies on the record's offset numbers array grouping
2521 * offset numbers by freeze plan. The sort order within each grouping
2522 * is ascending offset number order, just to keep things tidy.
2523 */
2524 offsets_out[i] = frz->offset;
2525 }
2526
2527 Assert(nplans > 0 && nplans <= ntuples);
2528
2529 return nplans;
2530}
int i
Definition isn.c:77
#define qsort(a, b, c, d)
Definition port.h:495
static int heap_log_freeze_cmp(const void *arg1, const void *arg2)
Definition pruneheap.c:2416
static bool heap_log_freeze_eq(xlhp_freeze_plan *plan, HeapTupleFreeze *frz)
Definition pruneheap.c:2400
static void heap_log_freeze_new_plan(xlhp_freeze_plan *plan, HeapTupleFreeze *frz)
Definition pruneheap.c:2462

References Assert, fb(), heap_log_freeze_cmp(), heap_log_freeze_eq(), heap_log_freeze_new_plan(), i, and qsort.

Referenced by log_heap_prune_and_freeze().

◆ heap_page_fix_vm_corruption()

static void heap_page_fix_vm_corruption ( PruneState prstate,
OffsetNumber  offnum,
VMCorruptionType  ctype 
)
static

Definition at line 852 of file pruneheap.c.

854{
855 const char *relname = RelationGetRelationName(prstate->relation);
856 bool do_clear_vm = false;
857 bool do_clear_heap = false;
858
860
861 switch (corruption_type)
862 {
866 errmsg("dead line pointer found on page marked all-visible"),
867 errcontext("relation \"%s\", page %u, tuple %u",
868 relname, prstate->block, offnum)));
869 do_clear_vm = true;
870 do_clear_heap = true;
871 break;
872
874
875 /*
876 * A HEAPTUPLE_LIVE tuple on an all-visible page can appear to not
877 * be visible to everyone when
878 * GetOldestNonRemovableTransactionId() returns a conservative
879 * value that's older than the real safe xmin. That is not
880 * corruption -- the PD_ALL_VISIBLE flag is still correct.
881 *
882 * However, dead tuple versions, in-progress inserts, and
883 * in-progress deletes should never appear on a page marked
884 * all-visible. That indicates real corruption. PD_ALL_VISIBLE
885 * should have been cleared by the DML operation that deleted or
886 * inserted the tuple.
887 */
890 errmsg("tuple not visible to all transactions found on page marked all-visible"),
891 errcontext("relation \"%s\", page %u, tuple %u",
892 relname, prstate->block, offnum)));
893 do_clear_vm = true;
894 do_clear_heap = true;
895 break;
896
898
899 /*
900 * As of PostgreSQL 9.2, the visibility map bit should never be
901 * set if the page-level bit is clear. However, for vacuum, it's
902 * possible that the bit got cleared after
903 * heap_vac_scan_next_block() was called, so we must recheck now
904 * that we have the buffer lock before concluding that the VM is
905 * corrupt.
906 */
911 errmsg("page is not marked all-visible but visibility map bit is set"),
912 errcontext("relation \"%s\", page %u",
913 relname, prstate->block)));
914 do_clear_vm = true;
915 break;
916 }
917
919
920 /* Avoid marking the buffer dirty if PD_ALL_VISIBLE is already clear */
921 if (do_clear_heap)
922 {
925 MarkBufferDirtyHint(prstate->buffer, true);
926 }
927
928 if (do_clear_vm)
929 {
930 visibilitymap_clear(prstate->relation, prstate->block,
931 prstate->vmbuffer,
933 prstate->old_vmbits = 0;
934 }
935}
bool BufferIsLockedByMeInMode(Buffer buffer, BufferLockMode mode)
Definition bufmgr.c:3087
void MarkBufferDirtyHint(Buffer buffer, bool buffer_std)
Definition bufmgr.c:5821
@ BUFFER_LOCK_EXCLUSIVE
Definition bufmgr.h:222
static bool PageIsAllVisible(const PageData *page)
Definition bufpage.h:454
static void PageClearAllVisible(Page page)
Definition bufpage.h:464
int errcode(int sqlerrcode)
Definition elog.c:874
#define errcontext
Definition elog.h:200
#define WARNING
Definition elog.h:37
#define ereport(elevel,...)
Definition elog.h:152
static char * errmsg
#define ERRCODE_DATA_CORRUPTED
NameData relname
Definition pg_class.h:40
#define RelationGetRelationName(relation)
Definition rel.h:550
bool visibilitymap_clear(Relation rel, BlockNumber heapBlk, Buffer vmbuf, uint8 flags)
#define VISIBILITYMAP_VALID_BITS

References Assert, BUFFER_LOCK_EXCLUSIVE, BufferIsLockedByMeInMode(), ereport, errcode(), ERRCODE_DATA_CORRUPTED, errcontext, errmsg, fb(), MarkBufferDirtyHint(), PageClearAllVisible(), PageIsAllVisible(), RelationGetRelationName, relname, visibilitymap_clear(), VISIBILITYMAP_VALID_BITS, VM_CORRUPT_LPDEAD, VM_CORRUPT_MISSING_PAGE_HINT, VM_CORRUPT_TUPLE_VISIBILITY, and WARNING.

Referenced by heap_page_prune_and_freeze(), heap_prune_record_dead_or_unused(), heap_prune_record_prunable(), and heap_prune_record_unchanged_lp_dead().

◆ heap_page_prune_and_freeze()

void heap_page_prune_and_freeze ( PruneFreezeParams params,
PruneFreezeResult presult,
OffsetNumber off_loc,
TransactionId new_relfrozen_xid,
MultiXactId new_relmin_mxid 
)

Definition at line 1090 of file pruneheap.c.

1095{
1097 bool do_freeze;
1098 bool do_prune;
1099 bool do_hint_prune;
1100 bool do_set_vm;
1101 bool did_tuple_hint_fpi;
1104
1105 /* Initialize prstate */
1106 prune_freeze_setup(params,
1108 presult, &prstate);
1109
1110 /*
1111 * If the VM is set but PD_ALL_VISIBLE is clear, fix that corruption
1112 * before pruning and freezing so that the page and VM start out in a
1113 * consistent state.
1114 */
1115 if ((prstate.old_vmbits & VISIBILITYMAP_VALID_BITS) &&
1119
1120 /*
1121 * If the page is already all-frozen, or already all-visible when freezing
1122 * is not being attempted, take the fast path, skipping pruning and
1123 * freezing code entirely. This must be done after fixing any discrepancy
1124 * between the page-level visibility hint and the VM, since that may have
1125 * cleared old_vmbits.
1126 */
1127 if ((params->options & HEAP_PAGE_PRUNE_ALLOW_FAST_PATH) != 0 &&
1128 ((prstate.old_vmbits & VISIBILITYMAP_ALL_FROZEN) ||
1129 ((prstate.old_vmbits & VISIBILITYMAP_ALL_VISIBLE) &&
1130 !prstate.attempt_freeze)))
1131 {
1133 return;
1134 }
1135
1136 /*
1137 * Examine all line pointers and tuple visibility information to determine
1138 * which line pointers should change state and which tuples may be frozen.
1139 * Prepare queue of state changes to later be executed in a critical
1140 * section.
1141 */
1143
1144 /*
1145 * After processing all the live tuples on the page, if the newest xmin
1146 * amongst them may be considered running by any snapshot, the page cannot
1147 * be all-visible. This should be done before determining whether or not
1148 * to opportunistically freeze.
1149 */
1150 if (prstate.set_all_visible &&
1151 TransactionIdIsNormal(prstate.newest_live_xid) &&
1153 prstate.newest_live_xid,
1154 true))
1155 prstate.set_all_visible = prstate.set_all_frozen = false;
1156
1157 /*
1158 * If checksums are enabled, calling heap_prune_satisfies_vacuum() while
1159 * checking tuple visibility information in prune_freeze_plan() may have
1160 * caused an FPI to be emitted.
1161 */
1163
1164 do_prune = prstate.nredirected > 0 ||
1165 prstate.ndead > 0 ||
1166 prstate.nunused > 0;
1167
1168 /*
1169 * Even if we don't prune anything, if we found a new value for the
1170 * pd_prune_xid field or the page was marked full, we will update the hint
1171 * bit.
1172 */
1173 do_hint_prune = PageGetPruneXid(prstate.page) != prstate.new_prune_xid ||
1174 PageIsFull(prstate.page);
1175
1176 /*
1177 * Decide if we want to go ahead with freezing according to the freeze
1178 * plans we prepared, or not.
1179 */
1181 do_prune,
1183 &prstate);
1184
1185 /*
1186 * While scanning the line pointers, we did not clear
1187 * set_all_visible/set_all_frozen when encountering LP_DEAD items because
1188 * we wanted the decision whether or not to freeze the page to be
1189 * unaffected by the short-term presence of LP_DEAD items. These LP_DEAD
1190 * items are effectively assumed to be LP_UNUSED items in the making. It
1191 * doesn't matter which vacuum heap pass (initial pass or final pass) ends
1192 * up setting the page all-frozen, as long as the ongoing VACUUM does it.
1193 *
1194 * Now that we finished determining whether or not to freeze the page,
1195 * update set_all_visible and set_all_frozen so that they reflect the true
1196 * state of the page for setting PD_ALL_VISIBLE and VM bits.
1197 */
1198 if (prstate.lpdead_items > 0)
1199 prstate.set_all_visible = prstate.set_all_frozen = false;
1200
1201 Assert(!prstate.set_all_frozen || prstate.set_all_visible);
1202 Assert(!prstate.set_all_visible || prstate.attempt_set_vm);
1203 Assert(!prstate.set_all_visible || (prstate.lpdead_items == 0));
1204
1206
1207 /*
1208 * new_vmbits should be 0 regardless of whether or not the page is
1209 * all-visible if we do not intend to set the VM.
1210 */
1211 Assert(do_set_vm || prstate.new_vmbits == 0);
1212
1213 /*
1214 * The snapshot conflict horizon for the whole record is the most
1215 * conservative (newest) horizon required by any change in the record.
1216 */
1218 if (do_set_vm)
1219 conflict_xid = prstate.newest_live_xid;
1220 if (do_freeze && TransactionIdFollows(prstate.pagefrz.FreezePageConflictXid, conflict_xid))
1221 conflict_xid = prstate.pagefrz.FreezePageConflictXid;
1222 if (do_prune && TransactionIdFollows(prstate.latest_xid_removed, conflict_xid))
1223 conflict_xid = prstate.latest_xid_removed;
1224
1225 /* Lock vmbuffer before entering a critical section */
1226 if (do_set_vm)
1228
1229 /* Any error while applying the changes is critical */
1231
1232 if (do_hint_prune)
1233 {
1234 /*
1235 * Update the page's pd_prune_xid field to either zero, or the lowest
1236 * XID of any soon-prunable tuple.
1237 */
1238 ((PageHeader) prstate.page)->pd_prune_xid = prstate.new_prune_xid;
1239
1240 /*
1241 * Also clear the "page is full" flag, since there's no point in
1242 * repeating the prune/defrag process until something else happens to
1243 * the page.
1244 */
1245 PageClearFull(prstate.page);
1246
1247 /*
1248 * If that's all we had to do to the page, this is a non-WAL-logged
1249 * hint. If we are going to freeze or prune the page or set
1250 * PD_ALL_VISIBLE, we will mark the buffer dirty below.
1251 *
1252 * Setting PD_ALL_VISIBLE is fully WAL-logged because it is forbidden
1253 * for the VM to be set and PD_ALL_VISIBLE to be clear.
1254 */
1255 if (!do_freeze && !do_prune && !do_set_vm)
1256 MarkBufferDirtyHint(prstate.buffer, true);
1257 }
1258
1259 if (do_prune || do_freeze || do_set_vm)
1260 {
1261 /* Apply the planned item changes and repair page fragmentation. */
1262 if (do_prune)
1263 {
1264 heap_page_prune_execute(prstate.buffer, false,
1265 prstate.redirected, prstate.nredirected,
1266 prstate.nowdead, prstate.ndead,
1267 prstate.nowunused, prstate.nunused);
1268 }
1269
1270 if (do_freeze)
1271 heap_freeze_prepared_tuples(prstate.buffer, prstate.frozen, prstate.nfrozen);
1272
1273 /* Set the visibility map and page visibility hint */
1274 if (do_set_vm)
1275 {
1276 /*
1277 * While it is valid for PD_ALL_VISIBLE to be set when the
1278 * corresponding VM bit is clear, we strongly prefer to keep them
1279 * in sync.
1280 *
1281 * The heap buffer must be marked dirty before adding it to the
1282 * WAL chain when setting the VM. We don't worry about
1283 * unnecessarily dirtying the heap buffer if PD_ALL_VISIBLE is
1284 * already set, though. It is extremely rare to have a clean heap
1285 * buffer with PD_ALL_VISIBLE already set and the VM bits clear,
1286 * so there is no point in optimizing it.
1287 */
1290 visibilitymap_set(prstate.block, prstate.vmbuffer, prstate.new_vmbits,
1291 prstate.relation->rd_locator);
1292 }
1293
1294 MarkBufferDirty(prstate.buffer);
1295
1296 /*
1297 * Emit a WAL XLOG_HEAP2_PRUNE* record showing what we did
1298 */
1299 if (RelationNeedsWAL(prstate.relation))
1300 {
1301 log_heap_prune_and_freeze(prstate.relation, prstate.buffer,
1302 do_set_vm ? prstate.vmbuffer : InvalidBuffer,
1303 do_set_vm ? prstate.new_vmbits : 0,
1305 true, /* cleanup lock */
1306 params->reason,
1307 prstate.frozen, prstate.nfrozen,
1308 prstate.redirected, prstate.nredirected,
1309 prstate.nowdead, prstate.ndead,
1310 prstate.nowunused, prstate.nunused);
1311 }
1312 }
1313
1315
1316 if (do_set_vm)
1318
1319 /*
1320 * During its second pass over the heap, VACUUM calls
1321 * heap_page_would_be_all_visible() to determine whether a page is
1322 * all-visible and all-frozen. The logic here is similar. After completing
1323 * pruning and freezing, use an assertion to verify that our results
1324 * remain consistent with heap_page_would_be_all_visible(). It's also a
1325 * valuable cross-check of the page state after pruning and freezing.
1326 */
1327#ifdef USE_ASSERT_CHECKING
1328 if (prstate.set_all_visible)
1329 {
1331 bool debug_all_frozen;
1332
1333 Assert(prstate.lpdead_items == 0);
1334
1336 prstate.vistest,
1339
1341 debug_cutoff == prstate.newest_live_xid);
1342
1343 /*
1344 * It's possible the page is composed entirely of frozen tuples but is
1345 * not set all-frozen in the VM and did not pass
1346 * HEAP_PAGE_PRUNE_FREEZE. In this case, it's possible
1347 * heap_page_is_all_visible() finds the page completely frozen, even
1348 * though prstate.set_all_frozen is false.
1349 */
1350 Assert(!prstate.set_all_frozen || debug_all_frozen);
1351 }
1352#endif
1353
1354 /* Copy information back for caller */
1355 presult->ndeleted = prstate.ndeleted;
1356 presult->nnewlpdead = prstate.ndead;
1357 presult->nfrozen = prstate.nfrozen;
1358 presult->live_tuples = prstate.live_tuples;
1359 presult->recently_dead_tuples = prstate.recently_dead_tuples;
1360 presult->hastup = prstate.hastup;
1361
1362 presult->lpdead_items = prstate.lpdead_items;
1363 /* the presult->deadoffsets array was already filled in */
1364
1365 presult->newly_all_visible = false;
1366 presult->newly_all_frozen = false;
1367 presult->newly_all_visible_frozen = false;
1368 if (do_set_vm)
1369 {
1370 if ((prstate.old_vmbits & VISIBILITYMAP_ALL_VISIBLE) == 0)
1371 {
1372 presult->newly_all_visible = true;
1373 if (prstate.set_all_frozen)
1374 presult->newly_all_visible_frozen = true;
1375 }
1376 else if ((prstate.old_vmbits & VISIBILITYMAP_ALL_FROZEN) == 0 &&
1377 prstate.set_all_frozen)
1378 presult->newly_all_frozen = true;
1379 }
1380
1381 if (prstate.attempt_freeze)
1382 {
1383 if (presult->nfrozen > 0)
1384 {
1385 *new_relfrozen_xid = prstate.pagefrz.FreezePageRelfrozenXid;
1386 *new_relmin_mxid = prstate.pagefrz.FreezePageRelminMxid;
1387 }
1388 else
1389 {
1390 *new_relfrozen_xid = prstate.pagefrz.NoFreezePageRelfrozenXid;
1391 *new_relmin_mxid = prstate.pagefrz.NoFreezePageRelminMxid;
1392 }
1393 }
1394}
#define InvalidBuffer
Definition buf.h:25
void MarkBufferDirty(Buffer buffer)
Definition bufmgr.c:3147
@ BUFFER_LOCK_UNLOCK
Definition bufmgr.h:207
static void LockBuffer(Buffer buffer, BufferLockMode mode)
Definition bufmgr.h:334
PageHeaderData * PageHeader
Definition bufpage.h:199
static TransactionId PageGetPruneXid(const PageData *page)
Definition bufpage.h:470
static void PageClearFull(Page page)
Definition bufpage.h:448
static void PageSetAllVisible(Page page)
Definition bufpage.h:459
#define PageClearPrunable(page)
Definition bufpage.h:485
static bool PageIsFull(const PageData *page)
Definition bufpage.h:438
int64_t int64
Definition c.h:621
void heap_freeze_prepared_tuples(Buffer buffer, HeapTupleFreeze *tuples, int ntuples)
Definition heapam.c:7360
#define HEAP_PAGE_PRUNE_ALLOW_FAST_PATH
Definition heapam.h:44
WalUsage pgWalUsage
Definition instrument.c:27
return true
Definition isn.c:130
#define START_CRIT_SECTION()
Definition miscadmin.h:152
#define END_CRIT_SECTION()
Definition miscadmin.h:154
bool GlobalVisTestXidConsideredRunning(GlobalVisState *state, TransactionId xid, bool allow_update)
Definition procarray.c:4328
static void prune_freeze_fast_path(PruneState *prstate, PruneFreezeResult *presult)
Definition pruneheap.c:1007
static void prune_freeze_plan(PruneState *prstate, OffsetNumber *off_loc)
Definition pruneheap.c:531
static bool heap_page_will_freeze(bool did_tuple_hint_fpi, bool do_prune, bool do_hint_prune, PruneState *prstate)
Definition pruneheap.c:734
static bool heap_page_will_set_vm(PruneState *prstate, PruneReason reason, bool do_prune, bool do_freeze)
Definition pruneheap.c:950
void log_heap_prune_and_freeze(Relation relation, Buffer buffer, Buffer vmbuffer, uint8 vmflags, TransactionId conflict_xid, bool cleanup_lock, PruneReason reason, HeapTupleFreeze *frozen, int nfrozen, OffsetNumber *redirected, int nredirected, OffsetNumber *dead, int ndead, OffsetNumber *unused, int nunused)
Definition pruneheap.c:2561
static void prune_freeze_setup(PruneFreezeParams *params, TransactionId *new_relfrozen_xid, MultiXactId *new_relmin_mxid, PruneFreezeResult *presult, PruneState *prstate)
Definition pruneheap.c:400
static void heap_page_fix_vm_corruption(PruneState *prstate, OffsetNumber offnum, VMCorruptionType ctype)
Definition pruneheap.c:852
void heap_page_prune_execute(Buffer buffer, bool lp_truncate_only, OffsetNumber *redirected, int nredirected, OffsetNumber *nowdead, int ndead, OffsetNumber *nowunused, int nunused)
Definition pruneheap.c:2065
#define RelationNeedsWAL(relation)
Definition rel.h:639
PruneReason reason
Definition heapam.h:276
int64 wal_fpi
Definition instrument.h:54
static bool TransactionIdFollows(TransactionId id1, TransactionId id2)
Definition transam.h:297
#define TransactionIdIsNormal(xid)
Definition transam.h:42
void visibilitymap_set(BlockNumber heapBlk, Buffer vmBuf, uint8 flags, const RelFileLocator rlocator)
#define VISIBILITYMAP_ALL_FROZEN
#define VISIBILITYMAP_ALL_VISIBLE

References Assert, BUFFER_LOCK_EXCLUSIVE, BUFFER_LOCK_UNLOCK, END_CRIT_SECTION, fb(), GlobalVisTestXidConsideredRunning(), heap_freeze_prepared_tuples(), heap_page_fix_vm_corruption(), HEAP_PAGE_PRUNE_ALLOW_FAST_PATH, heap_page_prune_execute(), heap_page_will_freeze(), heap_page_will_set_vm(), InvalidBuffer, InvalidOffsetNumber, InvalidTransactionId, LockBuffer(), log_heap_prune_and_freeze(), MarkBufferDirty(), MarkBufferDirtyHint(), PruneFreezeParams::options, PageClearFull(), PageClearPrunable, PageGetPruneXid(), PageIsAllVisible(), PageIsFull(), PageSetAllVisible(), pgWalUsage, prune_freeze_fast_path(), prune_freeze_plan(), prune_freeze_setup(), PruneFreezeParams::reason, RelationNeedsWAL, START_CRIT_SECTION, TransactionIdFollows(), TransactionIdIsNormal, TransactionIdIsValid, VISIBILITYMAP_ALL_FROZEN, VISIBILITYMAP_ALL_VISIBLE, visibilitymap_set(), VISIBILITYMAP_VALID_BITS, VM_CORRUPT_MISSING_PAGE_HINT, and WalUsage::wal_fpi.

Referenced by heap_page_prune_opt(), and lazy_scan_prune().

◆ heap_page_prune_execute()

void heap_page_prune_execute ( Buffer  buffer,
bool  lp_truncate_only,
OffsetNumber redirected,
int  nredirected,
OffsetNumber nowdead,
int  ndead,
OffsetNumber nowunused,
int  nunused 
)

Definition at line 2065 of file pruneheap.c.

2069{
2070 Page page = BufferGetPage(buffer);
2071 OffsetNumber *offnum;
2073
2074 /* Shouldn't be called unless there's something to do */
2075 Assert(nredirected > 0 || ndead > 0 || nunused > 0);
2076
2077 /* If 'lp_truncate_only', we can only remove already-dead line pointers */
2078 Assert(!lp_truncate_only || (nredirected == 0 && ndead == 0));
2079
2080 /* Update all redirected line pointers */
2081 offnum = redirected;
2082 for (int i = 0; i < nredirected; i++)
2083 {
2084 OffsetNumber fromoff = *offnum++;
2085 OffsetNumber tooff = *offnum++;
2088
2089#ifdef USE_ASSERT_CHECKING
2090
2091 /*
2092 * Any existing item that we set as an LP_REDIRECT (any 'from' item)
2093 * must be the first item from a HOT chain. If the item has tuple
2094 * storage then it can't be a heap-only tuple. Otherwise we are just
2095 * maintaining an existing LP_REDIRECT from an existing HOT chain that
2096 * has been pruned at least once before now.
2097 */
2099 {
2101
2102 htup = (HeapTupleHeader) PageGetItem(page, fromlp);
2104 }
2105 else
2106 {
2107 /* We shouldn't need to redundantly set the redirect */
2109 }
2110
2111 /*
2112 * The item that we're about to set as an LP_REDIRECT (the 'from'
2113 * item) will point to an existing item (the 'to' item) that is
2114 * already a heap-only tuple. There can be at most one LP_REDIRECT
2115 * item per HOT chain.
2116 *
2117 * We need to keep around an LP_REDIRECT item (after original
2118 * non-heap-only root tuple gets pruned away) so that it's always
2119 * possible for VACUUM to easily figure out what TID to delete from
2120 * indexes when an entire HOT chain becomes dead. A heap-only tuple
2121 * can never become LP_DEAD; an LP_REDIRECT item or a regular heap
2122 * tuple can.
2123 *
2124 * This check may miss problems, e.g. the target of a redirect could
2125 * be marked as unused subsequently. The page_verify_redirects() check
2126 * below will catch such problems.
2127 */
2128 tolp = PageGetItemId(page, tooff);
2130 htup = (HeapTupleHeader) PageGetItem(page, tolp);
2132#endif
2133
2135 }
2136
2137 /* Update all now-dead line pointers */
2138 offnum = nowdead;
2139 for (int i = 0; i < ndead; i++)
2140 {
2141 OffsetNumber off = *offnum++;
2142 ItemId lp = PageGetItemId(page, off);
2143
2144#ifdef USE_ASSERT_CHECKING
2145
2146 /*
2147 * An LP_DEAD line pointer must be left behind when the original item
2148 * (which is dead to everybody) could still be referenced by a TID in
2149 * an index. This should never be necessary with any individual
2150 * heap-only tuple item, though. (It's not clear how much of a problem
2151 * that would be, but there is no reason to allow it.)
2152 */
2153 if (ItemIdHasStorage(lp))
2154 {
2156 htup = (HeapTupleHeader) PageGetItem(page, lp);
2158 }
2159 else
2160 {
2161 /* Whole HOT chain becomes dead */
2163 }
2164#endif
2165
2167 }
2168
2169 /* Update all now-unused line pointers */
2170 offnum = nowunused;
2171 for (int i = 0; i < nunused; i++)
2172 {
2173 OffsetNumber off = *offnum++;
2174 ItemId lp = PageGetItemId(page, off);
2175
2176#ifdef USE_ASSERT_CHECKING
2177
2178 if (lp_truncate_only)
2179 {
2180 /* Setting LP_DEAD to LP_UNUSED in vacuum's second pass */
2182 }
2183 else
2184 {
2185 /*
2186 * When heap_page_prune_and_freeze() was called, mark_unused_now
2187 * may have been passed as true, which allows would-be LP_DEAD
2188 * items to be made LP_UNUSED instead. This is only possible if
2189 * the relation has no indexes. If there are any dead items, then
2190 * mark_unused_now was not true and every item being marked
2191 * LP_UNUSED must refer to a heap-only tuple.
2192 */
2193 if (ndead > 0)
2194 {
2196 htup = (HeapTupleHeader) PageGetItem(page, lp);
2198 }
2199 else
2201 }
2202
2203#endif
2204
2206 }
2207
2208 if (lp_truncate_only)
2210 else
2211 {
2212 /*
2213 * Finally, repair any fragmentation, and update the page's hint bit
2214 * about whether it has free pointers.
2215 */
2217
2218 /*
2219 * Now that the page has been modified, assert that redirect items
2220 * still point to valid targets.
2221 */
2223 }
2224}
static Page BufferGetPage(Buffer buffer)
Definition bufmgr.h:468
void PageRepairFragmentation(Page page)
Definition bufpage.c:708
void PageTruncateLinePointerArray(Page page)
Definition bufpage.c:844
PageData * Page
Definition bufpage.h:81
#define PG_USED_FOR_ASSERTS_ONLY
Definition c.h:249
#define ItemIdSetRedirect(itemId, link)
Definition itemid.h:152
#define ItemIdSetDead(itemId)
Definition itemid.h:164
#define ItemIdSetUnused(itemId)
Definition itemid.h:128
#define ItemIdHasStorage(itemId)
Definition itemid.h:120
static void page_verify_redirects(Page page)
Definition pruneheap.c:2241

References Assert, BufferGetPage(), fb(), HeapTupleHeaderIsHeapOnly(), i, ItemIdGetRedirect, ItemIdHasStorage, ItemIdIsDead, ItemIdIsNormal, ItemIdIsRedirected, ItemIdIsUsed, ItemIdSetDead, ItemIdSetRedirect, ItemIdSetUnused, page_verify_redirects(), PageGetItem(), PageGetItemId(), PageRepairFragmentation(), PageTruncateLinePointerArray(), and PG_USED_FOR_ASSERTS_ONLY.

Referenced by heap_page_prune_and_freeze(), and heap_xlog_prune_freeze().

◆ heap_page_prune_opt()

void heap_page_prune_opt ( Relation  relation,
Buffer  buffer,
Buffer vmbuffer,
bool  rel_read_only 
)

Definition at line 271 of file pruneheap.c.

273{
274 Page page = BufferGetPage(buffer);
276 GlobalVisState *vistest;
278
279 /*
280 * We can't write WAL in recovery mode, so there's no point trying to
281 * clean the page. The primary will likely issue a cleaning WAL record
282 * soon anyway, so this is no particular loss.
283 */
284 if (RecoveryInProgress())
285 return;
286
287 /*
288 * First check whether there's any chance there's something to prune,
289 * determining the appropriate horizon is a waste if there's no prune_xid
290 * (i.e. no updates/deletes left potentially dead tuples around and no
291 * inserts inserted new tuples that may be visible to all).
292 */
295 return;
296
297 /*
298 * Check whether prune_xid indicates that there may be dead rows that can
299 * be cleaned up.
300 */
301 vistest = GlobalVisTestFor(relation);
302
303 if (!GlobalVisTestIsRemovableXid(vistest, prune_xid, true))
304 return;
305
306 /*
307 * We prune when a previous UPDATE failed to find enough space on the page
308 * for a new tuple version, or when free space falls below the relation's
309 * fill-factor target (but not less than 10%).
310 *
311 * Checking free space here is questionable since we aren't holding any
312 * lock on the buffer; in the worst case we could get a bogus answer. It's
313 * unlikely to be *seriously* wrong, though, since reading either pd_lower
314 * or pd_upper is probably atomic. Avoiding taking a lock seems more
315 * important than sometimes getting a wrong answer in what is after all
316 * just a heuristic estimate.
317 */
320 minfree = Max(minfree, BLCKSZ / 10);
321
322 if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
323 {
324 /* OK, try to get exclusive buffer lock */
326 return;
327
328 /*
329 * Now that we have buffer lock, get accurate information about the
330 * page's free space, and recheck the heuristic about whether to
331 * prune.
332 */
333 if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
334 {
337 PruneFreezeParams params;
338
339 visibilitymap_pin(relation, BufferGetBlockNumber(buffer),
340 vmbuffer);
341
342 params.relation = relation;
343 params.buffer = buffer;
344 params.vmbuffer = *vmbuffer;
345 params.reason = PRUNE_ON_ACCESS;
346 params.vistest = vistest;
347 params.cutoffs = NULL;
348
349 /*
350 * We don't pass the HEAP_PAGE_PRUNE_MARK_UNUSED_NOW option
351 * regardless of whether or not the relation has indexes, since we
352 * cannot safely determine that during on-access pruning with the
353 * current implementation.
354 */
356 if (rel_read_only)
358
360 NULL, NULL);
361
362 /*
363 * Report the number of tuples reclaimed to pgstats. This is
364 * presult.ndeleted minus the number of newly-LP_DEAD-set items.
365 *
366 * We derive the number of dead tuples like this to avoid totally
367 * forgetting about items that were set to LP_DEAD, since they
368 * still need to be cleaned up by VACUUM. We only want to count
369 * heap-only tuples that just became LP_UNUSED in our report,
370 * which don't.
371 *
372 * VACUUM doesn't have to compensate in the same way when it
373 * tracks ndeleted, since it will set the same LP_DEAD items to
374 * LP_UNUSED separately.
375 */
376 if (presult.ndeleted > presult.nnewlpdead)
378 presult.ndeleted - presult.nnewlpdead);
379 }
380
381 /* And release buffer lock */
383
384 /*
385 * We avoid reuse of any free space created on the page by unrelated
386 * UPDATEs/INSERTs by opting to not update the FSM at this point. The
387 * free space should be reused by UPDATEs to *this* page.
388 */
389 }
390}
BlockNumber BufferGetBlockNumber(Buffer buffer)
Definition bufmgr.c:4446
bool ConditionalLockBufferForCleanup(Buffer buffer)
Definition bufmgr.c:6843
Size PageGetHeapFreeSpace(const PageData *page)
Definition bufpage.c:1000
#define Max(x, y)
Definition c.h:1085
size_t Size
Definition c.h:689
#define HEAP_PAGE_PRUNE_SET_VM
Definition heapam.h:45
@ PRUNE_ON_ACCESS
Definition heapam.h:252
void pgstat_update_heap_dead_tuples(Relation rel, int delta)
GlobalVisState * GlobalVisTestFor(Relation rel)
Definition procarray.c:4127
bool GlobalVisTestIsRemovableXid(GlobalVisState *state, TransactionId xid, bool allow_update)
Definition procarray.c:4290
void heap_page_prune_and_freeze(PruneFreezeParams *params, PruneFreezeResult *presult, OffsetNumber *off_loc, TransactionId *new_relfrozen_xid, MultiXactId *new_relmin_mxid)
Definition pruneheap.c:1090
#define RelationGetTargetPageFreeSpace(relation, defaultff)
Definition rel.h:391
#define HEAP_DEFAULT_FILLFACTOR
Definition rel.h:362
VacuumCutoffs * cutoffs
Definition heapam.h:301
GlobalVisState * vistest
Definition heapam.h:292
Relation relation
Definition heapam.h:262
Buffer vmbuffer
Definition heapam.h:270
void visibilitymap_pin(Relation rel, BlockNumber heapBlk, Buffer *vmbuf)
bool RecoveryInProgress(void)
Definition xlog.c:6830

References PruneFreezeParams::buffer, BUFFER_LOCK_UNLOCK, BufferGetBlockNumber(), BufferGetPage(), ConditionalLockBufferForCleanup(), PruneFreezeParams::cutoffs, fb(), GlobalVisTestFor(), GlobalVisTestIsRemovableXid(), HEAP_DEFAULT_FILLFACTOR, HEAP_PAGE_PRUNE_ALLOW_FAST_PATH, heap_page_prune_and_freeze(), HEAP_PAGE_PRUNE_SET_VM, LockBuffer(), Max, PruneFreezeParams::options, PageGetHeapFreeSpace(), PageGetPruneXid(), PageIsFull(), pgstat_update_heap_dead_tuples(), PRUNE_ON_ACCESS, PruneFreezeParams::reason, RecoveryInProgress(), PruneFreezeParams::relation, RelationGetTargetPageFreeSpace, TransactionIdIsValid, visibilitymap_pin(), PruneFreezeParams::vistest, and PruneFreezeParams::vmbuffer.

Referenced by BitmapHeapScanNextBlock(), heap_prepare_pagescan(), and heapam_index_fetch_tuple().

◆ heap_page_will_freeze()

static bool heap_page_will_freeze ( bool  did_tuple_hint_fpi,
bool  do_prune,
bool  do_hint_prune,
PruneState prstate 
)
static

Definition at line 734 of file pruneheap.c.

738{
739 bool do_freeze = false;
740
741 /*
742 * If the caller specified we should not attempt to freeze any tuples,
743 * validate that everything is in the right state and return.
744 */
745 if (!prstate->attempt_freeze)
746 {
747 Assert(!prstate->set_all_frozen && prstate->nfrozen == 0);
748 return false;
749 }
750
751 if (prstate->pagefrz.freeze_required)
752 {
753 /*
754 * heap_prepare_freeze_tuple indicated that at least one XID/MXID from
755 * before FreezeLimit/MultiXactCutoff is present. Must freeze to
756 * advance relfrozenxid/relminmxid.
757 */
758 do_freeze = true;
759 }
760 else
761 {
762 /*
763 * Opportunistically freeze the page if we are generating an FPI
764 * anyway and if doing so means that we can set the page all-frozen
765 * afterwards (might not happen until VACUUM's final heap pass).
766 *
767 * XXX: Previously, we knew if pruning emitted an FPI by checking
768 * pgWalUsage.wal_fpi before and after pruning. Once the freeze and
769 * prune records were combined, this heuristic couldn't be used
770 * anymore. The opportunistic freeze heuristic must be improved;
771 * however, for now, try to approximate the old logic.
772 */
773 if (prstate->set_all_frozen && prstate->nfrozen > 0)
774 {
775 Assert(prstate->set_all_visible);
776
777 /*
778 * Freezing would make the page all-frozen. Have already emitted
779 * an FPI or will do so anyway?
780 */
781 if (RelationNeedsWAL(prstate->relation))
782 {
784 do_freeze = true;
785 else if (do_prune)
786 {
788 do_freeze = true;
789 }
790 else if (do_hint_prune)
791 {
792 if (XLogHintBitIsNeeded() &&
794 do_freeze = true;
795 }
796 }
797 }
798 }
799
800 if (do_freeze)
801 {
802 /*
803 * Validate the tuples we will be freezing before entering the
804 * critical section.
805 */
806 heap_pre_freeze_checks(prstate->buffer, prstate->frozen, prstate->nfrozen);
807 Assert(TransactionIdPrecedes(prstate->pagefrz.FreezePageConflictXid,
808 prstate->cutoffs->OldestXmin));
809 }
810 else if (prstate->nfrozen > 0)
811 {
812 /*
813 * The page contained some tuples that were not already frozen, and we
814 * chose not to freeze them now. The page won't be all-frozen then.
815 */
816 Assert(!prstate->pagefrz.freeze_required);
817
818 prstate->set_all_frozen = false;
819 prstate->nfrozen = 0; /* avoid miscounts in instrumentation */
820 }
821 else
822 {
823 /*
824 * We have no freeze plans to execute. The page might already be
825 * all-frozen (perhaps only following pruning), though. Such pages
826 * can be marked all-frozen in the VM by our caller, even though none
827 * of its tuples were newly frozen here.
828 */
829 }
830
831 return do_freeze;
832}
void heap_pre_freeze_checks(Buffer buffer, HeapTupleFreeze *tuples, int ntuples)
Definition heapam.c:7307
static bool TransactionIdPrecedes(TransactionId id1, TransactionId id2)
Definition transam.h:263
#define XLogHintBitIsNeeded()
Definition xlog.h:123
bool XLogCheckBufferNeedsBackup(Buffer buffer)

References Assert, fb(), heap_pre_freeze_checks(), RelationNeedsWAL, TransactionIdPrecedes(), XLogCheckBufferNeedsBackup(), and XLogHintBitIsNeeded.

Referenced by heap_page_prune_and_freeze().

◆ heap_page_will_set_vm()

static bool heap_page_will_set_vm ( PruneState prstate,
PruneReason  reason,
bool  do_prune,
bool  do_freeze 
)
static

Definition at line 950 of file pruneheap.c.

952{
953 if (!prstate->attempt_set_vm)
954 return false;
955
956 if (!prstate->set_all_visible)
957 return false;
958
959 /*
960 * If this is an on-access call and we're not actually pruning, avoid
961 * setting the visibility map if it would newly dirty the heap page or, if
962 * the page is already dirty, if doing so would require including a
963 * full-page image (FPI) of the heap page in the WAL.
964 */
965 if (reason == PRUNE_ON_ACCESS && !do_prune && !do_freeze &&
967 {
968 prstate->set_all_visible = prstate->set_all_frozen = false;
969 return false;
970 }
971
973
974 if (prstate->set_all_frozen)
975 prstate->new_vmbits |= VISIBILITYMAP_ALL_FROZEN;
976
977 if (prstate->new_vmbits == prstate->old_vmbits)
978 {
979 prstate->new_vmbits = 0;
980 return false;
981 }
982
983 return true;
984}
bool BufferIsDirty(Buffer buffer)
Definition bufmgr.c:3114

References BufferIsDirty(), fb(), PRUNE_ON_ACCESS, VISIBILITYMAP_ALL_FROZEN, VISIBILITYMAP_ALL_VISIBLE, and XLogCheckBufferNeedsBackup().

Referenced by heap_page_prune_and_freeze().

◆ heap_prune_chain()

static void heap_prune_chain ( OffsetNumber  maxoff,
OffsetNumber  rootoffnum,
PruneState prstate 
)
static

Definition at line 1483 of file pruneheap.c.

1485{
1487 ItemId rootlp;
1488 OffsetNumber offnum;
1490 Page page = prstate->page;
1491
1492 /*
1493 * After traversing the HOT chain, ndeadchain is the index in chainitems
1494 * of the first live successor after the last dead item.
1495 */
1496 int ndeadchain = 0,
1497 nchain = 0;
1498
1500
1501 /* Start from the root tuple */
1502 offnum = rootoffnum;
1503
1504 /* while not end of the chain */
1505 for (;;)
1506 {
1507 HeapTupleHeader htup;
1508 ItemId lp;
1509
1510 /* Sanity check (pure paranoia) */
1511 if (offnum < FirstOffsetNumber)
1512 break;
1513
1514 /*
1515 * An offset past the end of page's line pointer array is possible
1516 * when the array was truncated (original item must have been unused)
1517 */
1518 if (offnum > maxoff)
1519 break;
1520
1521 /* If item is already processed, stop --- it must not be same chain */
1522 if (prstate->processed[offnum])
1523 break;
1524
1525 lp = PageGetItemId(page, offnum);
1526
1527 /*
1528 * Unused item obviously isn't part of the chain. Likewise, a dead
1529 * line pointer can't be part of the chain. Both of those cases were
1530 * already marked as processed.
1531 */
1534
1535 /*
1536 * If we are looking at the redirected root line pointer, jump to the
1537 * first normal tuple in the chain. If we find a redirect somewhere
1538 * else, stop --- it must not be same chain.
1539 */
1541 {
1542 if (nchain > 0)
1543 break; /* not at start of chain */
1544 chainitems[nchain++] = offnum;
1545 offnum = ItemIdGetRedirect(rootlp);
1546 continue;
1547 }
1548
1550
1551 htup = (HeapTupleHeader) PageGetItem(page, lp);
1552
1553 /*
1554 * Check the tuple XMIN against prior XMAX, if any
1555 */
1558 break;
1559
1560 /*
1561 * OK, this tuple is indeed a member of the chain.
1562 */
1563 chainitems[nchain++] = offnum;
1564
1565 switch (htsv_get_valid_status(prstate->htsv[offnum]))
1566 {
1567 case HEAPTUPLE_DEAD:
1568
1569 /* Remember the last DEAD tuple seen */
1572 &prstate->latest_xid_removed);
1573 /* Advance to next chain member */
1574 break;
1575
1577
1578 /*
1579 * We don't need to advance the conflict horizon for
1580 * RECENTLY_DEAD tuples, even if we are removing them. This
1581 * is because we only remove RECENTLY_DEAD tuples if they
1582 * precede a DEAD tuple, and the DEAD tuple must have been
1583 * inserted by a newer transaction than the RECENTLY_DEAD
1584 * tuple by virtue of being later in the chain. We will have
1585 * advanced the conflict horizon for the DEAD tuple.
1586 */
1587
1588 /*
1589 * Advance past RECENTLY_DEAD tuples just in case there's a
1590 * DEAD one after them. We have to make sure that we don't
1591 * miss any DEAD tuples, since DEAD tuples that still have
1592 * tuple storage after pruning will confuse VACUUM.
1593 */
1594 break;
1595
1597 case HEAPTUPLE_LIVE:
1599 goto process_chain;
1600
1601 default:
1602 elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
1603 goto process_chain;
1604 }
1605
1606 /*
1607 * If the tuple is not HOT-updated, then we are at the end of this
1608 * HOT-update chain.
1609 */
1610 if (!HeapTupleHeaderIsHotUpdated(htup))
1611 goto process_chain;
1612
1613 /* HOT implies it can't have moved to different partition */
1615
1616 /*
1617 * Advance to next chain member.
1618 */
1620 offnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
1622 }
1623
1624 if (ItemIdIsRedirected(rootlp) && nchain < 2)
1625 {
1626 /*
1627 * We found a redirect item that doesn't point to a valid follow-on
1628 * item. This can happen if the loop in heap_page_prune_and_freeze()
1629 * caused us to visit the dead successor of a redirect item before
1630 * visiting the redirect item. We can clean up by setting the
1631 * redirect item to LP_DEAD state or LP_UNUSED if the caller
1632 * indicated.
1633 */
1635 return;
1636 }
1637
1639
1640 if (ndeadchain == 0)
1641 {
1642 /*
1643 * No DEAD tuple was found, so the chain is entirely composed of
1644 * normal, unchanged tuples. Leave it alone.
1645 */
1646 int i = 0;
1647
1649 {
1651 i++;
1652 }
1653 for (; i < nchain; i++)
1655 }
1656 else if (ndeadchain == nchain)
1657 {
1658 /*
1659 * The entire chain is dead. Mark the root line pointer LP_DEAD, and
1660 * fully remove the other tuples in the chain.
1661 */
1663 for (int i = 1; i < nchain; i++)
1665 }
1666 else
1667 {
1668 /*
1669 * We found a DEAD tuple in the chain. Redirect the root line pointer
1670 * to the first non-DEAD tuple, and mark as unused each intermediate
1671 * item that we are able to remove from the chain.
1672 */
1675 for (int i = 1; i < ndeadchain; i++)
1677
1678 /* the rest of tuples in the chain are normal, unchanged tuples */
1679 for (int i = ndeadchain; i < nchain; i++)
1681 }
1682}
#define ERROR
Definition elog.h:40
#define elog(elevel,...)
Definition elog.h:228
void HeapTupleHeaderAdvanceConflictHorizon(HeapTupleHeader tuple, TransactionId *snapshotConflictHorizon)
Definition heapam.c:7954
@ HEAPTUPLE_RECENTLY_DEAD
Definition heapam.h:140
@ HEAPTUPLE_INSERT_IN_PROGRESS
Definition heapam.h:141
@ HEAPTUPLE_LIVE
Definition heapam.h:139
@ HEAPTUPLE_DELETE_IN_PROGRESS
Definition heapam.h:142
@ HEAPTUPLE_DEAD
Definition heapam.h:138
static BlockNumber ItemPointerGetBlockNumber(const ItemPointerData *pointer)
Definition itemptr.h:103
static HTSV_Result htsv_get_valid_status(int status)
Definition pruneheap.c:1444
static void heap_prune_record_unused(PruneState *prstate, OffsetNumber offnum, bool was_normal)
Definition pruneheap.c:1801
static void heap_prune_record_redirect(PruneState *prstate, OffsetNumber offnum, OffsetNumber rdoffnum, bool was_normal)
Definition pruneheap.c:1709
static void heap_prune_record_dead_or_unused(PruneState *prstate, OffsetNumber offnum, bool was_normal)
Definition pruneheap.c:1775
static void heap_prune_record_unchanged_lp_redirect(PruneState *prstate, OffsetNumber offnum)
Definition pruneheap.c:2040
static void heap_prune_record_unchanged_lp_normal(PruneState *prstate, OffsetNumber offnum)
Definition pruneheap.c:1834

References Assert, elog, ERROR, fb(), FirstOffsetNumber, heap_prune_record_dead_or_unused(), heap_prune_record_redirect(), heap_prune_record_unchanged_lp_normal(), heap_prune_record_unchanged_lp_redirect(), heap_prune_record_unused(), HEAPTUPLE_DEAD, HEAPTUPLE_DELETE_IN_PROGRESS, HEAPTUPLE_INSERT_IN_PROGRESS, HEAPTUPLE_LIVE, HEAPTUPLE_RECENTLY_DEAD, HeapTupleHeaderAdvanceConflictHorizon(), HeapTupleHeaderGetUpdateXid(), HeapTupleHeaderGetXmin(), HeapTupleHeaderIndicatesMovedPartitions(), HeapTupleHeaderIsHotUpdated(), htsv_get_valid_status(), i, InvalidTransactionId, ItemIdGetRedirect, ItemIdIsDead, ItemIdIsNormal, ItemIdIsRedirected, ItemIdIsUsed, ItemPointerGetBlockNumber(), ItemPointerGetOffsetNumber(), MaxHeapTuplesPerPage, PageGetItem(), PageGetItemId(), HeapTupleHeaderData::t_ctid, TransactionIdEquals, and TransactionIdIsValid.

Referenced by prune_freeze_plan().

◆ heap_prune_record_dead()

static void heap_prune_record_dead ( PruneState prstate,
OffsetNumber  offnum,
bool  was_normal 
)
static

Definition at line 1740 of file pruneheap.c.

1742{
1743 Assert(!prstate->processed[offnum]);
1744 prstate->processed[offnum] = true;
1745
1747 prstate->nowdead[prstate->ndead] = offnum;
1748 prstate->ndead++;
1749
1750 /*
1751 * Deliberately delay unsetting set_all_visible and set_all_frozen until
1752 * later during pruning. Removable dead tuples shouldn't preclude freezing
1753 * the page.
1754 */
1755
1756 /* Record the dead offset for vacuum */
1757 prstate->deadoffsets[prstate->lpdead_items++] = offnum;
1758
1759 /*
1760 * If the root entry had been a normal tuple, we are deleting it, so count
1761 * it in the result. But changing a redirect (even to DEAD state) doesn't
1762 * count.
1763 */
1764 if (was_normal)
1765 prstate->ndeleted++;
1766}

References Assert, fb(), and MaxHeapTuplesPerPage.

Referenced by heap_prune_record_dead_or_unused().

◆ heap_prune_record_dead_or_unused()

static void heap_prune_record_dead_or_unused ( PruneState prstate,
OffsetNumber  offnum,
bool  was_normal 
)
static

Definition at line 1775 of file pruneheap.c.

1777{
1778 /*
1779 * If the caller set mark_unused_now to true, we can remove dead tuples
1780 * during pruning instead of marking their line pointers dead. Set this
1781 * tuple's line pointer LP_UNUSED. We hint that this option is less
1782 * likely.
1783 */
1784 if (unlikely(prstate->mark_unused_now))
1786 else
1788
1789 /*
1790 * It's incorrect for the page to be set all-visible if it contains dead
1791 * items. Fix that on the heap page and check the VM for corruption as
1792 * well. Do that here rather than in heap_prune_record_dead() so we also
1793 * cover tuples that are directly marked LP_UNUSED via mark_unused_now.
1794 */
1795 if (PageIsAllVisible(prstate->page))
1797}
#define unlikely(x)
Definition c.h:438
static void heap_prune_record_dead(PruneState *prstate, OffsetNumber offnum, bool was_normal)
Definition pruneheap.c:1740

References fb(), heap_page_fix_vm_corruption(), heap_prune_record_dead(), heap_prune_record_unused(), PageIsAllVisible(), unlikely, and VM_CORRUPT_LPDEAD.

Referenced by heap_prune_chain().

◆ heap_prune_record_prunable()

static void heap_prune_record_prunable ( PruneState prstate,
TransactionId  xid,
OffsetNumber  offnum 
)
static

Definition at line 1686 of file pruneheap.c.

1688{
1689 /*
1690 * This should exactly match the PageSetPrunable macro. We can't store
1691 * directly into the page header yet, so we update working state.
1692 */
1694 if (!TransactionIdIsValid(prstate->new_prune_xid) ||
1695 TransactionIdPrecedes(xid, prstate->new_prune_xid))
1696 prstate->new_prune_xid = xid;
1697
1698 /*
1699 * It's incorrect for a page to be marked all-visible if it contains
1700 * prunable items.
1701 */
1702 if (PageIsAllVisible(prstate->page))
1705}

References Assert, fb(), heap_page_fix_vm_corruption(), PageIsAllVisible(), TransactionIdIsNormal, TransactionIdIsValid, TransactionIdPrecedes(), and VM_CORRUPT_TUPLE_VISIBILITY.

Referenced by heap_prune_record_unchanged_lp_normal().

◆ heap_prune_record_redirect()

static void heap_prune_record_redirect ( PruneState prstate,
OffsetNumber  offnum,
OffsetNumber  rdoffnum,
bool  was_normal 
)
static

Definition at line 1709 of file pruneheap.c.

1712{
1713 Assert(!prstate->processed[offnum]);
1714 prstate->processed[offnum] = true;
1715
1716 /*
1717 * Do not mark the redirect target here. It needs to be counted
1718 * separately as an unchanged tuple.
1719 */
1720
1721 Assert(prstate->nredirected < MaxHeapTuplesPerPage);
1722 prstate->redirected[prstate->nredirected * 2] = offnum;
1723 prstate->redirected[prstate->nredirected * 2 + 1] = rdoffnum;
1724
1725 prstate->nredirected++;
1726
1727 /*
1728 * If the root entry had been a normal tuple, we are deleting it, so count
1729 * it in the result. But changing a redirect (even to DEAD state) doesn't
1730 * count.
1731 */
1732 if (was_normal)
1733 prstate->ndeleted++;
1734
1735 prstate->hastup = true;
1736}

References Assert, fb(), and MaxHeapTuplesPerPage.

Referenced by heap_prune_chain().

◆ heap_prune_record_unchanged_lp_dead()

static void heap_prune_record_unchanged_lp_dead ( PruneState prstate,
OffsetNumber  offnum 
)
static

Definition at line 2005 of file pruneheap.c.

2006{
2007 Assert(!prstate->processed[offnum]);
2008 prstate->processed[offnum] = true;
2009
2010 /*
2011 * Deliberately don't set hastup for LP_DEAD items. We make the soft
2012 * assumption that any LP_DEAD items encountered here will become
2013 * LP_UNUSED later on, before count_nondeletable_pages is reached. If we
2014 * don't make this assumption then rel truncation will only happen every
2015 * other VACUUM, at most. Besides, VACUUM must treat
2016 * hastup/nonempty_pages as provisional no matter how LP_DEAD items are
2017 * handled (handled here, or handled later on).
2018 *
2019 * Similarly, don't unset set_all_visible and set_all_frozen until later,
2020 * at the end of heap_page_prune_and_freeze(). This will allow us to
2021 * attempt to freeze the page after pruning. As long as we unset it
2022 * before updating the visibility map, this will be correct.
2023 */
2024
2025 /* Record the dead offset for vacuum */
2026 prstate->deadoffsets[prstate->lpdead_items++] = offnum;
2027
2028 /*
2029 * It's incorrect for a page to be marked all-visible if it contains dead
2030 * items.
2031 */
2032 if (PageIsAllVisible(prstate->page))
2034}

References Assert, fb(), heap_page_fix_vm_corruption(), PageIsAllVisible(), and VM_CORRUPT_LPDEAD.

Referenced by prune_freeze_plan().

◆ heap_prune_record_unchanged_lp_normal()

static void heap_prune_record_unchanged_lp_normal ( PruneState prstate,
OffsetNumber  offnum 
)
static

Definition at line 1834 of file pruneheap.c.

1835{
1836 HeapTupleHeader htup;
1837 TransactionId xmin;
1838 Page page = prstate->page;
1839
1840 Assert(!prstate->processed[offnum]);
1841 prstate->processed[offnum] = true;
1842
1843 prstate->hastup = true; /* the page is not empty */
1844
1845 /*
1846 * The criteria for counting a tuple as live in this block need to match
1847 * what analyze.c's acquire_sample_rows() does, otherwise VACUUM and
1848 * ANALYZE may produce wildly different reltuples values, e.g. when there
1849 * are many recently-dead tuples.
1850 *
1851 * The logic here is a bit simpler than acquire_sample_rows(), as VACUUM
1852 * can't run inside a transaction block, which makes some cases impossible
1853 * (e.g. in-progress insert from the same transaction).
1854 *
1855 * HEAPTUPLE_DEAD are handled by the other heap_prune_record_*()
1856 * subroutines. They don't count dead items like acquire_sample_rows()
1857 * does, because we assume that all dead items will become LP_UNUSED
1858 * before VACUUM finishes. This difference is only superficial. VACUUM
1859 * effectively agrees with ANALYZE about DEAD items, in the end. VACUUM
1860 * won't remember LP_DEAD items, but only because they're not supposed to
1861 * be left behind when it is done. (Cases where we bypass index vacuuming
1862 * will violate this optimistic assumption, but the overall impact of that
1863 * should be negligible.)
1864 */
1865 htup = (HeapTupleHeader) PageGetItem(page, PageGetItemId(page, offnum));
1866
1867 switch (prstate->htsv[offnum])
1868 {
1869 case HEAPTUPLE_LIVE:
1870
1871 /*
1872 * Count it as live. Not only is this natural, but it's also what
1873 * acquire_sample_rows() does.
1874 */
1875 prstate->live_tuples++;
1876
1877 /*
1878 * Is the tuple definitely visible to all transactions?
1879 *
1880 * NB: Like with per-tuple hint bits, we can't set the
1881 * PD_ALL_VISIBLE flag if the inserter committed asynchronously.
1882 * See SetHintBits for more info. Check that the tuple is hinted
1883 * xmin-committed because of that.
1884 */
1886 {
1887 prstate->set_all_visible = false;
1888 prstate->set_all_frozen = false;
1889 break;
1890 }
1891
1892 /*
1893 * The inserter definitely committed. But we don't know if it is
1894 * old enough that everyone sees it as committed. Later, after
1895 * processing all the tuples on the page, we'll check if there is
1896 * any snapshot that still considers the newest xid on the page to
1897 * be running. If so, we don't consider the page all-visible.
1898 */
1899 xmin = HeapTupleHeaderGetXmin(htup);
1900
1901 /* Track newest xmin on page. */
1902 if (TransactionIdFollows(xmin, prstate->newest_live_xid) &&
1904 prstate->newest_live_xid = xmin;
1905
1906 break;
1907
1909 prstate->recently_dead_tuples++;
1910 prstate->set_all_visible = false;
1911 prstate->set_all_frozen = false;
1912
1913 /*
1914 * This tuple will soon become DEAD. Update the hint field so
1915 * that the page is reconsidered for pruning in future.
1916 */
1919 offnum);
1920 break;
1921
1923
1924 /*
1925 * We do not count these rows as live, because we expect the
1926 * inserting transaction to update the counters at commit, and we
1927 * assume that will happen only after we report our results. This
1928 * assumption is a bit shaky, but it is what acquire_sample_rows()
1929 * does, so be consistent.
1930 */
1931 prstate->set_all_visible = false;
1932 prstate->set_all_frozen = false;
1933
1934 /*
1935 * Though there is nothing "prunable" on the page, we maintain
1936 * pd_prune_xid for inserts so that we have the opportunity to
1937 * mark them all-visible during the next round of pruning.
1938 */
1941 offnum);
1942 break;
1943
1945
1946 /*
1947 * This an expected case during concurrent vacuum. Count such
1948 * rows as live. As above, we assume the deleting transaction
1949 * will commit and update the counters after we report.
1950 */
1951 prstate->live_tuples++;
1952 prstate->set_all_visible = false;
1953 prstate->set_all_frozen = false;
1954
1955 /*
1956 * This tuple may soon become DEAD. Update the hint field so that
1957 * the page is reconsidered for pruning in future.
1958 */
1961 offnum);
1962 break;
1963
1964 default:
1965
1966 /*
1967 * DEAD tuples should've been passed to heap_prune_record_dead()
1968 * or heap_prune_record_unused() instead.
1969 */
1970 elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result %d",
1971 prstate->htsv[offnum]);
1972 break;
1973 }
1974
1975 /* Consider freezing any normal tuples which will not be removed */
1976 if (prstate->attempt_freeze)
1977 {
1978 bool totally_frozen;
1979
1980 if ((heap_prepare_freeze_tuple(htup,
1981 prstate->cutoffs,
1982 &prstate->pagefrz,
1983 &prstate->frozen[prstate->nfrozen],
1984 &totally_frozen)))
1985 {
1986 /* Save prepared freeze plan for later */
1987 prstate->frozen[prstate->nfrozen++].offset = offnum;
1988 }
1989
1990 /*
1991 * If any tuple isn't either totally frozen already or eligible to
1992 * become totally frozen (according to its freeze plan), then the page
1993 * definitely cannot be set all-frozen in the visibility map later on.
1994 */
1995 if (!totally_frozen)
1996 prstate->set_all_frozen = false;
1997 }
1998}
bool heap_prepare_freeze_tuple(HeapTupleHeader tuple, const struct VacuumCutoffs *cutoffs, HeapPageFreeze *pagefrz, HeapTupleFreeze *frz, bool *totally_frozen)
Definition heapam.c:7027
static bool HeapTupleHeaderXminCommitted(const HeapTupleHeaderData *tup)
static void heap_prune_record_prunable(PruneState *prstate, TransactionId xid, OffsetNumber offnum)
Definition pruneheap.c:1686

References Assert, elog, ERROR, fb(), heap_prepare_freeze_tuple(), heap_prune_record_prunable(), HEAPTUPLE_DELETE_IN_PROGRESS, HEAPTUPLE_INSERT_IN_PROGRESS, HEAPTUPLE_LIVE, HEAPTUPLE_RECENTLY_DEAD, HeapTupleHeaderGetUpdateXid(), HeapTupleHeaderGetXmin(), HeapTupleHeaderXminCommitted(), PageGetItem(), PageGetItemId(), TransactionIdFollows(), and TransactionIdIsNormal.

Referenced by heap_prune_chain(), and prune_freeze_plan().

◆ heap_prune_record_unchanged_lp_redirect()

static void heap_prune_record_unchanged_lp_redirect ( PruneState prstate,
OffsetNumber  offnum 
)
static

Definition at line 2040 of file pruneheap.c.

2041{
2042 /*
2043 * A redirect line pointer doesn't count as a live tuple.
2044 *
2045 * If we leave a redirect line pointer in place, there will be another
2046 * tuple on the page that it points to. We will do the bookkeeping for
2047 * that separately. So we have nothing to do here, except remember that
2048 * we processed this item.
2049 */
2050 Assert(!prstate->processed[offnum]);
2051 prstate->processed[offnum] = true;
2052}

References Assert, and fb().

Referenced by heap_prune_chain().

◆ heap_prune_record_unchanged_lp_unused()

static void heap_prune_record_unchanged_lp_unused ( PruneState prstate,
OffsetNumber  offnum 
)
static

Definition at line 1823 of file pruneheap.c.

1824{
1825 Assert(!prstate->processed[offnum]);
1826 prstate->processed[offnum] = true;
1827}

References Assert, and fb().

Referenced by prune_freeze_plan().

◆ heap_prune_record_unused()

static void heap_prune_record_unused ( PruneState prstate,
OffsetNumber  offnum,
bool  was_normal 
)
static

Definition at line 1801 of file pruneheap.c.

1802{
1803 Assert(!prstate->processed[offnum]);
1804 prstate->processed[offnum] = true;
1805
1807 prstate->nowunused[prstate->nunused] = offnum;
1808 prstate->nunused++;
1809
1810 /*
1811 * If the root entry had been a normal tuple, we are deleting it, so count
1812 * it in the result. But changing a redirect (even to DEAD state) doesn't
1813 * count.
1814 */
1815 if (was_normal)
1816 prstate->ndeleted++;
1817}

References Assert, fb(), and MaxHeapTuplesPerPage.

Referenced by heap_prune_chain(), heap_prune_record_dead_or_unused(), and prune_freeze_plan().

◆ heap_prune_satisfies_vacuum()

static HTSV_Result heap_prune_satisfies_vacuum ( PruneState prstate,
HeapTuple  tup 
)
static

Definition at line 1401 of file pruneheap.c.

1402{
1403 HTSV_Result res;
1405
1407
1408 if (res != HEAPTUPLE_RECENTLY_DEAD)
1409 return res;
1410
1411 /*
1412 * For VACUUM, we must be sure to prune tuples with xmax older than
1413 * OldestXmin -- a visibility cutoff determined at the beginning of
1414 * vacuuming the relation. OldestXmin is used for freezing determination
1415 * and we cannot freeze dead tuples' xmaxes.
1416 */
1417 if (prstate->cutoffs &&
1418 TransactionIdIsValid(prstate->cutoffs->OldestXmin) &&
1419 NormalTransactionIdPrecedes(dead_after, prstate->cutoffs->OldestXmin))
1420 return HEAPTUPLE_DEAD;
1421
1422 /*
1423 * Determine whether or not the tuple is considered dead when compared
1424 * with the provided GlobalVisState. On-access pruning does not provide
1425 * VacuumCutoffs. And for vacuum, even if the tuple's xmax is not older
1426 * than OldestXmin, GlobalVisTestIsRemovableXid() could find the row dead
1427 * if the GlobalVisState has been updated since the beginning of vacuuming
1428 * the relation.
1429 */
1430 if (GlobalVisTestIsRemovableXid(prstate->vistest, dead_after, true))
1431 return HEAPTUPLE_DEAD;
1432
1433 return res;
1434}
HTSV_Result
Definition heapam.h:137
HTSV_Result HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *dead_after)
#define NormalTransactionIdPrecedes(id1, id2)
Definition transam.h:147

References fb(), GlobalVisTestIsRemovableXid(), HEAPTUPLE_DEAD, HEAPTUPLE_RECENTLY_DEAD, HeapTupleSatisfiesVacuumHorizon(), NormalTransactionIdPrecedes, and TransactionIdIsValid.

Referenced by prune_freeze_plan().

◆ htsv_get_valid_status()

static HTSV_Result htsv_get_valid_status ( int  status)
inlinestatic

Definition at line 1444 of file pruneheap.c.

1445{
1446 Assert(status >= HEAPTUPLE_DEAD &&
1448 return (HTSV_Result) status;
1449}

References Assert, HEAPTUPLE_DEAD, and HEAPTUPLE_DELETE_IN_PROGRESS.

Referenced by heap_prune_chain().

◆ log_heap_prune_and_freeze()

void log_heap_prune_and_freeze ( Relation  relation,
Buffer  buffer,
Buffer  vmbuffer,
uint8  vmflags,
TransactionId  conflict_xid,
bool  cleanup_lock,
PruneReason  reason,
HeapTupleFreeze frozen,
int  nfrozen,
OffsetNumber redirected,
int  nredirected,
OffsetNumber dead,
int  ndead,
OffsetNumber unused,
int  nunused 
)

Definition at line 2561 of file pruneheap.c.

2570{
2573 uint8 info;
2575
2576 Page heap_page = BufferGetPage(buffer);
2577
2578 /* The following local variables hold data registered in the WAL record: */
2582 xlhp_prune_items dead_items;
2585 bool do_prune = nredirected > 0 || ndead > 0 || nunused > 0;
2587 bool heap_fpi_allowed = true;
2588
2590
2591 xlrec.flags = 0;
2593
2594 /*
2595 * We can avoid an FPI of the heap page if the only modification we are
2596 * making to it is to set PD_ALL_VISIBLE and checksums/wal_log_hints are
2597 * disabled.
2598 *
2599 * However, if the page has never been WAL-logged (LSN is invalid), we
2600 * must force an FPI regardless. This can happen when another backend
2601 * extends the heap, initializes the page, and then fails before WAL-
2602 * logging it. Since heap extension is not WAL-logged, recovery might try
2603 * to replay our record and find that the page isn't initialized, which
2604 * would cause a PANIC.
2605 */
2608 else if (!do_prune && nfrozen == 0 && (!do_set_vm || !XLogHintBitIsNeeded()))
2609 {
2611 heap_fpi_allowed = false;
2612 }
2613
2614 /*
2615 * Prepare data for the buffer. The arrays are not actually in the
2616 * buffer, but we pretend that they are. When XLogInsert stores a full
2617 * page image, the arrays can be omitted.
2618 */
2621
2622 if (do_set_vm)
2623 XLogRegisterBuffer(1, vmbuffer, 0);
2624
2625 if (nfrozen > 0)
2626 {
2627 int nplans;
2628
2630
2631 /*
2632 * Prepare deduplicated representation for use in the WAL record. This
2633 * destructively sorts frozen tuples array in-place.
2634 */
2635 nplans = heap_log_freeze_plan(frozen, nfrozen, plans, frz_offsets);
2636
2637 freeze_plans.nplans = nplans;
2639 offsetof(xlhp_freeze_plans, plans));
2640 XLogRegisterBufData(0, plans,
2641 sizeof(xlhp_freeze_plan) * nplans);
2642 }
2643 if (nredirected > 0)
2644 {
2646
2647 redirect_items.ntargets = nredirected;
2650 XLogRegisterBufData(0, redirected,
2651 sizeof(OffsetNumber[2]) * nredirected);
2652 }
2653 if (ndead > 0)
2654 {
2655 xlrec.flags |= XLHP_HAS_DEAD_ITEMS;
2656
2657 dead_items.ntargets = ndead;
2658 XLogRegisterBufData(0, &dead_items,
2660 XLogRegisterBufData(0, dead,
2661 sizeof(OffsetNumber) * ndead);
2662 }
2663 if (nunused > 0)
2664 {
2666
2667 unused_items.ntargets = nunused;
2670 XLogRegisterBufData(0, unused,
2671 sizeof(OffsetNumber) * nunused);
2672 }
2673 if (nfrozen > 0)
2675 sizeof(OffsetNumber) * nfrozen);
2676
2677 /*
2678 * Prepare the main xl_heap_prune record. We already set the XLHP_HAS_*
2679 * flag above.
2680 */
2682 {
2683 xlrec.flags |= XLHP_VM_ALL_VISIBLE;
2685 xlrec.flags |= XLHP_VM_ALL_FROZEN;
2686 }
2688 xlrec.flags |= XLHP_IS_CATALOG_REL;
2691 if (cleanup_lock)
2692 xlrec.flags |= XLHP_CLEANUP_LOCK;
2693 else
2694 {
2695 Assert(nredirected == 0 && ndead == 0);
2696 /* also, any items in 'unused' must've been LP_DEAD previously */
2697 }
2701
2702 switch (reason)
2703 {
2704 case PRUNE_ON_ACCESS:
2706 break;
2707 case PRUNE_VACUUM_SCAN:
2709 break;
2712 break;
2713 default:
2714 elog(ERROR, "unrecognized prune reason: %d", (int) reason);
2715 break;
2716 }
2717 recptr = XLogInsert(RM_HEAP2_ID, info);
2718
2719 if (do_set_vm)
2720 {
2721 Assert(BufferIsDirty(vmbuffer));
2722 PageSetLSN(BufferGetPage(vmbuffer), recptr);
2723 }
2724
2725 /*
2726 * If we explicitly skip an FPI, we must not stamp the heap page with this
2727 * record's LSN. Recovery skips records <= the stamped LSN, so this could
2728 * lead to skipping an earlier FPI needed to repair a torn page.
2729 */
2730 if (heap_fpi_allowed)
2731 {
2732 Assert(BufferIsDirty(buffer));
2734 }
2735}
static void PageSetLSN(Page page, XLogRecPtr lsn)
Definition bufpage.h:416
static XLogRecPtr PageGetLSN(const PageData *page)
Definition bufpage.h:410
uint8_t uint8
Definition c.h:622
@ PRUNE_VACUUM_CLEANUP
Definition heapam.h:254
@ PRUNE_VACUUM_SCAN
Definition heapam.h:253
#define XLHP_HAS_CONFLICT_HORIZON
#define XLHP_HAS_FREEZE_PLANS
#define XLHP_VM_ALL_VISIBLE
#define SizeOfHeapPrune
#define XLHP_HAS_NOW_UNUSED_ITEMS
#define XLHP_VM_ALL_FROZEN
#define XLHP_HAS_REDIRECTIONS
#define XLOG_HEAP2_PRUNE_VACUUM_SCAN
Definition heapam_xlog.h:61
#define XLOG_HEAP2_PRUNE_ON_ACCESS
Definition heapam_xlog.h:60
#define XLHP_CLEANUP_LOCK
#define XLHP_HAS_DEAD_ITEMS
#define XLOG_HEAP2_PRUNE_VACUUM_CLEANUP
Definition heapam_xlog.h:62
#define XLHP_IS_CATALOG_REL
const void * data
static int heap_log_freeze_plan(HeapTupleFreeze *tuples, int ntuples, xlhp_freeze_plan *plans_out, OffsetNumber *offsets_out)
Definition pruneheap.c:2482
#define RelationIsAccessibleInLogicalDecoding(relation)
Definition rel.h:695
#define XLogRecPtrIsValid(r)
Definition xlogdefs.h:29
uint64 XLogRecPtr
Definition xlogdefs.h:21
XLogRecPtr XLogInsert(RmgrId rmid, uint8 info)
Definition xloginsert.c:482
void XLogRegisterBufData(uint8 block_id, const void *data, uint32 len)
Definition xloginsert.c:413
void XLogRegisterData(const void *data, uint32 len)
Definition xloginsert.c:372
void XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
Definition xloginsert.c:246
void XLogBeginInsert(void)
Definition xloginsert.c:153
#define REGBUF_STANDARD
Definition xloginsert.h:35
#define REGBUF_FORCE_IMAGE
Definition xloginsert.h:32
#define REGBUF_NO_IMAGE
Definition xloginsert.h:33

References Assert, BufferGetPage(), BufferIsDirty(), data, elog, ERROR, fb(), heap_log_freeze_plan(), MaxHeapTuplesPerPage, xlhp_prune_items::ntargets, PageGetLSN(), PageSetLSN(), PRUNE_ON_ACCESS, PRUNE_VACUUM_CLEANUP, PRUNE_VACUUM_SCAN, REGBUF_FORCE_IMAGE, REGBUF_NO_IMAGE, REGBUF_STANDARD, RelationIsAccessibleInLogicalDecoding, SizeOfHeapPrune, TransactionIdIsValid, VISIBILITYMAP_ALL_FROZEN, VISIBILITYMAP_ALL_VISIBLE, VISIBILITYMAP_VALID_BITS, XLHP_CLEANUP_LOCK, XLHP_HAS_CONFLICT_HORIZON, XLHP_HAS_DEAD_ITEMS, XLHP_HAS_FREEZE_PLANS, XLHP_HAS_NOW_UNUSED_ITEMS, XLHP_HAS_REDIRECTIONS, XLHP_IS_CATALOG_REL, XLHP_VM_ALL_FROZEN, XLHP_VM_ALL_VISIBLE, XLOG_HEAP2_PRUNE_ON_ACCESS, XLOG_HEAP2_PRUNE_VACUUM_CLEANUP, XLOG_HEAP2_PRUNE_VACUUM_SCAN, XLogBeginInsert(), XLogHintBitIsNeeded, XLogInsert(), XLogRecPtrIsValid, XLogRegisterBufData(), XLogRegisterBuffer(), and XLogRegisterData().

Referenced by heap_page_prune_and_freeze(), lazy_scan_new_or_empty(), and lazy_vacuum_heap_page().

◆ page_verify_redirects()

static void page_verify_redirects ( Page  page)
static

Definition at line 2241 of file pruneheap.c.

2242{
2243#ifdef USE_ASSERT_CHECKING
2244 OffsetNumber offnum;
2245 OffsetNumber maxoff;
2246
2247 maxoff = PageGetMaxOffsetNumber(page);
2248 for (offnum = FirstOffsetNumber;
2249 offnum <= maxoff;
2250 offnum = OffsetNumberNext(offnum))
2251 {
2252 ItemId itemid = PageGetItemId(page, offnum);
2255 HeapTupleHeader htup;
2256
2257 if (!ItemIdIsRedirected(itemid))
2258 continue;
2259
2260 targoff = ItemIdGetRedirect(itemid);
2262
2266 htup = (HeapTupleHeader) PageGetItem(page, targitem);
2268 }
2269#endif
2270}

References Assert, fb(), FirstOffsetNumber, HeapTupleHeaderIsHeapOnly(), ItemIdGetRedirect, ItemIdHasStorage, ItemIdIsNormal, ItemIdIsRedirected, ItemIdIsUsed, OffsetNumberNext, PageGetItem(), PageGetItemId(), and PageGetMaxOffsetNumber().

Referenced by heap_page_prune_execute().

◆ prune_freeze_fast_path()

static void prune_freeze_fast_path ( PruneState prstate,
PruneFreezeResult presult 
)
static

Definition at line 1007 of file pruneheap.c.

1008{
1010 Page page = prstate->page;
1011
1012 Assert((prstate->old_vmbits & VISIBILITYMAP_ALL_FROZEN) ||
1013 ((prstate->old_vmbits & VISIBILITYMAP_ALL_VISIBLE) &&
1014 !prstate->attempt_freeze));
1015
1016 /* We'll fill in presult for the caller */
1017 memset(presult, 0, sizeof(PruneFreezeResult));
1018
1019 /* Clear any stale prune hint */
1021 {
1022 PageClearPrunable(page);
1023 MarkBufferDirtyHint(prstate->buffer, true);
1024 }
1025
1026 if (PageIsEmpty(page))
1027 return;
1028
1029 /*
1030 * Since the page is all-visible, a count of the normal ItemIds on the
1031 * page should be sufficient for vacuum's live tuple count.
1032 */
1034 off <= maxoff;
1035 off = OffsetNumberNext(off))
1036 {
1037 ItemId lp = PageGetItemId(page, off);
1038
1039 if (!ItemIdIsUsed(lp))
1040 continue;
1041
1042 presult->hastup = true;
1043
1044 if (ItemIdIsNormal(lp))
1045 prstate->live_tuples++;
1046 }
1047
1048 presult->live_tuples = prstate->live_tuples;
1049}
static bool PageIsEmpty(const PageData *page)
Definition bufpage.h:248

References Assert, fb(), FirstOffsetNumber, ItemIdIsNormal, ItemIdIsUsed, MarkBufferDirtyHint(), OffsetNumberNext, PageClearPrunable, PageGetItemId(), PageGetMaxOffsetNumber(), PageGetPruneXid(), PageIsEmpty(), TransactionIdIsValid, VISIBILITYMAP_ALL_FROZEN, and VISIBILITYMAP_ALL_VISIBLE.

Referenced by heap_page_prune_and_freeze().

◆ prune_freeze_plan()

static void prune_freeze_plan ( PruneState prstate,
OffsetNumber off_loc 
)
static

Definition at line 531 of file pruneheap.c.

532{
533 Page page = prstate->page;
534 BlockNumber blockno = prstate->block;
536 OffsetNumber offnum;
538
540
541 /*
542 * Determine HTSV for all tuples, and queue them up for processing as HOT
543 * chain roots or as heap-only items.
544 *
545 * Determining HTSV only once for each tuple is required for correctness,
546 * to deal with cases where running HTSV twice could result in different
547 * results. For example, RECENTLY_DEAD can turn to DEAD if another
548 * checked item causes GlobalVisTestIsRemovableFullXid() to update the
549 * horizon, or INSERT_IN_PROGRESS can change to DEAD if the inserting
550 * transaction aborts.
551 *
552 * It's also good for performance. Most commonly tuples within a page are
553 * stored at decreasing offsets (while the items are stored at increasing
554 * offsets). When processing all tuples on a page this leads to reading
555 * memory at decreasing offsets within a page, with a variable stride.
556 * That's hard for CPU prefetchers to deal with. Processing the items in
557 * reverse order (and thus the tuples in increasing order) increases
558 * prefetching efficiency significantly / decreases the number of cache
559 * misses.
560 */
561 for (offnum = maxoff;
562 offnum >= FirstOffsetNumber;
563 offnum = OffsetNumberPrev(offnum))
564 {
565 ItemId itemid = PageGetItemId(page, offnum);
566 HeapTupleHeader htup;
567
568 /*
569 * Set the offset number so that we can display it along with any
570 * error that occurred while processing this tuple.
571 */
572 *off_loc = offnum;
573
574 prstate->processed[offnum] = false;
575 prstate->htsv[offnum] = -1;
576
577 /* Nothing to do if slot doesn't contain a tuple */
578 if (!ItemIdIsUsed(itemid))
579 {
581 continue;
582 }
583
584 if (ItemIdIsDead(itemid))
585 {
586 /*
587 * If the caller set mark_unused_now true, we can set dead line
588 * pointers LP_UNUSED now.
589 */
590 if (unlikely(prstate->mark_unused_now))
591 heap_prune_record_unused(prstate, offnum, false);
592 else
594 continue;
595 }
596
597 if (ItemIdIsRedirected(itemid))
598 {
599 /* This is the start of a HOT chain */
600 prstate->root_items[prstate->nroot_items++] = offnum;
601 continue;
602 }
603
604 Assert(ItemIdIsNormal(itemid));
605
606 /*
607 * Get the tuple's visibility status and queue it up for processing.
608 */
609 htup = (HeapTupleHeader) PageGetItem(page, itemid);
610 tup.t_data = htup;
611 tup.t_len = ItemIdGetLength(itemid);
612 ItemPointerSet(&tup.t_self, blockno, offnum);
613
615
616 if (!HeapTupleHeaderIsHeapOnly(htup))
617 prstate->root_items[prstate->nroot_items++] = offnum;
618 else
619 prstate->heaponly_items[prstate->nheaponly_items++] = offnum;
620 }
621
622 /*
623 * Process HOT chains.
624 *
625 * We added the items to the array starting from 'maxoff', so by
626 * processing the array in reverse order, we process the items in
627 * ascending offset number order. The order doesn't matter for
628 * correctness, but some quick micro-benchmarking suggests that this is
629 * faster. (Earlier PostgreSQL versions, which scanned all the items on
630 * the page instead of using the root_items array, also did it in
631 * ascending offset number order.)
632 */
633 for (int i = prstate->nroot_items - 1; i >= 0; i--)
634 {
635 offnum = prstate->root_items[i];
636
637 /* Ignore items already processed as part of an earlier chain */
638 if (prstate->processed[offnum])
639 continue;
640
641 /* see preceding loop */
642 *off_loc = offnum;
643
644 /* Process this item or chain of items */
645 heap_prune_chain(maxoff, offnum, prstate);
646 }
647
648 /*
649 * Process any heap-only tuples that were not already processed as part of
650 * a HOT chain.
651 */
652 for (int i = prstate->nheaponly_items - 1; i >= 0; i--)
653 {
654 offnum = prstate->heaponly_items[i];
655
656 if (prstate->processed[offnum])
657 continue;
658
659 /* see preceding loop */
660 *off_loc = offnum;
661
662 /*
663 * If the tuple is DEAD and doesn't chain to anything else, mark it
664 * unused. (If it does chain, we can only remove it as part of
665 * pruning its chain.)
666 *
667 * We need this primarily to handle aborted HOT updates, that is,
668 * XMIN_INVALID heap-only tuples. Those might not be linked to by any
669 * chain, since the parent tuple might be re-updated before any
670 * pruning occurs. So we have to be able to reap them separately from
671 * chain-pruning. (Note that HeapTupleHeaderIsHotUpdated will never
672 * return true for an XMIN_INVALID tuple, so this code will work even
673 * when there were sequential updates within the aborted transaction.)
674 */
675 if (prstate->htsv[offnum] == HEAPTUPLE_DEAD)
676 {
677 ItemId itemid = PageGetItemId(page, offnum);
678 HeapTupleHeader htup = (HeapTupleHeader) PageGetItem(page, itemid);
679
681 {
683 &prstate->latest_xid_removed);
684 heap_prune_record_unused(prstate, offnum, true);
685 }
686 else
687 {
688 /*
689 * This tuple should've been processed and removed as part of
690 * a HOT chain, so something's wrong. To preserve evidence,
691 * we don't dare to remove it. We cannot leave behind a DEAD
692 * tuple either, because that will cause VACUUM to error out.
693 * Throwing an error with a distinct error message seems like
694 * the least bad option.
695 */
696 elog(ERROR, "dead heap-only tuple (%u, %d) is not linked to from any HOT chain",
697 blockno, offnum);
698 }
699 }
700 else
702 }
703
704 /* We should now have processed every tuple exactly once */
705#ifdef USE_ASSERT_CHECKING
706 for (offnum = FirstOffsetNumber;
707 offnum <= maxoff;
708 offnum = OffsetNumberNext(offnum))
709 {
710 *off_loc = offnum;
711
712 Assert(prstate->processed[offnum]);
713 }
714#endif
715
716 /* Clear the offset information once we have processed the given page. */
718}
uint32 BlockNumber
Definition block.h:31
#define likely(x)
Definition c.h:437
#define ItemIdGetLength(itemId)
Definition itemid.h:59
static void ItemPointerSet(ItemPointerData *pointer, BlockNumber blockNumber, OffsetNumber offNum)
Definition itemptr.h:135
#define OffsetNumberPrev(offsetNumber)
Definition off.h:54
static void heap_prune_chain(OffsetNumber maxoff, OffsetNumber rootoffnum, PruneState *prstate)
Definition pruneheap.c:1483
static void heap_prune_record_unchanged_lp_unused(PruneState *prstate, OffsetNumber offnum)
Definition pruneheap.c:1823
static void heap_prune_record_unchanged_lp_dead(PruneState *prstate, OffsetNumber offnum)
Definition pruneheap.c:2005
static HTSV_Result heap_prune_satisfies_vacuum(PruneState *prstate, HeapTuple tup)
Definition pruneheap.c:1401
#define RelationGetRelid(relation)
Definition rel.h:516
Oid t_tableOid
Definition htup.h:66

References Assert, elog, ERROR, fb(), FirstOffsetNumber, heap_prune_chain(), heap_prune_record_unchanged_lp_dead(), heap_prune_record_unchanged_lp_normal(), heap_prune_record_unchanged_lp_unused(), heap_prune_record_unused(), heap_prune_satisfies_vacuum(), HEAPTUPLE_DEAD, HeapTupleHeaderAdvanceConflictHorizon(), HeapTupleHeaderIsHeapOnly(), HeapTupleHeaderIsHotUpdated(), i, InvalidOffsetNumber, ItemIdGetLength, ItemIdIsDead, ItemIdIsNormal, ItemIdIsRedirected, ItemIdIsUsed, ItemPointerSet(), likely, OffsetNumberNext, OffsetNumberPrev, PageGetItem(), PageGetItemId(), PageGetMaxOffsetNumber(), RelationGetRelid, HeapTupleData::t_tableOid, and unlikely.

Referenced by heap_page_prune_and_freeze().

◆ prune_freeze_setup()

static void prune_freeze_setup ( PruneFreezeParams params,
TransactionId new_relfrozen_xid,
MultiXactId new_relmin_mxid,
PruneFreezeResult presult,
PruneState prstate 
)
static

Definition at line 400 of file pruneheap.c.

405{
406 /* Copy parameters to prstate */
407 prstate->vistest = params->vistest;
408 prstate->mark_unused_now =
410
411 /* cutoffs must be provided if we will attempt freezing */
412 Assert(!(params->options & HEAP_PAGE_PRUNE_FREEZE) || params->cutoffs);
413 prstate->attempt_freeze = (params->options & HEAP_PAGE_PRUNE_FREEZE) != 0;
414 prstate->attempt_set_vm = (params->options & HEAP_PAGE_PRUNE_SET_VM) != 0;
415 prstate->cutoffs = params->cutoffs;
416 prstate->relation = params->relation;
417 prstate->block = BufferGetBlockNumber(params->buffer);
418 prstate->buffer = params->buffer;
419 prstate->page = BufferGetPage(params->buffer);
420
421 Assert(BufferIsValid(params->vmbuffer));
422 prstate->vmbuffer = params->vmbuffer;
423 prstate->new_vmbits = 0;
424 prstate->old_vmbits = visibilitymap_get_status(prstate->relation,
425 prstate->block,
426 &prstate->vmbuffer);
427
428 /*
429 * Our strategy is to scan the page and make lists of items to change,
430 * then apply the changes within a critical section. This keeps as much
431 * logic as possible out of the critical section, and also ensures that
432 * WAL replay will work the same as the normal case.
433 *
434 * First, initialize the new pd_prune_xid value to zero (indicating no
435 * prunable tuples). If we find any tuples which may soon become
436 * prunable, we will save the lowest relevant XID in new_prune_xid. Also
437 * initialize the rest of our working state.
438 */
439 prstate->new_prune_xid = InvalidTransactionId;
440 prstate->latest_xid_removed = InvalidTransactionId;
441 prstate->nredirected = prstate->ndead = prstate->nunused = 0;
442 prstate->nfrozen = 0;
443 prstate->nroot_items = 0;
444 prstate->nheaponly_items = 0;
445
446 /* initialize page freezing working state */
447 prstate->pagefrz.freeze_required = false;
448 prstate->pagefrz.FreezePageConflictXid = InvalidTransactionId;
449 if (prstate->attempt_freeze)
450 {
452 prstate->pagefrz.FreezePageRelfrozenXid = *new_relfrozen_xid;
453 prstate->pagefrz.NoFreezePageRelfrozenXid = *new_relfrozen_xid;
454 prstate->pagefrz.FreezePageRelminMxid = *new_relmin_mxid;
455 prstate->pagefrz.NoFreezePageRelminMxid = *new_relmin_mxid;
456 }
457 else
458 {
460 prstate->pagefrz.FreezePageRelminMxid = InvalidMultiXactId;
461 prstate->pagefrz.NoFreezePageRelminMxid = InvalidMultiXactId;
462 prstate->pagefrz.FreezePageRelfrozenXid = InvalidTransactionId;
463 prstate->pagefrz.NoFreezePageRelfrozenXid = InvalidTransactionId;
464 }
465
466 prstate->ndeleted = 0;
467 prstate->live_tuples = 0;
468 prstate->recently_dead_tuples = 0;
469 prstate->hastup = false;
470 prstate->lpdead_items = 0;
471
472 /*
473 * deadoffsets are filled in during pruning but are only used to populate
474 * PruneFreezeResult->deadoffsets. To avoid needing two copies of the
475 * array, just save a pointer to the result offsets array in the
476 * PruneState.
477 */
478 prstate->deadoffsets = presult->deadoffsets;
479
480 /*
481 * We track whether the page will be all-visible/all-frozen at the end of
482 * pruning and freezing. While examining tuple visibility, we'll set
483 * set_all_visible to false if there are tuples on the page not visible to
484 * all running and future transactions. If setting the VM is enabled for
485 * this scan, we will do so if the page ends up being all-visible.
486 *
487 * We also keep track of the newest live XID, which is used to calculate
488 * the snapshot conflict horizon for a WAL record setting the VM.
489 */
490 prstate->set_all_visible = prstate->attempt_set_vm;
491 prstate->newest_live_xid = InvalidTransactionId;
492
493 /*
494 * Currently, only VACUUM performs freezing, but other callers may in the
495 * future. We must initialize set_all_frozen based on whether or not the
496 * caller passed HEAP_PAGE_PRUNE_FREEZE, because if they did not, we won't
497 * call heap_prepare_freeze_tuple() for each tuple, and set_all_frozen
498 * will never be cleared for tuples that need freezing. This would lead to
499 * incorrectly setting the visibility map all-frozen for this page. We
500 * can't set the page all-frozen in the VM if the caller didn't pass
501 * HEAP_PAGE_PRUNE_SET_VM.
502 *
503 * When freezing is not required (no XIDs/MXIDs older than the freeze
504 * cutoff), we may still choose to "opportunistically" freeze if doing so
505 * would make the page all-frozen.
506 *
507 * We will not be able to freeze the whole page at the end of vacuum if
508 * there are tuples present that are not visible to everyone or if there
509 * are dead tuples which will not be removable. However, dead tuples that
510 * will be removed by the end of vacuum should not prevent this
511 * opportunistic freezing.
512 *
513 * Therefore, we do not clear set_all_visible and set_all_frozen when we
514 * encounter LP_DEAD items. Instead, we correct them after deciding
515 * whether to freeze, but before updating the VM, to avoid setting the VM
516 * bits incorrectly.
517 */
518 prstate->set_all_frozen = prstate->attempt_freeze && prstate->attempt_set_vm;
519}
static bool BufferIsValid(Buffer bufnum)
Definition bufmgr.h:419
#define HEAP_PAGE_PRUNE_FREEZE
Definition heapam.h:43
#define HEAP_PAGE_PRUNE_MARK_UNUSED_NOW
Definition heapam.h:42
#define InvalidMultiXactId
Definition multixact.h:25
uint8 visibilitymap_get_status(Relation rel, BlockNumber heapBlk, Buffer *vmbuf)

References Assert, PruneFreezeParams::buffer, BufferGetBlockNumber(), BufferGetPage(), BufferIsValid(), PruneFreezeParams::cutoffs, fb(), HEAP_PAGE_PRUNE_FREEZE, HEAP_PAGE_PRUNE_MARK_UNUSED_NOW, HEAP_PAGE_PRUNE_SET_VM, InvalidMultiXactId, InvalidTransactionId, PruneFreezeParams::options, PruneFreezeParams::relation, visibilitymap_get_status(), PruneFreezeParams::vistest, and PruneFreezeParams::vmbuffer.

Referenced by heap_page_prune_and_freeze().