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/visibilitymapdefs.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
 

Functions

static void prune_freeze_setup (PruneFreezeParams *params, TransactionId *new_relfrozen_xid, MultiXactId *new_relmin_mxid, PruneFreezeResult *presult, PruneState *prstate)
 
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)
 
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)
 
void heap_page_prune_opt (Relation relation, Buffer buffer, Buffer *vmbuffer)
 
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)
 

Function Documentation

◆ heap_get_root_tuples()

void heap_get_root_tuples ( Page  page,
OffsetNumber root_offsets 
)

Definition at line 1890 of file pruneheap.c.

1891{
1892 OffsetNumber offnum,
1893 maxoff;
1894
1897
1898 maxoff = PageGetMaxOffsetNumber(page);
1899 for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum))
1900 {
1901 ItemId lp = PageGetItemId(page, offnum);
1902 HeapTupleHeader htup;
1905
1906 /* skip unused and dead items */
1907 if (!ItemIdIsUsed(lp) || ItemIdIsDead(lp))
1908 continue;
1909
1910 if (ItemIdIsNormal(lp))
1911 {
1912 htup = (HeapTupleHeader) PageGetItem(page, lp);
1913
1914 /*
1915 * Check if this tuple is part of a HOT-chain rooted at some other
1916 * tuple. If so, skip it for now; we'll process it when we find
1917 * its root.
1918 */
1919 if (HeapTupleHeaderIsHeapOnly(htup))
1920 continue;
1921
1922 /*
1923 * This is either a plain tuple or the root of a HOT-chain.
1924 * Remember it in the mapping.
1925 */
1926 root_offsets[offnum - 1] = offnum;
1927
1928 /* If it's not the start of a HOT-chain, we're done with it */
1929 if (!HeapTupleHeaderIsHotUpdated(htup))
1930 continue;
1931
1932 /* Set up to scan the HOT-chain */
1935 }
1936 else
1937 {
1938 /* Must be a redirect item. We do not set its root_offsets entry */
1940 /* Set up to scan the HOT-chain */
1943 }
1944
1945 /*
1946 * Now follow the HOT-chain and collect other tuples in the chain.
1947 *
1948 * Note: Even though this is a nested loop, the complexity of the
1949 * function is O(N) because a tuple in the page should be visited not
1950 * more than twice, once in the outer loop and once in HOT-chain
1951 * chases.
1952 */
1953 for (;;)
1954 {
1955 /* Sanity check (pure paranoia) */
1956 if (offnum < FirstOffsetNumber)
1957 break;
1958
1959 /*
1960 * An offset past the end of page's line pointer array is possible
1961 * when the array was truncated
1962 */
1963 if (offnum > maxoff)
1964 break;
1965
1966 lp = PageGetItemId(page, nextoffnum);
1967
1968 /* Check for broken chains */
1969 if (!ItemIdIsNormal(lp))
1970 break;
1971
1972 htup = (HeapTupleHeader) PageGetItem(page, lp);
1973
1976 break;
1977
1978 /* Remember the root line pointer for this item */
1979 root_offsets[nextoffnum - 1] = offnum;
1980
1981 /* Advance to next chain member, if any */
1982 if (!HeapTupleHeaderIsHotUpdated(htup))
1983 break;
1984
1985 /* HOT implies it can't have moved to different partition */
1987
1990 }
1991 }
1992}
static ItemId PageGetItemId(Page page, OffsetNumber offsetNumber)
Definition bufpage.h:269
static void * PageGetItem(PageData *page, const ItemIdData *itemId)
Definition bufpage.h:379
static OffsetNumber PageGetMaxOffsetNumber(const PageData *page)
Definition bufpage.h:397
#define Assert(condition)
Definition c.h:945
#define MemSet(start, val, len)
Definition c.h:1109
uint32 TransactionId
Definition c.h:738
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 2017 of file pruneheap.c.

2018{
2019 const HeapTupleFreeze *frz1 = arg1;
2020 const HeapTupleFreeze *frz2 = arg2;
2021
2022 if (frz1->xmax < frz2->xmax)
2023 return -1;
2024 else if (frz1->xmax > frz2->xmax)
2025 return 1;
2026
2027 if (frz1->t_infomask2 < frz2->t_infomask2)
2028 return -1;
2029 else if (frz1->t_infomask2 > frz2->t_infomask2)
2030 return 1;
2031
2032 if (frz1->t_infomask < frz2->t_infomask)
2033 return -1;
2034 else if (frz1->t_infomask > frz2->t_infomask)
2035 return 1;
2036
2037 if (frz1->frzflags < frz2->frzflags)
2038 return -1;
2039 else if (frz1->frzflags > frz2->frzflags)
2040 return 1;
2041
2042 /*
2043 * heap_log_freeze_eq would consider these tuple-wise plans to be equal.
2044 * (So the tuples will share a single canonical freeze plan.)
2045 *
2046 * We tiebreak on page offset number to keep each freeze plan's page
2047 * offset number array individually sorted. (Unnecessary, but be tidy.)
2048 */
2049 if (frz1->offset < frz2->offset)
2050 return -1;
2051 else if (frz1->offset > frz2->offset)
2052 return 1;
2053
2054 Assert(false);
2055 return 0;
2056}

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 2001 of file pruneheap.c.

2002{
2003 if (plan->xmax == frz->xmax &&
2004 plan->t_infomask2 == frz->t_infomask2 &&
2005 plan->t_infomask == frz->t_infomask &&
2006 plan->frzflags == frz->frzflags)
2007 return true;
2008
2009 /* Caller must call heap_log_freeze_new_plan again for frz */
2010 return false;
2011}
#define plan(x)
Definition pg_regress.c:161

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 2063 of file pruneheap.c.

2064{
2065 plan->xmax = frz->xmax;
2066 plan->t_infomask2 = frz->t_infomask2;
2067 plan->t_infomask = frz->t_infomask;
2068 plan->frzflags = frz->frzflags;
2069 plan->ntuples = 1; /* for now */
2070}

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 2083 of file pruneheap.c.

2086{
2087 int nplans = 0;
2088
2089 /* Sort tuple-based freeze plans in the order required to deduplicate */
2090 qsort(tuples, ntuples, sizeof(HeapTupleFreeze), heap_log_freeze_cmp);
2091
2092 for (int i = 0; i < ntuples; i++)
2093 {
2094 HeapTupleFreeze *frz = tuples + i;
2095
2096 if (i == 0)
2097 {
2098 /* New canonical freeze plan starting with first tup */
2100 nplans++;
2101 }
2102 else if (heap_log_freeze_eq(plans_out, frz))
2103 {
2104 /* tup matches open canonical plan -- include tup in it */
2105 Assert(offsets_out[i - 1] < frz->offset);
2106 plans_out->ntuples++;
2107 }
2108 else
2109 {
2110 /* Tup doesn't match current plan -- done with it now */
2111 plans_out++;
2112
2113 /* New canonical freeze plan starting with this tup */
2115 nplans++;
2116 }
2117
2118 /*
2119 * Save page offset number in dedicated buffer in passing.
2120 *
2121 * REDO routine relies on the record's offset numbers array grouping
2122 * offset numbers by freeze plan. The sort order within each grouping
2123 * is ascending offset number order, just to keep things tidy.
2124 */
2125 offsets_out[i] = frz->offset;
2126 }
2127
2128 Assert(nplans > 0 && nplans <= ntuples);
2129
2130 return nplans;
2131}
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:2017
static bool heap_log_freeze_eq(xlhp_freeze_plan *plan, HeapTupleFreeze *frz)
Definition pruneheap.c:2001
static void heap_log_freeze_new_plan(xlhp_freeze_plan *plan, HeapTupleFreeze *frz)
Definition pruneheap.c:2063

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_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 815 of file pruneheap.c.

820{
822 bool do_freeze;
823 bool do_prune;
824 bool do_hint_prune;
827
828 /* Initialize prstate */
829 prune_freeze_setup(params,
831 presult, &prstate);
832
833 /*
834 * Examine all line pointers and tuple visibility information to determine
835 * which line pointers should change state and which tuples may be frozen.
836 * Prepare queue of state changes to later be executed in a critical
837 * section.
838 */
840
841 /*
842 * If checksums are enabled, calling heap_prune_satisfies_vacuum() while
843 * checking tuple visibility information in prune_freeze_plan() may have
844 * caused an FPI to be emitted.
845 */
847
848 do_prune = prstate.nredirected > 0 ||
849 prstate.ndead > 0 ||
850 prstate.nunused > 0;
851
852 /*
853 * Even if we don't prune anything, if we found a new value for the
854 * pd_prune_xid field or the page was marked full, we will update the hint
855 * bit.
856 */
857 do_hint_prune = PageGetPruneXid(prstate.page) != prstate.new_prune_xid ||
858 PageIsFull(prstate.page);
859
860 /*
861 * Decide if we want to go ahead with freezing according to the freeze
862 * plans we prepared, or not.
863 */
865 do_prune,
867 &prstate);
868
869 /*
870 * While scanning the line pointers, we did not clear
871 * set_all_visible/set_all_frozen when encountering LP_DEAD items because
872 * we wanted the decision whether or not to freeze the page to be
873 * unaffected by the short-term presence of LP_DEAD items. These LP_DEAD
874 * items are effectively assumed to be LP_UNUSED items in the making. It
875 * doesn't matter which vacuum heap pass (initial pass or final pass) ends
876 * up setting the page all-frozen, as long as the ongoing VACUUM does it.
877 *
878 * Now that we finished determining whether or not to freeze the page,
879 * update set_all_visible and set_all_frozen so that they reflect the true
880 * state of the page for setting PD_ALL_VISIBLE and VM bits.
881 */
882 if (prstate.lpdead_items > 0)
883 prstate.set_all_visible = prstate.set_all_frozen = false;
884
885 Assert(!prstate.set_all_frozen || prstate.set_all_visible);
886
887 /* Any error while applying the changes is critical */
889
890 if (do_hint_prune)
891 {
892 /*
893 * Update the page's pd_prune_xid field to either zero, or the lowest
894 * XID of any soon-prunable tuple.
895 */
896 ((PageHeader) prstate.page)->pd_prune_xid = prstate.new_prune_xid;
897
898 /*
899 * Also clear the "page is full" flag, since there's no point in
900 * repeating the prune/defrag process until something else happens to
901 * the page.
902 */
904
905 /*
906 * If that's all we had to do to the page, this is a non-WAL-logged
907 * hint. If we are going to freeze or prune the page, we will mark
908 * the buffer dirty below.
909 */
910 if (!do_freeze && !do_prune)
911 MarkBufferDirtyHint(prstate.buffer, true);
912 }
913
914 if (do_prune || do_freeze)
915 {
916 /* Apply the planned item changes and repair page fragmentation. */
917 if (do_prune)
918 {
919 heap_page_prune_execute(prstate.buffer, false,
920 prstate.redirected, prstate.nredirected,
921 prstate.nowdead, prstate.ndead,
922 prstate.nowunused, prstate.nunused);
923 }
924
925 if (do_freeze)
926 heap_freeze_prepared_tuples(prstate.buffer, prstate.frozen, prstate.nfrozen);
927
928 MarkBufferDirty(prstate.buffer);
929
930 /*
931 * Emit a WAL XLOG_HEAP2_PRUNE* record showing what we did
932 */
933 if (RelationNeedsWAL(prstate.relation))
934 {
935 /*
936 * The snapshotConflictHorizon for the whole record should be the
937 * most conservative of all the horizons calculated for any of the
938 * possible modifications. If this record will prune tuples, any
939 * queries on the standby older than the newest xid of the most
940 * recently removed tuple this record will prune will conflict. If
941 * this record will freeze tuples, any queries on the standby with
942 * xids older than the newest tuple this record will freeze will
943 * conflict.
944 */
946
947 if (TransactionIdFollows(prstate.pagefrz.FreezePageConflictXid,
948 prstate.latest_xid_removed))
949 conflict_xid = prstate.pagefrz.FreezePageConflictXid;
950 else
951 conflict_xid = prstate.latest_xid_removed;
952
954 InvalidBuffer, /* vmbuffer */
955 0, /* vmflags */
957 true, params->reason,
958 prstate.frozen, prstate.nfrozen,
959 prstate.redirected, prstate.nredirected,
960 prstate.nowdead, prstate.ndead,
961 prstate.nowunused, prstate.nunused);
962 }
963 }
964
966
967 /* Copy information back for caller */
968 presult->ndeleted = prstate.ndeleted;
969 presult->nnewlpdead = prstate.ndead;
970 presult->nfrozen = prstate.nfrozen;
971 presult->live_tuples = prstate.live_tuples;
972 presult->recently_dead_tuples = prstate.recently_dead_tuples;
973 presult->set_all_visible = prstate.set_all_visible;
974 presult->set_all_frozen = prstate.set_all_frozen;
975 presult->hastup = prstate.hastup;
976
977 /*
978 * For callers planning to update the visibility map, the conflict horizon
979 * for that record must be the newest xmin on the page. However, if the
980 * page is completely frozen, there can be no conflict and the
981 * vm_conflict_horizon should remain InvalidTransactionId. This includes
982 * the case that we just froze all the tuples; the prune-freeze record
983 * included the conflict XID already so the caller doesn't need it.
984 */
985 if (presult->set_all_frozen)
986 presult->vm_conflict_horizon = InvalidTransactionId;
987 else
988 presult->vm_conflict_horizon = prstate.visibility_cutoff_xid;
989
990 presult->lpdead_items = prstate.lpdead_items;
991 /* the presult->deadoffsets array was already filled in */
992
993 if (prstate.attempt_freeze)
994 {
995 if (presult->nfrozen > 0)
996 {
997 *new_relfrozen_xid = prstate.pagefrz.FreezePageRelfrozenXid;
998 *new_relmin_mxid = prstate.pagefrz.FreezePageRelminMxid;
999 }
1000 else
1001 {
1002 *new_relfrozen_xid = prstate.pagefrz.NoFreezePageRelfrozenXid;
1003 *new_relmin_mxid = prstate.pagefrz.NoFreezePageRelminMxid;
1004 }
1005 }
1006}
#define InvalidBuffer
Definition buf.h:25
void MarkBufferDirty(Buffer buffer)
Definition bufmgr.c:3063
void MarkBufferDirtyHint(Buffer buffer, bool buffer_std)
Definition bufmgr.c:5688
PageHeaderData * PageHeader
Definition bufpage.h:199
static TransactionId PageGetPruneXid(const PageData *page)
Definition bufpage.h:471
static void PageClearFull(Page page)
Definition bufpage.h:449
static bool PageIsFull(const PageData *page)
Definition bufpage.h:439
int64_t int64
Definition c.h:615
void heap_freeze_prepared_tuples(Buffer buffer, HeapTupleFreeze *tuples, int ntuples)
Definition heapam.c:7479
WalUsage pgWalUsage
Definition instrument.c:22
#define START_CRIT_SECTION()
Definition miscadmin.h:150
#define END_CRIT_SECTION()
Definition miscadmin.h:152
static void prune_freeze_plan(PruneState *prstate, OffsetNumber *off_loc)
Definition pruneheap.c:469
static bool heap_page_will_freeze(bool did_tuple_hint_fpi, bool do_prune, bool do_hint_prune, PruneState *prstate)
Definition pruneheap.c:672
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:2162
static void prune_freeze_setup(PruneFreezeParams *params, TransactionId *new_relfrozen_xid, MultiXactId *new_relmin_mxid, PruneFreezeResult *presult, PruneState *prstate)
Definition pruneheap.c:337
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:1666
#define RelationNeedsWAL(relation)
Definition rel.h:637
PruneReason reason
Definition heapam.h:269
int64 wal_fpi
Definition instrument.h:54
static bool TransactionIdFollows(TransactionId id1, TransactionId id2)
Definition transam.h:297

References Assert, END_CRIT_SECTION, fb(), heap_freeze_prepared_tuples(), heap_page_prune_execute(), heap_page_will_freeze(), InvalidBuffer, InvalidTransactionId, log_heap_prune_and_freeze(), MarkBufferDirty(), MarkBufferDirtyHint(), PageClearFull(), PageGetPruneXid(), PageIsFull(), pgWalUsage, prune_freeze_plan(), prune_freeze_setup(), PruneFreezeParams::reason, RelationNeedsWAL, START_CRIT_SECTION, TransactionIdFollows(), 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 1666 of file pruneheap.c.

1670{
1671 Page page = BufferGetPage(buffer);
1672 OffsetNumber *offnum;
1674
1675 /* Shouldn't be called unless there's something to do */
1676 Assert(nredirected > 0 || ndead > 0 || nunused > 0);
1677
1678 /* If 'lp_truncate_only', we can only remove already-dead line pointers */
1679 Assert(!lp_truncate_only || (nredirected == 0 && ndead == 0));
1680
1681 /* Update all redirected line pointers */
1682 offnum = redirected;
1683 for (int i = 0; i < nredirected; i++)
1684 {
1685 OffsetNumber fromoff = *offnum++;
1686 OffsetNumber tooff = *offnum++;
1689
1690#ifdef USE_ASSERT_CHECKING
1691
1692 /*
1693 * Any existing item that we set as an LP_REDIRECT (any 'from' item)
1694 * must be the first item from a HOT chain. If the item has tuple
1695 * storage then it can't be a heap-only tuple. Otherwise we are just
1696 * maintaining an existing LP_REDIRECT from an existing HOT chain that
1697 * has been pruned at least once before now.
1698 */
1700 {
1702
1703 htup = (HeapTupleHeader) PageGetItem(page, fromlp);
1705 }
1706 else
1707 {
1708 /* We shouldn't need to redundantly set the redirect */
1710 }
1711
1712 /*
1713 * The item that we're about to set as an LP_REDIRECT (the 'from'
1714 * item) will point to an existing item (the 'to' item) that is
1715 * already a heap-only tuple. There can be at most one LP_REDIRECT
1716 * item per HOT chain.
1717 *
1718 * We need to keep around an LP_REDIRECT item (after original
1719 * non-heap-only root tuple gets pruned away) so that it's always
1720 * possible for VACUUM to easily figure out what TID to delete from
1721 * indexes when an entire HOT chain becomes dead. A heap-only tuple
1722 * can never become LP_DEAD; an LP_REDIRECT item or a regular heap
1723 * tuple can.
1724 *
1725 * This check may miss problems, e.g. the target of a redirect could
1726 * be marked as unused subsequently. The page_verify_redirects() check
1727 * below will catch such problems.
1728 */
1729 tolp = PageGetItemId(page, tooff);
1731 htup = (HeapTupleHeader) PageGetItem(page, tolp);
1733#endif
1734
1736 }
1737
1738 /* Update all now-dead line pointers */
1739 offnum = nowdead;
1740 for (int i = 0; i < ndead; i++)
1741 {
1742 OffsetNumber off = *offnum++;
1743 ItemId lp = PageGetItemId(page, off);
1744
1745#ifdef USE_ASSERT_CHECKING
1746
1747 /*
1748 * An LP_DEAD line pointer must be left behind when the original item
1749 * (which is dead to everybody) could still be referenced by a TID in
1750 * an index. This should never be necessary with any individual
1751 * heap-only tuple item, though. (It's not clear how much of a problem
1752 * that would be, but there is no reason to allow it.)
1753 */
1754 if (ItemIdHasStorage(lp))
1755 {
1757 htup = (HeapTupleHeader) PageGetItem(page, lp);
1759 }
1760 else
1761 {
1762 /* Whole HOT chain becomes dead */
1764 }
1765#endif
1766
1768 }
1769
1770 /* Update all now-unused line pointers */
1771 offnum = nowunused;
1772 for (int i = 0; i < nunused; i++)
1773 {
1774 OffsetNumber off = *offnum++;
1775 ItemId lp = PageGetItemId(page, off);
1776
1777#ifdef USE_ASSERT_CHECKING
1778
1779 if (lp_truncate_only)
1780 {
1781 /* Setting LP_DEAD to LP_UNUSED in vacuum's second pass */
1783 }
1784 else
1785 {
1786 /*
1787 * When heap_page_prune_and_freeze() was called, mark_unused_now
1788 * may have been passed as true, which allows would-be LP_DEAD
1789 * items to be made LP_UNUSED instead. This is only possible if
1790 * the relation has no indexes. If there are any dead items, then
1791 * mark_unused_now was not true and every item being marked
1792 * LP_UNUSED must refer to a heap-only tuple.
1793 */
1794 if (ndead > 0)
1795 {
1797 htup = (HeapTupleHeader) PageGetItem(page, lp);
1799 }
1800 else
1802 }
1803
1804#endif
1805
1807 }
1808
1809 if (lp_truncate_only)
1811 else
1812 {
1813 /*
1814 * Finally, repair any fragmentation, and update the page's hint bit
1815 * about whether it has free pointers.
1816 */
1818
1819 /*
1820 * Now that the page has been modified, assert that redirect items
1821 * still point to valid targets.
1822 */
1824 }
1825}
static Page BufferGetPage(Buffer buffer)
Definition bufmgr.h:470
void PageRepairFragmentation(Page page)
Definition bufpage.c:698
void PageTruncateLinePointerArray(Page page)
Definition bufpage.c:834
PageData * Page
Definition bufpage.h:81
#define PG_USED_FOR_ASSERTS_ONLY
Definition c.h:243
#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:1842

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 
)

Definition at line 216 of file pruneheap.c.

217{
218 Page page = BufferGetPage(buffer);
220 GlobalVisState *vistest;
222
223 /*
224 * We can't write WAL in recovery mode, so there's no point trying to
225 * clean the page. The primary will likely issue a cleaning WAL record
226 * soon anyway, so this is no particular loss.
227 */
228 if (RecoveryInProgress())
229 return;
230
231 /*
232 * First check whether there's any chance there's something to prune,
233 * determining the appropriate horizon is a waste if there's no prune_xid
234 * (i.e. no updates/deletes left potentially dead tuples around).
235 */
238 return;
239
240 /*
241 * Check whether prune_xid indicates that there may be dead rows that can
242 * be cleaned up.
243 */
244 vistest = GlobalVisTestFor(relation);
245
247 return;
248
249 /*
250 * We prune when a previous UPDATE failed to find enough space on the page
251 * for a new tuple version, or when free space falls below the relation's
252 * fill-factor target (but not less than 10%).
253 *
254 * Checking free space here is questionable since we aren't holding any
255 * lock on the buffer; in the worst case we could get a bogus answer. It's
256 * unlikely to be *seriously* wrong, though, since reading either pd_lower
257 * or pd_upper is probably atomic. Avoiding taking a lock seems more
258 * important than sometimes getting a wrong answer in what is after all
259 * just a heuristic estimate.
260 */
263 minfree = Max(minfree, BLCKSZ / 10);
264
265 if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
266 {
267 /* OK, try to get exclusive buffer lock */
269 return;
270
271 /*
272 * Now that we have buffer lock, get accurate information about the
273 * page's free space, and recheck the heuristic about whether to
274 * prune.
275 */
276 if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
277 {
280
281 /*
282 * We don't pass the HEAP_PAGE_PRUNE_MARK_UNUSED_NOW option
283 * regardless of whether or not the relation has indexes, since we
284 * cannot safely determine that during on-access pruning with the
285 * current implementation.
286 */
287 PruneFreezeParams params = {
288 .relation = relation,
289 .buffer = buffer,
290 .reason = PRUNE_ON_ACCESS,
291 .options = 0,
292 .vistest = vistest,
293 .cutoffs = NULL,
294 };
295
297 NULL, NULL);
298
299 /*
300 * Report the number of tuples reclaimed to pgstats. This is
301 * presult.ndeleted minus the number of newly-LP_DEAD-set items.
302 *
303 * We derive the number of dead tuples like this to avoid totally
304 * forgetting about items that were set to LP_DEAD, since they
305 * still need to be cleaned up by VACUUM. We only want to count
306 * heap-only tuples that just became LP_UNUSED in our report,
307 * which don't.
308 *
309 * VACUUM doesn't have to compensate in the same way when it
310 * tracks ndeleted, since it will set the same LP_DEAD items to
311 * LP_UNUSED separately.
312 */
313 if (presult.ndeleted > presult.nnewlpdead)
315 presult.ndeleted - presult.nnewlpdead);
316 }
317
318 /* And release buffer lock */
320
321 /*
322 * We avoid reuse of any free space created on the page by unrelated
323 * UPDATEs/INSERTs by opting to not update the FSM at this point. The
324 * free space should be reused by UPDATEs to *this* page.
325 */
326 }
327}
bool ConditionalLockBufferForCleanup(Buffer buffer)
Definition bufmgr.c:6710
@ BUFFER_LOCK_UNLOCK
Definition bufmgr.h:205
static void LockBuffer(Buffer buffer, BufferLockMode mode)
Definition bufmgr.h:332
Size PageGetHeapFreeSpace(const PageData *page)
Definition bufpage.c:990
#define Max(x, y)
Definition c.h:1087
size_t Size
Definition c.h:691
@ PRUNE_ON_ACCESS
Definition heapam.h:252
void pgstat_update_heap_dead_tuples(Relation rel, int delta)
bool GlobalVisTestIsRemovableXid(GlobalVisState *state, TransactionId xid)
Definition procarray.c:4271
GlobalVisState * GlobalVisTestFor(Relation rel)
Definition procarray.c:4114
void heap_page_prune_and_freeze(PruneFreezeParams *params, PruneFreezeResult *presult, OffsetNumber *off_loc, TransactionId *new_relfrozen_xid, MultiXactId *new_relmin_mxid)
Definition pruneheap.c:815
#define RelationGetTargetPageFreeSpace(relation, defaultff)
Definition rel.h:389
#define HEAP_DEFAULT_FILLFACTOR
Definition rel.h:360
Relation relation
Definition heapam.h:262
bool RecoveryInProgress(void)
Definition xlog.c:6444

References BUFFER_LOCK_UNLOCK, BufferGetPage(), ConditionalLockBufferForCleanup(), fb(), GlobalVisTestFor(), GlobalVisTestIsRemovableXid(), HEAP_DEFAULT_FILLFACTOR, heap_page_prune_and_freeze(), LockBuffer(), Max, PageGetHeapFreeSpace(), PageGetPruneXid(), PageIsFull(), pgstat_update_heap_dead_tuples(), PRUNE_ON_ACCESS, RecoveryInProgress(), PruneFreezeParams::relation, RelationGetTargetPageFreeSpace, and TransactionIdIsValid.

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 672 of file pruneheap.c.

676{
677 bool do_freeze = false;
678
679 /*
680 * If the caller specified we should not attempt to freeze any tuples,
681 * validate that everything is in the right state and return.
682 */
683 if (!prstate->attempt_freeze)
684 {
685 Assert(!prstate->set_all_frozen && prstate->nfrozen == 0);
686 Assert(prstate->lpdead_items == 0 || !prstate->set_all_visible);
687 return false;
688 }
689
690 if (prstate->pagefrz.freeze_required)
691 {
692 /*
693 * heap_prepare_freeze_tuple indicated that at least one XID/MXID from
694 * before FreezeLimit/MultiXactCutoff is present. Must freeze to
695 * advance relfrozenxid/relminmxid.
696 */
697 do_freeze = true;
698 }
699 else
700 {
701 /*
702 * Opportunistically freeze the page if we are generating an FPI
703 * anyway and if doing so means that we can set the page all-frozen
704 * afterwards (might not happen until VACUUM's final heap pass).
705 *
706 * XXX: Previously, we knew if pruning emitted an FPI by checking
707 * pgWalUsage.wal_fpi before and after pruning. Once the freeze and
708 * prune records were combined, this heuristic couldn't be used
709 * anymore. The opportunistic freeze heuristic must be improved;
710 * however, for now, try to approximate the old logic.
711 */
712 if (prstate->set_all_frozen && prstate->nfrozen > 0)
713 {
714 Assert(prstate->set_all_visible);
715
716 /*
717 * Freezing would make the page all-frozen. Have already emitted
718 * an FPI or will do so anyway?
719 */
720 if (RelationNeedsWAL(prstate->relation))
721 {
723 do_freeze = true;
724 else if (do_prune)
725 {
727 do_freeze = true;
728 }
729 else if (do_hint_prune)
730 {
731 if (XLogHintBitIsNeeded() &&
733 do_freeze = true;
734 }
735 }
736 }
737 }
738
739 if (do_freeze)
740 {
741 /*
742 * Validate the tuples we will be freezing before entering the
743 * critical section.
744 */
745 heap_pre_freeze_checks(prstate->buffer, prstate->frozen, prstate->nfrozen);
746 Assert(TransactionIdPrecedes(prstate->pagefrz.FreezePageConflictXid,
747 prstate->cutoffs->OldestXmin));
748 }
749 else if (prstate->nfrozen > 0)
750 {
751 /*
752 * The page contained some tuples that were not already frozen, and we
753 * chose not to freeze them now. The page won't be all-frozen then.
754 */
755 Assert(!prstate->pagefrz.freeze_required);
756
757 prstate->set_all_frozen = false;
758 prstate->nfrozen = 0; /* avoid miscounts in instrumentation */
759 }
760 else
761 {
762 /*
763 * We have no freeze plans to execute. The page might already be
764 * all-frozen (perhaps only following pruning), though. Such pages
765 * can be marked all-frozen in the VM by our caller, even though none
766 * of its tuples were newly frozen here.
767 */
768 }
769
770 return do_freeze;
771}
void heap_pre_freeze_checks(Buffer buffer, HeapTupleFreeze *tuples, int ntuples)
Definition heapam.c:7426
static bool TransactionIdPrecedes(TransactionId id1, TransactionId id2)
Definition transam.h:263
#define XLogHintBitIsNeeded()
Definition xlog.h:122
bool XLogCheckBufferNeedsBackup(Buffer buffer)

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

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 1095 of file pruneheap.c.

1097{
1099 ItemId rootlp;
1100 OffsetNumber offnum;
1102 Page page = prstate->page;
1103
1104 /*
1105 * After traversing the HOT chain, ndeadchain is the index in chainitems
1106 * of the first live successor after the last dead item.
1107 */
1108 int ndeadchain = 0,
1109 nchain = 0;
1110
1112
1113 /* Start from the root tuple */
1114 offnum = rootoffnum;
1115
1116 /* while not end of the chain */
1117 for (;;)
1118 {
1119 HeapTupleHeader htup;
1120 ItemId lp;
1121
1122 /* Sanity check (pure paranoia) */
1123 if (offnum < FirstOffsetNumber)
1124 break;
1125
1126 /*
1127 * An offset past the end of page's line pointer array is possible
1128 * when the array was truncated (original item must have been unused)
1129 */
1130 if (offnum > maxoff)
1131 break;
1132
1133 /* If item is already processed, stop --- it must not be same chain */
1134 if (prstate->processed[offnum])
1135 break;
1136
1137 lp = PageGetItemId(page, offnum);
1138
1139 /*
1140 * Unused item obviously isn't part of the chain. Likewise, a dead
1141 * line pointer can't be part of the chain. Both of those cases were
1142 * already marked as processed.
1143 */
1146
1147 /*
1148 * If we are looking at the redirected root line pointer, jump to the
1149 * first normal tuple in the chain. If we find a redirect somewhere
1150 * else, stop --- it must not be same chain.
1151 */
1153 {
1154 if (nchain > 0)
1155 break; /* not at start of chain */
1156 chainitems[nchain++] = offnum;
1157 offnum = ItemIdGetRedirect(rootlp);
1158 continue;
1159 }
1160
1162
1163 htup = (HeapTupleHeader) PageGetItem(page, lp);
1164
1165 /*
1166 * Check the tuple XMIN against prior XMAX, if any
1167 */
1170 break;
1171
1172 /*
1173 * OK, this tuple is indeed a member of the chain.
1174 */
1175 chainitems[nchain++] = offnum;
1176
1177 switch (htsv_get_valid_status(prstate->htsv[offnum]))
1178 {
1179 case HEAPTUPLE_DEAD:
1180
1181 /* Remember the last DEAD tuple seen */
1184 &prstate->latest_xid_removed);
1185 /* Advance to next chain member */
1186 break;
1187
1189
1190 /*
1191 * We don't need to advance the conflict horizon for
1192 * RECENTLY_DEAD tuples, even if we are removing them. This
1193 * is because we only remove RECENTLY_DEAD tuples if they
1194 * precede a DEAD tuple, and the DEAD tuple must have been
1195 * inserted by a newer transaction than the RECENTLY_DEAD
1196 * tuple by virtue of being later in the chain. We will have
1197 * advanced the conflict horizon for the DEAD tuple.
1198 */
1199
1200 /*
1201 * Advance past RECENTLY_DEAD tuples just in case there's a
1202 * DEAD one after them. We have to make sure that we don't
1203 * miss any DEAD tuples, since DEAD tuples that still have
1204 * tuple storage after pruning will confuse VACUUM.
1205 */
1206 break;
1207
1209 case HEAPTUPLE_LIVE:
1211 goto process_chain;
1212
1213 default:
1214 elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
1215 goto process_chain;
1216 }
1217
1218 /*
1219 * If the tuple is not HOT-updated, then we are at the end of this
1220 * HOT-update chain.
1221 */
1222 if (!HeapTupleHeaderIsHotUpdated(htup))
1223 goto process_chain;
1224
1225 /* HOT implies it can't have moved to different partition */
1227
1228 /*
1229 * Advance to next chain member.
1230 */
1232 offnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
1234 }
1235
1236 if (ItemIdIsRedirected(rootlp) && nchain < 2)
1237 {
1238 /*
1239 * We found a redirect item that doesn't point to a valid follow-on
1240 * item. This can happen if the loop in heap_page_prune_and_freeze()
1241 * caused us to visit the dead successor of a redirect item before
1242 * visiting the redirect item. We can clean up by setting the
1243 * redirect item to LP_DEAD state or LP_UNUSED if the caller
1244 * indicated.
1245 */
1247 return;
1248 }
1249
1251
1252 if (ndeadchain == 0)
1253 {
1254 /*
1255 * No DEAD tuple was found, so the chain is entirely composed of
1256 * normal, unchanged tuples. Leave it alone.
1257 */
1258 int i = 0;
1259
1261 {
1263 i++;
1264 }
1265 for (; i < nchain; i++)
1267 }
1268 else if (ndeadchain == nchain)
1269 {
1270 /*
1271 * The entire chain is dead. Mark the root line pointer LP_DEAD, and
1272 * fully remove the other tuples in the chain.
1273 */
1275 for (int i = 1; i < nchain; i++)
1277 }
1278 else
1279 {
1280 /*
1281 * We found a DEAD tuple in the chain. Redirect the root line pointer
1282 * to the first non-DEAD tuple, and mark as unused each intermediate
1283 * item that we are able to remove from the chain.
1284 */
1287 for (int i = 1; i < ndeadchain; i++)
1289
1290 /* the rest of tuples in the chain are normal, unchanged tuples */
1291 for (int i = ndeadchain; i < nchain; i++)
1293 }
1294}
#define ERROR
Definition elog.h:39
#define elog(elevel,...)
Definition elog.h:226
void HeapTupleHeaderAdvanceConflictHorizon(HeapTupleHeader tuple, TransactionId *snapshotConflictHorizon)
Definition heapam.c:8073
@ 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:1056
static void heap_prune_record_unused(PruneState *prstate, OffsetNumber offnum, bool was_normal)
Definition pruneheap.c:1395
static void heap_prune_record_redirect(PruneState *prstate, OffsetNumber offnum, OffsetNumber rdoffnum, bool was_normal)
Definition pruneheap.c:1312
static void heap_prune_record_dead_or_unused(PruneState *prstate, OffsetNumber offnum, bool was_normal)
Definition pruneheap.c:1378
static void heap_prune_record_unchanged_lp_redirect(PruneState *prstate, OffsetNumber offnum)
Definition pruneheap.c:1641
static void heap_prune_record_unchanged_lp_normal(PruneState *prstate, OffsetNumber offnum)
Definition pruneheap.c:1428

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 1343 of file pruneheap.c.

