On Mon, Feb 16, 2015 at 4:11 PM, Peter Geoghegan <p...@heroku.com> wrote:
>>> Jim Nasby said something about setting the HEAP_XMIN_INVALID hint bit.
>>> Maybe he is right...if that can be made to be reliable (always
>>> WAL-logged), it could be marginally better than setting xmin to
>>> invalidTransactionId.
>>
>>
>> I'm not a big fan of that. The xmin-invalid bit is currently always just a
>> hint.
>
> Well, Andres makes the point that that isn't quite so.

Hmm. So the tqual.c routines actually check "if
(HeapTupleHeaderXminInvalid(tuple))". Which is:

#define HeapTupleHeaderXminInvalid(tup) \
( \
    ((tup)->t_infomask & (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)) == \
        HEAP_XMIN_INVALID \
)

What appears to happen if I try to only use the HEAP_XMIN_INVALID bit
(and not explicitly set xmin to InvalidTransactionId, and not
explicitly check that xmin isn't InvalidTransactionId in each
HeapTupleSatisfies* routine) is that after a little while, Jeff Janes'
tool shows up spurious unique violations, as if some tuple has become
visible when it shouldn't have. I guess this is because the
HEAP_XMIN_COMMITTED hint bit can still be set, which in effect
invalidates the HEAP_XMIN_INVALID hint bit.

It takes about 2 minutes before this happens for the first time when
fsync = off, following a fresh initdb, which is unacceptable. I'm not
sure if it's worth trying to figure out how HEAP_XMIN_COMMITTED gets
set. Not that I'm 100% sure that that's what this really is; it just
seems very likely.

Attached broken patch (broken_visible.patch) shows the changes made to
reveal this problem. Only the changes to tqual.c and heap_delete()
should matter here, since I did not test recovery.

I also thought about unifying the check for "if
(TransactionIdEquals(HeapTupleHeaderGetRawXmin(tuple),
InvalidTransactionId))" with the conventional
HeapTupleHeaderXminInvalid() macro, and leaving everything else as-is.
This is no good either, and for similar reasons - control often won't
reach the macro, which is behind a check of "if
(!HeapTupleHeaderXminCommitted(tuple))".

The best I think we can hope for is having a dedicated "if
(TransactionIdEquals(HeapTupleHeaderGetRawXmin(tuple),
InvalidTransactionId))" macro HeapTupleHeaderSuperDeleted() to do the
work each time, which does not need to be checked so often. A second
attached patch (compact_tqual_works.patch - which is non-broken,
AFAICT) shows how this is possible, while also moving the check
further down within each tqual.c routine (which seems more in keeping
with the fact that super deletion is a relatively obscure concept). I
haven't been able to break this variant using my existing test suite,
so this seems promising as a way of reducing tqual.c clutter. However,
as I said the other day, this is basically just polish.

-- 
Peter Geoghegan
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 0aa3e57..b777c56 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2899,7 +2899,7 @@ l1:
 	}
 	else
 	{
-		HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+		HeapTupleHeaderSetXminInvalid(tp.t_data);
 	}
 
 	/* Make sure there is no forward chain link in t_ctid */
@@ -7382,12 +7382,12 @@ heap_xlog_delete(XLogReaderState *record)
 		htup->t_infomask &= ~(HEAP_XMAX_BITS | HEAP_MOVED);
 		htup->t_infomask2 &= ~HEAP_KEYS_UPDATED;
 		HeapTupleHeaderClearHotUpdated(htup);
+		if (xlrec->flags & XLOG_HEAP_KILLED_SPECULATIVE_TUPLE)
+			HeapTupleHeaderSetXminInvalid(htup);
+
 		fix_infomask_from_infobits(xlrec->infobits_set,
 								   &htup->t_infomask, &htup->t_infomask2);
-		if (!(xlrec->flags & XLOG_HEAP_KILLED_SPECULATIVE_TUPLE))
-			HeapTupleHeaderSetXmax(htup, xlrec->xmax);
-		else
-			HeapTupleHeaderSetXmin(htup, InvalidTransactionId);
+		HeapTupleHeaderSetXmax(htup, xlrec->xmax);
 		HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
 
 		/* Mark the page as a candidate for pruning */
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index 99bb417..fd857b1 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -170,13 +170,6 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
 	Assert(ItemPointerIsValid(&htup->t_self));
 	Assert(htup->t_tableOid != InvalidOid);
 
