-
Type: Bug
-
Resolution: Unresolved
-
Priority: Major - P3
-
None
-
Affects Version/s: 8.1.0-rc0, 8.0.4, 7.0.16
-
Component/s: None
-
None
-
Query Execution
-
ALL
While working on SERVER-95976 I discovered a bug that in case of the following events, updateLookup will return an empty document:
- create a collection A (UUID1)
- insert a document D1
- perform an update on D1
- reshard the collection A (UUID2)
When reading the update event, we will not receive the fullDocument. The reason for that is _getCollectionDefaultCollator() call that occurs during creating of the ExpCtx for the updateLookup call (https://github.com/10gen/mongo/blob/v8.0/src/mongo/db/pipeline/process_interface/common_mongod_process_interface.cpp#L620). In order to read the collation from the collection, it tries to acquire a lock on it by passing the UUID, however, reshardCollection command changes the UUID (UUID2), which causes the lock acquisition to fail with an NamespaceNotFound exception.
In didn't run the test on older versions, but the code didn't seem to be modified recently and therefore should be reproducible on older versions
Sample code:
import { assertCreateCollection, assertDropCollection } from "jstests/libs/collection_drop_recreate.js"; import {FixtureHelpers} from "jstests/libs/fixture_helpers.js"; import {ChangeStreamTest} from "jstests/libs/query/change_stream_util.js"; function assertUpdateLookupSemantcis(scenario, changeStreamOptions) { jsTest.log(`Running change stream with update lookup for ${scenario} case with ${tojson(changeStreamOptions)}`); const collNameA = "collA"; const collNameB = "collB"; assertCreateCollection(db, collNameA); let cst = new ChangeStreamTest(db); let cursor = cst.startWatchingChanges({ pipeline: [{$changeStream: {...changeStreamOptions, fullDocument: "updateLookup"}}], collection: collNameA }); // Insert 'doc' into 'collA' and ensure it is seen in the change stream. const doc = {_id: 0, a: 1}; assert.commandWorked(db.getCollection(collNameA).insert(doc)); let expected = { documentKey: {_id: doc._id}, fullDocument: doc, ns: {db: "test", coll: collNameA}, operationType: "insert", }; cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]}); // Update the 'doc' in order to generate the update event. assert.commandWorked(db.getCollection(collNameA).update({_id: doc._id}, {$inc: {a: 1}})); const updatedDocInCollA = {...doc, a: 2}; // In case where a change stream is opened with the 'checkUUIDOnUpdateLookup' flag set to true, // no 'fullDocument' should be returned to the user as the collection on which 'updateLookup' // has been performed has a different UUID from the collection on which change stream has been // opened. In case of the flag being set to false or not present return the latest document on // the collection with the same name. let expectedFullDocument; if (scenario === 'rename') { // Rename collection collA -> collB. assert.commandWorked(db.getCollection(collNameA).renameCollection(collNameB)); // Create new collection with the old name, "collA", (yet UUID will be different) and insert // document with the same id. assertCreateCollection(db, collNameA); const newDocInNewCollA = { ...doc, b: "extra field in the new document in the new collection" }; assert.commandWorked(db.getCollection(collNameA).insert(newDocInNewCollA)); expectedFullDocument = changeStreamOptions.checkUUIDOnUpdateLookup ? null : newDocInNewCollA; } else if (scenario === 'reshard') { // Reshard the collection in order to generate the new collection with the same name, but // different UUID. assert.commandWorked(db.adminCommand({ reshardCollection: db.getCollection(collNameA).getFullName(), key: {_id: 1}, numInitialChunks: 2 })); expectedFullDocument = changeStreamOptions.checkUUIDOnUpdateLookup ? null : updatedDocInCollA; } expected = { documentKey: {_id: doc._id}, fullDocument: expectedFullDocument, ns: {db: "test", coll: collNameA}, operationType: "update", }; cst.assertNextChangesEqual({cursor: cursor, expectedChanges: [expected]}); // Cleanup. cst.cleanUp(); assertDropCollection(db, collNameA); assertDropCollection(db, collNameB); } if (FixtureHelpers.isMongos(db)) { assertUpdateLookupSemantcis('reshard', {}); assertUpdateLookupSemantcis('reshard', {checkUUIDOnUpdateLookup: false}); assertUpdateLookupSemantcis('reshard', {checkUUIDOnUpdateLookup: true}); }
- is related to
-
SERVER-95976 Introduce "matchCollectionUUIDForUpdateLookup" parameter in the change stream stage
- In Code Review