Uploaded image for project: 'Core Server'
  1. Core Server
  2. SERVER-40167

Index key removal should not encounter prepare conflicts on unrelated keys

    • Storage Execution
    • ALL
    • Hide
      (function() {
          "use strict";
          load("jstests/core/txns/libs/prepare_helpers.js");
      
          const rst = new ReplSetTest({nodes: 1});
          rst.startSet();
          rst.initiate();
      
          const dbName = "test";
          const collName = "delete_prepare_conflict";
      
          const primary = rst.getPrimary();
          const testDB = primary.getDB(dbName);
          const testColl = testDB.getCollection(collName);
      
          assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}}));
      
          // Create an index on "num" so that updates and deletes will not encounter prepare conflicts.
          assert.commandWorked(
              testDB.runCommand({createIndexes: collName, indexes: [{key: {"num": 1}, name: "num_1"}]}));
      
          const session = primary.startSession({causalConsistency: false});
          const sessionDB = session.getDatabase(dbName);
          const sessionColl = sessionDB.getCollection(collName);
      
          session.startTransaction();
          assert.commandWorked(sessionColl.insert({_id: 42}));  // num: null
      
          PrepareHelpers.prepareTransaction(session);
      
          // Uncommenting this allows the remove() to succeed.    // By inserting another document, search_near will positions the cursor on this index key, rather than the null key for the document {_id: 42}.
          /*   assert.commandWorked(testColl.insert({num: 1}));   */
      
          assert.commandWorked(testColl.insert({num: 100}));
      
          // Blocks due to a prepare conflict after the document has been deleted because the index cursor is repositioned on the document {_id: 42}
          assert.commandWorked(testColl.remove({num: 100}));
      
          session.abortTransaction_forTesting();
          rst.stopSet();
      })();
      
      Show
      ( function () { "use strict" ; load( "jstests/core/txns/libs/prepare_helpers.js" ); const rst = new ReplSetTest({nodes: 1}); rst.startSet(); rst.initiate(); const dbName = "test" ; const collName = "delete_prepare_conflict" ; const primary = rst.getPrimary(); const testDB = primary.getDB(dbName); const testColl = testDB.getCollection(collName); assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority" }})); // Create an index on "num" so that updates and deletes will not encounter prepare conflicts. assert.commandWorked( testDB.runCommand({createIndexes: collName, indexes: [{key: { "num" : 1}, name: "num_1" }]})); const session = primary.startSession({causalConsistency: false }); const sessionDB = session.getDatabase(dbName); const sessionColl = sessionDB.getCollection(collName); session.startTransaction(); assert.commandWorked(sessionColl.insert({_id: 42})); // num: null PrepareHelpers.prepareTransaction(session); // Uncommenting this allows the remove() to succeed. // By inserting another document, search_near will positions the cursor on this index key, rather than the null key for the document {_id: 42}. /* assert.commandWorked(testColl.insert({num: 1})); */ assert.commandWorked(testColl.insert({num: 100})); // Blocks due to a prepare conflict after the document has been deleted because the index cursor is repositioned on the document {_id: 42} assert.commandWorked(testColl.remove({num: 100})); session.abortTransaction_forTesting(); rst.stopSet(); })();
    • Storage NYC 2019-05-06, Execution Team 2021-02-08, Execution Team 2021-02-22
    • 8

      After an index key is deleted, the cursor on the index is re-established by calling restoreState(). This restore uses a search_near to reposition the cursor at the original position. Since this key has now been deleted, the search_near positions the cursor at the logically adjacent value. If this adjacent key is involved in a prepared transaction, the cursor restoration encounters a prepare conflict, when it would not have otherwise until this point.

      Instead of calling restoreState() after deleting each key, which effectively repositions the cursor on a deleted entry every single time, it may make sense to instead directly position the cursor on the next key for deletion.

      Note: This is only true once SERVER-39074 is completed, and all write operations start enforcing prepare conflicts.

      Example stacktrace:

      #0  0x00007f1f580f83f7 in poll () from /lib64/libc.so.6
      #1  0x00007f1f6598529c in mongo::transport::TransportLayerASIO::BatonASIO::run (this=0x7f1ee00c35f0, clkSource=0x561bc5e03b20)
          at src/mongo/transport/baton_asio_linux.h:315
      #2  0x00007f1f5a44c2b2 in mongo::Waitable::wait(mongo::Waitable*, mongo::ClockSource*, mongo::stdx::condition_variable&, std::unique_lock<std::mutex>&)::{lambda()#1}::operator()() const (__closure=0x7f1f3c4cd1b0) at src/mongo/util/waitable.h:59
      #3  0x00007f1f5a44d083 in mongo::stdx::condition_variable::_runWithNotifyable<mongo::Waitable::wait(mongo::Waitable*, mongo::ClockSource*, mongo::stdx::condition_variable&, std::unique_lock<std::mutex>&)::{lambda()#1}>(mongo::Notifyable&, mongo::Waitable::wait(mongo::Waitable*, mongo::ClockSource*, mongo::stdx::condi
      tion_variable&, std::unique_lock<std::mutex>&)::{lambda()#1}&&) (this=0x561bc5f84330, notifyable=..., cb=...) at src/mongo/stdx/condition_variable.h:164
      #4  0x00007f1f5a44c326 in mongo::Waitable::wait (waitable=0x7f1ee00c35f0, clkSource=0x561bc5e03b20, cv=..., lk=...) at src/mongo/util/waitable.h:57
      #5  0x00007f1f5a44a8d9 in mongo::OperationContext::<lambda()>::operator()(void) const (__closure=0x7f1f3c4cd270) at src/mongo/db/operation_context.cpp:287
      #6  0x00007f1f5a44aae0 in mongo::OperationContext::waitForConditionOrInterruptNoAssertUntil (this=0x7f1ee00e3fe0, cv=..., m=..., deadline=...)
          at src/mongo/db/operation_context.cpp:292
      #7  0x00007f1f659797f6 in mongo::Interruptible::waitForConditionOrInterruptNoAssert (this=0x7f1ee00e3fe0, cv=..., m=...) at src/mongo/util/interruptible.h:229
      #8  0x00007f1f65979728 in mongo::Interruptible::waitForConditionOrInterrupt (this=0x7f1ee00e3fe0, cv=..., m=...) at src/mongo/util/interruptible.h:206
      #9  0x00007f1f63ab541f in mongo::Interruptible::waitForConditionOrInterrupt<mongo::WiredTigerSessionCache::waitUntilPreparedUnitOfWorkCommitsOrAborts(mongo::OperationContext*, uint64_t)::<lambda()> >(mongo::stdx::condition_variable &, std::unique_lock<std::mutex> &, mongo::WiredTigerSessionCache::<lambda()>) (
          this=0x7f1ee00e3fe0, cv=..., m=..., pred=...) at src/mongo/util/interruptible.h:219
      #10 0x00007f1f63ab44ac in mongo::WiredTigerSessionCache::waitUntilPreparedUnitOfWorkCommitsOrAborts (this=0x561bc5f841f0, opCtx=0x7f1ee00e3fe0, lastCount=0)
          at src/mongo/db/storage/wiredtiger/wiredtiger_session_cache.cpp:310
      #11 0x00007f1f63a4d23e in mongo::wiredTigerPrepareConflictRetry<mongo::(anonymous namespace)::WiredTigerIndexCursorBase::seekWTCursor(const mongo::KeyString&)::<lambda()> >(mongo::OperationContext *, mongo::(anonymous namespace)::WiredTigerIndexCursorBase::<lambda()> &&) (opCtx=0x7f1ee00e3fe0, f=...)
          at src/mongo/db/storage/wiredtiger/wiredtiger_prepare_conflict.h:137
      #12 0x00007f1f63a47420 in mongo::(anonymous namespace)::WiredTigerIndexCursorBase::seekWTCursor (this=0x7f1ee00c7500, query=...)
          at src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp:1028
      #13 0x00007f1f63a46966 in mongo::(anonymous namespace)::WiredTigerIndexCursorBase::restore (this=0x7f1ee00c7500)
          at src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp:902
      #14 0x00007f1f5f6bfec9 in mongo::IndexScan::doRestoreStateRequiresIndex (this=0x561bc5f801a0) at src/mongo/db/exec/index_scan.cpp:246
      #15 0x00007f1f5f6f7163 in mongo::RequiresIndexStage::doRestoreStateRequiresCollection (this=0x561bc5f801a0) at src/mongo/db/exec/requires_index_stage.cpp:73
      #16 0x00007f1f5f6f63a3 in mongo::RequiresCollectionStageBase<mongo::Collection const*>::doRestoreState (this=0x561bc5f801a0)
          at src/mongo/db/exec/requires_collection_stage.cpp:70
      #17 0x00007f1f5f6d689f in mongo::PlanStage::restoreState (this=0x561bc5f801a0) at src/mongo/db/exec/plan_stage.cpp:75
      #18 0x00007f1f5f6d687a in mongo::PlanStage::restoreState (this=0x7f1ee0058640) at src/mongo/db/exec/plan_stage.cpp:72
      #19 0x00007f1f5f69f0b9 in mongo::DeleteStage::doWork (this=0x7f1ee00c3c70, out=0x7f1f3c4ce210) at src/mongo/db/exec/delete.cpp:235
      #20 0x00007f1f5f6d6674 in mongo::PlanStage::work (this=0x7f1ee00c3c70, out=0x7f1f3c4ce210) at src/mongo/db/exec/plan_stage.cpp:47
      #21 0x00007f1f5f772295 in mongo::PlanExecutorImpl::_getNextImpl (this=0x7f1ee0004230, objOut=0x7f1f3c4ce380, dlOut=0x0)
          at src/mongo/db/query/plan_executor_impl.cpp:529
      #22 0x00007f1f5f77149d in mongo::PlanExecutorImpl::getNext (this=0x7f1ee0004230, objOut=0x7f1f3c4ce3e0, dlOut=0x0)
          at src/mongo/db/query/plan_executor_impl.cpp:382
      #23 0x00007f1f5f772c13 in mongo::PlanExecutorImpl::executePlan (this=0x7f1ee0004230) at src/mongo/db/query/plan_executor_impl.cpp:644
      #24 0x00007f1f61dcdb08 in mongo::performSingleDeleteOp (opCtx=0x7f1ee00e3fe0, ns=..., stmtId=-1, op=...) at src/mongo/db/ops/write_ops_exec.cpp:889
      #25 0x00007f1f61dce79f in mongo::performDeletes (opCtx=0x7f1ee00e3fe0, wholeOp=...) at src/mongo/db/ops/write_ops_exec.cpp:967
      #26 0x00007f1f56ee85e5 in mongo::(anonymous namespace)::CmdDelete::Invocation::runImpl (this=0x7f1ee00da0b0, opCtx=0x7f1ee00e3fe0, result=...)
          at src/mongo/db/commands/write_commands/write_commands.cpp:428
      

            Assignee:
            backlog-server-execution [DO NOT USE] Backlog - Storage Execution Team
            Reporter:
            louis.williams@mongodb.com Louis Williams
            Votes:
            0 Vote for this issue
            Watchers:
            18 Start watching this issue

              Created:
              Updated:
              Resolved: