PostgreSQL Source Code  git master
pruneheap.c File Reference
#include "postgres.h"
#include "access/heapam.h"
#include "access/heapam_xlog.h"
#include "access/htup_details.h"
#include "access/transam.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "utils/snapmgr.h"
#include "utils/rel.h"
Include dependency graph for pruneheap.c:

Go to the source code of this file.

Data Structures

struct  PruneState
 

Functions

static int heap_prune_chain (Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
 
static void heap_prune_record_prunable (PruneState *prstate, TransactionId xid)
 
static void heap_prune_record_redirect (PruneState *prstate, OffsetNumber offnum, OffsetNumber rdoffnum)
 
static void heap_prune_record_dead (PruneState *prstate, OffsetNumber offnum)
 
static void heap_prune_record_unused (PruneState *prstate, OffsetNumber offnum)
 
void heap_page_prune_opt (Relation relation, Buffer buffer)
 
int heap_page_prune (Relation relation, Buffer buffer, GlobalVisState *vistest, TransactionId old_snap_xmin, TimestampTz old_snap_ts, bool report_stats, OffsetNumber *off_loc)
 
static HTSV_Result heap_prune_satisfies_vacuum (PruneState *prstate, HeapTuple tup, Buffer buffer)
 
void heap_page_prune_execute (Buffer buffer, OffsetNumber *redirected, int nredirected, OffsetNumber *nowdead, int ndead, OffsetNumber *nowunused, int nunused)
 
void heap_get_root_tuples (Page page, OffsetNumber *root_offsets)
 

Function Documentation

◆ heap_get_root_tuples()

void heap_get_root_tuples ( Page  page,
OffsetNumber root_offsets 
)

Definition at line 907 of file pruneheap.c.

References Assert, 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().

908 {
909  OffsetNumber offnum,
910  maxoff;
911 
912  MemSet(root_offsets, InvalidOffsetNumber,
914 
915  maxoff = PageGetMaxOffsetNumber(page);
916  for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum))
917  {
918  ItemId lp = PageGetItemId(page, offnum);
919  HeapTupleHeader htup;
920  OffsetNumber nextoffnum;
921  TransactionId priorXmax;
922 
923  /* skip unused and dead items */
924  if (!ItemIdIsUsed(lp) || ItemIdIsDead(lp))
925  continue;
926 
927  if (ItemIdIsNormal(lp))
928  {
929  htup = (HeapTupleHeader) PageGetItem(page, lp);
930 
931  /*
932  * Check if this tuple is part of a HOT-chain rooted at some other
933  * tuple. If so, skip it for now; we'll process it when we find
934  * its root.
935  */
936  if (HeapTupleHeaderIsHeapOnly(htup))
937  continue;
938 
939  /*
940  * This is either a plain tuple or the root of a HOT-chain.
941  * Remember it in the mapping.
942  */
943  root_offsets[offnum - 1] = offnum;
944 
945  /* If it's not the start of a HOT-chain, we're done with it */
946  if (!HeapTupleHeaderIsHotUpdated(htup))
947  continue;
948 
949  /* Set up to scan the HOT-chain */
950  nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
951  priorXmax = HeapTupleHeaderGetUpdateXid(htup);
952  }
953  else
954  {
955  /* Must be a redirect item. We do not set its root_offsets entry */
957  /* Set up to scan the HOT-chain */
958  nextoffnum = ItemIdGetRedirect(lp);
959  priorXmax = InvalidTransactionId;
960  }
961 
962  /*
963  * Now follow the HOT-chain and collect other tuples in the chain.
964  *
965  * Note: Even though this is a nested loop, the complexity of the
966  * function is O(N) because a tuple in the page should be visited not
967  * more than twice, once in the outer loop and once in HOT-chain
968  * chases.
969  */
970  for (;;)
971  {
972  /* Sanity check (pure paranoia) */
973  if (offnum < FirstOffsetNumber)
974  break;
975 
976  /*
977  * An offset past the end of page's line pointer array is possible
978  * when the array was truncated
979  */
980  if (offnum > maxoff)
981  break;
982 
983  lp = PageGetItemId(page, nextoffnum);
984 
985  /* Check for broken chains */
986  if (!ItemIdIsNormal(lp))
987  break;
988 
989  htup = (HeapTupleHeader) PageGetItem(page, lp);
990 
991  if (TransactionIdIsValid(priorXmax) &&
992  !TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(htup)))
993  break;
994 
995  /* Remember the root line pointer for this item */
996  root_offsets[nextoffnum - 1] = offnum;
997 
998  /* Advance to next chain member, if any */
999  if (!HeapTupleHeaderIsHotUpdated(htup))
1000  break;
1001 
1002  /* HOT implies it can't have moved to different partition */
1004 
1005  nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
1006  priorXmax = HeapTupleHeaderGetUpdateXid(htup);
1007  }
1008  }
1009 }
#define HeapTupleHeaderGetUpdateXid(tup)
Definition: htup_details.h:365
#define ItemIdIsRedirected(itemId)
Definition: itemid.h:106
#define TransactionIdEquals(id1, id2)
Definition: transam.h:43
uint32 TransactionId
Definition: c.h:587
HeapTupleHeaderData * HeapTupleHeader
Definition: htup.h:23
#define ItemIdGetRedirect(itemId)
Definition: itemid.h:78
#define ItemIdIsUsed(itemId)
Definition: itemid.h:92
#define MaxHeapTuplesPerPage
Definition: htup_details.h:573
#define MemSet(start, val, len)
Definition: c.h:1008
#define HeapTupleHeaderIndicatesMovedPartitions(tup)
Definition: htup_details.h:445
#define ItemIdIsDead(itemId)
Definition: itemid.h:113
#define PageGetMaxOffsetNumber(page)
Definition: bufpage.h:357
uint16 OffsetNumber
Definition: off.h:24
#define HeapTupleHeaderIsHeapOnly(tup)
Definition: htup_details.h:500
ItemPointerData t_ctid
Definition: htup_details.h:160
#define FirstOffsetNumber
Definition: off.h:27
#define InvalidTransactionId
Definition: transam.h:31
#define HeapTupleHeaderIsHotUpdated(tup)
Definition: htup_details.h:483
#define PageGetItemId(page, offsetNumber)
Definition: bufpage.h:235
#define InvalidOffsetNumber
Definition: off.h:26
#define Assert(condition)
Definition: c.h:804
#define ItemIdIsNormal(itemId)
Definition: itemid.h:99
#define HeapTupleHeaderGetXmin(tup)
Definition: htup_details.h:313
#define OffsetNumberNext(offsetNumber)
Definition: off.h:52
#define ItemPointerGetOffsetNumber(pointer)
Definition: itemptr.h:117
#define TransactionIdIsValid(xid)
Definition: transam.h:41
#define PageGetItem(page, itemId)
Definition: bufpage.h:340

◆ heap_page_prune()

int heap_page_prune ( Relation  relation,
Buffer  buffer,
GlobalVisState vistest,
TransactionId  old_snap_xmin,
TimestampTz  old_snap_ts,
bool  report_stats,
OffsetNumber off_loc 
)

Definition at line 219 of file pruneheap.c.

References BufferGetPage, END_CRIT_SECTION, FirstOffsetNumber, heap_page_prune_execute(), heap_prune_chain(), InvalidOffsetNumber, InvalidTransactionId, ItemIdIsDead, ItemIdIsUsed, PruneState::latestRemovedXid, xl_heap_prune::latestRemovedXid, MarkBufferDirty(), MarkBufferDirtyHint(), PruneState::marked, PruneState::ndead, xl_heap_prune::ndead, PruneState::new_prune_xid, PruneState::nowdead, PruneState::nowunused, PruneState::nredirected, xl_heap_prune::nredirected, PruneState::nunused, OffsetNumberNext, PruneState::old_snap_ts, PruneState::old_snap_used, PruneState::old_snap_xmin, PageClearFull, PageGetItemId, PageGetMaxOffsetNumber, PageIsFull, PageSetLSN, pgstat_update_heap_dead_tuples(), PruneState::redirected, REGBUF_STANDARD, PruneState::rel, RelationNeedsWAL, SizeOfHeapPrune, START_CRIT_SECTION, PruneState::vistest, XLOG_HEAP2_PRUNE, XLogBeginInsert(), XLogInsert(), XLogRegisterBufData(), XLogRegisterBuffer(), and XLogRegisterData().

Referenced by heap_page_prune_opt(), and lazy_scan_prune().

