aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorAndres Freund <andres@anarazel.de>2017-11-03 07:52:29 -0700
committerAndres Freund <andres@anarazel.de>2017-12-14 18:20:47 -0800
commit9c2f0a6c3cc8bb85b78191579760dbe9fb7814ec (patch)
tree6e65ee67d1ef7a73bf99b7f0330ab01768cd5a29 /src/backend
parent9220b00e57352fda988b187940f5d5ac4851a8bb (diff)
downloadpostgresql-9c2f0a6c3cc8bb85b78191579760dbe9fb7814ec.tar.gz
postgresql-9c2f0a6c3cc8bb85b78191579760dbe9fb7814ec.zip
Fix pruning of locked and updated tuples.
Previously it was possible that a tuple was not pruned during vacuum, even though its update xmax (i.e. the updating xid in a multixact with both key share lockers and an updater) was below the cutoff horizon. As the freezing code assumed, rightly so, that that's not supposed to happen, xmax would be preserved (as a member of a new multixact or xmax directly). That causes two problems: For one the tuple is below the xmin horizon, which can cause problems if the clog is truncated or once there's an xid wraparound. The bigger problem is that that will break HOT chains, which in turn can lead two to breakages: First, failing index lookups, which in turn can e.g lead to constraints being violated. Second, future hot prunes / vacuums can end up making invisible tuples visible again. There's other harmful scenarios. Fix the problem by recognizing that tuples can be DEAD instead of RECENTLY_DEAD, even if the multixactid has alive members, if the update_xid is below the xmin horizon. That's safe because newer versions of the tuple will contain the locking xids. A followup commit will harden the code somewhat against future similar bugs and already corrupted data. Author: Andres Freund, with changes by Alvaro Herrera Reported-By: Daniel Wood Analyzed-By: Andres Freund, Alvaro Herrera, Robert Haas, Peter Geoghegan, Daniel Wood, Yi Wen Wong, Michael Paquier Reviewed-By: Alvaro Herrera, Robert Haas, Michael Paquier Discussion: https://postgr.es/m/E5711E62-8FDF-4DCA-A888-C200BF6B5742@amazon.com https://postgr.es/m/20171102112019.33wb7g5wp4zpjelu@alap3.anarazel.de Backpatch: 9.3-
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/utils/time/tqual.c57
1 files changed, 24 insertions, 33 deletions
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index a821e2eed10..2b218e07e61 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -1311,49 +1311,40 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
- TransactionId xmax;
-
- if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
- {
- /* already checked above */
- Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
-
- xmax = HeapTupleGetUpdateXid(tuple);
-
- /* not LOCKED_ONLY, so it has to have an xmax */
- Assert(TransactionIdIsValid(xmax));
-
- if (TransactionIdIsInProgress(xmax))
- return HEAPTUPLE_DELETE_IN_PROGRESS;
- else if (TransactionIdDidCommit(xmax))
- /* there are still lockers around -- can't return DEAD here */
- return HEAPTUPLE_RECENTLY_DEAD;
- /* updating transaction aborted */
- return HEAPTUPLE_LIVE;
- }
-
- Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
+ TransactionId xmax = HeapTupleGetUpdateXid(tuple);
- xmax = HeapTupleGetUpdateXid(tuple);
+ /* already checked above */
+ Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
/* not LOCKED_ONLY, so it has to have an xmax */
Assert(TransactionIdIsValid(xmax));
- /* multi is not running -- updating xact cannot be */
- Assert(!TransactionIdIsInProgress(xmax));
- if (TransactionIdDidCommit(xmax))
+ if (TransactionIdIsInProgress(xmax))
+ return HEAPTUPLE_DELETE_IN_PROGRESS;
+ else if (TransactionIdDidCommit(xmax))
{
+ /*
+ * The multixact might still be running due to lockers. If the
+ * updater is below the xid horizon, we have to return DEAD
+ * regardless -- otherwise we could end up with a tuple where the
+ * updater has to be removed due to the horizon, but is not pruned
+ * away. It's not a problem to prune that tuple, because any
+ * remaining lockers will also be present in newer tuple versions.
+ */
if (!TransactionIdPrecedes(xmax, OldestXmin))
return HEAPTUPLE_RECENTLY_DEAD;
- else
- return HEAPTUPLE_DEAD;
+
+ return HEAPTUPLE_DEAD;
+ }
+ else if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
+ {
+ /*
+ * Not in Progress, Not Committed, so either Aborted or crashed.
+ * Mark the Xmax as invalid.
+ */
+ SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
}
- /*
- * Not in Progress, Not Committed, so either Aborted or crashed.
- * Remove the Xmax.
- */
- SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
return HEAPTUPLE_LIVE;
}