Files
mongo/jstests/replsets/commit_transaction_recovery_data_already_applied.js
2018-11-08 14:43:11 -05:00

94 lines
4.3 KiB
JavaScript

/**
* Test that startup recovery successfully replays the commitTransaction oplog entry, but does not
* re-apply the operations from the transaction if the data already reflects the transaction. If the
* operations are replayed, this will cause a BSONTooLarge exception.
*
* @tags: [uses_transactions, uses_prepare_transaction]
*/
(function() {
"use strict";
load("jstests/core/txns/libs/prepare_helpers.js");
const replTest = new ReplSetTest({nodes: 1});
replTest.startSet();
replTest.initiate();
let primary = replTest.getPrimary();
const dbName = "test";
const collName = "commit_transaction_recovery";
let testDB = primary.getDB(dbName);
const testColl = testDB.getCollection(collName);
testDB.runCommand({drop: collName});
assert.commandWorked(testDB.runCommand({create: collName}));
let session = primary.startSession({causalConsistency: false});
let sessionDB = session.getDatabase(dbName);
const sessionColl = sessionDB.getCollection(collName);
// Construct a large array such that two arrays in the same document are not greater than the
// 16MB limit, but that three such arrays in the same document are greater than 16MB. This will
// be helpful in recreating an idempotency issue that exists when applying the operations from
// a transaction after the data already reflects the transaction.
const largeArray = new Array(7 * 1024 * 1024).join('x');
assert.commandWorked(testColl.insert({_id: 1, "a": largeArray}));
session.startTransaction();
assert.commandWorked(sessionColl.update({_id: 1}, {$set: {"b": largeArray}}));
assert.commandWorked(sessionColl.update({_id: 1}, {$unset: {"b": 1}}));
assert.commandWorked(sessionColl.update({_id: 1}, {$set: {"c": largeArray}}));
let prepareTimestamp = PrepareHelpers.prepareTransaction(session);
const commitTimestamp =
assert.commandWorked(testColl.runCommand("insert", {documents: [{_id: 2}]})).operationTime;
const recoveryTimestamp =
assert.commandWorked(testColl.runCommand("insert", {documents: [{_id: 3}]})).operationTime;
jsTestLog("Holding back the stable timestamp to right after the commitTimestamp");
// Hold back the stable timestamp to be right after the commitTimestamp, but before the
// commitTransaction oplog entry so that the data will reflect the transaction during recovery.
assert.commandWorked(testDB.adminCommand({
"configureFailPoint": 'holdStableTimestampAtSpecificTimestamp',
"mode": 'alwaysOn',
"data": {"timestamp": recoveryTimestamp}
}));
jsTestLog("Committing the transaction");
// Since the commitTimestamp is after the last snapshot, this oplog entry will be replayed
// during replication recovery during restart.
assert.commandWorked(PrepareHelpers.commitTransaction(session, commitTimestamp));
jsTestLog("Restarting node");
// Perform a clean shutdown and restart. Since the data already reflects the transaction, this
// will error with BSONTooLarge only if recovery reapplies the operations from the transaction.
// Note that the 'disableSnapshotting' failpoint will be unset on the node following the
// restart.
replTest.stop(primary, undefined, {skipValidation: true});
replTest.start(primary, {}, true);
jsTestLog("Node was restarted");
primary = replTest.getPrimary();
testDB = primary.getDB(dbName);
session = primary.startSession({causalConsistency: false});
sessionDB = session.getDatabase(dbName);
session.startTransaction();
// Make sure that the data reflects all the operations from the transaction after recovery.
const res = testDB[collName].findOne({_id: 1});
assert.eq(res, {_id: 1, "a": largeArray, "c": largeArray}, tojson(res));
// Make sure that another write on the same document from the transaction has no write conflict.
// Also, make sure that we can run another transaction after recovery without any problems.
assert.commandWorked(sessionDB[collName].update({_id: 1}, {_id: 1, a: 1}));
prepareTimestamp = PrepareHelpers.prepareTransaction(session);
assert.commandWorked(PrepareHelpers.commitTransactionAfterPrepareTS(session, prepareTimestamp));
assert.eq(testDB[collName].findOne({_id: 1}), {_id: 1, a: 1});
replTest.stopSet();
}());