1345{
1346 Assert(!prstate->processed[offnum]);
1347 prstate->processed[offnum] = true;
1348
1350 prstate->nowdead[prstate->ndead] = offnum;
1351 prstate->ndead++;
1352
1353 /*
1354 * Deliberately delay unsetting set_all_visible and set_all_frozen until
1355 * later during pruning. Removable dead tuples shouldn't preclude freezing
1356 * the page.
1357 */
1358
1359 /* Record the dead offset for vacuum */
1360 prstate->deadoffsets[prstate->lpdead_items++] = offnum;
1361
1362 /*
1363 * If the root entry had been a normal tuple, we are deleting it, so count
1364 * it in the result. But changing a redirect (even to DEAD state) doesn't
1365 * count.
1366 */
1367 if (was_normal)
1368 prstate->ndeleted++;
1369}

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 1378 of file pruneheap.c.

1380{
1381 /*
1382 * If the caller set mark_unused_now to true, we can remove dead tuples
1383 * during pruning instead of marking their line pointers dead. Set this
1384 * tuple's line pointer LP_UNUSED. We hint that this option is less
1385 * likely.
1386 */
1387 if (unlikely(prstate->mark_unused_now))
1389 else
1391}
#define unlikely(x)
Definition c.h:432
static void heap_prune_record_dead(PruneState *prstate, OffsetNumber offnum, bool was_normal)
Definition pruneheap.c:1343

References fb(), heap_prune_record_dead(), heap_prune_record_unused(), and unlikely.

Referenced by heap_prune_chain().

◆ heap_prune_record_prunable()

static void heap_prune_record_prunable ( PruneState prstate,
TransactionId  xid 
)
static

Definition at line 1298 of file pruneheap.c.

1299{
1300 /*
1301 * This should exactly match the PageSetPrunable macro. We can't store
1302 * directly into the page header yet, so we update working state.
1303 */
1305 if (!TransactionIdIsValid(prstate->new_prune_xid) ||
1306 TransactionIdPrecedes(xid, prstate->new_prune_xid))
1307 prstate->new_prune_xid = xid;
1308}
#define TransactionIdIsNormal(xid)
Definition transam.h:42

References Assert, fb(), TransactionIdIsNormal, TransactionIdIsValid, and TransactionIdPrecedes().

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 1312 of file pruneheap.c.

1315{
1316 Assert(!prstate->processed[offnum]);
1317 prstate->processed[offnum] = true;
1318
1319 /*
1320 * Do not mark the redirect target here. It needs to be counted
1321 * separately as an unchanged tuple.
1322 */
1323
1324 Assert(prstate->nredirected < MaxHeapTuplesPerPage);
1325 prstate->redirected[prstate->nredirected * 2] = offnum;
1326 prstate->redirected[prstate->nredirected * 2 + 1] = rdoffnum;
1327
1328 prstate->nredirected++;
1329
1330 /*
1331 * If the root entry had been a normal tuple, we are deleting it, so count
1332 * it in the result. But changing a redirect (even to DEAD state) doesn't
1333 * count.
1334 */
1335 if (was_normal)
1336 prstate->ndeleted++;
1337
1338 prstate->hastup = true;
1339}

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 1613 of file pruneheap.c.

1614{
1615 Assert(!prstate->processed[offnum]);
1616 prstate->processed[offnum] = true;
1617
1618 /*
1619 * Deliberately don't set hastup for LP_DEAD items. We make the soft
1620 * assumption that any LP_DEAD items encountered here will become
1621 * LP_UNUSED later on, before count_nondeletable_pages is reached. If we
1622 * don't make this assumption then rel truncation will only happen every
1623 * other VACUUM, at most. Besides, VACUUM must treat
1624 * hastup/nonempty_pages as provisional no matter how LP_DEAD items are
1625 * handled (handled here, or handled later on).
1626 *
1627 * Similarly, don't unset set_all_visible and set_all_frozen until later,
1628 * at the end of heap_page_prune_and_freeze(). This will allow us to
1629 * attempt to freeze the page after pruning. As long as we unset it
1630 * before updating the visibility map, this will be correct.
1631 */
1632
1633 /* Record the dead offset for vacuum */
1634 prstate->deadoffsets[prstate->lpdead_items++] = offnum;
1635}

References Assert, and fb().

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 1428 of file pruneheap.c.