225 {
226  int ndeleted = 0;
227  Page page = BufferGetPage(buffer);
228  OffsetNumber offnum,
229  maxoff;
230  PruneState prstate;
231 
232  /*
233  * Our strategy is to scan the page and make lists of items to change,
234  * then apply the changes within a critical section. This keeps as much
235  * logic as possible out of the critical section, and also ensures that
236  * WAL replay will work the same as the normal case.
237  *
238  * First, initialize the new pd_prune_xid value to zero (indicating no
239  * prunable tuples). If we find any tuples which may soon become
240  * prunable, we will save the lowest relevant XID in new_prune_xid. Also
241  * initialize the rest of our working state.
242  */
244  prstate.rel = relation;
245  prstate.vistest = vistest;
246  prstate.old_snap_xmin = old_snap_xmin;
247  prstate.old_snap_ts = old_snap_ts;
248  prstate.old_snap_used = false;
250  prstate.nredirected = prstate.ndead = prstate.nunused = 0;
251  memset(prstate.marked, 0, sizeof(prstate.marked));
252 
253  /* Scan the page */
254  maxoff = PageGetMaxOffsetNumber(page);
255  for (offnum = FirstOffsetNumber;
256  offnum <= maxoff;
257  offnum = OffsetNumberNext(offnum))
258  {
259  ItemId itemid;
260 
261  /* Ignore items already processed as part of an earlier chain */
262  if (prstate.marked[offnum])
263  continue;
264 
265  /*
266  * Set the offset number so that we can display it along with any
267  * error that occurred while processing this tuple.
268  */
269  if (off_loc)
270  *off_loc = offnum;
271 
272  /* Nothing to do if slot is empty or already dead */
273  itemid = PageGetItemId(page, offnum);
274  if (!ItemIdIsUsed(itemid) || ItemIdIsDead(itemid))
275  continue;
276 
277  /* Process this item or chain of items */
278  ndeleted += heap_prune_chain(buffer, offnum, &prstate);
279  }
280 
281  /* Clear the offset information once we have processed the given page. */
282  if (off_loc)
283  *off_loc = InvalidOffsetNumber;
284 
285  /* Any error while applying the changes is critical */
287 
288  /* Have we found any prunable items? */
289  if (prstate.nredirected > 0 || prstate.ndead > 0 || prstate.nunused > 0)
290  {
291  /*
292  * Apply the planned item changes, then repair page fragmentation, and
293  * update the page's hint bit about whether it has free line pointers.
294  */
296  prstate.redirected, prstate.nredirected,
297  prstate.nowdead, prstate.ndead,
298  prstate.nowunused, prstate.nunused);
299 
300  /*
301  * Update the page's pd_prune_xid field to either zero, or the lowest
302  * XID of any soon-prunable tuple.
303  */
304  ((PageHeader) page)->pd_prune_xid = prstate.new_prune_xid;
305 
306  /*
307  * Also clear the "page is full" flag, since there's no point in
308  * repeating the prune/defrag process until something else happens to
309  * the page.
310  */
311  PageClearFull(page);
312 
313  MarkBufferDirty(buffer);
314 
315  /*
316  * Emit a WAL XLOG_HEAP2_PRUNE record showing what we did
317  */
318  if (RelationNeedsWAL(relation))
319  {
320  xl_heap_prune xlrec;
321  XLogRecPtr recptr;
322 
323  xlrec.latestRemovedXid = prstate.latestRemovedXid;
324  xlrec.nredirected = prstate.nredirected;
325  xlrec.ndead = prstate.ndead;
326 
327  XLogBeginInsert();
328  XLogRegisterData((char *) &xlrec, SizeOfHeapPrune);
329 
331 
332  /*
333  * The OffsetNumber arrays are not actually in the buffer, but we
334  * pretend that they are. When XLogInsert stores the whole
335  * buffer, the offset arrays need not be stored too.
336  */
337  if (prstate.nredirected > 0)
338  XLogRegisterBufData(0, (char *) prstate.redirected,
339  prstate.nredirected *
340  sizeof(OffsetNumber) * 2);
341 
342  if (prstate.ndead > 0)
343  XLogRegisterBufData(0, (char *) prstate.nowdead,
344  prstate.ndead * sizeof(OffsetNumber));
345 
346  if (prstate.nunused > 0)
347  XLogRegisterBufData(0, (char *) prstate.nowunused,
348  prstate.nunused * sizeof(OffsetNumber));
349 
350  recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_PRUNE);
351 
352  PageSetLSN(BufferGetPage(buffer), recptr);
353  }
354  }
355  else
356  {
357  /*
358  * If we didn't prune anything, but have found a new value for the
359  * pd_prune_xid field, update it and mark the buffer dirty. This is
360  * treated as a non-WAL-logged hint.
361  *
362  * Also clear the "page is full" flag if it is set, since there's no
363  * point in repeating the prune/defrag process until something else
364  * happens to the page.
365  */
366  if (((PageHeader) page)->pd_prune_xid != prstate.new_prune_xid ||
367  PageIsFull(page))
368  {
369  ((PageHeader) page)->pd_prune_xid = prstate.new_prune_xid;
370  PageClearFull(page);
371  MarkBufferDirtyHint(buffer, true);
372  }
373  }
374 
376 
377  /*
378  * If requested, report the number of tuples reclaimed to pgstats. This is
379  * ndeleted minus ndead, because we don't want to count a now-DEAD root
380  * item as a deletion for this purpose.
381  */
382  if (report_stats && ndeleted > prstate.ndead)
383  pgstat_update_heap_dead_tuples(relation, ndeleted - prstate.ndead);
384 
385  /*
386  * XXX Should we update the FSM information of this page ?
387  *
388  * There are two schools of thought here. We may not want to update FSM
389  * information so that the page is not used for unrelated UPDATEs/INSERTs
390  * and any free space in this page will remain available for further
391  * UPDATEs in *this* page, thus improving chances for doing HOT updates.
392  *
393  * But for a large table and where a page does not receive further UPDATEs
394  * for a long time, we might waste this space by not updating the FSM
395  * information. The relation may get extended and fragmented further.
396  *
397  * One possibility is to leave "fillfactor" worth of space in this page
398  * and update FSM with the remaining space.
399  */
400 
401  return ndeleted;
402 }
void XLogRegisterBufData(uint8 block_id, char *data, int len)
Definition: xloginsert.c:378
int nredirected
Definition: pruneheap.c:52
static int heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum, PruneState *prstate)
Definition: pruneheap.c:510
void pgstat_update_heap_dead_tuples(Relation rel, int delta)
Definition: pgstat.c:2373
void MarkBufferDirtyHint(Buffer buffer, bool buffer_std)
Definition: bufmgr.c:3838
void MarkBufferDirty(Buffer buffer)
Definition: bufmgr.c:1565
void XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
Definition: xloginsert.c:232
bool old_snap_used
Definition: pruneheap.c:48
Relation rel
Definition: pruneheap.c:33
#define END_CRIT_SECTION()
Definition: miscadmin.h:149
#define ItemIdIsUsed(itemId)
Definition: itemid.h:92
TimestampTz old_snap_ts
Definition: pruneheap.c:46
#define START_CRIT_SECTION()
Definition: miscadmin.h:147
#define XLOG_HEAP2_PRUNE
Definition: heapam_xlog.h:54
OffsetNumber nowdead[MaxHeapTuplesPerPage]
Definition: pruneheap.c:57
#define ItemIdIsDead(itemId)
Definition: itemid.h:113
bool marked[MaxHeapTuplesPerPage+1]
Definition: pruneheap.c:60
#define PageGetMaxOffsetNumber(page)
Definition: bufpage.h:357
TransactionId new_prune_xid
Definition: pruneheap.c:50
#define PageIsFull(page)
Definition: bufpage.h:378
int nunused
Definition: pruneheap.c:54
uint16 OffsetNumber
Definition: off.h:24
uint16 nredirected
Definition: heapam_xlog.h:246
TransactionId latestRemovedXid
Definition: heapam_xlog.h:245
TransactionId latestRemovedXid
Definition: pruneheap.c:51
#define FirstOffsetNumber
Definition: off.h:27
#define REGBUF_STANDARD
Definition: xloginsert.h:35
#define InvalidTransactionId
Definition: transam.h:31
#define BufferGetPage(buffer)
Definition: bufmgr.h:169
#define PageClearFull(page)
Definition: bufpage.h:382
#define PageGetItemId(page, offsetNumber)
Definition: bufpage.h:235
void XLogRegisterData(char *data, int len)
Definition: xloginsert.c:340
XLogRecPtr XLogInsert(RmgrId rmid, uint8 info)
Definition: xloginsert.c:434
TransactionId old_snap_xmin
Definition: pruneheap.c:47
#define InvalidOffsetNumber
Definition: off.h:26
GlobalVisState * vistest
Definition: pruneheap.c:36
void heap_page_prune_execute(Buffer buffer, OffsetNumber *redirected, int nredirected, OffsetNumber *nowdead, int ndead, OffsetNumber *nowunused, int nunused)
Definition: pruneheap.c:840
PageHeaderData * PageHeader
Definition: bufpage.h:166
uint64 XLogRecPtr
Definition: xlogdefs.h:21
#define OffsetNumberNext(offsetNumber)
Definition: off.h:52
OffsetNumber nowunused[MaxHeapTuplesPerPage]
Definition: pruneheap.c:58
#define RelationNeedsWAL(relation)
Definition: rel.h:601
OffsetNumber redirected[MaxHeapTuplesPerPage *2]
Definition: pruneheap.c:56
int ndead
Definition: pruneheap.c:53
#define SizeOfHeapPrune
Definition: heapam_xlog.h:251
void XLogBeginInsert(void)
Definition: xloginsert.c:135
#define PageSetLSN(page, lsn)
Definition: bufpage.h:368
Pointer Page
Definition: bufpage.h:78

◆ heap_page_prune_execute()

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

Definition at line 840 of file pruneheap.c.

References Assert, BufferGetPage, i, ItemIdSetDead, ItemIdSetRedirect, ItemIdSetUnused, PageGetItemId, and PageRepairFragmentation().

