Skip to content

Commit d3044f8

Browse files
committed
Perform a lot more sanity checks when freezing tuples.
The previous commit has shown that the sanity checks around freezing aren't strong enough. Strengthening them seems especially important because the existance of the bug has caused corruption that we don't want to make even worse during future vacuum cycles. The errors are emitted with ereport rather than elog, despite being "should never happen" messages, so a proper error code is emitted. To avoid superflous translations, mark messages as internal. Author: Andres Freund and Alvaro Herrera Reviewed-By: Alvaro Herrera, Michael Paquier Discussion: https://postgr.es/m/20171102112019.33wb7g5wp4zpjelu@alap3.anarazel.de Backpatch: 9.3-
1 parent 1224383 commit d3044f8

File tree

5 files changed

+114
-25
lines changed

5 files changed

+114
-25
lines changed

src/backend/access/heap/heapam.c

Lines changed: 92 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6338,6 +6338,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
63386338
*/
63396339
static TransactionId
63406340
FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
6341+
TransactionId relfrozenxid, TransactionId relminmxid,
63416342
TransactionId cutoff_xid, MultiXactId cutoff_multi,
63426343
uint16 *flags)
63436344
{
@@ -6364,16 +6365,26 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
63646365
*flags |= FRM_INVALIDATE_XMAX;
63656366
return InvalidTransactionId;
63666367
}
6368+
else if (MultiXactIdPrecedes(multi, relminmxid))
6369+
ereport(ERROR,
6370+
(errcode(ERRCODE_DATA_CORRUPTED),
6371+
errmsg_internal("found multixact %u from before relminmxid %u",
6372+
multi, relminmxid)));
63676373
else if (MultiXactIdPrecedes(multi, cutoff_multi))
63686374
{
63696375
/*
6370-
* This old multi cannot possibly have members still running. If it
6371-
* was a locker only, it can be removed without any further
6372-
* consideration; but if it contained an update, we might need to
6373-
* preserve it.
6376+
* This old multi cannot possibly have members still running, but
6377+
* verify just in case. If it was a locker only, it can be removed
6378+
* without any further consideration; but if it contained an update, we
6379+
* might need to preserve it.
63746380
*/
6375-
Assert(!MultiXactIdIsRunning(multi,
6376-
HEAP_XMAX_IS_LOCKED_ONLY(t_infomask)));
6381+
if (MultiXactIdIsRunning(multi,
6382+
HEAP_XMAX_IS_LOCKED_ONLY(t_infomask)))
6383+
ereport(ERROR,
6384+
(errcode(ERRCODE_DATA_CORRUPTED),
6385+
errmsg_internal("multixact %u from before cutoff %u found to be still running",
6386+
multi, cutoff_multi)));
6387+
63776388
if (HEAP_XMAX_IS_LOCKED_ONLY(t_infomask))
63786389
{
63796390
*flags |= FRM_INVALIDATE_XMAX;
@@ -6387,13 +6398,22 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
63876398
/* wasn't only a lock, xid needs to be valid */
63886399
Assert(TransactionIdIsValid(xid));
63896400

6401+
if (TransactionIdPrecedes(xid, relfrozenxid))
6402+
ereport(ERROR,
6403+
(errcode(ERRCODE_DATA_CORRUPTED),
6404+
errmsg_internal("found update xid %u from before relfrozenxid %u",
6405+
xid, relfrozenxid)));
6406+
63906407
/*
63916408
* If the xid is older than the cutoff, it has to have aborted,
63926409
* otherwise the tuple would have gotten pruned away.
63936410
*/
63946411
if (TransactionIdPrecedes(xid, cutoff_xid))
63956412
{
6396-
Assert(!TransactionIdDidCommit(xid));
6413+
if (TransactionIdDidCommit(xid))
6414+
ereport(ERROR,
6415+
(errcode(ERRCODE_DATA_CORRUPTED),
6416+
errmsg_internal("cannot freeze committed update xid %u", xid)));
63976417
*flags |= FRM_INVALIDATE_XMAX;
63986418
xid = InvalidTransactionId; /* not strictly necessary */
63996419
}
@@ -6465,6 +6485,13 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
64656485
{
64666486
TransactionId xid = members[i].xid;
64676487

6488+
Assert(TransactionIdIsValid(xid));
6489+
if (TransactionIdPrecedes(xid, relfrozenxid))
6490+
ereport(ERROR,
6491+
(errcode(ERRCODE_DATA_CORRUPTED),
6492+
errmsg_internal("found update xid %u from before relfrozenxid %u",
6493+
xid, relfrozenxid)));
6494+
64686495
/*
64696496
* It's an update; should we keep it? If the transaction is known
64706497
* aborted or crashed then it's okay to ignore it, otherwise not.
@@ -6493,18 +6520,26 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
64936520
update_committed = true;
64946521
update_xid = xid;
64956522
}
6496-
6497-
/*
6498-
* Not in progress, not committed -- must be aborted or crashed;
6499-
* we can ignore it.
6500-
*/
6523+
else
6524+
{
6525+
/*
6526+
* Not in progress, not committed -- must be aborted or crashed;
6527+
* we can ignore it.
6528+
*/
6529+
}
65016530

65026531
/*
65036532
* Since the tuple wasn't marked HEAPTUPLE_DEAD by vacuum, the
6504-
* update Xid cannot possibly be older than the xid cutoff.
6533+
* update Xid cannot possibly be older than the xid cutoff. The
6534+
* presence of such a tuple would cause corruption, so be paranoid
6535+
* and check.
65056536
*/
6506-
Assert(!TransactionIdIsValid(update_xid) ||
6507-
!TransactionIdPrecedes(update_xid, cutoff_xid));
6537+
if (TransactionIdIsValid(update_xid) &&
6538+
TransactionIdPrecedes(update_xid, cutoff_xid))
6539+
ereport(ERROR,
6540+
(errcode(ERRCODE_DATA_CORRUPTED),
6541+
errmsg_internal("found update xid %u from before xid cutoff %u",
6542+
update_xid, cutoff_xid)));
65086543

65096544
/*
65106545
* If we determined that it's an Xid corresponding to an update
@@ -6601,8 +6636,9 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
66016636
* recovery. We really need to remove old xids.
66026637
*/
66036638
bool
6604-
heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
6605-
TransactionId cutoff_multi,
6639+
heap_prepare_freeze_tuple(HeapTupleHeader tuple,
6640+
TransactionId relfrozenxid, TransactionId relminmxid,
6641+
TransactionId cutoff_xid, TransactionId cutoff_multi,
66066642
xl_heap_freeze_tuple *frz, bool *totally_frozen_p)
66076643
{
66086644
bool changed = false;
@@ -6619,8 +6655,20 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
66196655
xid = HeapTupleHeaderGetXmin(tuple);
66206656
if (TransactionIdIsNormal(xid))
66216657
{
6658+
if (TransactionIdPrecedes(xid, relfrozenxid))
6659+
ereport(ERROR,
6660+
(errcode(ERRCODE_DATA_CORRUPTED),
6661+
errmsg_internal("found xmin %u from before relfrozenxid %u",
6662+
xid, relfrozenxid)));
6663+
66226664
if (TransactionIdPrecedes(xid, cutoff_xid))
66236665
{
6666+
if (!TransactionIdDidCommit(xid))
6667+
ereport(ERROR,
6668+
(errcode(ERRCODE_DATA_CORRUPTED),
6669+
errmsg_internal("uncommitted xmin %u from before xid cutoff %u needs to be frozen",
6670+
xid, cutoff_xid)));
6671+
66246672
frz->t_infomask |= HEAP_XMIN_FROZEN;
66256673
changed = true;
66266674
}
@@ -6645,6 +6693,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
66456693
uint16 flags;
66466694

66476695
newxmax = FreezeMultiXactId(xid, tuple->t_infomask,
6696+
relfrozenxid, relminmxid,
66486697
cutoff_xid, cutoff_multi, &flags);
66496698

66506699
if (flags & FRM_INVALIDATE_XMAX)
@@ -6694,8 +6743,28 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
66946743
}
66956744
else if (TransactionIdIsNormal(xid))
66966745
{
6746+
if (TransactionIdPrecedes(xid, relfrozenxid))
6747+
ereport(ERROR,
6748+
(errcode(ERRCODE_DATA_CORRUPTED),
6749+
errmsg_internal("found xmax %u from before relfrozenxid %u",
6750+
xid, relfrozenxid)));
6751+
66976752
if (TransactionIdPrecedes(xid, cutoff_xid))
6753+
{
6754+
/*
6755+
* If we freeze xmax, make absolutely sure that it's not an XID
6756+
* that is important. (Note, a lock-only xmax can be removed
6757+
* independent of committedness, since a committed lock holder has
6758+
* released the lock).
6759+
*/
6760+
if (!(tuple->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
6761+
TransactionIdDidCommit(xid))
6762+
ereport(ERROR,
6763+
(errcode(ERRCODE_DATA_CORRUPTED),
6764+
errmsg_internal("cannot freeze committed xmax %u",
6765+
xid)));
66986766
freeze_xmax = true;
6767+
}
66996768
else
67006769
totally_frozen = false;
67016770
}
@@ -6800,14 +6869,17 @@ heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
68006869
* Useful for callers like CLUSTER that perform their own WAL logging.
68016870
*/
68026871
bool
6803-
heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
6804-
TransactionId cutoff_multi)
6872+
heap_freeze_tuple(HeapTupleHeader tuple,
6873+
TransactionId relfrozenxid, TransactionId relminmxid,
6874+
TransactionId cutoff_xid, TransactionId cutoff_multi)
68056875
{
68066876
xl_heap_freeze_tuple frz;
68076877
bool do_freeze;
68086878
bool tuple_totally_frozen;
68096879

6810-
do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
6880+
do_freeze = heap_prepare_freeze_tuple(tuple,
6881+
relfrozenxid, relminmxid,
6882+
cutoff_xid, cutoff_multi,
68116883
&frz, &tuple_totally_frozen);
68126884

68136885
/*

src/backend/access/heap/rewriteheap.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,10 @@ rewrite_heap_tuple(RewriteState state,
407407
* While we have our hands on the tuple, we may as well freeze any
408408
* eligible xmin or xmax, so that future VACUUM effort can be saved.
409409
*/
410-
heap_freeze_tuple(new_tuple->t_data, state->rs_freeze_xid,
410+
heap_freeze_tuple(new_tuple->t_data,
411+
state->rs_old_rel->rd_rel->relfrozenxid,
412+
state->rs_old_rel->rd_rel->relminmxid,
413+
state->rs_freeze_xid,
411414
state->rs_cutoff_multi);
412415

413416
/*

src/backend/commands/vacuumlazy.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,8 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
462462
blkno;
463463
HeapTupleData tuple;
464464
char *relname;
465+
TransactionId relfrozenxid = onerel->rd_rel->relfrozenxid;
466+
TransactionId relminmxid = onerel->rd_rel->relminmxid;
465467
BlockNumber empty_pages,
466468
vacuumed_pages;
467469
double num_tuples,
@@ -993,6 +995,13 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
993995
* tuple, we choose to keep it, because it'll be a lot
994996
* cheaper to get rid of it in the next pruning pass than
995997
* to treat it like an indexed tuple.
998+
*
999+
* If this were to happen for a tuple that actually needed
1000+
* to be deleted, we'd be in trouble, because it'd
1001+
* possibly leave a tuple below the relation's xmin
1002+
* horizon alive. heap_prepare_freeze_tuple() is prepared
1003+
* to detect that case and abort the transaction,
1004+
* preventing corruption.
9961005
*/
9971006
if (HeapTupleIsHotUpdated(&tuple) ||
9981007
HeapTupleIsHeapOnly(&tuple))
@@ -1084,8 +1093,10 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
10841093
* Each non-removable tuple must be checked to see if it needs
10851094
* freezing. Note we already have exclusive buffer lock.
10861095
*/
1087-
if (heap_prepare_freeze_tuple(tuple.t_data, FreezeLimit,
1088-
MultiXactCutoff, &frozen[nfrozen],
1096+
if (heap_prepare_freeze_tuple(tuple.t_data,
1097+
relfrozenxid, relminmxid,
1098+
FreezeLimit, MultiXactCutoff,
1099+
&frozen[nfrozen],
10891100
&tuple_totally_frozen))
10901101
frozen[nfrozen++].offset = offnum;
10911102

src/include/access/heapam.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,9 @@ extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
168168
bool follow_update,
169169
Buffer *buffer, HeapUpdateFailureData *hufd);
170170
extern void heap_inplace_update(Relation relation, HeapTuple tuple);
171-
extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
172-
TransactionId cutoff_multi);
171+
extern bool heap_freeze_tuple(HeapTupleHeader tuple,
172+
TransactionId relfrozenxid, TransactionId relminmxid,
173+
TransactionId cutoff_xid, TransactionId cutoff_multi);
173174
extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
174175
MultiXactId cutoff_multi, Buffer buf);
175176
extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);

src/include/access/heapam_xlog.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,8 @@ extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
384384
TransactionId cutoff_xid, xl_heap_freeze_tuple *tuples,
385385
int ntuples);
386386
extern bool heap_prepare_freeze_tuple(HeapTupleHeader tuple,
387+
TransactionId relfrozenxid,
388+
TransactionId relminmxid,
387389
TransactionId cutoff_xid,
388390
TransactionId cutoff_multi,
389391
xl_heap_freeze_tuple *frz,

0 commit comments

Comments
 (0)