1429{
1430 HeapTupleHeader htup;
1431 Page page = prstate->page;
1432
1433 Assert(!prstate->processed[offnum]);
1434 prstate->processed[offnum] = true;
1435
1436 prstate->hastup = true; /* the page is not empty */
1437
1438 /*
1439 * The criteria for counting a tuple as live in this block need to match
1440 * what analyze.c's acquire_sample_rows() does, otherwise VACUUM and
1441 * ANALYZE may produce wildly different reltuples values, e.g. when there
1442 * are many recently-dead tuples.
1443 *
1444 * The logic here is a bit simpler than acquire_sample_rows(), as VACUUM
1445 * can't run inside a transaction block, which makes some cases impossible
1446 * (e.g. in-progress insert from the same transaction).
1447 *
1448 * HEAPTUPLE_DEAD are handled by the other heap_prune_record_*()
1449 * subroutines. They don't count dead items like acquire_sample_rows()
1450 * does, because we assume that all dead items will become LP_UNUSED
1451 * before VACUUM finishes. This difference is only superficial. VACUUM
1452 * effectively agrees with ANALYZE about DEAD items, in the end. VACUUM
1453 * won't remember LP_DEAD items, but only because they're not supposed to
1454 * be left behind when it is done. (Cases where we bypass index vacuuming
1455 * will violate this optimistic assumption, but the overall impact of that
1456 * should be negligible.)
1457 */
1458 htup = (HeapTupleHeader) PageGetItem(page, PageGetItemId(page, offnum));
1459
1460 switch (prstate->htsv[offnum])
1461 {
1462 case HEAPTUPLE_LIVE:
1463
1464 /*
1465 * Count it as live. Not only is this natural, but it's also what
1466 * acquire_sample_rows() does.
1467 */
1468 prstate->live_tuples++;
1469
1470 /*
1471 * Is the tuple definitely visible to all transactions?
1472 *
1473 * NB: Like with per-tuple hint bits, we can't set the
1474 * PD_ALL_VISIBLE flag if the inserter committed asynchronously.
1475 * See SetHintBits for more info. Check that the tuple is hinted
1476 * xmin-committed because of that.
1477 */
1478 if (prstate->set_all_visible)
1479 {
1480 TransactionId xmin;
1481
1483 {
1484 prstate->set_all_visible = false;
1485 prstate->set_all_frozen = false;
1486 break;
1487 }
1488
1489 /*
1490 * The inserter definitely committed. But is it old enough
1491 * that everyone sees it as committed? A FrozenTransactionId
1492 * is seen as committed to everyone. Otherwise, we check if
1493 * there is a snapshot that considers this xid to still be
1494 * running, and if so, we don't consider the page all-visible.
1495 */
1496 xmin = HeapTupleHeaderGetXmin(htup);
1497
1498 /*
1499 * For now always use prstate->cutoffs for this test, because
1500 * we only update 'set_all_visible' and 'set_all_frozen' when
1501 * freezing is requested. We could use
1502 * GlobalVisTestIsRemovableXid instead, if a non-freezing
1503 * caller wanted to set the VM bit.
1504 */
1505 Assert(prstate->cutoffs);
1506 if (!TransactionIdPrecedes(xmin, prstate->cutoffs->OldestXmin))
1507 {
1508 prstate->set_all_visible = false;
1509 prstate->set_all_frozen = false;
1510 break;
1511 }
1512
1513 /* Track newest xmin on page. */
1514 if (TransactionIdFollows(xmin, prstate->visibility_cutoff_xid) &&
1516 prstate->visibility_cutoff_xid = xmin;
1517 }
1518 break;
1519
1521 prstate->recently_dead_tuples++;
1522 prstate->set_all_visible = false;
1523 prstate->set_all_frozen = false;
1524
1525 /*
1526 * This tuple will soon become DEAD. Update the hint field so
1527 * that the page is reconsidered for pruning in future.
1528 */
1531 break;
1532
1534
1535 /*
1536 * We do not count these rows as live, because we expect the
1537 * inserting transaction to update the counters at commit, and we
1538 * assume that will happen only after we report our results. This
1539 * assumption is a bit shaky, but it is what acquire_sample_rows()
1540 * does, so be consistent.
1541 */
1542 prstate->set_all_visible = false;
1543 prstate->set_all_frozen = false;
1544
1545 /*
1546 * If we wanted to optimize for aborts, we might consider marking
1547 * the page prunable when we see INSERT_IN_PROGRESS. But we
1548 * don't. See related decisions about when to mark the page
1549 * prunable in heapam.c.
1550 */
1551 break;
1552
1554
1555 /*
1556 * This an expected case during concurrent vacuum. Count such
1557 * rows as live. As above, we assume the deleting transaction
1558 * will commit and update the counters after we report.
1559 */
1560 prstate->live_tuples++;
1561 prstate->set_all_visible = false;
1562 prstate->set_all_frozen = false;
1563
1564 /*
1565 * This tuple may soon become DEAD. Update the hint field so that
1566 * the page is reconsidered for pruning in future.
1567 */
1570 break;
1571
1572 default:
1573
1574 /*
1575 * DEAD tuples should've been passed to heap_prune_record_dead()
1576 * or heap_prune_record_unused() instead.
1577 */
1578 elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result %d",
1579 prstate->htsv[offnum]);
1580 break;
1581 }
1582
1583 /* Consider freezing any normal tuples which will not be removed */
1584 if (prstate->attempt_freeze)
1585 {
1586 bool totally_frozen;
1587
1588 if ((heap_prepare_freeze_tuple(htup,
1589 prstate->cutoffs,
1590 &prstate->pagefrz,
1591 &prstate->frozen[prstate->nfrozen],
1592 &totally_frozen)))
1593 {
1594 /* Save prepared freeze plan for later */
1595 prstate->frozen[prstate->nfrozen++].offset = offnum;
1596 }
1597
1598 /*
1599 * If any tuple isn't either totally frozen already or eligible to
1600 * become totally frozen (according to its freeze plan), then the page
1601 * definitely cannot be set all-frozen in the visibility map later on.
1602 */
1603 if (!totally_frozen)
1604 prstate->set_all_frozen = false;
1605 }
1606}
bool heap_prepare_freeze_tuple(HeapTupleHeader tuple, const struct VacuumCutoffs *cutoffs, HeapPageFreeze *pagefrz, HeapTupleFreeze *frz, bool *totally_frozen)
Definition heapam.c:7146
static bool HeapTupleHeaderXminCommitted(const HeapTupleHeaderData *tup)
static void heap_prune_record_prunable(PruneState *prstate, TransactionId xid)
Definition pruneheap.c:1298

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(), TransactionIdIsNormal, and TransactionIdPrecedes().

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 1641 of file pruneheap.c.

1642{
1643 /*
1644 * A redirect line pointer doesn't count as a live tuple.
1645 *
1646 * If we leave a redirect line pointer in place, there will be another
1647 * tuple on the page that it points to. We will do the bookkeeping for
1648 * that separately. So we have nothing to do here, except remember that
1649 * we processed this item.
1650 */
1651 Assert(!prstate->processed[offnum]);
1652 prstate->processed[offnum] = true;
1653}

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 1417 of file pruneheap.c.

1418{
1419 Assert(!prstate->processed[offnum]);
1420 prstate->processed[offnum] = true;
1421}

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 1395 of file pruneheap.c.

1396{
1397 Assert(!prstate->processed[offnum]);
1398 prstate->processed[offnum] = true;
1399
1401 prstate->nowunused[prstate->nunused] = offnum;
1402 prstate->nunused++;
1403
1404 /*
1405 * If the root entry had been a normal tuple, we are deleting it, so count
1406 * it in the result. But changing a redirect (even to DEAD state) doesn't
1407 * count.
1408 */
1409 if (was_normal)
1410 prstate->ndeleted++;
1411}

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 1013 of file pruneheap.c.

1014{
1015 HTSV_Result res;
1017
1019
1020 if (res != HEAPTUPLE_RECENTLY_DEAD)
1021 return res;
1022
1023 /*
1024 * For VACUUM, we must be sure to prune tuples with xmax older than
1025 * OldestXmin -- a visibility cutoff determined at the beginning of
1026 * vacuuming the relation. OldestXmin is used for freezing determination
1027 * and we cannot freeze dead tuples' xmaxes.
1028 */
1029 if (prstate->cutoffs &&
1030 TransactionIdIsValid(prstate->cutoffs->OldestXmin) &&
1031 NormalTransactionIdPrecedes(dead_after, prstate->cutoffs->OldestXmin))
1032 return HEAPTUPLE_DEAD;
1033
1034 /*
1035 * Determine whether or not the tuple is considered dead when compared
1036 * with the provided GlobalVisState. On-access pruning does not provide
1037 * VacuumCutoffs. And for vacuum, even if the tuple's xmax is not older
1038 * than OldestXmin, GlobalVisTestIsRemovableXid() could find the row dead
1039 * if the GlobalVisState has been updated since the beginning of vacuuming
1040 * the relation.
1041 */
1043 return HEAPTUPLE_DEAD;
1044
1045 return res;
1046}
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 1056 of file pruneheap.c.

