mirror of
https://github.com/mongodb/mongo.git
synced 2024-11-30 00:56:44 +01:00
203 lines
11 KiB
JavaScript
203 lines
11 KiB
JavaScript
// Tests multi-document transactions metrics in the serverStatus output.
|
|
// @tags: [uses_transactions]
|
|
(function() {
|
|
"use strict";
|
|
|
|
// Verifies that the server status response has the fields that we expect.
|
|
function verifyServerStatusFields(serverStatusResponse) {
|
|
assert(serverStatusResponse.hasOwnProperty("transactions"),
|
|
"Expected the serverStatus response to have a 'transactions' field\n" +
|
|
tojson(serverStatusResponse));
|
|
assert(serverStatusResponse.transactions.hasOwnProperty("currentActive"),
|
|
"The 'transactions' field in serverStatus did not have the 'currentActive' field\n" +
|
|
tojson(serverStatusResponse.transactions));
|
|
assert(serverStatusResponse.transactions.hasOwnProperty("currentInactive"),
|
|
"The 'transactions' field in serverStatus did not have the 'currentInactive' field\n" +
|
|
tojson(serverStatusResponse.transactions));
|
|
assert(serverStatusResponse.transactions.hasOwnProperty("currentOpen"),
|
|
"The 'transactions' field in serverStatus did not have the 'currentOpen' field\n" +
|
|
tojson(serverStatusResponse.transactions));
|
|
assert(serverStatusResponse.transactions.hasOwnProperty("totalAborted"),
|
|
"The 'transactions' field in serverStatus did not have the 'totalAborted' field\n" +
|
|
tojson(serverStatusResponse.transactions));
|
|
assert(serverStatusResponse.transactions.hasOwnProperty("totalCommitted"),
|
|
"The 'transactions' field in serverStatus did not have the 'totalCommitted' field\n" +
|
|
tojson(serverStatusResponse.transactions));
|
|
assert(serverStatusResponse.transactions.hasOwnProperty("totalStarted"),
|
|
"The 'transactions' field in serverStatus did not have the 'totalStarted' field\n" +
|
|
tojson(serverStatusResponse.transactions));
|
|
}
|
|
|
|
// Verifies that the given value of the server status response is incremented in the way
|
|
// we expect.
|
|
function verifyServerStatusChange(initialStats, newStats, valueName, expectedIncrement) {
|
|
assert.eq(initialStats[valueName] + expectedIncrement,
|
|
newStats[valueName],
|
|
"expected " + valueName + " to increase by " + expectedIncrement);
|
|
}
|
|
|
|
// Set up the replica set.
|
|
const rst = new ReplSetTest({nodes: 1});
|
|
rst.startSet();
|
|
rst.initiate();
|
|
const primary = rst.getPrimary();
|
|
|
|
// Set up the test database.
|
|
const dbName = "test";
|
|
const collName = "server_transactions_metrics";
|
|
const testDB = primary.getDB(dbName);
|
|
const adminDB = rst.getPrimary().getDB('admin');
|
|
testDB.runCommand({drop: collName, writeConcern: {w: "majority"}});
|
|
assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}}));
|
|
|
|
// Start the session.
|
|
const sessionOptions = {
|
|
causalConsistency: false
|
|
};
|
|
const session = testDB.getMongo().startSession(sessionOptions);
|
|
const sessionDb = session.getDatabase(dbName);
|
|
const sessionColl = sessionDb[collName];
|
|
|
|
// Get state of server status before the transaction.
|
|
let initialStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
|
|
verifyServerStatusFields(initialStatus);
|
|
|
|
// This transaction will commit.
|
|
jsTest.log("Start a transaction and then commit it.");
|
|
|
|
// Compare server status after starting a transaction with the server status before.
|
|
session.startTransaction();
|
|
assert.commandWorked(sessionColl.insert({_id: "insert-1"}));
|
|
|
|
let newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
|
|
verifyServerStatusFields(newStatus);
|
|
// Verify that the open transaction counter is incremented while inside the transaction.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentOpen", 1);
|
|
// Verify that when not running an operation, the transaction is inactive.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentActive", 0);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentInactive", 1);
|
|
|
|
// Compare server status after the transaction commit with the server status before.
|
|
assert.commandWorked(session.commitTransaction_forTesting());
|
|
newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
|
|
verifyServerStatusFields(newStatus);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "totalStarted", 1);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "totalCommitted", 1);
|
|
// Verify that current open counter is decremented on commit.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentOpen", 0);
|
|
// Verify that both active and inactive are 0 on commit.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentActive", 0);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentInactive", 0);
|
|
|
|
// This transaction will abort.
|
|
jsTest.log("Start a transaction and then abort it.");
|
|
|
|
// Compare server status after starting a transaction with the server status before.
|
|
session.startTransaction();
|
|
assert.commandWorked(sessionColl.insert({_id: "insert-2"}));
|
|
|
|
newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
|
|
verifyServerStatusFields(newStatus);
|
|
// Verify that the open transaction counter is incremented while inside the transaction.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentOpen", 1);
|
|
// Verify that when not running an operation, the transaction is inactive.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentActive", 0);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentInactive", 1);
|
|
|
|
// Compare server status after the transaction abort with the server status before.
|
|
assert.commandWorked(session.abortTransaction_forTesting());
|
|
newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
|
|
verifyServerStatusFields(newStatus);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "totalStarted", 2);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "totalCommitted", 1);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "totalAborted", 1);
|
|
// Verify that current open counter is decremented on abort.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentOpen", 0);
|
|
// Verify that both active and inactive are 0 on abort.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentActive", 0);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentInactive", 0);
|
|
|
|
// This transaction will abort due to a duplicate key insert.
|
|
jsTest.log("Start a transaction that will abort on a duplicated key error.");
|
|
|
|
// Compare server status after starting a transaction with the server status before.
|
|
session.startTransaction();
|
|
// Inserting a new document will work fine, and the transaction starts.
|
|
assert.commandWorked(sessionColl.insert({_id: "insert-3"}));
|
|
|
|
newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
|
|
verifyServerStatusFields(newStatus);
|
|
// Verify that the open transaction counter is incremented while inside the transaction.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentOpen", 1);
|
|
// Verify that when not running an operation, the transaction is inactive.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentActive", 0);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentInactive", 1);
|
|
|
|
// Compare server status after the transaction abort with the server status before.
|
|
// The duplicated insert will fail, causing the transaction to abort.
|
|
assert.commandFailedWithCode(sessionColl.insert({_id: "insert-3"}), ErrorCodes.DuplicateKey);
|
|
// Ensure that the transaction was aborted on failure.
|
|
assert.commandFailedWithCode(session.commitTransaction_forTesting(), ErrorCodes.NoSuchTransaction);
|
|
newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
|
|
verifyServerStatusFields(newStatus);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "totalStarted", 3);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "totalCommitted", 1);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "totalAborted", 2);
|
|
// Verify that current open counter is decremented on abort caused by an error.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentOpen", 0);
|
|
// Verify that both active and inactive are 0 on abort.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentActive", 0);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentInactive", 0);
|
|
|
|
// Hang the transaction on a failpoint in the middle of an operation to check active and
|
|
// inactive counters while operation is running inside a transaction.
|
|
jsTest.log("Start a transaction that will hang in the middle of an operation due to a fail point.");
|
|
assert.commandWorked(
|
|
testDB.adminCommand({configureFailPoint: 'hangDuringBatchUpdate', mode: 'alwaysOn'}));
|
|
|
|
const transactionFn = function() {
|
|
const collName = 'server_transactions_metrics';
|
|
const session = db.getMongo().startSession({causalConsistency: false});
|
|
const sessionDb = session.getDatabase('test');
|
|
const sessionColl = sessionDb[collName];
|
|
|
|
session.startTransaction({readConcern: {level: 'snapshot'}});
|
|
assert.commandWorked(sessionColl.update({}, {"update-1": 2}));
|
|
assert.commandWorked(session.commitTransaction_forTesting());
|
|
};
|
|
const transactionProcess = startParallelShell(transactionFn, primary.port);
|
|
|
|
// Keep running currentOp() until we see the transaction subdocument.
|
|
assert.soon(function() {
|
|
const transactionFilter = {
|
|
active: true,
|
|
'lsid': {$exists: true},
|
|
'transaction.parameters.txnNumber': {$eq: 0}
|
|
};
|
|
return 1 === adminDB.aggregate([{$currentOp: {}}, {$match: transactionFilter}]).itcount();
|
|
});
|
|
newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
|
|
verifyServerStatusFields(newStatus);
|
|
// Verify that the open transaction counter is incremented while inside the transaction.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentOpen", 1);
|
|
// Verify that the metrics show that the transaction is active while inside the operation.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentActive", 1);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentInactive", 0);
|
|
|
|
// Now the transaction can proceed.
|
|
assert.commandWorked(
|
|
testDB.adminCommand({configureFailPoint: 'hangDuringBatchUpdate', mode: 'off'}));
|
|
transactionProcess();
|
|
newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1}));
|
|
verifyServerStatusFields(newStatus);
|
|
// Verify that current open counter is decremented on commit.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentOpen", 0);
|
|
// Verify that both active and inactive are 0 after the transaction finishes.
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentActive", 0);
|
|
verifyServerStatusChange(initialStatus.transactions, newStatus.transactions, "currentInactive", 0);
|
|
|
|
// End the session and stop the replica set.
|
|
session.endSession();
|
|
rst.stopSet();
|
|
}());
|