[SERVER-14703] Snapshot queries can miss records if there are concurrent updates Created: 27/Jul/14  Updated: 31/Oct/16  Resolved: 14/Dec/15

Status: Closed
Project: Core Server
Component/s: Querying
Affects Version/s: None
Fix Version/s: None

Type: Improvement Priority: Major - P3
Reporter: Bruce Lucas (Inactive) Assignee: David Storch
Resolution: Duplicate Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Duplicate
duplicates SERVER-17635 Improve SortedDataInterface::Cursor API Closed
is duplicated by SERVER-17174 Updates can fail to find document dur... Closed
Related
related to TOOLS-9 mongodump --forceTableScan --oplog ne... Closed
related to DRIVERS-180 Docs and driver API refs inconsistent... Closed
is related to SERVER-14766 Indexed queries should not miss docum... Closed
is related to SERVER-21403 $snapshot can return duplicates on 3.... Closed
is related to SERVER-13992 UpdateTicket should handle record moves Closed
is related to SERVER-700 Overwrite index key rather than delet... Closed
Sprint: QuInt E (01/11/16)
Participants:

 Description   

"Snapshot" queries (queries using a unique index) can miss records if there are concurrent updates that move the records being queried (even if they don't modify the query keys). It would be useful if this could be improved so that snapshot queries were guaranteed to return all records that existed for the lifetime of the query (neither inserted nor deleted during the query).

Reproduce as follows:

function repro() {
 
    // insert 5 records
    db.c.drop()
    for (var i=0; i<5; i++)
        db.c.insert({_id:i, x:[]})
 
    // start query, fetch first batch of 2
    cursor = db.c.find().snapshot().batchSize(2)
    print('got', cursor.next()._id)
        
    // server cursor is now pointing to {_id:2} waiting for our getmore
    // so let's update in a way that requires the {_id:2} record to move
    db.c.update({_id:2}, {$push: {x:0}})
 
    // use our cursor to get the rest; note that {_id:2} is omitted
    while (cursor.hasNext())
        print('got', cursor.next()._id)
}



 Comments   
Comment by David Storch [ 14/Dec/15 ]

Per jason.rassi's earlier comment, we believe that this issue was fixed in 3.1.2 as part of the SortedDataInterface::Cursor refactor. I am therefore closing this ticket as a duplicate of SERVER-17635 (the ticket under which the fix was committed).

Also note that SERVER-21403 added regression tests for past issues in which snapshot queries could miss documents or return duplicates on the MMAPv1 storage engine. In particular, see https://github.com/mongodb/mongo/blob/master/jstests/core/snapshot_queries.js.

Comment by J Rassi [ 31/Jul/15 ]

Per discussion with Dave and Bruce, we believe that it is no longer possible to reproduce a case where a simple _id index scan misses a document due to a move (Bruce/others: do add a comment here if you can manage to reproduce this on 3.1.2+).

We could add the following dbtest for the IndexScan stage as a regression test for this issue:

  • Construct/initialize an IndexScan stage with shouldDedup=false.
  • Call saveState() (and possibly detachFromOperationContext()?).
  • Issue an INVALIDATION_DELETION of the RecordId corresponding to the index key about to be returned.
  • Restore state.
  • Verify that the index key is actually returned.

I suggest we consider the work for this ticket to be adding the above dbtest, and resolve this issue once it's committed.

Comment by J Rassi [ 29/Jul/15 ]

The test from the ticket description seems to fail on 2.4.14 and earlier, but pass on 2.6.0 and later (Bruce, is this consistent with your recollection?).

The below script is similar to the one in the ticket description, but the update moves the document to a lower diskloc instead of a higher diskloc. This script fails on versions earlier than 3.1.1 (I tried as far back as 2.4), but passes on 3.1.2 through 3.1.6.

db.foo.drop();
db.foo.insert({_id:0,a:Array(1000).toString()});
db.foo.insert({_id:1});
db.foo.insert({_id:2});
db.foo.insert({_id:3});
db.foo.remove({_id:0});
var cursor = db.foo.find().snapshot().batchSize(2);
cursor.hasNext();
cursor.next();
cursor.hasNext();
cursor.next();
db.foo.update({_id:3},{$set:{a:Array(100).toString()}}); // {_id: 3} document gets original diskloc of {_id: 0}
assert(cursor.hasNext()); // Assertion fails on 3.1.1 and earlier: {_id: 3} document is skipped.

I confirmed with git bisect that db59e0f3 contains the fix that causes this test to pass. Interestingly, Mathias tells me that this fix was not intentional, and I can reaffirm Scott's claim that the system does not make any guarantee about whether concurrently updated documents are returned by these queries.

Bruce: given all this, do you think we should resolve this issue?

Comment by Scott Hernandez (Inactive) [ 27/Jul/14 ]

The system is designed to work this way and snapshot queries only ensure you don't get dups. It does not ensure that you get a snapshot of "time" as all cursors are live and affected by writes.

Generated at Thu Feb 08 03:35:42 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.