1057{
1058 Assert(status >= HEAPTUPLE_DEAD &&
1060 return (HTSV_Result) status;
1061}

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 2162 of file pruneheap.c.

2171{
2174 uint8 info;
2176
2177 /* The following local variables hold data registered in the WAL record: */
2181 xlhp_prune_items dead_items;
2184 bool do_prune = nredirected > 0 || ndead > 0 || nunused > 0;
2186
2188
2189 xlrec.flags = 0;
2191
2192 /*
2193 * We can avoid an FPI of the heap page if the only modification we are
2194 * making to it is to set PD_ALL_VISIBLE and checksums/wal_log_hints are
2195 * disabled. Note that if we explicitly skip an FPI, we must not stamp the
2196 * heap page with this record's LSN. Recovery skips records <= the stamped
2197 * LSN, so this could lead to skipping an earlier FPI needed to repair a
2198 * torn page.
2199 */
2200 if (!do_prune &&
2201 nfrozen == 0 &&
2204
2205 /*
2206 * Prepare data for the buffer. The arrays are not actually in the
2207 * buffer, but we pretend that they are. When XLogInsert stores a full
2208 * page image, the arrays can be omitted.
2209 */
2212
2213 if (do_set_vm)
2214 XLogRegisterBuffer(1, vmbuffer, 0);
2215
2216 if (nfrozen > 0)
2217 {
2218 int nplans;
2219
2221
2222 /*
2223 * Prepare deduplicated representation for use in the WAL record. This
2224 * destructively sorts frozen tuples array in-place.
2225 */
2226 nplans = heap_log_freeze_plan(frozen, nfrozen, plans, frz_offsets);
2227
2228 freeze_plans.nplans = nplans;
2230 offsetof(xlhp_freeze_plans, plans));
2231 XLogRegisterBufData(0, plans,
2232 sizeof(xlhp_freeze_plan) * nplans);
2233 }
2234 if (nredirected > 0)
2235 {
2237
2238 redirect_items.ntargets = nredirected;
2241 XLogRegisterBufData(0, redirected,
2242 sizeof(OffsetNumber[2]) * nredirected);
2243 }
2244 if (ndead > 0)
2245 {
2246 xlrec.flags |= XLHP_HAS_DEAD_ITEMS;
2247
2248 dead_items.ntargets = ndead;
2249 XLogRegisterBufData(0, &dead_items,
2251 XLogRegisterBufData(0, dead,
2252 sizeof(OffsetNumber) * ndead);
2253 }
2254 if (nunused > 0)
2255 {
2257
2258 unused_items.ntargets = nunused;
2261 XLogRegisterBufData(0, unused,
2262 sizeof(OffsetNumber) * nunused);
2263 }
2264 if (nfrozen > 0)
2266 sizeof(OffsetNumber) * nfrozen);
2267
2268 /*
2269 * Prepare the main xl_heap_prune record. We already set the XLHP_HAS_*
2270 * flag above.
2271 */
2273 {
2274 xlrec.flags |= XLHP_VM_ALL_VISIBLE;
2276 xlrec.flags |= XLHP_VM_ALL_FROZEN;
2277 }
2279 xlrec.flags |= XLHP_IS_CATALOG_REL;
2282 if (cleanup_lock)
2283 xlrec.flags |= XLHP_CLEANUP_LOCK;
2284 else
2285 {
2286 Assert(nredirected == 0 && ndead == 0);
2287 /* also, any items in 'unused' must've been LP_DEAD previously */
2288 }
2292
2293 switch (reason)
2294 {
2295 case PRUNE_ON_ACCESS:
2297 break;
2298 case PRUNE_VACUUM_SCAN:
2300 break;
2303 break;
2304 default:
2305 elog(ERROR, "unrecognized prune reason: %d", (int) reason);
2306 break;
2307 }
2308 recptr = XLogInsert(RM_HEAP2_ID, info);
2309
2310 if (do_set_vm)
2311 {
2312 Assert(BufferIsDirty(vmbuffer));
2313 PageSetLSN(BufferGetPage(vmbuffer), recptr);
2314 }
2315
2316 /*
2317 * See comment at the top of the function about regbuf_flags_heap for
2318 * details on when we can advance the page LSN.
2319 */
2320 if (do_prune || nfrozen > 0 || (do_set_vm && XLogHintBitIsNeeded()))
2321 {
2322 Assert(BufferIsDirty(buffer));
2324 }
2325}
bool BufferIsDirty(Buffer buffer)
Definition bufmgr.c:3030
static void PageSetLSN(Page page, XLogRecPtr lsn)
Definition bufpage.h:417
uint8_t uint8
Definition c.h:616
@ 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:2083
#define RelationIsAccessibleInLogicalDecoding(relation)
Definition rel.h:693
#define VISIBILITYMAP_VALID_BITS
#define VISIBILITYMAP_ALL_FROZEN
#define VISIBILITYMAP_ALL_VISIBLE
uint64 XLogRecPtr
Definition xlogdefs.h:21
XLogRecPtr XLogInsert(RmgrId rmid, uint8 info)
Definition xloginsert.c:479
void XLogRegisterBufData(uint8 block_id, const void *data, uint32 len)
Definition xloginsert.c:410
void XLogRegisterData(const void *data, uint32 len)
Definition xloginsert.c:369
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_NO_IMAGE
Definition xloginsert.h:33

References Assert, BufferGetPage(), BufferIsDirty(), data, elog, ERROR, fb(), heap_log_freeze_plan(), MaxHeapTuplesPerPage, xlhp_prune_items::ntargets, PageSetLSN(), PRUNE_ON_ACCESS, PRUNE_VACUUM_CLEANUP, PRUNE_VACUUM_SCAN, 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(), XLogRegisterBufData(), XLogRegisterBuffer(), and XLogRegisterData().

Referenced by heap_page_prune_and_freeze(), and lazy_vacuum_heap_page().

◆ page_verify_redirects()

static void page_verify_redirects ( Page  page)
static

Definition at line 1842 of file pruneheap.c.

1843{
1844#ifdef USE_ASSERT_CHECKING
1845 OffsetNumber offnum;
1846 OffsetNumber maxoff;
1847
1848 maxoff = PageGetMaxOffsetNumber(page);
1849 for (offnum = FirstOffsetNumber;
1850 offnum <= maxoff;
1851 offnum = OffsetNumberNext(offnum))
1852 {
1853 ItemId itemid = PageGetItemId(page, offnum);
1856 HeapTupleHeader htup;
1857
1858 if (!ItemIdIsRedirected(itemid))
1859 continue;
1860
1861 targoff = ItemIdGetRedirect(itemid);
1863
1867 htup = (HeapTupleHeader) PageGetItem(page, targitem);
1869 }
1870#endif
1871}

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

Referenced by heap_page_prune_execute().

◆ prune_freeze_plan()

static void prune_freeze_plan ( PruneState prstate,
OffsetNumber off_loc 
)
static

Definition at line 469 of file pruneheap.c.

470{
471 Page page = prstate->page;
472 BlockNumber blockno = prstate->block;
474 OffsetNumber offnum;
476
478
479 /*
480 * Determine HTSV for all tuples, and queue them up for processing as HOT
481 * chain roots or as heap-only items.
482 *
483 * Determining HTSV only once for each tuple is required for correctness,
484 * to deal with cases where running HTSV twice could result in different
485 * results. For example, RECENTLY_DEAD can turn to DEAD if another
486 * checked item causes GlobalVisTestIsRemovableFullXid() to update the
487 * horizon, or INSERT_IN_PROGRESS can change to DEAD if the inserting
488 * transaction aborts.
489 *
490 * It's also good for performance. Most commonly tuples within a page are
491 * stored at decreasing offsets (while the items are stored at increasing
492 * offsets). When processing all tuples on a page this leads to reading
493 * memory at decreasing offsets within a page, with a variable stride.
494 * That's hard for CPU prefetchers to deal with. Processing the items in
495 * reverse order (and thus the tuples in increasing order) increases
496 * prefetching efficiency significantly / decreases the number of cache
497 * misses.
498 */
499 for (offnum = maxoff;
500 offnum >= FirstOffsetNumber;
501 offnum = OffsetNumberPrev(offnum))
502 {
503 ItemId itemid = PageGetItemId(page, offnum);
504 HeapTupleHeader htup;
505
506 /*
507 * Set the offset number so that we can display it along with any
508 * error that occurred while processing this tuple.
509 */
510 *off_loc = offnum;
511
512 prstate->processed[offnum] = false;
513 prstate->htsv[offnum] = -1;
514
515 /* Nothing to do if slot doesn't contain a tuple */
516 if (!ItemIdIsUsed(itemid))
517 {
519 continue;
520 }
521
522 if (ItemIdIsDead(itemid))
523 {
524 /*
525 * If the caller set mark_unused_now true, we can set dead line
526 * pointers LP_UNUSED now.
527 */
528 if (unlikely(prstate->mark_unused_now))
529 heap_prune_record_unused(prstate, offnum, false);
530 else
532 continue;
533 }
534
535 if (ItemIdIsRedirected(itemid))
536 {
537 /* This is the start of a HOT chain */
538 prstate->root_items[prstate->nroot_items++] = offnum;
539 continue;
540 }
541
542 Assert(ItemIdIsNormal(itemid));
543
544 /*
545 * Get the tuple's visibility status and queue it up for processing.
546 */
547 htup = (HeapTupleHeader) PageGetItem(page, itemid);
548 tup.t_data = htup;
549 tup.t_len = ItemIdGetLength(itemid);
550 ItemPointerSet(&tup.t_self, blockno, offnum);
551
553
554 if (!HeapTupleHeaderIsHeapOnly(htup))
555 prstate->root_items[prstate->nroot_items++] = offnum;
556 else
557 prstate->heaponly_items[prstate->nheaponly_items++] = offnum;
558 }
559
560 /*
561 * Process HOT chains.
562 *
563 * We added the items to the array starting from 'maxoff', so by
564 * processing the array in reverse order, we process the items in
565 * ascending offset number order. The order doesn't matter for
566 * correctness, but some quick micro-benchmarking suggests that this is
567 * faster. (Earlier PostgreSQL versions, which scanned all the items on
568 * the page instead of using the root_items array, also did it in
569 * ascending offset number order.)
570 */
571 for (int i = prstate->nroot_items - 1; i >= 0; i--)
572 {
573 offnum = prstate->root_items[i];
574
575 /* Ignore items already processed as part of an earlier chain */
576 if (prstate->processed[offnum])
577 continue;
578
579 /* see preceding loop */
580 *off_loc = offnum;
581
582 /* Process this item or chain of items */
583 heap_prune_chain(maxoff, offnum, prstate);
584 }
585
586 /*
587 * Process any heap-only tuples that were not already processed as part of
588 * a HOT chain.
589 */
590 for (int i = prstate->nheaponly_items - 1; i >= 0; i--)
591 {
592 offnum = prstate->heaponly_items[i];
593
594 if (prstate->processed[offnum])
595 continue;
596
597 /* see preceding loop */
598 *off_loc = offnum;
599
600 /*
601 * If the tuple is DEAD and doesn't chain to anything else, mark it
602 * unused. (If it does chain, we can only remove it as part of
603 * pruning its chain.)
604 *
605 * We need this primarily to handle aborted HOT updates, that is,
606 * XMIN_INVALID heap-only tuples. Those might not be linked to by any
607 * chain, since the parent tuple might be re-updated before any
608 * pruning occurs. So we have to be able to reap them separately from
609 * chain-pruning. (Note that HeapTupleHeaderIsHotUpdated will never
610 * return true for an XMIN_INVALID tuple, so this code will work even
611 * when there were sequential updates within the aborted transaction.)
612 */
613 if (prstate->htsv[offnum] == HEAPTUPLE_DEAD)
614 {
615 ItemId itemid = PageGetItemId(page, offnum);
616 HeapTupleHeader htup = (HeapTupleHeader) PageGetItem(page, itemid);
617
619 {
621 &prstate->latest_xid_removed);
622 heap_prune_record_unused(prstate, offnum, true);
623 }
624 else
625 {
626 /*
627 * This tuple should've been processed and removed as part of
628 * a HOT chain, so something's wrong. To preserve evidence,
629 * we don't dare to remove it. We cannot leave behind a DEAD
630 * tuple either, because that will cause VACUUM to error out.
631 * Throwing an error with a distinct error message seems like
632 * the least bad option.
633 */
634 elog(ERROR, "dead heap-only tuple (%u, %d) is not linked to from any HOT chain",
635 blockno, offnum);
636 }
637 }
638 else
640 }
641
642 /* We should now have processed every tuple exactly once */
643#ifdef USE_ASSERT_CHECKING
644 for (offnum = FirstOffsetNumber;
645 offnum <= maxoff;
646 offnum = OffsetNumberNext(offnum))
647 {
648 *off_loc = offnum;
649
650 Assert(prstate->processed[offnum]);
651 }
652#endif
653
654 /* Clear the offset information once we have processed the given page. */
656}
uint32 BlockNumber
Definition block.h:31
#define likely(x)
Definition c.h:431
#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:1095
static void heap_prune_record_unchanged_lp_unused(PruneState *prstate, OffsetNumber offnum)
Definition pruneheap.c:1417
static void heap_prune_record_unchanged_lp_dead(PruneState *prstate, OffsetNumber offnum)
Definition pruneheap.c:1613
static HTSV_Result heap_prune_satisfies_vacuum(PruneState *prstate, HeapTuple tup)
Definition pruneheap.c:1013
#define RelationGetRelid(relation)
Definition rel.h:514
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 337 of file pruneheap.c.

