mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-29 22:49:41 +03:00 
			
		
		
		
	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
This commit is contained in:
		| @@ -1737,7 +1737,8 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, | |||||||
| 		 * broken. | 		 * broken. | ||||||
| 		 */ | 		 */ | ||||||
| 		if (TransactionIdIsValid(prev_xmax) && | 		if (TransactionIdIsValid(prev_xmax) && | ||||||
| 			!HeapTupleUpdateXmaxMatchesXmin(prev_xmax, heapTuple->t_data)) | 			!TransactionIdEquals(prev_xmax, | ||||||
|  | 								 HeapTupleHeaderGetXmin(heapTuple->t_data))) | ||||||
| 			break; | 			break; | ||||||
|  |  | ||||||
| 		/* | 		/* | ||||||
| @@ -1919,7 +1920,7 @@ heap_get_latest_tid(Relation relation, | |||||||
| 		 * tuple.  Check for XMIN match. | 		 * tuple.  Check for XMIN match. | ||||||
| 		 */ | 		 */ | ||||||
| 		if (TransactionIdIsValid(priorXmax) && | 		if (TransactionIdIsValid(priorXmax) && | ||||||
| 			!HeapTupleUpdateXmaxMatchesXmin(priorXmax, tp.t_data)) | 		  !TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data))) | ||||||
| 		{ | 		{ | ||||||
| 			UnlockReleaseBuffer(buffer); | 			UnlockReleaseBuffer(buffer); | ||||||
| 			break; | 			break; | ||||||
| @@ -1951,50 +1952,6 @@ heap_get_latest_tid(Relation relation, | |||||||
| 	}							/* end of loop */ | 	}							/* 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 |  * UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends | ||||||
| @@ -5283,7 +5240,8 @@ l4: | |||||||
| 		 * end of the chain, we're done, so return success. | 		 * end of the chain, we're done, so return success. | ||||||
| 		 */ | 		 */ | ||||||
| 		if (TransactionIdIsValid(priorXmax) && | 		if (TransactionIdIsValid(priorXmax) && | ||||||
| 			!HeapTupleUpdateXmaxMatchesXmin(priorXmax, mytup.t_data)) | 			!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data), | ||||||
|  | 								 priorXmax)) | ||||||
| 		{ | 		{ | ||||||
| 			UnlockReleaseBuffer(buf); | 			UnlockReleaseBuffer(buf); | ||||||
| 			return HeapTupleMayBeUpdated; | 			return HeapTupleMayBeUpdated; | ||||||
| @@ -5729,23 +5687,14 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, | |||||||
| 			Assert(TransactionIdIsValid(xid)); | 			Assert(TransactionIdIsValid(xid)); | ||||||
|  |  | ||||||
| 			/* | 			/* | ||||||
| 			 * The updating transaction cannot possibly be still running, but | 			 * If the xid is older than the cutoff, it has to have aborted, | ||||||
| 			 * verify whether it has committed, and request to set the | 			 * otherwise the tuple would have gotten pruned away. | ||||||
| 			 * 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 (TransactionIdPrecedes(xid, cutoff_xid)) | 			if (TransactionIdPrecedes(xid, cutoff_xid)) | ||||||
| 			{ | 			{ | ||||||
| 				if (TransactionIdDidCommit(xid)) | 				Assert(!TransactionIdDidCommit(xid)); | ||||||
| 					*flags = FRM_MARK_COMMITTED | FRM_RETURN_IS_XID; | 				*flags |= FRM_INVALIDATE_XMAX; | ||||||
| 				else | 				xid = InvalidTransactionId;		/* not strictly necessary */ | ||||||
| 				{ |  | ||||||
| 					*flags |= FRM_INVALIDATE_XMAX; |  | ||||||
| 					xid = InvalidTransactionId;	/* not strictly necessary */ |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
| 			else | 			else | ||||||
| 			{ | 			{ | ||||||
| @@ -5816,17 +5765,18 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, | |||||||
|  |  | ||||||
| 			/* | 			/* | ||||||
| 			 * It's an update; should we keep it?  If the transaction is known | 			 * 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 | 			 * 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. | 			 * 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) || | 			if (TransactionIdIsCurrentTransactionId(xid) || | ||||||
| 				TransactionIdIsInProgress(xid)) | 				TransactionIdIsInProgress(xid)) | ||||||
| @@ -5834,33 +5784,46 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, | |||||||
| 				Assert(!TransactionIdIsValid(update_xid)); | 				Assert(!TransactionIdIsValid(update_xid)); | ||||||
| 				update_xid = xid; | 				update_xid = xid; | ||||||
| 			} | 			} | ||||||
| 			else if (TransactionIdDidCommit(xid)) | 			else if (!TransactionIdDidAbort(xid)) | ||||||
| 			{ | 			{ | ||||||
| 				/* | 				/* | ||||||
| 				 * The transaction committed, so we can tell caller to set | 				 * Test whether to tell caller to set HEAP_XMAX_COMMITTED | ||||||
| 				 * HEAP_XMAX_COMMITTED.  (We can only do this because we know | 				 * while we have the Xid still in cache.  Note this can only | ||||||
| 				 * the transaction is not running.) | 				 * be done if the transaction is known not running. | ||||||
| 				 */ | 				 */ | ||||||
|  | 				if (TransactionIdDidCommit(xid)) | ||||||
|  | 					update_committed = true; | ||||||
| 				Assert(!TransactionIdIsValid(update_xid)); | 				Assert(!TransactionIdIsValid(update_xid)); | ||||||
| 				update_committed = true; |  | ||||||
| 				update_xid = xid; | 				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 | 			 * If we determined that it's an Xid corresponding to an update | ||||||
| 			 * that must be retained, additionally add it to the list of | 			 * 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, | 			 * 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 | 			 * but it's easier to maintain the list as we walk the old members | ||||||
| 			 * list.) | 			 * 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)) | 			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 | 		else | ||||||
| 		{ | 		{ | ||||||
| @@ -5927,10 +5890,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, | |||||||
|  * |  * | ||||||
|  * It is assumed that the caller has checked the tuple with |  * It is assumed that the caller has checked the tuple with | ||||||
|  * HeapTupleSatisfiesVacuum() and determined that it is not HEAPTUPLE_DEAD |  * HeapTupleSatisfiesVacuum() and determined that it is not HEAPTUPLE_DEAD | ||||||
|  * (else we should be removing the tuple, not freezing it).  However, note |  * (else we should be removing the tuple, not freezing it). | ||||||
|  * 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. |  | ||||||
|  * |  * | ||||||
|  * NB: cutoff_xid *must* be <= the current global xmin, to ensure that any |  * 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 |  * 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) && | 	else if (TransactionIdIsNormal(xid) && | ||||||
| 			 TransactionIdPrecedes(xid, cutoff_xid)) | 			 TransactionIdPrecedes(xid, cutoff_xid)) | ||||||
| 	{ | 	{ | ||||||
| 		/* | 		freeze_xmax = true; | ||||||
| 		 * 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; |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (freeze_xmax) | 	if (freeze_xmax) | ||||||
|   | |||||||
| @@ -465,7 +465,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum, | |||||||
| 		 * Check the tuple XMIN against prior XMAX, if any | 		 * Check the tuple XMIN against prior XMAX, if any | ||||||
| 		 */ | 		 */ | ||||||
| 		if (TransactionIdIsValid(priorXmax) && | 		if (TransactionIdIsValid(priorXmax) && | ||||||
| 			!HeapTupleUpdateXmaxMatchesXmin(priorXmax, htup)) | 			!TransactionIdEquals(HeapTupleHeaderGetXmin(htup), priorXmax)) | ||||||
| 			break; | 			break; | ||||||
|  |  | ||||||
| 		/* | 		/* | ||||||
| @@ -805,7 +805,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets) | |||||||
| 			htup = (HeapTupleHeader) PageGetItem(page, lp); | 			htup = (HeapTupleHeader) PageGetItem(page, lp); | ||||||
|  |  | ||||||
| 			if (TransactionIdIsValid(priorXmax) && | 			if (TransactionIdIsValid(priorXmax) && | ||||||
| 				!HeapTupleUpdateXmaxMatchesXmin(priorXmax, htup)) | 				!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(htup))) | ||||||
| 				break; | 				break; | ||||||
|  |  | ||||||
| 			/* Remember the root line pointer for this item */ | 			/* Remember the root line pointer for this item */ | ||||||
|   | |||||||
| @@ -1688,15 +1688,15 @@ lazy_record_dead_tuple(LVRelStats *vacrelstats, | |||||||
| 					   ItemPointer itemptr) | 					   ItemPointer itemptr) | ||||||
| { | { | ||||||
| 	/* | 	/* | ||||||
| 	 * The array must never overflow, since we rely on all deletable tuples | 	 * The array shouldn't overflow under normal behavior, but perhaps it | ||||||
| 	 * being removed; inability to remove a tuple might cause an old XID to | 	 * could if we are given a really small maintenance_work_mem. In that | ||||||
| 	 * persist beyond the freeze limit, which could be disastrous later on. | 	 * case, just forget the last few tuples (we'll get 'em next time). | ||||||
| 	 */ | 	 */ | ||||||
| 	if (vacrelstats->num_dead_tuples >= vacrelstats->max_dead_tuples) | 	if (vacrelstats->num_dead_tuples < vacrelstats->max_dead_tuples) | ||||||
| 		elog(ERROR, "dead tuple array overflow"); | 	{ | ||||||
|  | 		vacrelstats->dead_tuples[vacrelstats->num_dead_tuples] = *itemptr; | ||||||
| 	vacrelstats->dead_tuples[vacrelstats->num_dead_tuples] = *itemptr; | 		vacrelstats->num_dead_tuples++; | ||||||
| 	vacrelstats->num_dead_tuples++; | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|   | |||||||
| @@ -2080,7 +2080,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, bool noWait, | |||||||
| 			 * buffer's content lock, since xmin never changes in an existing | 			 * buffer's content lock, since xmin never changes in an existing | ||||||
| 			 * tuple.) | 			 * tuple.) | ||||||
| 			 */ | 			 */ | ||||||
| 			if (!HeapTupleUpdateXmaxMatchesXmin(priorXmax, tuple.t_data)) | 			if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data), | ||||||
|  | 									 priorXmax)) | ||||||
| 			{ | 			{ | ||||||
| 				ReleaseBuffer(buffer); | 				ReleaseBuffer(buffer); | ||||||
| 				return NULL; | 				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. | 		 * 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); | 			ReleaseBuffer(buffer); | ||||||
| 			return NULL; | 			return NULL; | ||||||
|   | |||||||
| @@ -129,9 +129,6 @@ extern void heap_get_latest_tid(Relation relation, Snapshot snapshot, | |||||||
| 					ItemPointer tid); | 					ItemPointer tid); | ||||||
| extern void setLastTid(const ItemPointer tid); | extern void setLastTid(const ItemPointer tid); | ||||||
|  |  | ||||||
| extern bool HeapTupleUpdateXmaxMatchesXmin(TransactionId xmax, |  | ||||||
| 							   HeapTupleHeader htup); |  | ||||||
|  |  | ||||||
| extern BulkInsertState GetBulkInsertState(void); | extern BulkInsertState GetBulkInsertState(void); | ||||||
| extern void FreeBulkInsertState(BulkInsertState); | extern void FreeBulkInsertState(BulkInsertState); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,7 +30,6 @@ test: lock-committed-keyupdate | |||||||
| test: update-locked-tuple | test: update-locked-tuple | ||||||
| test: propagate-lock-delete | test: propagate-lock-delete | ||||||
| test: tuplelock-conflict | test: tuplelock-conflict | ||||||
| test: freeze-the-dead |  | ||||||
| test: nowait | test: nowait | ||||||
| test: nowait-2 | test: nowait-2 | ||||||
| test: nowait-3 | test: nowait-3 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user