Commit 8523492d authored by Peter Geoghegan's avatar Peter Geoghegan

Remove tupgone special case from vacuumlazy.c.

Retry the call to heap_prune_page() in rare cases where there is
disagreement between the heap_prune_page() call and the call to
HeapTupleSatisfiesVacuum() that immediately follows.  Disagreement is
possible when a concurrently-aborted transaction makes a tuple DEAD
during the tiny window between each step.  This was the only case where
a tuple considered DEAD by VACUUM still had storage following pruning.
VACUUM's definition of dead tuples is now uniformly simple and
unambiguous: dead tuples from each page are always LP_DEAD line pointers
that were encountered just after we performed pruning (and just before
we considered freezing remaining items with tuple storage).

Eliminating the tupgone=true special case enables INDEX_CLEANUP=off
style skipping of index vacuuming that takes place based on flexible,
dynamic criteria.  The INDEX_CLEANUP=off case had to know about skipping
indexes up-front before now, due to a subtle interaction with the
special case (see commit dd695979) -- this was a special case unto
itself.  Now there are no special cases.  And so now it won't matter
when or how we decide to skip index vacuuming: it won't affect how
pruning behaves, and it won't be affected by any of the implementation
details of pruning or freezing.

Also remove XLOG_HEAP2_CLEANUP_INFO records.  These are no longer
necessary because we now rely entirely on heap pruning taking care of
recovery conflicts.  There is no longer any need to generate recovery
conflicts for DEAD tuples that pruning just missed.  This also means
that heap vacuuming now uses exactly the same strategy for recovery
conflicts as index vacuuming always has: REDO routines never need to
process a latestRemovedXid from the WAL record, since earlier REDO of
the WAL record from pruning is sufficient in all cases.  The generic
XLOG_HEAP2_CLEAN record type is now split into two new record types to
reflect this new division (these are called XLOG_HEAP2_PRUNE and
XLOG_HEAP2_VACUUM).

Also stop acquiring a super-exclusive lock for heap pages when they're
vacuumed during VACUUM's second heap pass.  A regular exclusive lock is
enough.  This is correct because heap page vacuuming is now strictly a
matter of setting the LP_DEAD line pointers to LP_UNUSED.  No other
backend can have a pointer to a tuple located in a pinned buffer that
can be invalidated by a concurrent heap page vacuum operation.

Heap vacuuming can now be thought of as conceptually similar to index
vacuuming and conceptually dissimilar to heap pruning.  Heap pruning now
has sole responsibility for anything involving the logical contents of
the database (e.g., managing transaction status information, recovery
conflicts, considering what to do with HOT chains).  Index vacuuming and
heap vacuuming are now only concerned with recycling garbage items from
physical data structures that back the logical database.

Bump XLOG_PAGE_MAGIC due to pruning and heap page vacuum WAL record
changes.