Referenced by heap_page_prune(), and heap_xlog_prune().

844 {
845  Page page = (Page) BufferGetPage(buffer);
846  OffsetNumber *offnum;
847  int i;
848 
849  /* Shouldn't be called unless there's something to do */
850  Assert(nredirected > 0 || ndead > 0 || nunused > 0);
851 
852  /* Update all redirected line pointers */
853  offnum = redirected;
854  for (i = 0; i < nredirected; i++)
855  {
856  OffsetNumber fromoff = *offnum++;
857  OffsetNumber tooff = *offnum++;
858  ItemId fromlp = PageGetItemId(page, fromoff);
859 
860  ItemIdSetRedirect(fromlp, tooff);
861  }
862 
863  /* Update all now-dead line pointers */
864  offnum = nowdead;
865  for (i = 0; i < ndead; i++)
866  {
867  OffsetNumber off = *offnum++;
868  ItemId lp = PageGetItemId(page, off);
869 
870  ItemIdSetDead(lp);
871  }
872 
873  /* Update all now-unused line pointers */
874  offnum = nowunused;
875  for (i = 0; i < nunused; i++)
876  {
877  OffsetNumber off = *offnum++;
878  ItemId lp = PageGetItemId(page, off);
879 
880  ItemIdSetUnused(lp);
881  }
882 
883  /*
884  * Finally, repair any fragmentation, and update the page's hint bit about
885  * whether it has free pointers.
886  */
888 }
uint16 OffsetNumber
Definition: off.h:24
#define ItemIdSetRedirect(itemId, link)
Definition: itemid.h:152
#define BufferGetPage(buffer)
Definition: bufmgr.h:169
#define PageGetItemId(page, offsetNumber)
Definition: bufpage.h:235
#define Assert(condition)
Definition: c.h:804
void PageRepairFragmentation(Page page)
Definition: bufpage.c:709
int i
#define ItemIdSetDead(itemId)
Definition: itemid.h:164
#define ItemIdSetUnused(itemId)
Definition: itemid.h:128
Pointer Page
Definition: bufpage.h:78

◆ heap_page_prune_opt()

void heap_page_prune_opt ( Relation  relation,
Buffer  buffer 
)

Definition at line 87 of file pruneheap.c.

References BUFFER_LOCK_UNLOCK, BufferGetPage, ConditionalLockBufferForCleanup(), GlobalVisTestFor(), GlobalVisTestIsRemovableXid(), GlobalVisTestNonRemovableHorizon(), HEAP_DEFAULT_FILLFACTOR, heap_page_prune(), InvalidTransactionId, LockBuffer(), Max, old_snapshot_threshold, OldSnapshotThresholdActive(), PageGetHeapFreeSpace(), PageIsFull, RecoveryInProgress(), RelationGetTargetPageFreeSpace, SnapshotTooOldMagicForTest(), TransactionIdIsValid, TransactionIdLimitedForOldSnapshots(), and TransactionIdPrecedes().

Referenced by heapam_index_fetch_tuple(), heapam_scan_bitmap_next_block(), and heapgetpage().

88 {
89  Page page = BufferGetPage(buffer);
90  TransactionId prune_xid;
91  GlobalVisState *vistest;
92  TransactionId limited_xmin = InvalidTransactionId;
93  TimestampTz limited_ts = 0;
94  Size minfree;
95 
96  /*
97  * We can't write WAL in recovery mode, so there's no point trying to
98  * clean the page. The primary will likely issue a cleaning WAL record
99  * soon anyway, so this is no particular loss.
100  */
101  if (RecoveryInProgress())
102  return;
103 
104  /*
105  * XXX: Magic to keep old_snapshot_threshold tests appear "working". They
106  * currently are broken, and discussion of what to do about them is
107  * ongoing. See
108  * https://www.postgresql.org/message-id/20200403001235.e6jfdll3gh2ygbuc%40alap3.anarazel.de
109  */
110  if (old_snapshot_threshold == 0)
112 
113  /*
114  * First check whether there's any chance there's something to prune,
115  * determining the appropriate horizon is a waste if there's no prune_xid
116  * (i.e. no updates/deletes left potentially dead tuples around).
117  */
118  prune_xid = ((PageHeader) page)->pd_prune_xid;
119  if (!TransactionIdIsValid(prune_xid))
120  return;
121 
122  /*
123  * Check whether prune_xid indicates that there may be dead rows that can
124  * be cleaned up.
125  *
126  * It is OK to check the old snapshot limit before acquiring the cleanup
127  * lock because the worst that can happen is that we are not quite as
128  * aggressive about the cleanup (by however many transaction IDs are
129  * consumed between this point and acquiring the lock). This allows us to
130  * save significant overhead in the case where the page is found not to be
131  * prunable.
132  *
133  * Even if old_snapshot_threshold is set, we first check whether the page
134  * can be pruned without. Both because
135  * TransactionIdLimitedForOldSnapshots() is not cheap, and because not
136  * unnecessarily relying on old_snapshot_threshold avoids causing
137  * conflicts.
138  */
139  vistest = GlobalVisTestFor(relation);
140 
141  if (!GlobalVisTestIsRemovableXid(vistest, prune_xid))
142  {
144  return;
145 
147  relation,
148  &limited_xmin, &limited_ts))
149  return;
150 
151  if (!TransactionIdPrecedes(prune_xid, limited_xmin))
152  return;
153  }
154 
155  /*
156  * We prune when a previous UPDATE failed to find enough space on the page
157  * for a new tuple version, or when free space falls below the relation's
158  * fill-factor target (but not less than 10%).
159  *
160  * Checking free space here is questionable since we aren't holding any
161  * lock on the buffer; in the worst case we could get a bogus answer. It's
162  * unlikely to be *seriously* wrong, though, since reading either pd_lower
163  * or pd_upper is probably atomic. Avoiding taking a lock seems more
164  * important than sometimes getting a wrong answer in what is after all
165  * just a heuristic estimate.
166  */
167  minfree = RelationGetTargetPageFreeSpace(relation,
169  minfree = Max(minfree, BLCKSZ / 10);
170 
171  if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
172  {
173  /* OK, try to get exclusive buffer lock */
174  if (!ConditionalLockBufferForCleanup(buffer))
175  return;
176 
177  /*
178  * Now that we have buffer lock, get accurate information about the
179  * page's free space, and recheck the heuristic about whether to
180  * prune. (We needn't recheck PageIsPrunable, since no one else could
181  * have pruned while we hold pin.)
182  */
183  if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
184  {
185  /* OK to prune */
186  (void) heap_page_prune(relation, buffer, vistest,
187  limited_xmin, limited_ts,
188  true, NULL);
189  }
190 
191  /* And release buffer lock */
193  }
194 }
#define BUFFER_LOCK_UNLOCK
Definition: bufmgr.h:96
uint32 TransactionId
Definition: c.h:587
int64 TimestampTz
Definition: timestamp.h:39
static bool OldSnapshotThresholdActive(void)
Definition: snapmgr.h:101
bool TransactionIdLimitedForOldSnapshots(TransactionId recentXmin, Relation relation, TransactionId *limit_xid, TimestampTz *limit_ts)
Definition: snapmgr.c:1766
bool RecoveryInProgress(void)
Definition: xlog.c:8328
bool GlobalVisTestIsRemovableXid(GlobalVisState *state, TransactionId xid)
Definition: procarray.c:4186
#define PageIsFull(page)
Definition: bufpage.h:378
GlobalVisState * GlobalVisTestFor(Relation rel)
Definition: procarray.c:4029
int heap_page_prune(Relation relation, Buffer buffer, GlobalVisState *vistest, TransactionId old_snap_xmin, TimestampTz old_snap_ts, bool report_stats, OffsetNumber *off_loc)
Definition: pruneheap.c:219
bool ConditionalLockBufferForCleanup(Buffer buffer)
Definition: bufmgr.c:4241
Size PageGetHeapFreeSpace(Page page)
Definition: bufpage.c:984
void SnapshotTooOldMagicForTest(void)
Definition: snapmgr.c:1704
#define InvalidTransactionId
Definition: transam.h:31
#define BufferGetPage(buffer)
Definition: bufmgr.h:169
bool TransactionIdPrecedes(TransactionId id1, TransactionId id2)
Definition: transam.c:300
#define RelationGetTargetPageFreeSpace(relation, defaultff)
Definition: rel.h:361
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:4007
#define Max(x, y)
Definition: c.h:980
PageHeaderData * PageHeader
Definition: bufpage.h:166
size_t Size
Definition: c.h:540
int old_snapshot_threshold
Definition: snapmgr.c:78
#define HEAP_DEFAULT_FILLFACTOR
Definition: rel.h:332
#define TransactionIdIsValid(xid)
Definition: transam.h:41
TransactionId GlobalVisTestNonRemovableHorizon(GlobalVisState *state)
Definition: procarray.c:4224
Pointer Page
Definition: bufpage.h:78

