SERVER-117623 Revert SERVER-95330 + extend test for sharded txn and DDL interleaving (#47057)

GitOrigin-RevId: d4433faafb9ded7ac911e3c3116d43e4457421f8
This commit is contained in:
Jordi Serra Torrens 2026-02-03 17:06:06 +01:00 committed by MongoDB Bot
parent 5710e50f4f
commit f55f2b459f
7 changed files with 27 additions and 179 deletions

View File

@ -325,13 +325,14 @@ const commands = ['find', 'aggregate', 'update', 'bulkWrite'];
}));
}
// Test transaction involving sharded collection with concurrent drop, where the transaction
// attempts to read the dropped collection.
// Test transaction involving sharded collection with concurrent drop or rename, where the
// transaction attempts to read the dropped or renamed-from collection.
{
function runTest(readConcernLevel, command) {
jsTest.log("Test transaction with concurrent drop. Command: " + command +
" Read Concern: " + readConcernLevel);
const dbName = 'test_txn_and_drop_with_' + command + '_and_' + readConcernLevel;
function runTest(readConcernLevel, operation, command) {
jsTest.log("Test transaction with concurrent " + operation + ". Command: " + command +
", read concern: " + readConcernLevel);
const dbName =
'test_txn_and_' + operation + '_with_' + command + '_and_' + readConcernLevel;
const collName1 = 'coll1';
const collName2 = 'coll2';
const ns1 = dbName + '.' + collName1;
@ -339,8 +340,8 @@ const commands = ['find', 'aggregate', 'update', 'bulkWrite'];
let coll1 = st.s.getDB(dbName)[collName1];
let coll2 = st.s.getDB(dbName)[collName2];
jsTest.log("Running transaction + drop test with read concern " + readConcernLevel +
" and command " + command);
jsTest.log("Running transaction + " + operation + " test with read concern " +
readConcernLevel + " and command " + command);
assert(command === 'find' || command === 'aggregate' || command === 'update' ||
command === 'bulkWrite');
// Initial state:
@ -348,7 +349,7 @@ const commands = ['find', 'aggregate', 'update', 'bulkWrite'];
// shard1: collA(sharded)
//
// 1. Start txn, hit shard0 for collB
// 2. Drop collA
// 2. Drop or rename collA
// 3. Read collA. Will target only shard0 because the router believes it is no longer
// sharded, so it would read the sharded coll (but just half of it). Therefore, a
// conflict must be raised.
@ -374,8 +375,14 @@ const commands = ['find', 'aggregate', 'update', 'bulkWrite'];
session.startTransaction({readConcern: {level: readConcernLevel}});
assert.eq(1, sessionColl2.find().itcount()); // Targets shard0.
// While the transaction is still open, drop coll1.
assert(coll1.drop());
// While the transaction is still open, drop or rename coll1.
if (operation === "drop") {
assert(coll1.drop());
} else if (operation === "rename") {
assert.commandWorked(coll1.renameCollection("coll3"));
} else {
throw new Error("Unsupported operation: " + operation);
}
// Refresh the router so that it doesn't send a stale SV to the shard, which would cause
// the txn to be aborted.
@ -407,9 +414,13 @@ const commands = ['find', 'aggregate', 'update', 'bulkWrite'];
}
}
readConcerns.forEach((readConcern) => commands.forEach((command) => {
runTest(readConcern, command);
}));
["drop", "rename"].forEach(
(operation) => readConcerns.forEach(
(readConcern) => commands.forEach((command) => {
runTest(readConcern, operation, command);
}),
),
);
}
// Test transaction concurrent with reshardCollection.

View File

