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

Writing an oplog entry for prepare should not push the lastApplied timestamp

    XMLWordPrintable

    Details

    • Backwards Compatibility:
      Fully Compatible
    • Operating System:
      ALL
    • Steps To Reproduce:
      Hide

      commit: 8c5002b06fd737f75941a73785ce75e3ca7f5ce1
      1. Put a 5 second sleep between (1) and (2) to increase the chance that another transaction starts in between.

            opObserver->onTransactionPrepare(opCtx);                                    
            lk.lock();                                                                  
            _checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), true);                
            invariant(_txnState == MultiDocumentTransactionState::kPrepared);           
      +     sleep(5);                                                                   
                                                                                        
            opCtx->getWriteUnitOfWork()->prepare(); 
       

      2. repro.js:

      const session = db.getMongo().startSession({causalConsistency: false});            
      const sessionDB = session.getDatabase('test');                                     
      sessionDB.a.insert({x: 1});                                                        
      session.startTransaction({readConcern: {level: "snapshot"}});                      
      assert.commandWorked(sessionDB.runCommand({                                        
          update: 'a',                                                                   
          updates: [{q: {x: 1}, u: {$inc: {x: 1}}}],                                     
      }));                                                                               
                                                                                         
      let awaitShell = startParallelShell(function() {                                   
          const session = db.getMongo().startSession({causalConsistency: false});        
          const sessionDB = session.getDatabase('test');                                 
          while (1) {                                                                    
              session.startTransaction({readConcern: {level: "snapshot"}});              
              sessionDB.a.find().toArray();                                              
              session.abortTransaction();                                                
          }                                                                              
      });                                                                                
                                                                                         
      assert.commandWorked(sessionDB.adminCommand({prepareTransaction: 1}));             
      session.abortTransaction();                                                        
                                                                                         
      awaitShell(); 
      

      Show
      commit: 8c5002b06fd737f75941a73785ce75e3ca7f5ce1 1. Put a 5 second sleep between (1) and (2) to increase the chance that another transaction starts in between. opObserver->onTransactionPrepare(opCtx); lk.lock(); _checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), true); invariant(_txnState == MultiDocumentTransactionState::kPrepared); + sleep(5); opCtx->getWriteUnitOfWork()->prepare(); 2. repro.js: const session = db.getMongo().startSession({causalConsistency: false}); const sessionDB = session.getDatabase('test'); sessionDB.a.insert({x: 1}); session.startTransaction({readConcern: {level: "snapshot"}}); assert.commandWorked(sessionDB.runCommand({ update: 'a', updates: [{q: {x: 1}, u: {$inc: {x: 1}}}], })); let awaitShell = startParallelShell(function() { const session = db.getMongo().startSession({causalConsistency: false}); const sessionDB = session.getDatabase('test'); while (1) { session.startTransaction({readConcern: {level: "snapshot"}}); sessionDB.a.find().toArray(); session.abortTransaction(); } }); assert.commandWorked(sessionDB.adminCommand({prepareTransaction: 1})); session.abortTransaction(); awaitShell();
    • Sprint:
      Repl 2018-07-16, Repl 2018-07-30, Repl 2018-08-13
    • Linked BF Score:
      70

      Description

      Our current prepareTransaction() logic might need some changes: currently, (1) we first log the prepare and get a timestamp T. And then (2) use this timestamp T to set the prepare timestamp in WiredTiger. Since (1) pushes the lastApplied timestamp to T and another transaction could start between (1) and (2), with lastApplied T as the read timestamp, it breaks the rule that prepare timestamp should be greater than any active read timestamp.

       

      Here is what happened in BF-9432:
      1. A started a transaction, did the insertion, and tried to prepare.
      2. A called prepareTransaction() in session.cpp. It first logged the prepare and got a timestamp T. This operation also pushed the lastApplied timestamp to T.
      3. B started a transaction with a read, the read timestamp is set to the lastApplied which is T.
      4. A now tried to set the prepare timestamp to T in WiredTiger. It will cause a WiredTiger error that prepare timestamp being equal to an active read timestamp.

        Attachments

          Issue Links

            Activity

              People

              Assignee:
              judah.schvimer Judah Schvimer
              Reporter:
              xiangyu.yao Xiangyu Yao (Inactive)
              Participants:
              Votes:
              0 Vote for this issue
              Watchers:
              11 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved: