summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Chan <jason.chan@mongodb.com>2018-11-30 16:33:42 -0500
committerJason Chan <jason.chan@mongodb.com>2018-11-30 16:42:39 -0500
commit3bb72042c29ceac96ec628ee643cbc4db8e982ab (patch)
treea4c2b430c4b39771b83797df1d8132cb38a1b811
parent401151f1d29e7d88f4abf10c7ca92739dbc55336 (diff)
downloadmongo-3bb72042c29ceac96ec628ee643cbc4db8e982ab.tar.gz
SERVER-35713 Add oldestOpenReadTimestamp to serverStatus output
-rw-r--r--jstests/noPassthrough/server_transaction_metrics.js43
-rw-r--r--jstests/noPassthrough/server_transaction_metrics_for_prepared_transactions.js41
-rw-r--r--src/mongo/db/server_transactions_metrics.cpp14
-rw-r--r--src/mongo/db/server_transactions_metrics.h9
-rw-r--r--src/mongo/db/storage/ephemeral_for_test/ephemeral_for_test_engine.h2
-rw-r--r--src/mongo/db/storage/mobile/mobile_kv_engine.h2
-rw-r--r--src/mongo/db/transactions_stats.idl2
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