-	/*
-	 * Never return "super-deleted" tuples
-	 */
-	if (TransactionIdEquals(HeapTupleHeaderGetRawXmin(tuple),
-							InvalidTransactionId))
-		return false;
-
 	if (!HeapTupleHeaderXminCommitted(tuple))
 	{
 		if (HeapTupleHeaderXminInvalid(tuple))
@@ -735,15 +728,6 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
 	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
 	snapshot->speculativeToken = 0;
 
-	/*
-	 * Never return "super-deleted" tuples
-	 *
-	 * XXX:  Comment this code out and you'll get conflicts within
-	 * ExecLockUpdateTuple(), which result in an infinite loop.
-	 */
-	if (TransactionIdEquals(HeapTupleHeaderGetRawXmin(tuple),
-							InvalidTransactionId))
-		return false;
 
 	if (!HeapTupleHeaderXminCommitted(tuple))
 	{
@@ -960,13 +944,6 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
 	Assert(ItemPointerIsValid(&htup->t_self));
 	Assert(htup->t_tableOid != InvalidOid);
 
-	/*
-	 * Never return "super-deleted" tuples
-	 */
-	if (TransactionIdEquals(HeapTupleHeaderGetRawXmin(tuple),
-							InvalidTransactionId))
-		return false;
-
 	if (!HeapTupleHeaderXminCommitted(tuple))
 	{
 		if (HeapTupleHeaderXminInvalid(tuple))
@@ -1171,13 +1148,6 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
 	Assert(htup->t_tableOid != InvalidOid);
 
 	/*
-	 * Immediately VACUUM "super-deleted" tuples
-	 */
-	if (TransactionIdEquals(HeapTupleHeaderGetRawXmin(tuple),
-							InvalidTransactionId))
-		return HEAPTUPLE_DEAD;
-
-	/*
 	 * Has inserting transaction committed?
 	 *
 	 * If the inserting transaction aborted, then the tuple was never visible
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index 99bb417..aed0eeb 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -170,13 +170,6 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
 	Assert(ItemPointerIsValid(&htup->t_self));
 	Assert(htup->t_tableOid != InvalidOid);
 
-	/*
-	 * Never return "super-deleted" tuples
-	 */
-	if (TransactionIdEquals(HeapTupleHeaderGetRawXmin(tuple),
-							InvalidTransactionId))
-		return false;
-
 	if (!HeapTupleHeaderXminCommitted(tuple))
 	{
 		if (HeapTupleHeaderXminInvalid(tuple))
@@ -269,6 +262,9 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
 		}
 	}
 
+	if (HeapTupleHeaderSuperDeleted(tuple))
+		return false;
+
 	/* by here, the inserting transaction has committed */
 
 	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
@@ -735,16 +731,6 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
 	snapshot->xmin = snapshot->xmax = InvalidTransactionId;
 	snapshot->speculativeToken = 0;
 
-	/*
-	 * Never return "super-deleted" tuples
-	 *
-	 * XXX:  Comment this code out and you'll get conflicts within
-	 * ExecLockUpdateTuple(), which result in an infinite loop.
-	 */
-	if (TransactionIdEquals(HeapTupleHeaderGetRawXmin(tuple),
-							InvalidTransactionId))
-		return false;
-
 	if (!HeapTupleHeaderXminCommitted(tuple))
 	{
 		if (HeapTupleHeaderXminInvalid(tuple))
@@ -861,6 +847,9 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
 		}
 	}
 
+	if (HeapTupleHeaderSuperDeleted(tuple))
+		return false;
+
 	/* by here, the inserting transaction has committed */
 
 	if (tuple->t_infomask & HEAP_XMAX_INVALID)	/* xid invalid or aborted */
@@ -960,13 +949,6 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
 	Assert(ItemPointerIsValid(&htup->t_self));
 	Assert(htup->t_tableOid != InvalidOid);
 
-	/*
-	 * Never return "super-deleted" tuples
-	 */
-	if (TransactionIdEquals(HeapTupleHeaderGetRawXmin(tuple),
-							InvalidTransactionId))
-		return false;
-
 	if (!HeapTupleHeaderXminCommitted(tuple))
 	{
 		if (HeapTupleHeaderXminInvalid(tuple))
@@ -1067,6 +1049,9 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
 		}
 	}
 
+	if (HeapTupleHeaderSuperDeleted(tuple))
+		return false;
+
 	/*
 	 * By here, the inserting transaction has committed - have to check
 	 * when...
@@ -1171,13 +1156,6 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
 	Assert(htup->t_tableOid != InvalidOid);
 
 	/*
-	 * Immediately VACUUM "super-deleted" tuples
-	 */
-	if (TransactionIdEquals(HeapTupleHeaderGetRawXmin(tuple),
-							InvalidTransactionId))
-		return HEAPTUPLE_DEAD;
-
-	/*
 	 * Has inserting transaction committed?
 	 *
 	 * If the inserting transaction aborted, then the tuple was never visible
@@ -1270,6 +1248,9 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
 		 */
 	}
 
+	if (HeapTupleHeaderSuperDeleted(tuple))
+		return HEAPTUPLE_DEAD;
+
 	/*
 	 * Okay, the inserter committed, so it was good at some point.  Now what
 	 * about the deleting transaction?
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index d2ad910..5906df4 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -305,6 +305,15 @@ struct HeapTupleHeaderData
 )
 
 /*
+ * Was tuple "super deleted" following unsuccessful speculative insertion (i.e.
+ * conflict was detected at insertion time)?
+ */
+#define HeapTupleHeaderSuperDeleted(tup) \
+( \
+	(!TransactionIdIsValid(HeapTupleHeaderGetRawXmin(tup))) \
+)
+
+/*
  * HeapTupleHeaderGetRawXmax gets you the raw Xmax field.  To find out the Xid
  * that updated a tuple, you might need to resolve the MultiXactId if certain
  * bits are set.  HeapTupleHeaderGetUpdateXid checks those bits and takes care
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to