diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 546f80f05c..01c392d546 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -1569,11 +1569,22 @@ toast_save_datum(Relation rel, Datum value,
 	 */
 	if (!OidIsValid(rel->rd_toastoid))
 	{
-		/* normal case: just choose an unused OID */
-		toast_pointer.va_valueid =
-			GetNewOidWithIndex(toastrel,
-							   RelationGetRelid(toastidxs[validIndex]),
-							   (AttrNumber) 1);
+		/*
+		 * Normal case: just choose an unused OID. But we must scan the TOAST
+		 * table using SnapshotToast to ensure that the value does not exists.
+		 * Note that GetNewOidWithIndex() scans the table with SnapshotDirty
+		 * and that may not see a RECENTLY_DEAD tuple. But such tuples are
+		 * visible to SnapshotToast and hence we must refrain from using such
+		 * OIDs. Otherwise toast_fetch_datum() or friends might see duplicate
+		 * OIDs, leading to all kinds of errors.
+		 */
+		do
+		{
+			toast_pointer.va_valueid =
+				GetNewOidWithIndex(toastrel,
+						RelationGetRelid(toastidxs[validIndex]),
+						(AttrNumber) 1);
+		} while (toastrel_valueid_exists(toastrel, toast_pointer.va_valueid));
 	}
 	else
 	{
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b4fd8395b7..ba8b969cc2 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -9785,11 +9785,21 @@ xlog_redo(XLogReaderState *record)
 								  checkPoint.nextXid))
 			ShmemVariableCache->nextXid = checkPoint.nextXid;
 		LWLockRelease(XidGenLock);
-		/* ... but still treat OID counter as exact */
-		LWLockAcquire(OidGenLock, LW_EXCLUSIVE);
-		ShmemVariableCache->nextOid = checkPoint.nextOid;
-		ShmemVariableCache->oidCount = 0;
-		LWLockRelease(OidGenLock);
+
+		/*
+		 * In an ONLINE checkpoint, don't update the nextOid counter. If there
+		 * was no XLOG_NEXTOID record seen since the starting checkpoint, then
+		 * we continue to believe that the current value is correct. If we saw
+		 * a XLOG_NEXTOID record, then we should rather use that and not
+		 * overwrite with a potentially old value, thus issuing duplicate
+		 * OIDs when we are fully up.
+		 *
+		 * If you're still curious why that can happen, note that the online
+		 * checkpoint may have started before our original redo position and
+		 * hence it may have value later made stale by an explicit
+		 * XLOG_NEXTOID.
+		 */
+
 		MultiXactAdvanceNextMXact(checkPoint.nextMulti,
 								  checkPoint.nextMultiOffset);
 
