Skip to content

Commit dc6354c

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 f39f3e0 commit dc6354c

File tree

3 files changed

+40
-13
lines changed

3 files changed

+40
-13
lines changed

src/backend/access/heap/pruneheap.c

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

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

3940
/*
@@ -206,7 +207,8 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
206207
int ndeleted,
207208
nnewlpdead;
208209

209-
ndeleted = heap_page_prune(relation, buffer, vistest, limited_xmin,
210+
ndeleted = heap_page_prune(relation, buffer, InvalidTransactionId,
211+
vistest, limited_xmin,
210212
limited_ts, &nnewlpdead, NULL);
211213

212214
/*
@@ -248,11 +250,14 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
248250
* also need to account for a reduction in the length of the line pointer
249251
* array following array truncation by us.
250252
*
251-
* vistest is used to distinguish whether tuples are DEAD or RECENTLY_DEAD
252-
* (see heap_prune_satisfies_vacuum and
253-
* HeapTupleSatisfiesVacuum). old_snap_xmin / old_snap_ts need to
254-
* either have been set by TransactionIdLimitedForOldSnapshots, or
255-
* InvalidTransactionId/0 respectively.
253+
* vistest and oldest_xmin are used to distinguish whether tuples are DEAD or
254+
* RECENTLY_DEAD (see heap_prune_satisfies_vacuum and
255+
* HeapTupleSatisfiesVacuum). If oldest_xmin is provided by the caller, it is
256+
* used before consulting GlobalVisState.
257+
*
258+
* old_snap_xmin / old_snap_ts need to either have been set by
259+
* TransactionIdLimitedForOldSnapshots, or InvalidTransactionId/0
260+
* respectively.
256261
*
257262
* Sets *nnewlpdead for caller, indicating the number of items that were
258263
* newly set LP_DEAD during prune operation.
@@ -264,6 +269,7 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
264269
*/
265270
int
266271
heap_page_prune(Relation relation, Buffer buffer,
272+
TransactionId oldest_xmin,
267273
GlobalVisState *vistest,
268274
TransactionId old_snap_xmin,
269275
TimestampTz old_snap_ts,
@@ -291,6 +297,7 @@ heap_page_prune(Relation relation, Buffer buffer,
291297
*/
292298
prstate.new_prune_xid = InvalidTransactionId;
293299
prstate.rel = relation;
300+
prstate.oldest_xmin = oldest_xmin;
294301
prstate.vistest = vistest;
295302
prstate.old_snap_xmin = old_snap_xmin;
296303
prstate.old_snap_ts = old_snap_ts;
@@ -520,13 +527,31 @@ heap_prune_satisfies_vacuum(PruneState *prstate, HeapTuple tup, Buffer buffer)
520527
}
521528

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

src/backend/access/heap/vacuumlazy.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1585,7 +1585,8 @@ lazy_scan_prune(LVRelState *vacrel,
15851585
* lpdead_items's final value can be thought of as the number of tuples
15861586
* that were deleted from indexes.
15871587
*/
1588-
tuples_deleted = heap_page_prune(rel, buf, vacrel->vistest,
1588+
tuples_deleted = heap_page_prune(rel, buf, vacrel->OldestXmin,
1589+
vacrel->vistest,
15891590
InvalidTransactionId, 0, &nnewlpdead,
15901591
&vacrel->offnum);
15911592

src/include/access/heapam.h

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

0 commit comments

Comments
 (0)