◆ heap_prune_chain()

static int heap_prune_chain ( Buffer  buffer,
OffsetNumber  rootoffnum,
PruneState prstate 
)
static

Definition at line 510 of file pruneheap.c.

References Assert, BufferGetBlockNumber(), BufferGetPage, elog, ERROR, FirstOffsetNumber, heap_prune_record_dead(), heap_prune_record_prunable(), heap_prune_record_redirect(), heap_prune_record_unused(), heap_prune_satisfies_vacuum(), HEAPTUPLE_DEAD, HEAPTUPLE_DELETE_IN_PROGRESS, HEAPTUPLE_INSERT_IN_PROGRESS, HEAPTUPLE_LIVE, HEAPTUPLE_RECENTLY_DEAD, HeapTupleHeaderAdvanceLatestRemovedXid(), HeapTupleHeaderGetUpdateXid, HeapTupleHeaderGetXmin, HeapTupleHeaderIndicatesMovedPartitions, HeapTupleHeaderIsHeapOnly, HeapTupleHeaderIsHotUpdated, i, InvalidOffsetNumber, InvalidTransactionId, ItemIdGetLength, ItemIdGetRedirect, ItemIdIsDead, ItemIdIsNormal, ItemIdIsRedirected, ItemIdIsUsed, ItemPointerGetBlockNumber, ItemPointerGetOffsetNumber, ItemPointerSet, PruneState::latestRemovedXid, PruneState::marked, MaxHeapTuplesPerPage, OffsetNumberIsValid, PageGetItem, PageGetItemId, PageGetMaxOffsetNumber, PruneState::rel, RelationGetRelid, HeapTupleHeaderData::t_ctid, HeapTupleData::t_data, HeapTupleData::t_len, HeapTupleData::t_self, HeapTupleData::t_tableOid, TransactionIdEquals, and TransactionIdIsValid.

Referenced by heap_page_prune().

