Skip to content

Commit 45ce054

Browse files
Ensure vacuum removes all visibly dead tuples older than OldestXmin
If vacuum fails to remove a tuple with xmax older than VacuumCutoffs->OldestXmin and younger than GlobalVisState->maybe_needed, it will loop infinitely in lazy_scan_prune(), which compares tuples' visibility information to OldestXmin. Starting in version 14, which uses GlobalVisState for visibility testing during pruning, it is possible for GlobalVisState->maybe_needed to precede OldestXmin if maybe_needed is forced to go backward while vacuum is running. This can happen if a disconnected standby with a running transaction older than VacuumCutoffs->OldestXmin reconnects to the primary after vacuum initially calculates GlobalVisState and OldestXmin. Fix this by having vacuum always remove tuples older than OldestXmin during pruning. This is okay because the standby won't replay the tuple removal until the tuple is removable. Thus, the worst that can happen is a recovery conflict. Fixes BUG# 17257 Back-patched in versions 14-17 Author: Melanie Plageman Reviewed-by: Noah Misch, Peter Geoghegan, Robert Haas, Andres Freund, and Heikki Linnakangas Discussion: https://postgr.es/m/CAAKRu_Y_NJzF4-8gzTTeaOuUL3CcGoXPjXcAHbTTygT8AyVqag%40mail.gmail.com
1 parent 5e578ac commit 45ce054

File tree

3 files changed

+39
-13
lines changed

3 files changed

+39
-13
lines changed

src/backend/access/heap/pruneheap.c

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ typedef struct
3232
{
3333
Relation rel;
3434

35-
/* tuple visibility test, initialized for the relation */
35+
/* State used to test tuple visibility; Initialized for the relation */
36+
TransactionId oldest_xmin;
3637
GlobalVisState *vistest;
3738

3839
/*
@@ -202,7 +203,8 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
202203
if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
203204
{
204205
/* OK to prune */
205-
(void) heap_page_prune(relation, buffer, vistest,
206+
(void) heap_page_prune(relation, buffer, InvalidTransactionId,
207+
vistest,
206208
limited_xmin, limited_ts,
207209
true, NULL);
208210
}
@@ -218,11 +220,14 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
218220
*
219221
* Caller must have pin and buffer cleanup lock on the page.
220222
*
221-
* vistest is used to distinguish whether tuples are DEAD or RECENTLY_DEAD
222-
* (see heap_prune_satisfies_vacuum and
223-
* HeapTupleSatisfiesVacuum). old_snap_xmin / old_snap_ts need to
224-
* either have been set by TransactionIdLimitedForOldSnapshots, or
225-
* InvalidTransactionId/0 respectively.
223+
* vistest and oldest_xmin are used to distinguish whether tuples are DEAD or
224+
* RECENTLY_DEAD (see heap_prune_satisfies_vacuum and
225+
* HeapTupleSatisfiesVacuum). If oldest_xmin is provided by the caller, it is
226+
* used before consulting GlobalVisState.
227+
*
228+
* old_snap_xmin / old_snap_ts need to either have been set by
229+
* TransactionIdLimitedForOldSnapshots, or InvalidTransactionId/0
230+
* respectively.
226231
*
227232
* If report_stats is true then we send the number of reclaimed heap-only
228233
* tuples to pgstats. (This must be false during vacuum, since vacuum will
@@ -236,6 +241,7 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
236241
*/
237242
int
238243
heap_page_prune(Relation relation, Buffer buffer,
244+
TransactionId oldest_xmin,
239245
GlobalVisState *vistest,
240246
TransactionId old_snap_xmin,
241247
TimestampTz old_snap_ts,
@@ -262,6 +268,7 @@ heap_page_prune(Relation relation, Buffer buffer,
262268
*/
263269
prstate.new_prune_xid = InvalidTransactionId;
264270
prstate.rel = relation;
271+
prstate.oldest_xmin = oldest_xmin;
265272
prstate.vistest = vistest;
266273
prstate.old_snap_xmin = old_snap_xmin;
267274
prstate.old_snap_ts = old_snap_ts;
@@ -512,13 +519,31 @@ heap_prune_satisfies_vacuum(PruneState *prstate, HeapTuple tup, Buffer buffer)
512519
}
513520

514521
/*
515-
* First check if GlobalVisTestIsRemovableXid() is sufficient to find the
516-
* row dead. If not, and old_snapshot_threshold is enabled, try to use the
517-
* lowered horizon.
522+
* For VACUUM, we must be sure to prune tuples with xmax older than
523+
* oldest_xmin -- a visibility cutoff determined at the beginning of
524+
* vacuuming the relation. oldest_xmin is used for freezing determination
525+
* and we cannot freeze dead tuples' xmaxes.
526+
*/
527+
if (TransactionIdIsValid(prstate->oldest_xmin) &&
528+
NormalTransactionIdPrecedes(dead_after, prstate->oldest_xmin))
529+
return HEAPTUPLE_DEAD;
530+
531+
/*
532+
* Determine whether or not the tuple is considered dead when compared
533+
* with the provided GlobalVisState. On-access pruning does not provide
534+
* oldest_xmin. And for vacuum, even if the tuple's xmax is not older than
535+
* oldest_xmin, GlobalVisTestIsRemovableXid() could find the row dead if
536+
* the GlobalVisState has been updated since the beginning of vacuuming
537+
* the relation.
518538
*/
519539
if (GlobalVisTestIsRemovableXid(prstate->vistest, dead_after))
520-
res = HEAPTUPLE_DEAD;
521-
else if (OldSnapshotThresholdActive())
540+
return HEAPTUPLE_DEAD;
541+
542+
/*
543+
* If GlobalVisTestIsRemovableXid() is not sufficient to find the row dead
544+
* and old_snapshot_threshold is enabled, try to use the lowered horizon.
545+
*/
546+
if (OldSnapshotThresholdActive())
522547
{
523548
/* haven't determined limited horizon yet, requests */
524549
if (!TransactionIdIsValid(prstate->old_snap_xmin))

src/backend/access/heap/vacuumlazy.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1738,7 +1738,7 @@ lazy_scan_prune(LVRelState *vacrel,
17381738
* lpdead_items's final value can be thought of as the number of tuples
17391739
* that were deleted from indexes.
17401740
*/
1741-
tuples_deleted = heap_page_prune(rel, buf, vistest,
1741+
tuples_deleted = heap_page_prune(rel, buf, vacrel->OldestXmin, vistest,
17421742
InvalidTransactionId, 0, false,
17431743
&vacrel->offnum);
17441744

src/include/access/heapam.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ extern TransactionId heap_index_delete_tuples(Relation rel,
186186
struct GlobalVisState;
187187
extern void heap_page_prune_opt(Relation relation, Buffer buffer);
188188
extern int heap_page_prune(Relation relation, Buffer buffer,
189+
TransactionId oldest_xmin,
189190
struct GlobalVisState *vistest,
190191
TransactionId old_snap_xmin,
191192
TimestampTz old_snap_ts_ts,

0 commit comments

Comments
 (0)