@ -1836,30 +1836,6 @@ boost::optional<UUID> CollectionCatalog::lookupUUIDByNSS(OperationContext* opCtx
return boost::none;
}
bool CollectionCatalog::checkIfUUIDExistsAtLatest(OperationContext* opCtx, UUID uuid) const {
// First look within uncommitted collection creations performed under the currently open storage
// transaction (if any)...
auto& uncommittedCatalogUpdates = UncommittedCatalogUpdates::get(opCtx);
const auto& entries = uncommittedCatalogUpdates.entries();
auto entriesIt = std::find_if(
entries.begin(), entries.end(), [&uuid](const UncommittedCatalogUpdates::Entry& entry) {
return (entry.collection && entry.collection->uuid() == uuid);
});
if (entriesIt != entries.end()) {
return true;
}
// Then within the catalog instance itself...
auto* coll = _catalog.find(uuid);
if (coll) {
return true;
}
// ... and finally within not-yet flushed past commits.
coll = _pendingCommitUUIDs.find(uuid);
return coll != nullptr;
}
bool CollectionCatalog::isLatestCollection(OperationContext* opCtx,
const Collection* collection) const {
// Any writable Collection instance created under MODE_X lock is considered to belong to this

View File

@ -407,20 +407,10 @@ public:
* NOTE: the check is based on pointer equality between the 'collection' parameter and
* the object stored by the catalog under the same UUID; this may lead to unexpected false
* results when a different, but structurally equivalent parameter is passed in (like a
* 'collection' obtained via read-through). Consider using checkIfUUIDExistsAtLatest when such a
* behavior is not desirable.
* 'collection' obtained via read-through).
*/
bool isLatestCollection(OperationContext* opCtx, const Collection* collection) const;
/**
* Checks if the provided UUID is compatible with the latest version for this catalog version
* (plus uncommitted catalog updates).
* The function only returns a meaningful result when called against CollectionCatalog::latest()
* and it is exclusively meant to support the ShardRole API in the resource acquisition for
* unsharded collection.
*/
bool checkIfUUIDExistsAtLatest(OperationContext* opCtx, UUID uuid) const;
/**
* Verifies that the provided collection name doesn't exist in the catalog and is exclusively
* present in the uncommitted updates of the operation. For the check to be meaningful it should

View File

@ -1979,109 +1979,6 @@ TEST_F(CollectionCatalogTimestampTest, OpenNewCollectionUsingDropPendingCollecti
ASSERT_EQ(coll->getSharedIdent(), openedColl->getSharedIdent());
}
TEST_F(CollectionCatalogTimestampTest, checkIfUUIDExistsAtLatest) {
// A "committed on disk" collection UUID can be retrieved from the latest catalog snapshot.
{
const NamespaceString nssOnCatalog =
NamespaceString::createNamespaceString_forTest("a.committed");
const Timestamp createCollectionTs = Timestamp(10, 10);
const auto collUuidOnCatalog =
createCollection(opCtx.get(), nssOnCatalog, createCollectionTs);
auto catalog = CollectionCatalog::get(opCtx.get());
auto match = catalog->checkIfUUIDExistsAtLatest(opCtx.get(), collUuidOnCatalog);
ASSERT_TRUE(match);
}
// The UUID of an uncommitted createCollection can be retrieved from the open storage
// transaction.
{
const NamespaceString nssOnCatalog =
NamespaceString::createNamespaceString_forTest("a.uncommitted");
const Timestamp createCollectionTs = Timestamp(10, 10);
const UUID uncommittedCollUUID = UUID::gen();
boost::optional<WriteUnitOfWork> wuow;
createCollectionWithUUIDAndLeaveUncommitted(
opCtx.get(), nssOnCatalog, createCollectionTs, uncommittedCollUUID, wuow);
auto catalog = CollectionCatalog::get(opCtx.get());
auto match = catalog->checkIfUUIDExistsAtLatest(opCtx.get(), uncommittedCollUUID);
ASSERT_TRUE(match);
wuow->commit();
}
// The UUID of a commit-pending createCollection can be retrieved from the related data
// structure.
{
const NamespaceString commitPendingNss =
NamespaceString::createNamespaceString_forTest("a.createWithCommitPending");
const UUID commitPendingColluuid = UUID::gen();
const Timestamp createCollectionTs = Timestamp(10, 10);
concurrentCreateAndRunCatalogOperations(
opCtx.get(),
commitPendingNss,
commitPendingColluuid,
createCollectionTs,
[this, &commitPendingColluuid](OperationContext* opCtx) {
auto catalog = CollectionCatalog::get(opCtx);
auto match = catalog->checkIfUUIDExistsAtLatest(opCtx, commitPendingColluuid);
ASSERT_TRUE(match);
});
}
// The UUID of a commit-pending dropped collection can still be retrieved from the latest
// catalog snapshot.
{
const NamespaceString nssOnCatalog =
NamespaceString::createNamespaceString_forTest("a.dropWithCommitPending");
const Timestamp createCollectionTs = Timestamp(10, 10);
const Timestamp dropCollectionTs = Timestamp(20, 20);
const auto collUuidOnCatalog =
createCollection(opCtx.get(), nssOnCatalog, createCollectionTs);
concurrentDropAndRunCatalogOperations(opCtx.get(),
nssOnCatalog,
dropCollectionTs,
[this, &collUuidOnCatalog](OperationContext* opCtx) {
auto catalog = CollectionCatalog::get(opCtx);
auto match = catalog->checkIfUUIDExistsAtLatest(
opCtx, collUuidOnCatalog);
ASSERT_TRUE(match);
});
}
// A collection being dropped within the currently open storage transaction can still be
// retrieved through the latest catalog snapshot.
{
const NamespaceString nssOnCatalog =
NamespaceString::createNamespaceString_forTest("a.toBeDroppedInOpenTxn");
const Timestamp createCollectionTs = Timestamp(10, 10);
const Timestamp dropCollectionTs = Timestamp(20, 20);
const auto collUuidOnCatalog =
createCollection(opCtx.get(), nssOnCatalog, createCollectionTs);
boost::optional<WriteUnitOfWork> wuow;
dropCollectionAndLeaveUncommitted(opCtx.get(), nssOnCatalog, dropCollectionTs, wuow);
auto catalog = CollectionCatalog::get(opCtx.get());
auto match = catalog->checkIfUUIDExistsAtLatest(opCtx.get(), collUuidOnCatalog);
ASSERT_TRUE(match);
wuow->commit();
}
// A non-existing collection UUID matches no collection.
{
const UUID nonExistingUuid = UUID::gen();
auto catalog = CollectionCatalog::get(opCtx.get());
auto match = catalog->checkIfUUIDExistsAtLatest(opCtx.get(), nonExistingUuid);
ASSERT_FALSE(match);
}
}
TEST_F(CollectionCatalogTimestampTest, CollectionLifetimeTiedToStorageTransactionLifetime) {
const NamespaceString nss = NamespaceString::createNamespaceString_forTest("a.b");
const Timestamp createCollectionTs = Timestamp(10, 10);

View File

@ -2069,7 +2069,7 @@ void shard_role_details::checkLocalCatalogIsValidForUnshardedShardVersion(
// The transaction sees a collection exists.
uassert(ErrorCodes::SnapshotUnavailable,
makeErrorMessage(),
latestCatalog->checkIfUUIDExistsAtLatest(opCtx, collectionPtr->uuid()));
latestCatalog->isLatestCollection(opCtx, collectionPtr.get()));
} else if (const auto currentView = stashedCatalog.lookupView(opCtx, nss)) {
// The transaction sees a view exists.
uassert(ErrorCodes::SnapshotUnavailable,

View File

@ -966,31 +966,6 @@ TEST_F(ShardRoleTest, ConflictIsThrownWhenShardVersionUnshardedButStashedCatalog
}
}
TEST_F(ShardRoleTest, NoExceptionIsThrownWhenShardVersionUnshardedButStashedIsEquivalentToLatest) {
operationContext()->setInMultiDocumentTransaction();
shard_role_details::getRecoveryUnit(operationContext())->preallocateSnapshot();
CollectionCatalog::stash(operationContext(), CollectionCatalog::get(operationContext()));
// Re-open the local catalog at its latest version, simulating a rollback event that has no
// effects on the metadata of the unsharded collection (although it does force the creation of a
// new instance for the "most recent collection snapshot" held by the catalog).
simulateReplicationRollbackEvent(operationContext());
// The acquisition of the unsharded collection is expected to succeed, even though the stashed
// snapshot points to a different object than the one kept by the catalog.
{
ScopedSetShardRole setShardRole(
operationContext(), nssUnshardedCollection1, ShardVersion::UNSHARDED(), boost::none);
auto acquisition = acquireCollectionOrView(
operationContext(),
CollectionOrViewAcquisitionRequest::fromOpCtx(
operationContext(), nssUnshardedCollection1, AcquisitionPrerequisites::kRead),
MODE_IX);
ASSERT_TRUE(acquisition.collectionExists());
}
}
// ---------------------------------------------------------------------------
// MaybeLockFree
TEST_F(ShardRoleTest, AcquireCollectionMaybeLockFreeTakesLocksWhenInMultiDocTransaction) {

View File

@ -1 +0,0 @@
# Delete once this directory is populated