511 {
512  int ndeleted = 0;
513  Page dp = (Page) BufferGetPage(buffer);
515  ItemId rootlp;
516  HeapTupleHeader htup;
517  OffsetNumber latestdead = InvalidOffsetNumber,
518  maxoff = PageGetMaxOffsetNumber(dp),
519  offnum;
521  int nchain = 0,
522  i;
523  HeapTupleData tup;
524 
525  tup.t_tableOid = RelationGetRelid(prstate->rel);
526 
527  rootlp = PageGetItemId(dp, rootoffnum);
528 
529  /*
530  * If it's a heap-only tuple, then it is not the start of a HOT chain.
531  */
532  if (ItemIdIsNormal(rootlp))
533  {
534  htup = (HeapTupleHeader) PageGetItem(dp, rootlp);
535 
536  tup.t_data = htup;
537  tup.t_len = ItemIdGetLength(rootlp);
538  ItemPointerSet(&(tup.t_self), BufferGetBlockNumber(buffer), rootoffnum);
539 
540  if (HeapTupleHeaderIsHeapOnly(htup))
541  {
542  /*
543  * If the tuple is DEAD and doesn't chain to anything else, mark
544  * it unused immediately. (If it does chain, we can only remove
545  * it as part of pruning its chain.)
546  *
547  * We need this primarily to handle aborted HOT updates, that is,
548  * XMIN_INVALID heap-only tuples. Those might not be linked to by
549  * any chain, since the parent tuple might be re-updated before
550  * any pruning occurs. So we have to be able to reap them
551  * separately from chain-pruning. (Note that
552  * HeapTupleHeaderIsHotUpdated will never return true for an
553  * XMIN_INVALID tuple, so this code will work even when there were
554  * sequential updates within the aborted transaction.)
555  *
556  * Note that we might first arrive at a dead heap-only tuple
557  * either here or while following a chain below. Whichever path
558  * gets there first will mark the tuple unused.
559  */
560  if (heap_prune_satisfies_vacuum(prstate, &tup, buffer)
562  {
563  heap_prune_record_unused(prstate, rootoffnum);
565  &prstate->latestRemovedXid);
566  ndeleted++;
567  }
568 
569  /* Nothing more to do */
570  return ndeleted;
571  }
572  }
573 
574  /* Start from the root tuple */
575  offnum = rootoffnum;
576 
577  /* while not end of the chain */
578  for (;;)
579  {
580  ItemId lp;
581  bool tupdead,
582  recent_dead;
583 
584  /* Sanity check (pure paranoia) */
585  if (offnum < FirstOffsetNumber)
586  break;
587 
588  /*
589  * An offset past the end of page's line pointer array is possible
590  * when the array was truncated (original item must have been unused)
591  */
592  if (offnum > maxoff)
593  break;
594 
595  /* If item is already processed, stop --- it must not be same chain */
596  if (prstate->marked[offnum])
597  break;
598 
599  lp = PageGetItemId(dp, offnum);
600 
601  /* Unused item obviously isn't part of the chain */
602  if (!ItemIdIsUsed(lp))
603  break;
604 
605  /*
606  * If we are looking at the redirected root line pointer, jump to the
607  * first normal tuple in the chain. If we find a redirect somewhere
608  * else, stop --- it must not be same chain.
609  */
610  if (ItemIdIsRedirected(lp))
611  {
612  if (nchain > 0)
613  break; /* not at start of chain */
614  chainitems[nchain++] = offnum;
615  offnum = ItemIdGetRedirect(rootlp);
616  continue;
617  }
618 
619  /*
620  * Likewise, a dead line pointer can't be part of the chain. (We
621  * already eliminated the case of dead root tuple outside this
622  * function.)
623  */
624  if (ItemIdIsDead(lp))
625  break;
626 
627  Assert(ItemIdIsNormal(lp));
628  htup = (HeapTupleHeader) PageGetItem(dp, lp);
629 
630  tup.t_data = htup;
631  tup.t_len = ItemIdGetLength(lp);
632  ItemPointerSet(&(tup.t_self), BufferGetBlockNumber(buffer), offnum);
633 
634  /*
635  * Check the tuple XMIN against prior XMAX, if any
636  */
637  if (TransactionIdIsValid(priorXmax) &&
638  !TransactionIdEquals(HeapTupleHeaderGetXmin(htup), priorXmax))
639  break;
640 
641  /*
642  * OK, this tuple is indeed a member of the chain.
643  */
644  chainitems[nchain++] = offnum;
645 
646  /*
647  * Check tuple's visibility status.
648  */
649  tupdead = recent_dead = false;
650 
651  switch (heap_prune_satisfies_vacuum(prstate, &tup, buffer))
652  {
653  case HEAPTUPLE_DEAD:
654  tupdead = true;
655  break;
656 
658  recent_dead = true;
659 
660  /*
661  * This tuple may soon become DEAD. Update the hint field so
662  * that the page is reconsidered for pruning in future.
663  */
666  break;
667 
669 
670  /*
671  * This tuple may soon become DEAD. Update the hint field so
672  * that the page is reconsidered for pruning in future.
673  */
676  break;
677 
678  case HEAPTUPLE_LIVE:
680 
681  /*
682  * If we wanted to optimize for aborts, we might consider
683  * marking the page prunable when we see INSERT_IN_PROGRESS.
684  * But we don't. See related decisions about when to mark the
685  * page prunable in heapam.c.
686  */
687  break;
688 
689  default:
690  elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
691  break;
692  }
693 
694  /*
695  * Remember the last DEAD tuple seen. We will advance past
696  * RECENTLY_DEAD tuples just in case there's a DEAD one after them;
697  * but we can't advance past anything else. (XXX is it really worth
698  * continuing to scan beyond RECENTLY_DEAD? The case where we will
699  * find another DEAD tuple is a fairly unusual corner case.)
700  */
701  if (tupdead)
702  {
703  latestdead = offnum;
705  &prstate->latestRemovedXid);
706  }
707  else if (!recent_dead)
708  break;
709 
710  /*
711  * If the tuple is not HOT-updated, then we are at the end of this
712  * HOT-update chain.
713  */
714  if (!HeapTupleHeaderIsHotUpdated(htup))
715  break;
716 
717  /* HOT implies it can't have moved to different partition */
719 
720  /*
721  * Advance to next chain member.
722  */
724  BufferGetBlockNumber(buffer));
725  offnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
726  priorXmax = HeapTupleHeaderGetUpdateXid(htup);
727  }
728 
729  /*
730  * If we found a DEAD tuple in the chain, adjust the HOT chain so that all
731  * the DEAD tuples at the start of the chain are removed and the root line
732  * pointer is appropriately redirected.
733  */
734  if (OffsetNumberIsValid(latestdead))
735  {
736  /*
737  * Mark as unused each intermediate item that we are able to remove
738  * from the chain.
739  *
740  * When the previous item is the last dead tuple seen, we are at the
741  * right candidate for redirection.
742  */
743  for (i = 1; (i < nchain) && (chainitems[i - 1] != latestdead); i++)
744  {
745  heap_prune_record_unused(prstate, chainitems[i]);
746  ndeleted++;
747  }
748 
749  /*
750  * If the root entry had been a normal tuple, we are deleting it, so
751  * count it in the result. But changing a redirect (even to DEAD
752  * state) doesn't count.
753  */
754  if (ItemIdIsNormal(rootlp))
755  ndeleted++;
756 
757  /*
758  * If the DEAD tuple is at the end of the chain, the entire chain is
759  * dead and the root line pointer can be marked dead. Otherwise just
760  * redirect the root to the correct chain member.
761  */
762  if (i >= nchain)
763  heap_prune_record_dead(prstate, rootoffnum);
764  else
765  heap_prune_record_redirect(prstate, rootoffnum, chainitems[i]);
766  }
767  else if (nchain < 2 && ItemIdIsRedirected(rootlp))
768  {
769  /*
770  * We found a redirect item that doesn't point to a valid follow-on
771  * item. This can happen if the loop in heap_page_prune caused us to
772  * visit the dead successor of a redirect item before visiting the
773  * redirect item. We can clean up by setting the redirect item to
774  * DEAD state.
775  */
776  heap_prune_record_dead(prstate, rootoffnum);
777  }
778 
779  return ndeleted;
780 }
#define HeapTupleHeaderGetUpdateXid(tup)
Definition: htup_details.h:365
void HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple, TransactionId *latestRemovedXid)
Definition: heapam.c:7179
#define ItemIdIsRedirected(itemId)
Definition: itemid.h:106
#define TransactionIdEquals(id1, id2)
Definition: transam.h:43
uint32 TransactionId
Definition: c.h:587
HeapTupleHeaderData * HeapTupleHeader
Definition: htup.h:23
#define ItemIdGetRedirect(itemId)
Definition: itemid.h:78
Relation rel
Definition: pruneheap.c:33
#define ItemIdIsUsed(itemId)
Definition: itemid.h:92
#define MaxHeapTuplesPerPage
Definition: htup_details.h:573
static void heap_prune_record_dead(PruneState *prstate, OffsetNumber offnum)
Definition: pruneheap.c:813
#define HeapTupleHeaderIndicatesMovedPartitions(tup)
Definition: htup_details.h:445
#define ItemIdIsDead(itemId)
Definition: itemid.h:113
bool marked[MaxHeapTuplesPerPage+1]
Definition: pruneheap.c:60
#define PageGetMaxOffsetNumber(page)
Definition: bufpage.h:357
uint16 OffsetNumber
Definition: off.h:24
HeapTupleHeader t_data
Definition: htup.h:68
#define HeapTupleHeaderIsHeapOnly(tup)
Definition: htup_details.h:500
#define ItemIdGetLength(itemId)
Definition: itemid.h:59
#define ERROR
Definition: elog.h:46
ItemPointerData t_ctid
Definition: htup_details.h:160
ItemPointerData t_self
Definition: htup.h:65
uint32 t_len
Definition: htup.h:64
static void heap_prune_record_prunable(PruneState *prstate, TransactionId xid)
Definition: pruneheap.c:784
TransactionId latestRemovedXid
Definition: pruneheap.c:51
#define FirstOffsetNumber
Definition: off.h:27
#define InvalidTransactionId
Definition: transam.h:31
Oid t_tableOid
Definition: htup.h:66
#define BufferGetPage(buffer)
Definition: bufmgr.h:169
#define HeapTupleHeaderIsHotUpdated(tup)
Definition: htup_details.h:483
#define PageGetItemId(page, offsetNumber)
Definition: bufpage.h:235
static HTSV_Result heap_prune_satisfies_vacuum(PruneState *prstate, HeapTuple tup, Buffer buffer)
Definition: pruneheap.c:422
#define InvalidOffsetNumber
Definition: off.h:26
#define Assert(condition)
Definition: c.h:804
static void heap_prune_record_redirect(PruneState *prstate, OffsetNumber offnum, OffsetNumber rdoffnum)
Definition: pruneheap.c:798
#define ItemIdIsNormal(itemId)
Definition: itemid.h:99
#define HeapTupleHeaderGetXmin(tup)
Definition: htup_details.h:313
#define ItemPointerGetOffsetNumber(pointer)
Definition: itemptr.h:117
BlockNumber BufferGetBlockNumber(Buffer buffer)
Definition: bufmgr.c:2748
#define elog(elevel,...)
Definition: elog.h:232
int i
#define OffsetNumberIsValid(offsetNumber)
Definition: off.h:39
#define ItemPointerGetBlockNumber(pointer)
Definition: itemptr.h:98
#define TransactionIdIsValid(xid)
Definition: transam.h:41
#define RelationGetRelid(relation)
Definition: rel.h:477
#define PageGetItem(page, itemId)
Definition: bufpage.h:340
Pointer Page
Definition: bufpage.h:78
#define ItemPointerSet(pointer, blockNumber, offNum)
Definition: itemptr.h:127
static void heap_prune_record_unused(PruneState *prstate, OffsetNumber offnum)
Definition: pruneheap.c:824

