diff options
author | Alvaro Herrera <alvherre@alvh.no-ip.org> | 2017-11-02 15:51:05 +0100 |
---|---|---|
committer | Alvaro Herrera <alvherre@alvh.no-ip.org> | 2017-11-02 15:51:05 +0100 |
commit | ef0339ee5dcf933949f3751d98771e03b8c19aa2 (patch) | |
tree | 53384984c3d6f49150168989ffa3ba84f3cfddc9 | |
parent | f570ec5181e1aeff4fc8ea229e50bd666d35062f (diff) | |
download | postgresql-ef0339ee5dcf933949f3751d98771e03b8c19aa2.tar.gz postgresql-ef0339ee5dcf933949f3751d98771e03b8c19aa2.zip |
Revert bogus fixes of HOT-freezing bug
It turns out we misdiagnosed what the real problem was. Revert the
previous changes, because they may have worse consequences going
forward. A better fix is forthcoming.
The simplistic test case is kept, though disabled.
Discussion: https://postgr.es/m/20171102112019.33wb7g5wp4zpjelu@alap3.anarazel.de
-rw-r--r-- | src/backend/access/heap/heapam.c | 143 | ||||
-rw-r--r-- | src/backend/access/heap/pruneheap.c | 4 | ||||
-rw-r--r-- | src/backend/commands/vacuumlazy.c | 16 | ||||
-rw-r--r-- | src/backend/executor/execMain.c | 6 | ||||
-rw-r--r-- | src/include/access/heapam.h | 3 | ||||
-rw-r--r-- | src/test/isolation/isolation_schedule | 1 |
6 files changed, 60 insertions, 113 deletions
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index c2cb40c26f4..af101689282 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -1737,7 +1737,8 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, * broken. */ if (TransactionIdIsValid(prev_xmax) && - !HeapTupleUpdateXmaxMatchesXmin(prev_xmax, heapTuple->t_data)) + !TransactionIdEquals(prev_xmax, + HeapTupleHeaderGetXmin(heapTuple->t_data))) break; /* @@ -1919,7 +1920,7 @@ heap_get_latest_tid(Relation relation, * tuple. Check for XMIN match. */ if (TransactionIdIsValid(priorXmax) && - !HeapTupleUpdateXmaxMatchesXmin(priorXmax, tp.t_data)) + !TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data))) { UnlockReleaseBuffer(buffer); break; @@ -1951,50 +1952,6 @@ heap_get_latest_tid(Relation relation, } /* end of loop */ } -/* - * HeapTupleUpdateXmaxMatchesXmin - verify update chain xmax/xmin lineage - * - * Given the new version of a tuple after some update, verify whether the - * given Xmax (corresponding to the previous version) matches the tuple's - * Xmin, taking into account that the Xmin might have been frozen after the - * update. - */ -bool -HeapTupleUpdateXmaxMatchesXmin(TransactionId xmax, HeapTupleHeader htup) -{ - TransactionId xmin = HeapTupleHeaderGetXmin(htup); - - /* - * If the xmax of the old tuple is identical to the xmin of the new one, - * it's a match. - */ - if (TransactionIdEquals(xmax, xmin)) - return true; - - /* - * If the Xmin that was in effect prior to a freeze matches the Xmax, - * it's good too. - */ - if (HeapTupleHeaderXminFrozen(htup) && - TransactionIdEquals(HeapTupleHeaderGetRawXmin(htup), xmax)) - return true; - - /* - * When a tuple is frozen, the original Xmin is lost, but we know it's a - * committed transaction. So unless the Xmax is InvalidXid, we don't know - * for certain that there is a match, but there may be one; and we must - * return true so that a HOT chain that is half-frozen can be walked - * correctly. - * - * We no longer freeze tuples this way, but we must keep this in order to - * interpret pre-pg_upgrade pages correctly. - */ - if (TransactionIdEquals(xmin, FrozenTransactionId) && - TransactionIdIsValid(xmax)) - return true; - - return false; -} /* * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends @@ -5283,7 +5240,8 @@ l4: * end of the chain, we're done, so return success. */ if (TransactionIdIsValid(priorXmax) && - !HeapTupleUpdateXmaxMatchesXmin(priorXmax, mytup.t_data)) + !TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data), + priorXmax)) { UnlockReleaseBuffer(buf); return HeapTupleMayBeUpdated; @@ -5729,23 +5687,14 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, Assert(TransactionIdIsValid(xid)); /* - * The updating transaction cannot possibly be still running, but - * verify whether it has committed, and request to set the - * COMMITTED flag if so. (We normally don't see any tuples in - * this state, because they are removed by page pruning before we - * try to freeze the page; but this can happen if the updating - * transaction commits after the page is pruned but before - * HeapTupleSatisfiesVacuum). + * If the xid is older than the cutoff, it has to have aborted, + * otherwise the tuple would have gotten pruned away. */ if (TransactionIdPrecedes(xid, cutoff_xid)) { - if (TransactionIdDidCommit(xid)) - *flags = FRM_MARK_COMMITTED | FRM_RETURN_IS_XID; - else - { - *flags |= FRM_INVALIDATE_XMAX; - xid = InvalidTransactionId; /* not strictly necessary */ - } + Assert(!TransactionIdDidCommit(xid)); + *flags |= FRM_INVALIDATE_XMAX; + xid = InvalidTransactionId; /* not strictly necessary */ } else { @@ -5816,17 +5765,18 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, /* * It's an update; should we keep it? If the transaction is known - * aborted or crashed then it's okay to ignore it, otherwise not. + * aborted then it's okay to ignore it, otherwise not. However, + * if the Xid is older than the cutoff_xid, we must remove it. + * Note that such an old updater cannot possibly be committed, + * because HeapTupleSatisfiesVacuum would have returned + * HEAPTUPLE_DEAD and we would not be trying to freeze the tuple. + * + * Note the TransactionIdDidAbort() test is just an optimization + * and not strictly necessary for correctness. * * As with all tuple visibility routines, it's critical to test - * TransactionIdIsInProgress before TransactionIdDidCommit, + * TransactionIdIsInProgress before the transam.c routines, * because of race conditions explained in detail in tqual.c. - * - * We normally don't see committed updating transactions earlier - * than the cutoff xid, because they are removed by page pruning - * before we try to freeze the page; but it can happen if the - * updating transaction commits after the page is pruned but - * before HeapTupleSatisfiesVacuum. */ if (TransactionIdIsCurrentTransactionId(xid) || TransactionIdIsInProgress(xid)) @@ -5834,33 +5784,46 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, Assert(!TransactionIdIsValid(update_xid)); update_xid = xid; } - else if (TransactionIdDidCommit(xid)) + else if (!TransactionIdDidAbort(xid)) { /* - * The transaction committed, so we can tell caller to set - * HEAP_XMAX_COMMITTED. (We can only do this because we know - * the transaction is not running.) + * Test whether to tell caller to set HEAP_XMAX_COMMITTED + * while we have the Xid still in cache. Note this can only + * be done if the transaction is known not running. */ + if (TransactionIdDidCommit(xid)) + update_committed = true; Assert(!TransactionIdIsValid(update_xid)); - update_committed = true; update_xid = xid; } /* - * Not in progress, not committed -- must be aborted or crashed; - * we can ignore it. - */ - - /* * If we determined that it's an Xid corresponding to an update * that must be retained, additionally add it to the list of - * members of the new Multi, in case we end up using that. (We + * members of the new Multis, in case we end up using that. (We * might still decide to use only an update Xid and not a multi, * but it's easier to maintain the list as we walk the old members * list.) + * + * It is possible to end up with a very old updater Xid that + * crashed and thus did not mark itself as aborted in pg_clog. + * That would manifest as a pre-cutoff Xid. Make sure to ignore + * it. */ if (TransactionIdIsValid(update_xid)) - newmembers[nnewmembers++] = members[i]; + { + if (!TransactionIdPrecedes(update_xid, cutoff_xid)) + { + newmembers[nnewmembers++] = members[i]; + } + else + { + /* cannot have committed: would be HEAPTUPLE_DEAD */ + Assert(!TransactionIdDidCommit(update_xid)); + update_xid = InvalidTransactionId; + update_committed = false; + } + } } else { @@ -5927,10 +5890,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, * * It is assumed that the caller has checked the tuple with * HeapTupleSatisfiesVacuum() and determined that it is not HEAPTUPLE_DEAD - * (else we should be removing the tuple, not freezing it). However, note - * that we don't remove HOT tuples even if they are dead, and it'd be incorrect - * to freeze them (because that would make them visible), so we mark them as - * update-committed, and needing further freezing later on. + * (else we should be removing the tuple, not freezing it). * * NB: cutoff_xid *must* be <= the current global xmin, to ensure that any * XID older than it could neither be running nor seen as running by any @@ -6035,18 +5995,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid, else if (TransactionIdIsNormal(xid) && TransactionIdPrecedes(xid, cutoff_xid)) { - /* - * Must freeze regular XIDs older than the cutoff. We must not freeze - * a HOT-updated tuple, though; doing so would bring it back to life. - */ - if (HeapTupleHeaderIsHotUpdated(tuple)) - { - frz->t_infomask |= HEAP_XMAX_COMMITTED; - changed = true; - /* must not freeze */ - } - else - freeze_xmax = true; + freeze_xmax = true; } if (freeze_xmax) diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index beada274c0b..06b54889230 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -465,7 +465,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum, * Check the tuple XMIN against prior XMAX, if any */ if (TransactionIdIsValid(priorXmax) && - !HeapTupleUpdateXmaxMatchesXmin(priorXmax, htup)) + !TransactionIdEquals(HeapTupleHeaderGetXmin(htup), priorXmax)) break; /* @@ -805,7 +805,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets) htup = (HeapTupleHeader) PageGetItem(page, lp); if (TransactionIdIsValid(priorXmax) && - !HeapTupleUpdateXmaxMatchesXmin(priorXmax, htup)) + !TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(htup))) break; /* Remember the root line pointer for this item */ diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index a9a19dead25..95f5952f63f 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -1688,15 +1688,15 @@ lazy_record_dead_tuple(LVRelStats *vacrelstats, ItemPointer itemptr) { /* - * The array must never overflow, since we rely on all deletable tuples - * being removed; inability to remove a tuple might cause an old XID to - * persist beyond the freeze limit, which could be disastrous later on. + * The array shouldn't overflow under normal behavior, but perhaps it + * could if we are given a really small maintenance_work_mem. In that + * case, just forget the last few tuples (we'll get 'em next time). */ - if (vacrelstats->num_dead_tuples >= vacrelstats->max_dead_tuples) - elog(ERROR, "dead tuple array overflow"); - - vacrelstats->dead_tuples[vacrelstats->num_dead_tuples] = *itemptr; - vacrelstats->num_dead_tuples++; + if (vacrelstats->num_dead_tuples < vacrelstats->max_dead_tuples) + { + vacrelstats->dead_tuples[vacrelstats->num_dead_tuples] = *itemptr; + vacrelstats->num_dead_tuples++; + } } /* diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 6297e76adb3..e02507dd1fe 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -2080,7 +2080,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, bool noWait, * buffer's content lock, since xmin never changes in an existing * tuple.) */ - if (!HeapTupleUpdateXmaxMatchesXmin(priorXmax, tuple.t_data)) + if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data), + priorXmax)) { ReleaseBuffer(buffer); return NULL; @@ -2209,7 +2210,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, bool noWait, /* * As above, if xmin isn't what we're expecting, do nothing. */ - if (!HeapTupleUpdateXmaxMatchesXmin(priorXmax, tuple.t_data)) + if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data), + priorXmax)) { ReleaseBuffer(buffer); return NULL; diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index ee14e819997..493839f60e9 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -129,9 +129,6 @@ extern void heap_get_latest_tid(Relation relation, Snapshot snapshot, ItemPointer tid); extern void setLastTid(const ItemPointer tid); -extern bool HeapTupleUpdateXmaxMatchesXmin(TransactionId xmax, - HeapTupleHeader htup); - extern BulkInsertState GetBulkInsertState(void); extern void FreeBulkInsertState(BulkInsertState); diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index 3b4fd533b77..50ddb43a169 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -30,7 +30,6 @@ test: lock-committed-keyupdate test: update-locked-tuple test: propagate-lock-delete test: tuplelock-conflict -test: freeze-the-dead test: nowait test: nowait-2 test: nowait-3 |