When a query run against a WiredTiger-enabled mongod yields (or is saved for a getMore), the query's storage-layer snapshot is dropped and later replaced by a new snapshot. For queries that perform multiple index lookups for a single document, this creates an opportunity for index key data saved by the query stage tree to be stale by the time the new snapshot is created. As a result, queries can return documents that never match the given predicate, if they partially match the predicate in an old snapshot and partially match the predicate in a new shapshot. For example, the below script shows a query with predicate {a: 1, b: 1} returning a document that matches predicates {a: 1} and {b: 1} at separate times, but never at the same time.
This issue does not affect mongod configured with the MMAPv1 storage engine, as the write invalidation framework forces queries to perform a full predicate re-evaluation on documents that are mutated over the course of the query's lifetime.
To reproduce, run the following shell script against a mongod configured with the WiredTiger storage engine:
db.foo.drop(); for (var i=0; i<1000; i++) { assert.writeOK(db.foo.insert({_id: i, a: 2, b: 1})); } assert.writeOK(db.foo.insert({_id: -1, a: 1, b: 2})); assert.commandWorked(db.foo.ensureIndex({a: 1})); assert.commandWorked(db.foo.ensureIndex({b: 1})); // Add artificial score boost to AND_SORTED plans. assert.commandWorked(db.adminCommand({setParameter: 1, internalQueryForceIntersectionPlans: true})); // Increase frequency of yielding. assert.commandWorked(db.adminCommand({setParameter: 1, internalQueryExecYieldIterations: 2})); // In a parallel shell, continuously flop the "a" and "b" values of the {_id: -1} document. startParallelShell( "while (true) { " + " assert.writeOK(db.foo.update({_id: -1}, {$set: {a: 2, b: 1}})); " + " assert.writeOK(db.foo.update({_id: -1}, {$set: {a: 1, b: 2}})); " + "}" ); while (true) { // Run an AND_SORTED intersection query on a predicate that matches no documents. // This below assertion fails when the query incorrectly returns the {_id: -1} document. assert.eq(0, db.foo.find({a: 1, b: 1}, {_id: 0, a: 1, b: 1}).itcount()); }
- is related to
-
SERVER-70446 [CQF] Enable yield-safe plans
- Backlog
- related to
-
SERVER-16675 getMore looks up doc with invalid RecordId, fails with "Didn't find RecordId in WiredTigerRecordStore"
- Closed