◆ heap_prune_record_dead()

static void heap_prune_record_dead ( PruneState prstate,
OffsetNumber  offnum 
)
static

Definition at line 813 of file pruneheap.c.

References Assert, PruneState::marked, MaxHeapTuplesPerPage, PruneState::ndead, and PruneState::nowdead.

Referenced by heap_prune_chain().

814 {
815  Assert(prstate->ndead < MaxHeapTuplesPerPage);
816  prstate->nowdead[prstate->ndead] = offnum;
817  prstate->ndead++;
818  Assert(!prstate->marked[offnum]);
819  prstate->marked[offnum] = true;
820 }
#define MaxHeapTuplesPerPage
Definition: htup_details.h:573
OffsetNumber nowdead[MaxHeapTuplesPerPage]
Definition: pruneheap.c:57
bool marked[MaxHeapTuplesPerPage+1]
Definition: pruneheap.c:60
#define Assert(condition)
Definition: c.h:804
int ndead
Definition: pruneheap.c:53

◆ heap_prune_record_prunable()

static void heap_prune_record_prunable ( PruneState prstate,
TransactionId  xid 
)
static

Definition at line 784 of file pruneheap.c.

References Assert, PruneState::new_prune_xid, TransactionIdIsNormal, TransactionIdIsValid, and TransactionIdPrecedes().

Referenced by heap_prune_chain().

785 {
786  /*
787  * This should exactly match the PageSetPrunable macro. We can't store
788  * directly into the page header yet, so we update working state.
789  */
791  if (!TransactionIdIsValid(prstate->new_prune_xid) ||
792  TransactionIdPrecedes(xid, prstate->new_prune_xid))
793  prstate->new_prune_xid = xid;
794 }
TransactionId new_prune_xid
Definition: pruneheap.c:50
bool TransactionIdPrecedes(TransactionId id1, TransactionId id2)
Definition: transam.c:300
#define Assert(condition)
Definition: c.h:804
#define TransactionIdIsValid(xid)
Definition: transam.h:41
#define TransactionIdIsNormal(xid)
Definition: transam.h:42

