diff options
author | Jason Chan <jason.chan@mongodb.com> | 2018-11-30 16:33:42 -0500 |
---|---|---|
committer | Jason Chan <jason.chan@mongodb.com> | 2018-11-30 16:42:39 -0500 |
commit | 3bb72042c29ceac96ec628ee643cbc4db8e982ab (patch) | |
tree | a4c2b430c4b39771b83797df1d8132cb38a1b811 | |
parent | 401151f1d29e7d88f4abf10c7ca92739dbc55336 (diff) | |
download | mongo-3bb72042c29ceac96ec628ee643cbc4db8e982ab.tar.gz |
SERVER-35713 Add oldestOpenReadTimestamp to serverStatus output
7 files changed, 95 insertions, 18 deletions
diff --git a/jstests/noPassthrough/server_transaction_metrics.js b/jstests/noPassthrough/server_transaction_metrics.js index f83392a2ea5..1d437caba45 100644 --- a/jstests/noPassthrough/server_transaction_metrics.js +++ b/jstests/noPassthrough/server_transaction_metrics.js @@ -7,27 +7,32 @@ function verifyServerStatusFields(serverStatusResponse) { assert(serverStatusResponse.hasOwnProperty("transactions"), "Expected the serverStatus response to have a 'transactions' field\n" + - serverStatusResponse); + tojson(serverStatusResponse)); assert(serverStatusResponse.transactions.hasOwnProperty("currentActive"), "The 'transactions' field in serverStatus did not have the 'currentActive' field\n" + - serverStatusResponse.transactions); + tojson(serverStatusResponse.transactions)); assert( serverStatusResponse.transactions.hasOwnProperty("currentInactive"), "The 'transactions' field in serverStatus did not have the 'currentInactive' field\n" + - serverStatusResponse.transactions); + tojson(serverStatusResponse.transactions)); assert(serverStatusResponse.transactions.hasOwnProperty("currentOpen"), "The 'transactions' field in serverStatus did not have the 'currentOpen' field\n" + - serverStatusResponse.transactions); + tojson(serverStatusResponse.transactions)); assert(serverStatusResponse.transactions.hasOwnProperty("totalAborted"), "The 'transactions' field in serverStatus did not have the 'totalAborted' field\n" + - serverStatusResponse.transactions); + tojson(serverStatusResponse.transactions)); assert( serverStatusResponse.transactions.hasOwnProperty("totalCommitted"), "The 'transactions' field in serverStatus did not have the 'totalCommitted' field\n" + - serverStatusResponse.transactions); + tojson(serverStatusResponse.transactions)); assert(serverStatusResponse.transactions.hasOwnProperty("totalStarted"), "The 'transactions' field in serverStatus did not have the 'totalStarted' field\n" + - serverStatusResponse.transactions); + tojson(serverStatusResponse.transactions)); + assert( + serverStatusResponse.transactions.hasOwnProperty("oldestOpenUnpreparedReadTimestamp"), + "The 'transactions' field in serverStatus did not have the " + + "'oldestOpenUnpreparedReadTimestamp' field\n" + + tojson(serverStatusResponse.transactions)); } // Verifies that the given value of the server status response is incremented in the way @@ -68,6 +73,9 @@ // Compare server status after starting a transaction with the server status before. session.startTransaction(); assert.commandWorked(sessionColl.insert({_id: "insert-1"})); + // Trigger the oldestOpenUnpreparedReadTimestamp to be set. + assert.eq(sessionColl.find({_id: "insert-1"}).itcount(), 1); + let newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1})); verifyServerStatusFields(newStatus); // Verify that the open transaction counter is incremented while inside the transaction. @@ -77,6 +85,8 @@ initialStatus.transactions, newStatus.transactions, "currentActive", 0); verifyServerStatusChange( initialStatus.transactions, newStatus.transactions, "currentInactive", 1); + // Verify that the oldestOpenUnpreparedReadTimestamp has been set. + assert.gt(newStatus.transactions.oldestOpenUnpreparedReadTimestamp, Timestamp(0, 0)); // Compare server status after the transaction commit with the server status before. session.commitTransaction(); @@ -92,6 +102,9 @@ initialStatus.transactions, newStatus.transactions, "currentActive", 0); verifyServerStatusChange( initialStatus.transactions, newStatus.transactions, "currentInactive", 0); + // Verify that the oldestOpenUnpreparedReadTimestamp is a null timestamp since the transaction + // has closed. + assert.eq(newStatus.transactions.oldestOpenUnpreparedReadTimestamp, Timestamp(0, 0)); // This transaction will abort. jsTest.log("Start a transaction and then abort it."); @@ -99,6 +112,9 @@ // Compare server status after starting a transaction with the server status before. session.startTransaction(); assert.commandWorked(sessionColl.insert({_id: "insert-2"})); + // Trigger the oldestOpenUnpreparedReadTimestamp to be set. + assert.eq(sessionColl.find({_id: "insert-2"}).itcount(), 1); + newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1})); verifyServerStatusFields(newStatus); // Verify that the open transaction counter is incremented while inside the transaction. @@ -108,6 +124,8 @@ initialStatus.transactions, newStatus.transactions, "currentActive", 0); verifyServerStatusChange( initialStatus.transactions, newStatus.transactions, "currentInactive", 1); + // Verify that the oldestOpenUnpreparedReadTimestamp has been set. + assert.gt(newStatus.transactions.oldestOpenUnpreparedReadTimestamp, Timestamp(0, 0)); // Compare server status after the transaction abort with the server status before. session.abortTransaction_forTesting(); @@ -124,6 +142,9 @@ initialStatus.transactions, newStatus.transactions, "currentActive", 0); verifyServerStatusChange( initialStatus.transactions, newStatus.transactions, "currentInactive", 0); + // Verify that the oldestOpenUnpreparedReadTimestamp is a null timestamp since the transaction + // has closed. + assert.eq(newStatus.transactions.oldestOpenUnpreparedReadTimestamp, Timestamp(0, 0)); // This transaction will abort due to a duplicate key insert. jsTest.log("Start a transaction that will abort on a duplicated key error."); @@ -132,6 +153,9 @@ session.startTransaction(); // Inserting a new document will work fine, and the transaction starts. assert.commandWorked(sessionColl.insert({_id: "insert-3"})); + // Trigger the oldestOpenUnpreparedReadTimestamp to be set. + assert.eq(sessionColl.find({_id: "insert-3"}).itcount(), 1); + newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1})); verifyServerStatusFields(newStatus); // Verify that the open transaction counter is incremented while inside the transaction. @@ -141,6 +165,8 @@ initialStatus.transactions, newStatus.transactions, "currentActive", 0); verifyServerStatusChange( initialStatus.transactions, newStatus.transactions, "currentInactive", 1); + // Verify that the oldestOpenUnpreparedReadTimestamp has been set. + assert.gt(newStatus.transactions.oldestOpenUnpreparedReadTimestamp, Timestamp(0, 0)); // Compare server status after the transaction abort with the server status before. // The duplicated insert will fail, causing the transaction to abort. @@ -212,6 +238,9 @@ initialStatus.transactions, newStatus.transactions, "currentActive", 0); verifyServerStatusChange( initialStatus.transactions, newStatus.transactions, "currentInactive", 0); + // Verify that the oldestOpenUnpreparedReadTimestamp is a null timestamp since the transaction + // has closed. + assert.eq(newStatus.transactions.oldestOpenUnpreparedReadTimestamp, Timestamp(0, 0)); // End the session and stop the replica set. session.endSession(); diff --git a/jstests/noPassthrough/server_transaction_metrics_for_prepared_transactions.js b/jstests/noPassthrough/server_transaction_metrics_for_prepared_transactions.js index f59ba448b39..4924143dfc9 100644 --- a/jstests/noPassthrough/server_transaction_metrics_for_prepared_transactions.js +++ b/jstests/noPassthrough/server_transaction_metrics_for_prepared_transactions.js @@ -10,19 +10,23 @@ function verifyServerStatusFields(serverStatusResponse) { assert(serverStatusResponse.hasOwnProperty("transactions"), "Expected the serverStatus response to have a 'transactions' field: " + - serverStatusResponse); + tojson(serverStatusResponse)); assert(serverStatusResponse.transactions.hasOwnProperty("totalPrepared"), "Expected the serverStatus response to have a 'totalPrepared' field: " + - serverStatusResponse); + tojson(serverStatusResponse)); assert(serverStatusResponse.transactions.hasOwnProperty("totalPreparedThenCommitted"), "Expected the serverStatus response to have a 'totalPreparedThenCommitted' field: " + - serverStatusResponse); + tojson(serverStatusResponse)); assert(serverStatusResponse.transactions.hasOwnProperty("totalPreparedThenAborted"), "Expected the serverStatus response to have a 'totalPreparedThenAborted' field: " + - serverStatusResponse); + tojson(serverStatusResponse)); assert(serverStatusResponse.transactions.hasOwnProperty("currentPrepared"), "Expected the serverStatus response to have a 'currentPrepared' field: " + - serverStatusResponse); + tojson(serverStatusResponse)); + assert( + serverStatusResponse.transactions.hasOwnProperty("oldestOpenUnpreparedReadTimestamp"), + "Expected the serverStatus response to have a 'oldestOpenUnpreparedReadTimestamp' " + + "field: " + tojson(serverStatusResponse)); } /** @@ -67,11 +71,17 @@ // Start transaction and prepare transaction. session.startTransaction(); assert.commandWorked(sessionColl.insert(doc1)); + + // Trigger the oldestOpenUnpreparedReadTimestamp to be set. + assert.eq(sessionColl.find(doc1).itcount(), 1); + let newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1})); + assert.gt(newStatus.transactions.oldestOpenUnpreparedReadTimestamp, Timestamp(0, 0)); + const prepareTimestampForCommit = PrepareHelpers.prepareTransaction(session); // Verify the total and current prepared transaction counter is updated and the oldest active // oplog entry timestamp is shown. - let newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1})); + newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1})); verifyServerStatusFields(newStatus); verifyServerStatusChange( initialStatus.transactions, newStatus.transactions, "totalPrepared", 1); @@ -79,6 +89,9 @@ initialStatus.transactions, newStatus.transactions, "currentPrepared", 1); assert.eq(newStatus.transactions.oldestActiveOplogEntryTimestamp, prepareTimestampForCommit); + // Verify that the oldestOpenUnpreparedReadTimestamp is a null timestamp since the transaction + // has been prepared. + assert.eq(newStatus.transactions.oldestOpenUnpreparedReadTimestamp, Timestamp(0, 0)); // Verify the total prepared and committed transaction counters are updated after a commit // and that the current prepared counter is decremented. @@ -95,6 +108,9 @@ initialStatus.transactions, newStatus.transactions, "totalPreparedThenAborted", 0); verifyServerStatusChange( initialStatus.transactions, newStatus.transactions, "totalPrepared", 1); + // Verify that the oldestOpenUnpreparedReadTimestamp is a null timestamp since the transaction + // is closed. + assert.eq(newStatus.transactions.oldestOpenUnpreparedReadTimestamp, Timestamp(0, 0)); // Test server metrics for a prepared transaction that is aborted. jsTest.log("Prepare a transaction and then abort it"); @@ -104,6 +120,12 @@ // Start transaction and prepare transaction. session.startTransaction(); assert.commandWorked(sessionColl.insert(doc2)); + + // Trigger the oldestOpenUnpreparedReadTimestamp to be set. + assert.eq(sessionColl.find(doc2).itcount(), 1); + newStatus = assert.commandWorked(testDB.adminCommand({serverStatus: 1})); + assert.gt(newStatus.transactions.oldestOpenUnpreparedReadTimestamp, Timestamp(0, 0)); + const prepareTimestampForAbort = PrepareHelpers.prepareTransaction(session); // Verify that the total and current prepared counter is updated and the oldest active oplog @@ -116,6 +138,9 @@ initialStatus.transactions, newStatus.transactions, "currentPrepared", 1); assert.eq(newStatus.transactions.oldestActiveOplogEntryTimestamp, prepareTimestampForAbort); + // Verify that the oldestOpenUnpreparedReadTimestamp is a null timestamp since the transaction + // has been prepared. + assert.eq(newStatus.transactions.oldestOpenUnpreparedReadTimestamp, Timestamp(0, 0)); // Verify the total prepared and aborted transaction counters are updated after an abort and the // current prepared counter is decremented. @@ -127,6 +152,10 @@ verifyServerStatusChange( initialStatus.transactions, newStatus.transactions, "currentPrepared", 0); + // Verify that the oldestOpenUnpreparedReadTimestamp is a null timestamp since the transaction + // is closed. + assert.eq(newStatus.transactions.oldestOpenUnpreparedReadTimestamp, Timestamp(0, 0)); + // Verify that other prepared transaction metrics have not changed. verifyServerStatusChange( initialStatus.transactions, newStatus.transactions, "totalPreparedThenCommitted", 1); diff --git a/src/mongo/db/server_transactions_metrics.cpp b/src/mongo/db/server_transactions_metrics.cpp index 6afdd053532..e71f7a25d31 100644 --- a/src/mongo/db/server_transactions_metrics.cpp +++ b/src/mongo/db/server_transactions_metrics.cpp @@ -273,7 +273,15 @@ unsigned int ServerTransactionsMetrics::getTotalActiveOpTimes() const { return _oldestActiveOplogEntryOpTimes.size(); } -void ServerTransactionsMetrics::updateStats(TransactionsStats* stats) { +Timestamp ServerTransactionsMetrics::_getOldestOpenUnpreparedReadTimestamp( + OperationContext* opCtx) { + // The history is not pinned in memory once a transaction has been prepared since reads + // are no longer possible. Therefore, the timestamp returned by the storage engine refers + // to the oldest read timestamp for any open unprepared transaction. + return opCtx->getServiceContext()->getStorageEngine()->getOldestOpenReadTimestamp(); +} + +void ServerTransactionsMetrics::updateStats(TransactionsStats* stats, OperationContext* opCtx) { stats->setCurrentActive(_currentActive.load()); stats->setCurrentInactive(_currentInactive.load()); stats->setCurrentOpen(_currentOpen.load()); @@ -284,6 +292,8 @@ void ServerTransactionsMetrics::updateStats(TransactionsStats* stats) { stats->setTotalPreparedThenCommitted(_totalPreparedThenCommitted.load()); stats->setTotalPreparedThenAborted(_totalPreparedThenAborted.load()); stats->setCurrentPrepared(_currentPrepared.load()); + stats->setOldestOpenUnpreparedReadTimestamp( + ServerTransactionsMetrics::_getOldestOpenUnpreparedReadTimestamp(opCtx)); // Acquire _mutex before reading _oldestActiveOplogEntryOpTime. stdx::lock_guard<stdx::mutex> lm(_mutex); // To avoid compression loss, we have Timestamp(0, 0) be the default value if no oldest active @@ -313,7 +323,7 @@ public: // lifecycle within a session. Both are assigned transaction numbers, and so both are often // referred to as “transactions”. RetryableWritesStats::get(opCtx)->updateStats(&stats); - ServerTransactionsMetrics::get(opCtx)->updateStats(&stats); + ServerTransactionsMetrics::get(opCtx)->updateStats(&stats, opCtx); return stats.toBSON(); } diff --git a/src/mongo/db/server_transactions_metrics.h b/src/mongo/db/server_transactions_metrics.h index c29dd2b12f2..5bbcb661e8f 100644 --- a/src/mongo/db/server_transactions_metrics.h +++ b/src/mongo/db/server_transactions_metrics.h @@ -141,7 +141,7 @@ public: /** * Appends the accumulated stats to a transactions stats object. */ - void updateStats(TransactionsStats* stats); + void updateStats(TransactionsStats* stats, OperationContext* opCtx); private: /** @@ -150,6 +150,13 @@ private: */ boost::optional<repl::OpTime> _calculateOldestActiveOpTime(WithLock) const; + /** + * Returns the oldest read timestamp in use by any open unprepared transaction. This will + * return a null timestamp if there is no oldest open unprepared read timestamp to be + * returned. + */ + static Timestamp _getOldestOpenUnpreparedReadTimestamp(OperationContext* opCtx); + // // Member variables, excluding atomic variables, are labeled with the following code to // indicate the synchronization rules for accessing them. diff --git a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_engine.h b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_engine.h index a52d175bdc1..c6111a553d8 100644 --- a/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_engine.h +++ b/src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_engine.h @@ -120,7 +120,7 @@ public: } virtual Timestamp getOldestOpenReadTimestamp() const override { - MONGO_UNREACHABLE; + return Timestamp(); } private: diff --git a/src/mongo/db/storage/mobile/mobile_kv_engine.h b/src/mongo/db/storage/mobile/mobile_kv_engine.h index 716b2f97d74..6cb5cf42661 100644 --- a/src/mongo/db/storage/mobile/mobile_kv_engine.h +++ b/src/mongo/db/storage/mobile/mobile_kv_engine.h @@ -130,7 +130,7 @@ public: } virtual Timestamp getOldestOpenReadTimestamp() const override { - MONGO_UNREACHABLE; + return Timestamp(); } private: diff --git a/src/mongo/db/transactions_stats.idl b/src/mongo/db/transactions_stats.idl index e2eebd0e7e6..834399a331c 100644 --- a/src/mongo/db/transactions_stats.idl +++ b/src/mongo/db/transactions_stats.idl @@ -84,3 +84,5 @@ structs: default: 0 oldestActiveOplogEntryTimestamp: type: timestamp + oldestOpenUnpreparedReadTimestamp: + type: timestamp |