342{
343 /* Copy parameters to prstate */
344 prstate->vistest = params->vistest;
345 prstate->mark_unused_now =
347
348 /* cutoffs must be provided if we will attempt freezing */
349 Assert(!(params->options & HEAP_PAGE_PRUNE_FREEZE) || params->cutoffs);
350 prstate->attempt_freeze = (params->options & HEAP_PAGE_PRUNE_FREEZE) != 0;
351 prstate->cutoffs = params->cutoffs;
352 prstate->relation = params->relation;
353 prstate->block = BufferGetBlockNumber(params->buffer);
354 prstate->buffer = params->buffer;
355 prstate->page = BufferGetPage(params->buffer);
356
357 /*
358 * Our strategy is to scan the page and make lists of items to change,
359 * then apply the changes within a critical section. This keeps as much
360 * logic as possible out of the critical section, and also ensures that
361 * WAL replay will work the same as the normal case.
362 *
363 * First, initialize the new pd_prune_xid value to zero (indicating no
364 * prunable tuples). If we find any tuples which may soon become
365 * prunable, we will save the lowest relevant XID in new_prune_xid. Also
366 * initialize the rest of our working state.
367 */
368 prstate->new_prune_xid = InvalidTransactionId;
369 prstate->latest_xid_removed = InvalidTransactionId;
370 prstate->nredirected = prstate->ndead = prstate->nunused = 0;
371 prstate->nfrozen = 0;
372 prstate->nroot_items = 0;
373 prstate->nheaponly_items = 0;
374
375 /* initialize page freezing working state */
376 prstate->pagefrz.freeze_required = false;
377 prstate->pagefrz.FreezePageConflictXid = InvalidTransactionId;
378 if (prstate->attempt_freeze)
379 {
381 prstate->pagefrz.FreezePageRelfrozenXid = *new_relfrozen_xid;
382 prstate->pagefrz.NoFreezePageRelfrozenXid = *new_relfrozen_xid;
383 prstate->pagefrz.FreezePageRelminMxid = *new_relmin_mxid;
384 prstate->pagefrz.NoFreezePageRelminMxid = *new_relmin_mxid;
385 }
386 else
387 {
389 prstate->pagefrz.FreezePageRelminMxid = InvalidMultiXactId;
390 prstate->pagefrz.NoFreezePageRelminMxid = InvalidMultiXactId;
391 prstate->pagefrz.FreezePageRelfrozenXid = InvalidTransactionId;
392 prstate->pagefrz.NoFreezePageRelfrozenXid = InvalidTransactionId;
393 }
394
395 prstate->ndeleted = 0;
396 prstate->live_tuples = 0;
397 prstate->recently_dead_tuples = 0;
398 prstate->hastup = false;
399 prstate->lpdead_items = 0;
400
401 /*
402 * deadoffsets are filled in during pruning but are only used to populate
403 * PruneFreezeResult->deadoffsets. To avoid needing two copies of the
404 * array, just save a pointer to the result offsets array in the
405 * PruneState.
406 */
407 prstate->deadoffsets = presult->deadoffsets;
408
409 /*
410 * Vacuum may update the VM after we're done. We can keep track of
411 * whether the page will be all-visible and all-frozen after pruning and
412 * freezing to help the caller to do that.
413 *
414 * Currently, only VACUUM sets the VM bits. To save the effort, only do
415 * the bookkeeping if the caller needs it. Currently, that's tied to
416 * HEAP_PAGE_PRUNE_FREEZE, but it could be a separate flag if you wanted
417 * to update the VM bits without also freezing or freeze without also
418 * setting the VM bits.
419 *
420 * In addition to telling the caller whether it can set the VM bit, we
421 * also use 'set_all_visible' and 'set_all_frozen' for our own
422 * decision-making. If the whole page would become frozen, we consider
423 * opportunistically freezing tuples. We will not be able to freeze the
424 * whole page if there are tuples present that are not visible to everyone
425 * or if there are dead tuples which are not yet removable. However, dead
426 * tuples which will be removed by the end of vacuuming should not
427 * preclude us from opportunistically freezing. Because of that, we do
428 * not immediately clear set_all_visible and set_all_frozen when we see
429 * LP_DEAD items. We fix that after scanning the line pointers. We must
430 * correct set_all_visible and set_all_frozen before we return them to the
431 * caller, so that the caller doesn't set the VM bits incorrectly.
432 */
433 if (prstate->attempt_freeze)
434 {
435 prstate->set_all_visible = true;
436 prstate->set_all_frozen = true;
437 }
438 else
439 {
440 /*
441 * Initializing to false allows skipping the work to update them in
442 * heap_prune_record_unchanged_lp_normal().
443 */
444 prstate->set_all_visible = false;
445 prstate->set_all_frozen = false;
446 }
447
448 /*
449 * The visibility cutoff xid is the newest xmin of live tuples on the
450 * page. In the common case, this will be set as the conflict horizon the
451 * caller can use for updating the VM. If, at the end of freezing and
452 * pruning, the page is all-frozen, there is no possibility that any
453 * running transaction on the standby does not see tuples on the page as
454 * all-visible, so the conflict horizon remains InvalidTransactionId.
455 */
456 prstate->visibility_cutoff_xid = InvalidTransactionId;
457}
BlockNumber BufferGetBlockNumber(Buffer buffer)
Definition bufmgr.c:4357
#define HEAP_PAGE_PRUNE_FREEZE
Definition heapam.h:44
#define HEAP_PAGE_PRUNE_MARK_UNUSED_NOW
Definition heapam.h:43
#define InvalidMultiXactId
Definition multixact.h:25
GlobalVisState * vistest
Definition heapam.h:286
struct VacuumCutoffs * cutoffs
Definition heapam.h:295

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

Referenced by heap_page_prune_and_freeze().