◆ heap_prune_record_redirect()

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

Definition at line 798 of file pruneheap.c.

References Assert, PruneState::marked, MaxHeapTuplesPerPage, PruneState::nredirected, and PruneState::redirected.

Referenced by heap_prune_chain().

800 {
802  prstate->redirected[prstate->nredirected * 2] = offnum;
803  prstate->redirected[prstate->nredirected * 2 + 1] = rdoffnum;
804  prstate->nredirected++;
805  Assert(!prstate->marked[offnum]);
806  prstate->marked[offnum] = true;
807  Assert(!prstate->marked[rdoffnum]);
808  prstate->marked[rdoffnum] = true;
809 }
int nredirected
Definition: pruneheap.c:52
#define MaxHeapTuplesPerPage
Definition: htup_details.h:573
bool marked[MaxHeapTuplesPerPage+1]
Definition: pruneheap.c:60
#define Assert(condition)
Definition: c.h:804
OffsetNumber redirected[MaxHeapTuplesPerPage *2]
Definition: pruneheap.c:56

◆ heap_prune_record_unused()

static void heap_prune_record_unused ( PruneState prstate,
OffsetNumber  offnum 
)
static

Definition at line 824 of file pruneheap.c.

References Assert, PruneState::marked, MaxHeapTuplesPerPage, PruneState::nowunused, and PruneState::nunused.

Referenced by heap_prune_chain().

825 {
826  Assert(prstate->nunused < MaxHeapTuplesPerPage);
827  prstate->nowunused[prstate->nunused] = offnum;
828  prstate->nunused++;
829  Assert(!prstate->marked[offnum]);
830  prstate->marked[offnum] = true;
831 }
#define MaxHeapTuplesPerPage
Definition: htup_details.h:573
bool marked[MaxHeapTuplesPerPage+1]
Definition: pruneheap.c:60
int nunused
Definition: pruneheap.c:54
#define Assert(condition)
Definition: c.h:804
OffsetNumber nowunused[MaxHeapTuplesPerPage]
Definition: pruneheap.c:58

◆ heap_prune_satisfies_vacuum()

static HTSV_Result heap_prune_satisfies_vacuum ( PruneState prstate,
HeapTuple  tup,
Buffer  buffer 
)
static

Definition at line 422 of file pruneheap.c.

References Assert, GlobalVisTestIsRemovableXid(), GlobalVisTestNonRemovableHorizon(), HEAPTUPLE_DEAD, HEAPTUPLE_RECENTLY_DEAD, HeapTupleSatisfiesVacuumHorizon(), PruneState::old_snap_ts, PruneState::old_snap_used, PruneState::old_snap_xmin, OldSnapshotThresholdActive(), PruneState::rel, SetOldSnapshotThresholdTimestamp(), TransactionIdIsValid, TransactionIdLimitedForOldSnapshots(), TransactionIdPrecedes(), and PruneState::vistest.

Referenced by heap_prune_chain().

423 {
424  HTSV_Result res;
425  TransactionId dead_after;
426 
427  res = HeapTupleSatisfiesVacuumHorizon(tup, buffer, &dead_after);
428 
429  if (res != HEAPTUPLE_RECENTLY_DEAD)
430  return res;
431 
432  /*
433  * If we are already relying on the limited xmin, there is no need to
434  * delay doing so anymore.
435  */
436  if (prstate->old_snap_used)
437  {
439 
440  if (TransactionIdPrecedes(dead_after, prstate->old_snap_xmin))
441  res = HEAPTUPLE_DEAD;
442  return res;
443  }
444 
445  /*
446  * First check if GlobalVisTestIsRemovableXid() is sufficient to find the
447  * row dead. If not, and old_snapshot_threshold is enabled, try to use the
448  * lowered horizon.
449  */
450  if (GlobalVisTestIsRemovableXid(prstate->vistest, dead_after))
451  res = HEAPTUPLE_DEAD;
452  else if (OldSnapshotThresholdActive())
453  {
454  /* haven't determined limited horizon yet, requests */
455  if (!TransactionIdIsValid(prstate->old_snap_xmin))
456  {
457  TransactionId horizon =
459 
460  TransactionIdLimitedForOldSnapshots(horizon, prstate->rel,
461  &prstate->old_snap_xmin,
462  &prstate->old_snap_ts);
463  }
464 
465  if (TransactionIdIsValid(prstate->old_snap_xmin) &&
466  TransactionIdPrecedes(dead_after, prstate->old_snap_xmin))
467  {
468  /*
469  * About to remove row based on snapshot_too_old. Need to raise
470  * the threshold so problematic accesses would error.
471  */
472  Assert(!prstate->old_snap_used);
474  prstate->old_snap_xmin);
475  prstate->old_snap_used = true;
476  res = HEAPTUPLE_DEAD;
477  }
478  }
479 
480  return res;
481 }
uint32 TransactionId
Definition: c.h:587
bool old_snap_used
Definition: pruneheap.c:48
static bool OldSnapshotThresholdActive(void)
Definition: snapmgr.h:101
Relation rel
Definition: pruneheap.c:33
TimestampTz old_snap_ts
Definition: pruneheap.c:46
bool TransactionIdLimitedForOldSnapshots(TransactionId recentXmin, Relation relation, TransactionId *limit_xid, TimestampTz *limit_ts)
Definition: snapmgr.c:1766
bool GlobalVisTestIsRemovableXid(GlobalVisState *state, TransactionId xid)
Definition: procarray.c:4186
void SetOldSnapshotThresholdTimestamp(TimestampTz ts, TransactionId xlimit)
Definition: snapmgr.c:1687
HTSV_Result HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *dead_after)
bool TransactionIdPrecedes(TransactionId id1, TransactionId id2)
Definition: transam.c:300
TransactionId old_snap_xmin
Definition: pruneheap.c:47
GlobalVisState * vistest
Definition: pruneheap.c:36
#define Assert(condition)
Definition: c.h:804
HTSV_Result
Definition: heapam.h:93
#define TransactionIdIsValid(xid)
Definition: transam.h:41
TransactionId GlobalVisTestNonRemovableHorizon(GlobalVisState *state)
Definition: procarray.c:4224