Credit for the idea of retrying pruning a page to avoid the tupgone case
goes to Andres Freund.

Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-By: default avatarAndres Freund <andres@anarazel.de>
Reviewed-By: default avatarMasahiko Sawada <sawada.mshk@gmail.com>
Discussion: https://postgr.es/m/CAH2-WznneCXTzuFmcwx_EyRQgfsfJAAsu+CsqRFmFXCAar=nJw@mail.gmail.com
parent 789d81de
...@@ -184,10 +184,10 @@ gistRedoDeleteRecord(XLogReaderState *record) ...@@ -184,10 +184,10 @@ gistRedoDeleteRecord(XLogReaderState *record)
* *
* GiST delete records can conflict with standby queries. You might think * GiST delete records can conflict with standby queries. You might think
* that vacuum records would conflict as well, but we've handled that * that vacuum records would conflict as well, but we've handled that
* already. XLOG_HEAP2_CLEANUP_INFO records provide the highest xid * already. XLOG_HEAP2_PRUNE records provide the highest xid cleaned by
* cleaned by the vacuum of the heap and so we can resolve any conflicts * the vacuum of the heap and so we can resolve any conflicts just once
* just once when that arrives. After that we know that no conflicts * when that arrives. After that we know that no conflicts exist from
* exist from individual gist vacuum records on that index. * individual gist vacuum records on that index.
*/ */
if (InHotStandby) if (InHotStandby)
{ {
......
...@@ -992,10 +992,10 @@ hash_xlog_vacuum_one_page(XLogReaderState *record) ...@@ -992,10 +992,10 @@ hash_xlog_vacuum_one_page(XLogReaderState *record)
* Hash index records that are marked as LP_DEAD and being removed during * Hash index records that are marked as LP_DEAD and being removed during
* hash index tuple insertion can conflict with standby queries. You might * hash index tuple insertion can conflict with standby queries. You might
* think that vacuum records would conflict as well, but we've handled * think that vacuum records would conflict as well, but we've handled
* that already. XLOG_HEAP2_CLEANUP_INFO records provide the highest xid * that already. XLOG_HEAP2_PRUNE records provide the highest xid cleaned
* cleaned by the vacuum of the heap and so we can resolve any conflicts * by the vacuum of the heap and so we can resolve any conflicts just once
* just once when that arrives. After that we know that no conflicts * when that arrives. After that we know that no conflicts exist from
* exist from individual hash index vacuum records on that index. * individual hash index vacuum records on that index.
*/ */
if (InHotStandby) if (InHotStandby)
{ {
......
...@@ -7538,7 +7538,7 @@ heap_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate) ...@@ -7538,7 +7538,7 @@ heap_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate)
* must have considered the original tuple header as part of * must have considered the original tuple header as part of
* generating its own latestRemovedXid value. * generating its own latestRemovedXid value.
* *
* Relying on XLOG_HEAP2_CLEAN records like this is the same * Relying on XLOG_HEAP2_PRUNE records like this is the same
* strategy that index vacuuming uses in all cases. Index VACUUM * strategy that index vacuuming uses in all cases. Index VACUUM
* WAL records don't even have a latestRemovedXid field of their * WAL records don't even have a latestRemovedXid field of their
* own for this reason. * own for this reason.
...@@ -7957,88 +7957,6 @@ bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate) ...@@ -7957,88 +7957,6 @@ bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate)
return nblocksfavorable; return nblocksfavorable;
} }
/*
* Perform XLogInsert to register a heap cleanup info message. These
* messages are sent once per VACUUM and are required because
* of the phasing of removal operations during a lazy VACUUM.
* see comments for vacuum_log_cleanup_info().
*/
XLogRecPtr
log_heap_cleanup_info(RelFileNode rnode, TransactionId latestRemovedXid)
{
xl_heap_cleanup_info xlrec;
XLogRecPtr recptr;
xlrec.node = rnode;
xlrec.latestRemovedXid = latestRemovedXid;
XLogBeginInsert();
XLogRegisterData((char *) &xlrec, SizeOfHeapCleanupInfo);
recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_CLEANUP_INFO);
return recptr;
}
/*
* Perform XLogInsert for a heap-clean operation. Caller must already
* have modified the buffer and marked it dirty.
*
* Note: prior to Postgres 8.3, the entries in the nowunused[] array were
* zero-based tuple indexes. Now they are one-based like other uses
* of OffsetNumber.
*
* We also include latestRemovedXid, which is the greatest XID present in
* the removed tuples. That allows recovery processing to cancel or wait
* for long standby queries that can still see these tuples.
*/
XLogRecPtr
log_heap_clean(Relation reln, Buffer buffer,
OffsetNumber *redirected, int nredirected,
OffsetNumber *nowdead, int ndead,
OffsetNumber *nowunused, int nunused,
TransactionId latestRemovedXid)
{
xl_heap_clean xlrec;
XLogRecPtr recptr;
/* Caller should not call me on a non-WAL-logged relation */
Assert(RelationNeedsWAL(reln));
xlrec.latestRemovedXid = latestRemovedXid;
xlrec.nredirected = nredirected;
xlrec.ndead = ndead;
XLogBeginInsert();
XLogRegisterData((char *) &xlrec, SizeOfHeapClean);
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
/*
* The OffsetNumber arrays are not actually in the buffer, but we pretend
* that they are. When XLogInsert stores the whole buffer, the offset
* arrays need not be stored too. Note that even if all three arrays are
* empty, we want to expose the buffer as a candidate for whole-page
* storage, since this record type implies a defragmentation operation
* even if no line pointers changed state.
*/
if (nredirected > 0)
XLogRegisterBufData(0, (char *) redirected,
nredirected * sizeof(OffsetNumber) * 2);
if (ndead > 0)
XLogRegisterBufData(0, (char *) nowdead,
ndead * sizeof(OffsetNumber));
if (nunused > 0)
XLogRegisterBufData(0, (char *) nowunused,
nunused * sizeof(OffsetNumber));
recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_CLEAN);
return recptr;
}
/* /*
* Perform XLogInsert for a heap-freeze operation. Caller must have already * Perform XLogInsert for a heap-freeze operation. Caller must have already
* modified the buffer and marked it dirty. * modified the buffer and marked it dirty.
...@@ -8510,34 +8428,15 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_changed, ...@@ -8510,34 +8428,15 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_changed,
} }
/* /*
* Handles CLEANUP_INFO * Handles XLOG_HEAP2_PRUNE record type.
*/ *
static void * Acquires a super-exclusive lock.
heap_xlog_cleanup_info(XLogReaderState *record)
{
xl_heap_cleanup_info *xlrec = (xl_heap_cleanup_info *) XLogRecGetData(record);
if (InHotStandby)
ResolveRecoveryConflictWithSnapshot(xlrec->latestRemovedXid, xlrec->node);
/*
* Actual operation is a no-op. Record type exists to provide a means for
* conflict processing to occur before we begin index vacuum actions. see
* vacuumlazy.c and also comments in btvacuumpage()
*/
/* Backup blocks are not used in cleanup_info records */
Assert(!XLogRecHasAnyBlockRefs(record));
}
/*
* Handles XLOG_HEAP2_CLEAN record type
*/ */
static void static void
heap_xlog_clean(XLogReaderState *record) heap_xlog_prune(XLogReaderState *record)
{ {
XLogRecPtr lsn = record->EndRecPtr; XLogRecPtr lsn = record->EndRecPtr;
xl_heap_clean *xlrec = (xl_heap_clean *) XLogRecGetData(record); xl_heap_prune *xlrec = (xl_heap_prune *) XLogRecGetData(record);
Buffer buffer; Buffer buffer;
RelFileNode rnode; RelFileNode rnode;
BlockNumber blkno; BlockNumber blkno;
...@@ -8548,12 +8447,8 @@ heap_xlog_clean(XLogReaderState *record) ...@@ -8548,12 +8447,8 @@ heap_xlog_clean(XLogReaderState *record)
/* /*
* We're about to remove tuples. In Hot Standby mode, ensure that there's * We're about to remove tuples. In Hot Standby mode, ensure that there's
* no queries running for which the removed tuples are still visible. * no queries running for which the removed tuples are still visible.
*
* Not all HEAP2_CLEAN records remove tuples with xids, so we only want to
* conflict on the records that cause MVCC failures for user queries. If
* latestRemovedXid is invalid, skip conflict processing.
*/ */
if (InHotStandby && TransactionIdIsValid(xlrec->latestRemovedXid)) if (InHotStandby)
ResolveRecoveryConflictWithSnapshot(xlrec->latestRemovedXid, rnode); ResolveRecoveryConflictWithSnapshot(xlrec->latestRemovedXid, rnode);
/* /*
...@@ -8606,7 +8501,7 @@ heap_xlog_clean(XLogReaderState *record) ...@@ -8606,7 +8501,7 @@ heap_xlog_clean(XLogReaderState *record)
UnlockReleaseBuffer(buffer); UnlockReleaseBuffer(buffer);
/* /*
* After cleaning records from a page, it's useful to update the FSM * After pruning records from a page, it's useful to update the FSM
* about it, as it may cause the page become target for insertions * about it, as it may cause the page become target for insertions
* later even if vacuum decides not to visit it (which is possible if * later even if vacuum decides not to visit it (which is possible if
* gets marked all-visible.) * gets marked all-visible.)
...@@ -8618,6 +8513,80 @@ heap_xlog_clean(XLogReaderState *record) ...@@ -8618,6 +8513,80 @@ heap_xlog_clean(XLogReaderState *record)
} }
} }
/*
* Handles XLOG_HEAP2_VACUUM record type.
*
* Acquires an exclusive lock only.
*/
static void
heap_xlog_vacuum(XLogReaderState *record)
{
XLogRecPtr lsn = record->EndRecPtr;
xl_heap_vacuum *xlrec = (xl_heap_vacuum *) XLogRecGetData(record);
Buffer buffer;
BlockNumber blkno;
XLogRedoAction action;
/*
* If we have a full-page image, restore it (without using a cleanup lock)
* and we're done.
*/
action = XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL, false,
&buffer);
if (action == BLK_NEEDS_REDO)
{
Page page = (Page) BufferGetPage(buffer);
OffsetNumber *nowunused;
Size datalen;
OffsetNumber *offnum;
nowunused = (OffsetNumber *) XLogRecGetBlockData(record, 0, &datalen);
/* Shouldn't be a record unless there's something to do */
Assert(xlrec->nunused > 0);
/* Update all now-unused line pointers */
offnum = nowunused;
for (int i = 0; i < xlrec->nunused; i++)
{
OffsetNumber off = *offnum++;
ItemId lp = PageGetItemId(page, off);
Assert(ItemIdIsDead(lp) && !ItemIdHasStorage(lp));
ItemIdSetUnused(lp);
}
/*
* Update the page's hint bit about whether it has free pointers
*/
PageSetHasFreeLinePointers(page);
PageSetLSN(page, lsn);
MarkBufferDirty(buffer);
}
if (BufferIsValid(buffer))
{
Size freespace = PageGetHeapFreeSpace(BufferGetPage(buffer));
RelFileNode rnode;
XLogRecGetBlockTag(record, 0, &rnode, NULL, &blkno);
UnlockReleaseBuffer(buffer);
/*
* After vacuuming LP_DEAD items from a page, it's useful to update
* the FSM about it, as it may cause the page become target for
* insertions later even if vacuum decides not to visit it (which is
* possible if gets marked all-visible.)
*
* Do this regardless of a full-page image being applied, since the
* FSM data is not in the page anyway.
*/
XLogRecordPageWithFreeSpace(rnode, blkno, freespace);
}
}
/* /*
* Replay XLOG_HEAP2_VISIBLE record. * Replay XLOG_HEAP2_VISIBLE record.
* *
...@@ -9722,15 +9691,15 @@ heap2_redo(XLogReaderState *record) ...@@ -9722,15 +9691,15 @@ heap2_redo(XLogReaderState *record)
switch (info & XLOG_HEAP_OPMASK) switch (info & XLOG_HEAP_OPMASK)
{ {
case XLOG_HEAP2_CLEAN: case XLOG_HEAP2_PRUNE:
heap_xlog_clean(record); heap_xlog_prune(record);
break;
case XLOG_HEAP2_VACUUM:
heap_xlog_vacuum(record);
break; break;
case XLOG_HEAP2_FREEZE_PAGE: case XLOG_HEAP2_FREEZE_PAGE:
heap_xlog_freeze_page(record); heap_xlog_freeze_page(record);
break; break;
case XLOG_HEAP2_CLEANUP_INFO:
heap_xlog_cleanup_info(record);
break;
case XLOG_HEAP2_VISIBLE: case XLOG_HEAP2_VISIBLE:
heap_xlog_visible(record); heap_xlog_visible(record);
break; break;
......
...@@ -182,13 +182,10 @@ heap_page_prune_opt(Relation relation, Buffer buffer) ...@@ -182,13 +182,10 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
*/ */
if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree) if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
{ {
TransactionId ignore = InvalidTransactionId; /* return value not
* needed */
/* OK to prune */ /* OK to prune */
(void) heap_page_prune(relation, buffer, vistest, (void) heap_page_prune(relation, buffer, vistest,
limited_xmin, limited_ts, limited_xmin, limited_ts,
true, &ignore, NULL); true, NULL);
} }
/* And release buffer lock */ /* And release buffer lock */
...@@ -213,8 +210,6 @@ heap_page_prune_opt(Relation relation, Buffer buffer) ...@@ -213,8 +210,6 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
* send its own new total to pgstats, and we don't want this delta applied * send its own new total to pgstats, and we don't want this delta applied
* on top of that.) * on top of that.)
* *
* Sets latestRemovedXid for caller on return.
*
* off_loc is the offset location required by the caller to use in error * off_loc is the offset location required by the caller to use in error
* callback. * callback.
* *
...@@ -225,7 +220,7 @@ heap_page_prune(Relation relation, Buffer buffer, ...@@ -225,7 +220,7 @@ heap_page_prune(Relation relation, Buffer buffer,
GlobalVisState *vistest, GlobalVisState *vistest,
TransactionId old_snap_xmin, TransactionId old_snap_xmin,
TimestampTz old_snap_ts, TimestampTz old_snap_ts,
bool report_stats, TransactionId *latestRemovedXid, bool report_stats,
OffsetNumber *off_loc) OffsetNumber *off_loc)
{ {
int ndeleted = 0; int ndeleted = 0;
...@@ -251,7 +246,7 @@ heap_page_prune(Relation relation, Buffer buffer, ...@@ -251,7 +246,7 @@ heap_page_prune(Relation relation, Buffer buffer,
prstate.old_snap_xmin = old_snap_xmin; prstate.old_snap_xmin = old_snap_xmin;
prstate.old_snap_ts = old_snap_ts; prstate.old_snap_ts = old_snap_ts;
prstate.old_snap_used = false; prstate.old_snap_used = false;
prstate.latestRemovedXid = *latestRemovedXid; prstate.latestRemovedXid = InvalidTransactionId;
prstate.nredirected = prstate.ndead = prstate.nunused = 0; prstate.nredirected = prstate.ndead = prstate.nunused = 0;
memset(prstate.marked, 0, sizeof(prstate.marked)); memset(prstate.marked, 0, sizeof(prstate.marked));
...@@ -318,17 +313,41 @@ heap_page_prune(Relation relation, Buffer buffer, ...@@ -318,17 +313,41 @@ heap_page_prune(Relation relation, Buffer buffer,
MarkBufferDirty(buffer); MarkBufferDirty(buffer);
/* /*
* Emit a WAL XLOG_HEAP2_CLEAN record showing what we did * Emit a WAL XLOG_HEAP2_PRUNE record showing what we did
*/ */
if (RelationNeedsWAL(relation)) if (RelationNeedsWAL(relation))
{ {
xl_heap_prune xlrec;
XLogRecPtr recptr; XLogRecPtr recptr;
recptr = log_heap_clean(relation, buffer, xlrec.latestRemovedXid = prstate.latestRemovedXid;
prstate.redirected, prstate.nredirected, xlrec.nredirected = prstate.nredirected;
prstate.nowdead, prstate.ndead, xlrec.ndead = prstate.ndead;
prstate.nowunused, prstate.nunused,
prstate.latestRemovedXid); XLogBeginInsert();
XLogRegisterData((char *) &xlrec, SizeOfHeapPrune);
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
/*
* The OffsetNumber arrays are not actually in the buffer, but we
* pretend that they are. When XLogInsert stores the whole
* buffer, the offset arrays need not be stored too.
*/
if (prstate.nredirected > 0)
XLogRegisterBufData(0, (char *) prstate.redirected,
prstate.nredirected *
sizeof(OffsetNumber) * 2);
if (prstate.ndead > 0)
XLogRegisterBufData(0, (char *) prstate.nowdead,
prstate.ndead * sizeof(OffsetNumber));
if (prstate.nunused > 0)
XLogRegisterBufData(0, (char *) prstate.nowunused,
prstate.nunused * sizeof(OffsetNumber));
recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_PRUNE);
PageSetLSN(BufferGetPage(buffer), recptr); PageSetLSN(BufferGetPage(buffer), recptr);
} }
...@@ -363,8 +382,6 @@ heap_page_prune(Relation relation, Buffer buffer, ...@@ -363,8 +382,6 @@ heap_page_prune(Relation relation, Buffer buffer,
if (report_stats && ndeleted > prstate.ndead) if (report_stats && ndeleted > prstate.ndead)
pgstat_update_heap_dead_tuples(relation, ndeleted - prstate.ndead); pgstat_update_heap_dead_tuples(relation, ndeleted - prstate.ndead);
*latestRemovedXid = prstate.latestRemovedXid;
/* /*
* XXX Should we update the FSM information of this page ? * XXX Should we update the FSM information of this page ?
* *
...@@ -809,12 +826,8 @@ heap_prune_record_unused(PruneState *prstate, OffsetNumber offnum) ...@@ -809,12 +826,8 @@ heap_prune_record_unused(PruneState *prstate, OffsetNumber offnum)
/* /*
* Perform the actual page changes needed by heap_page_prune. * Perform the actual page changes needed by heap_page_prune.
* It is expected that the caller has suitable pin and lock on the * It is expected that the caller has a super-exclusive lock on the
* buffer, and is inside a critical section. * buffer.
*
* This is split out because it is also used by heap_xlog_clean()
* to replay the WAL record when needed after a crash. Note that the
* arguments are identical to those of log_heap_clean().
*/ */
void void
heap_page_prune_execute(Buffer buffer, heap_page_prune_execute(Buffer buffer,
...@@ -826,6 +839,9 @@ heap_page_prune_execute(Buffer buffer, ...@@ -826,6 +839,9 @@ heap_page_prune_execute(Buffer buffer,
OffsetNumber *offnum; OffsetNumber *offnum;
int i; int i;
/* Shouldn't be called unless there's something to do */
Assert(nredirected > 0 || ndead > 0 || nunused > 0);
/* Update all redirected line pointers */ /* Update all redirected line pointers */
offnum = redirected; offnum = redirected;
for (i = 0; i < nredirected; i++) for (i = 0; i < nredirected; i++)
......
This diff is collapsed.
...@@ -1213,10 +1213,10 @@ backtrack: ...@@ -1213,10 +1213,10 @@ backtrack:
* as long as the callback function only considers whether the * as long as the callback function only considers whether the
* index tuple refers to pre-cutoff heap tuples that were * index tuple refers to pre-cutoff heap tuples that were
* certainly already pruned away during VACUUM's initial heap * certainly already pruned away during VACUUM's initial heap
* scan by the time we get here. (heapam's XLOG_HEAP2_CLEAN * scan by the time we get here. (heapam's XLOG_HEAP2_PRUNE
* and XLOG_HEAP2_CLEANUP_INFO records produce conflicts using * records produce conflicts using a latestRemovedXid value
* a latestRemovedXid value for the pointed-to heap tuples, so * for the pointed-to heap tuples, so there is no need to
* there is no need to produce our own conflict now.) * produce our own conflict now.)
* *
* Backends with snapshots acquired after a VACUUM starts but * Backends with snapshots acquired after a VACUUM starts but
* before it finishes could have visibility cutoff with a * before it finishes could have visibility cutoff with a
......
...@@ -121,11 +121,20 @@ heap2_desc(StringInfo buf, XLogReaderState *record) ...@@ -121,11 +121,20 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
info &= XLOG_HEAP_OPMASK; info &= XLOG_HEAP_OPMASK;
if (info == XLOG_HEAP2_CLEAN) if (info == XLOG_HEAP2_PRUNE)
{ {
xl_heap_clean *xlrec = (xl_heap_clean *) rec; xl_heap_prune *xlrec = (xl_heap_prune *) rec;
appendStringInfo(buf, "latestRemovedXid %u", xlrec->latestRemovedXid); appendStringInfo(buf, "latestRemovedXid %u nredirected %u ndead %u",
xlrec->latestRemovedXid,
xlrec->nredirected,
xlrec->ndead);
}
else if (info == XLOG_HEAP2_VACUUM)
{
xl_heap_vacuum *xlrec = (xl_heap_vacuum *) rec;
appendStringInfo(buf, "nunused %u", xlrec->nunused);
} }
else if (info == XLOG_HEAP2_FREEZE_PAGE) else if (info == XLOG_HEAP2_FREEZE_PAGE)
{ {
...@@ -134,12 +143,6 @@ heap2_desc(StringInfo buf, XLogReaderState *record) ...@@ -134,12 +143,6 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
appendStringInfo(buf, "cutoff xid %u ntuples %u", appendStringInfo(buf, "cutoff xid %u ntuples %u",
xlrec->cutoff_xid, xlrec->ntuples); xlrec->cutoff_xid, xlrec->ntuples);
} }
else if (info == XLOG_HEAP2_CLEANUP_INFO)
{
xl_heap_cleanup_info *xlrec = (xl_heap_cleanup_info *) rec;
appendStringInfo(buf, "latestRemovedXid %u", xlrec->latestRemovedXid);
}
else if (info == XLOG_HEAP2_VISIBLE) else if (info == XLOG_HEAP2_VISIBLE)
{ {
xl_heap_visible *xlrec = (xl_heap_visible *) rec; xl_heap_visible *xlrec = (xl_heap_visible *) rec;
...@@ -229,15 +232,15 @@ heap2_identify(uint8 info) ...@@ -229,15 +232,15 @@ heap2_identify(uint8 info)
switch (info & ~XLR_INFO_MASK) switch (info & ~XLR_INFO_MASK)
{ {
case XLOG_HEAP2_CLEAN: case XLOG_HEAP2_PRUNE:
id = "CLEAN"; id = "PRUNE";
break;
case XLOG_HEAP2_VACUUM:
id = "VACUUM";
break; break;
case XLOG_HEAP2_FREEZE_PAGE: case XLOG_HEAP2_FREEZE_PAGE:
id = "FREEZE_PAGE"; id = "FREEZE_PAGE";
break; break;
case XLOG_HEAP2_CLEANUP_INFO:
id = "CLEANUP_INFO";
break;
case XLOG_HEAP2_VISIBLE: case XLOG_HEAP2_VISIBLE:
id = "VISIBLE"; id = "VISIBLE";
break; break;
......
...@@ -484,8 +484,8 @@ DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) ...@@ -484,8 +484,8 @@ DecodeHeap2Op(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
* interested in. * interested in.
*/ */
case XLOG_HEAP2_FREEZE_PAGE: case XLOG_HEAP2_FREEZE_PAGE:
case XLOG_HEAP2_CLEAN: case XLOG_HEAP2_PRUNE:
case XLOG_HEAP2_CLEANUP_INFO: case XLOG_HEAP2_VACUUM:
case XLOG_HEAP2_VISIBLE: case XLOG_HEAP2_VISIBLE:
case XLOG_HEAP2_LOCK_UPDATED: case XLOG_HEAP2_LOCK_UPDATED:
break; break;
......
...@@ -186,7 +186,7 @@ extern int heap_page_prune(Relation relation, Buffer buffer, ...@@ -186,7 +186,7 @@ extern int heap_page_prune(Relation relation, Buffer buffer,
struct GlobalVisState *vistest, struct GlobalVisState *vistest,
TransactionId old_snap_xmin, TransactionId old_snap_xmin,
TimestampTz old_snap_ts_ts, TimestampTz old_snap_ts_ts,
bool report_stats, TransactionId *latestRemovedXid, bool report_stats,
OffsetNumber *off_loc); OffsetNumber *off_loc);
extern void heap_page_prune_execute(Buffer buffer, extern void heap_page_prune_execute(Buffer buffer,
OffsetNumber *redirected, int nredirected, OffsetNumber *redirected, int nredirected,
......
...@@ -51,9 +51,9 @@ ...@@ -51,9 +51,9 @@
* these, too. * these, too.
*/ */
#define XLOG_HEAP2_REWRITE 0x00 #define XLOG_HEAP2_REWRITE 0x00
#define XLOG_HEAP2_CLEAN 0x10 #define XLOG_HEAP2_PRUNE 0x10
#define XLOG_HEAP2_FREEZE_PAGE 0x20 #define XLOG_HEAP2_VACUUM 0x20
#define XLOG_HEAP2_CLEANUP_INFO 0x30 #define XLOG_HEAP2_FREEZE_PAGE 0x30
#define XLOG_HEAP2_VISIBLE 0x40 #define XLOG_HEAP2_VISIBLE 0x40
#define XLOG_HEAP2_MULTI_INSERT 0x50 #define XLOG_HEAP2_MULTI_INSERT 0x50
#define XLOG_HEAP2_LOCK_UPDATED 0x60 #define XLOG_HEAP2_LOCK_UPDATED 0x60
...@@ -227,7 +227,8 @@ typedef struct xl_heap_update ...@@ -227,7 +227,8 @@ typedef struct xl_heap_update
#define SizeOfHeapUpdate (offsetof(xl_heap_update, new_offnum) + sizeof(OffsetNumber)) #define SizeOfHeapUpdate (offsetof(xl_heap_update, new_offnum) + sizeof(OffsetNumber))
/* /*
* This is what we need to know about vacuum page cleanup/redirect * This is what we need to know about page pruning (both during VACUUM and
* during opportunistic pruning)
* *
* The array of OffsetNumbers following the fixed part of the record contains: * The array of OffsetNumbers following the fixed part of the record contains:
* * for each redirected item: the item offset, then the offset redirected to * * for each redirected item: the item offset, then the offset redirected to
...@@ -236,29 +237,32 @@ typedef struct xl_heap_update ...@@ -236,29 +237,32 @@ typedef struct xl_heap_update
* The total number of OffsetNumbers is therefore 2*nredirected+ndead+nunused. * The total number of OffsetNumbers is therefore 2*nredirected+ndead+nunused.
* Note that nunused is not explicitly stored, but may be found by reference * Note that nunused is not explicitly stored, but may be found by reference
* to the total record length. * to the total record length.
*
* Requires a super-exclusive lock.
*/ */
typedef struct xl_heap_clean typedef struct xl_heap_prune
{ {
TransactionId latestRemovedXid; TransactionId latestRemovedXid;
uint16 nredirected; uint16 nredirected;
uint16 ndead; uint16 ndead;
/* OFFSET NUMBERS are in the block reference 0 */ /* OFFSET NUMBERS are in the block reference 0 */
} xl_heap_clean; } xl_heap_prune;
#define SizeOfHeapClean (offsetof(xl_heap_clean, ndead) + sizeof(uint16)) #define SizeOfHeapPrune (offsetof(xl_heap_prune, ndead) + sizeof(uint16))
/* /*
* Cleanup_info is required in some cases during a lazy VACUUM. * The vacuum page record is similar to the prune record, but can only mark
* Used for reporting the results of HeapTupleHeaderAdvanceLatestRemovedXid() * already dead items as unused
* see vacuumlazy.c for full explanation *
* Used by heap vacuuming only. Does not require a super-exclusive lock.
*/ */
typedef struct xl_heap_cleanup_info typedef struct xl_heap_vacuum
{ {
RelFileNode node; uint16 nunused;
TransactionId latestRemovedXid; /* OFFSET NUMBERS are in the block reference 0 */
} xl_heap_cleanup_info; } xl_heap_vacuum;
#define SizeOfHeapCleanupInfo (sizeof(xl_heap_cleanup_info)) #define SizeOfHeapVacuum (offsetof(xl_heap_vacuum, nunused) + sizeof(uint16))
/* flags for infobits_set */ /* flags for infobits_set */
#define XLHL_XMAX_IS_MULTI 0x01 #define XLHL_XMAX_IS_MULTI 0x01
...@@ -397,13 +401,6 @@ extern void heap2_desc(StringInfo buf, XLogReaderState *record); ...@@ -397,13 +401,6 @@ extern void heap2_desc(StringInfo buf, XLogReaderState *record);
extern const char *heap2_identify(uint8 info); extern const char *heap2_identify(uint8 info);
extern void heap_xlog_logical_rewrite(XLogReaderState *r); extern void heap_xlog_logical_rewrite(XLogReaderState *r);
extern XLogRecPtr log_heap_cleanup_info(RelFileNode rnode,
TransactionId latestRemovedXid);
extern XLogRecPtr log_heap_clean(Relation reln, Buffer buffer,
OffsetNumber *redirected, int nredirected,
OffsetNumber *nowdead, int ndead,
OffsetNumber *nowunused, int nunused,
TransactionId latestRemovedXid);
extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer, extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
TransactionId cutoff_xid, xl_heap_freeze_tuple *tuples, TransactionId cutoff_xid, xl_heap_freeze_tuple *tuples,
int ntuples); int ntuples);
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
/* /*
* Each page of XLOG file has a header like this: * Each page of XLOG file has a header like this:
*/ */
#define XLOG_PAGE_MAGIC 0xD10B /* can be used as WAL version indicator */ #define XLOG_PAGE_MAGIC 0xD10C /* can be used as WAL version indicator */
typedef struct XLogPageHeaderData typedef struct XLogPageHeaderData
{ {
......
...@@ -3554,8 +3554,6 @@ xl_hash_split_complete ...@@ -3554,8 +3554,6 @@ xl_hash_split_complete
xl_hash_squeeze_page xl_hash_squeeze_page
xl_hash_update_meta_page xl_hash_update_meta_page
xl_hash_vacuum_one_page xl_hash_vacuum_one_page
xl_heap_clean
xl_heap_cleanup_info
xl_heap_confirm xl_heap_confirm
xl_heap_delete xl_heap_delete
xl_heap_freeze_page xl_heap_freeze_page
...@@ -3567,9 +3565,11 @@ xl_heap_lock ...@@ -3567,9 +3565,11 @@ xl_heap_lock
xl_heap_lock_updated xl_heap_lock_updated
xl_heap_multi_insert xl_heap_multi_insert
xl_heap_new_cid xl_heap_new_cid
xl_heap_prune
xl_heap_rewrite_mapping xl_heap_rewrite_mapping
xl_heap_truncate xl_heap_truncate
xl_heap_update xl_heap_update
xl_heap_vacuum
xl_heap_visible xl_heap_visible
xl_invalid_page xl_invalid_page
xl_invalid_page_key xl_invalid_page_key
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment