summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosef Ahmad <josef.ahmad@mongodb.com>2022-09-30 14:24:43 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-09-30 15:47:57 +0000
commite6aee55a2491414e1c093715d61999f7269bc47e (patch)
treeaa750472fe6bef771f3ce9403fe522582beb0efb
parent5e64359d2afb0b1d3929a50e691f09ec3cd7d28a (diff)
downloadmongo-e6aee55a2491414e1c093715d61999f7269bc47e.tar.gz
SERVER-69847 Handle repl rollback of global indexes
-rw-r--r--jstests/noPassthrough/shardsvr_global_index_crud_bulk.js15
-rw-r--r--jstests/noPassthrough/shardsvr_global_index_ddl.js17
-rw-r--r--jstests/replsets/global_index_ddl_rollback.js56
-rw-r--r--jstests/replsets/global_index_rollback.js537
-rw-r--r--jstests/replsets/shardsvr_global_index_crud_rollback.js87
-rw-r--r--src/mongo/db/auth/auth_op_observer.h3
-rw-r--r--src/mongo/db/free_mon/free_mon_op_observer.h3
-rw-r--r--src/mongo/db/global_index.cpp35
-rw-r--r--src/mongo/db/global_index.h11
-rw-r--r--src/mongo/db/global_index_test.cpp54
-rw-r--r--src/mongo/db/op_observer/fcv_op_observer.h3
-rw-r--r--src/mongo/db/op_observer/op_observer.h3
-rw-r--r--src/mongo/db/op_observer/op_observer_impl.cpp24
-rw-r--r--src/mongo/db/op_observer/op_observer_impl.h3
-rw-r--r--src/mongo/db/op_observer/op_observer_noop.h3
-rw-r--r--src/mongo/db/op_observer/op_observer_registry.h5
-rw-r--r--src/mongo/db/op_observer/user_write_block_mode_op_observer.h3
-rw-r--r--src/mongo/db/repl/oplog.cpp18
-rw-r--r--src/mongo/db/repl/oplog_entry.cpp4
-rw-r--r--src/mongo/db/repl/primary_only_service_op_observer.h3
-rw-r--r--src/mongo/db/repl/rollback_impl.cpp55
-rw-r--r--src/mongo/db/repl/tenant_migration_donor_op_observer.h3
-rw-r--r--src/mongo/db/repl/tenant_migration_recipient_op_observer.h3
-rw-r--r--src/mongo/db/s/config_server_op_observer.h3
-rw-r--r--src/mongo/db/s/range_deleter_service_op_observer.h3
-rw-r--r--src/mongo/db/s/resharding/resharding_op_observer.h3
-rw-r--r--src/mongo/db/s/shard_server_op_observer.h3
-rw-r--r--src/mongo/db/serverless/shard_split_donor_op_observer.h3
-rw-r--r--src/mongo/idl/cluster_server_parameter_op_observer.h3
29 files changed, 723 insertions, 243 deletions
diff --git a/jstests/noPassthrough/shardsvr_global_index_crud_bulk.js b/jstests/noPassthrough/shardsvr_global_index_crud_bulk.js
index d875a0ac71b..d73cb9c7d7b 100644
--- a/jstests/noPassthrough/shardsvr_global_index_crud_bulk.js
+++ b/jstests/noPassthrough/shardsvr_global_index_crud_bulk.js
@@ -12,14 +12,11 @@
(function() {
"use strict";
-function uuidToString(uuid) {
- const [_, uuidString] = uuid.toString().match(/"((?:\\.|[^"\\])*)"/);
- return uuidString;
-}
+load('jstests/libs/uuid_util.js');
function entriesInContainer(primary, uuid) {
return primary.getDB("system")
- .getCollection("globalIndexes." + uuidToString(uuid))
+ .getCollection("globalIndexes." + extractUUIDFromObject(uuid))
.find()
.itcount();
}
@@ -449,7 +446,7 @@ assert.commandFailedWithCode(adminDB.runCommand({_shardsvrWriteGlobalIndexKeys:
// triggering DuplicateKey error on inserting the same index key.
assert.eq(1,
primary.getDB("system")
- .getCollection("globalIndexes." + uuidToString(globalIndexUUID))
+ .getCollection("globalIndexes." + extractUUIDFromObject(globalIndexUUID))
.find({_id: {sk0: "first", _id: "first"}})
.itcount());
session.startTransaction();
@@ -462,7 +459,7 @@ assert.commandFailedWithCode(adminDB.runCommand({_shardsvrWriteGlobalIndexKeys:
session.abortTransaction();
assert.eq(1,
primary.getDB("system")
- .getCollection("globalIndexes." + uuidToString(otherGlobalIndexUUID))
+ .getCollection("globalIndexes." + extractUUIDFromObject(otherGlobalIndexUUID))
.find({_id: {sk0: "secondOnSecondContainer", _id: "secondOnSecondContainer"}})
.itcount());
session.startTransaction();
@@ -506,12 +503,12 @@ assert.commandFailedWithCode(adminDB.runCommand({_shardsvrWriteGlobalIndexKeys:
// triggering DuplicateKey error on inserting the same index key.
assert.eq(1,
primary.getDB("system")
- .getCollection("globalIndexes." + uuidToString(globalIndexUUID))
+ .getCollection("globalIndexes." + extractUUIDFromObject(globalIndexUUID))
.find({_id: {sk0: "globalIndexKey", _id: "globalIndexKey"}})
.itcount());
assert.eq(1,
primary.getDB("system")
- .getCollection("globalIndexes." + uuidToString(globalIndexUUID))
+ .getCollection("globalIndexes." + extractUUIDFromObject(globalIndexUUID))
.find({_id: {sk0: "globalIndexKey2", _id: "globalIndexKey"}})
.itcount());
session.startTransaction();
diff --git a/jstests/noPassthrough/shardsvr_global_index_ddl.js b/jstests/noPassthrough/shardsvr_global_index_ddl.js
index c47df2a1c8c..39a0205ec25 100644
--- a/jstests/noPassthrough/shardsvr_global_index_ddl.js
+++ b/jstests/noPassthrough/shardsvr_global_index_ddl.js
@@ -11,11 +11,7 @@
"use strict";
load("jstests/libs/collection_drop_recreate.js"); // For assertDropCollection.
-
-function uuidToString(uuid) {
- const [_, uuidString] = uuid.toString().match(/"((?:\\.|[^"\\])*)"/);
- return uuidString;
-}
+load('jstests/libs/uuid_util.js');
function verifyCollectionExists(node, globalIndexUUID, namespace) {
const systemDB = node.getDB("system");
@@ -80,6 +76,11 @@ function verifyOplogEntry(node, globalIndexUUID, namespace, commandString, lsid,
assert.eq(oplogEntry.op, "c");
assert.eq(oplogEntry.ui, globalIndexUUID);
assert.docEq(oplogEntry.o, commandStringNamespaceObj);
+ if (commandString === "dropGlobalIndex") {
+ assert.eq(0, oplogEntry.o2.numRecords);
+ } else {
+ assert.eq(undefined, oplogEntry.o2);
+ }
// lsid and txnNumber are either both present (retryable writes) or absent.
assert((lsid && txnNumber) || (!lsid && !txnNumber));
@@ -96,7 +97,7 @@ function verifyCommandIsRetryableWrite(node, command, oplogCommandString, setup)
const lsid = session.getSessionId();
const txnNumber = NumberLong(10);
const indexUUID = UUID();
- const globalIndexCollName = "globalIndexes." + uuidToString(indexUUID);
+ const globalIndexCollName = "globalIndexes." + extractUUIDFromObject(indexUUID);
var commandObj = {};
commandObj[command] = indexUUID;
@@ -147,7 +148,7 @@ function testCreateGlobalIndex(rst) {
const primary = rst.getPrimary();
const adminDB = primary.getDB("admin");
const globalIndexUUID = UUID();
- const globalIndexCollName = "globalIndexes." + uuidToString(globalIndexUUID);
+ const globalIndexCollName = "globalIndexes." + extractUUIDFromObject(globalIndexUUID);
const oplogCommandString = "createGlobalIndex";
verifyMultiDocumentTransactionDisallowed(primary, {_shardsvrCreateGlobalIndex: UUID()});
@@ -173,7 +174,7 @@ function testDropGlobalIndex(rst) {
const primary = rst.getPrimary();
const adminDB = primary.getDB("admin");
const globalIndexUUID = UUID();
- const globalIndexCollName = "globalIndexes." + uuidToString(globalIndexUUID);
+ const globalIndexCollName = "globalIndexes." + extractUUIDFromObject(globalIndexUUID);
const oplogCommandString = "dropGlobalIndex";
verifyMultiDocumentTransactionDisallowed(primary, {_shardsvrDropGlobalIndex: UUID()});
diff --git a/jstests/replsets/global_index_ddl_rollback.js b/jstests/replsets/global_index_ddl_rollback.js
deleted file mode 100644
index 5243f6d4d3d..00000000000
--- a/jstests/replsets/global_index_ddl_rollback.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * Tests that global index container ddl operations can be rolled back.
- *
- * @tags: [
- * featureFlagGlobalIndexes,
- * requires_fcv_62,
- * requires_replication,
- * ]
- */
-(function() {
-'use strict';
-
-load('jstests/replsets/libs/rollback_test.js');
-
-function uuidToNss(uuid) {
- const [_, uuidString] = uuid.toString().match(/"((?:\\.|[^"\\])*)"/);
- return "globalIndexes." + uuidString;
-}
-
-const rollbackTest = new RollbackTest(jsTestName());
-
-const primary = rollbackTest.getPrimary();
-const adminDB = primary.getDB("admin");
-const globalIndexCreateUUID = UUID();
-const globalIndexDropUUID = UUID();
-
-// Create a global index container to be dropped.
-assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: globalIndexDropUUID}));
-
-rollbackTest.transitionToRollbackOperations();
-
-// Create a global index container to be rolled back.
-assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: globalIndexCreateUUID}));
-// Drop a global index container, operation should be rolled back.
-assert.commandWorked(adminDB.runCommand({_shardsvrDropGlobalIndex: globalIndexDropUUID}));
-
-// Perform the rollback.
-rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
-rollbackTest.transitionToSyncSourceOperationsDuringRollback();
-rollbackTest.transitionToSteadyStateOperations();
-
-rollbackTest.getTestFixture().nodes.forEach(function(node) {
- const nodeDB = node.getDB("system");
-
- // Check globalIndexCreateUUID creation is rolled back and does not exist.
- var res =
- nodeDB.runCommand({listCollections: 1, filter: {name: uuidToNss(globalIndexCreateUUID)}});
- assert.eq(res.cursor.firstBatch.length, 0);
-
- // Check globalIndexDropUUID drop is rolled back and still exists.
- res = nodeDB.runCommand({listCollections: 1, filter: {name: uuidToNss(globalIndexDropUUID)}});
- assert.eq(res.cursor.firstBatch.length, 1);
-});
-
-rollbackTest.stop();
-})();
diff --git a/jstests/replsets/global_index_rollback.js b/jstests/replsets/global_index_rollback.js
new file mode 100644
index 00000000000..de8a599d9cb
--- /dev/null
+++ b/jstests/replsets/global_index_rollback.js
@@ -0,0 +1,537 @@
+/**
+ * Tests replication rollback of global index container DDL and CRUD operations.
+ * Validates the generation of rollback files and efficient restoring of fast-counts.
+ *
+ * @tags: [
+ * featureFlagGlobalIndexes,
+ * requires_fcv_62,
+ * requires_replication,
+ * ]
+ */
+
+(function() {
+'use strict';
+
+load('jstests/replsets/libs/rollback_files.js');
+load('jstests/replsets/libs/rollback_test.js');
+load('jstests/libs/uuid_util.js');
+
+function uuidToCollName(uuid) {
+ return "globalIndexes." + extractUUIDFromObject(uuid);
+}
+
+const rollbackTest = new RollbackTest(jsTestName());
+
+function rollbackDDLOps() {
+ const node = rollbackTest.getPrimary();
+ const adminDB = node.getDB("admin");
+ const globalIndexCreateUUID = UUID();
+ const globalIndexDropUUID = UUID();
+ jsTestLog("rollbackDDLOps primary=" + node);
+
+ // Create a global index container whose drop won't be majority-committed.
+ assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: globalIndexDropUUID}));
+
+ rollbackTest.transitionToRollbackOperations();
+
+ // Create a global index container that's not majority-committed.
+ assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: globalIndexCreateUUID}));
+ // Drop a global index container, the operation is not majority-committed.
+ assert.commandWorked(adminDB.runCommand({_shardsvrDropGlobalIndex: globalIndexDropUUID}));
+
+ // Perform the rollback.
+ rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
+ rollbackTest.transitionToSyncSourceOperationsDuringRollback();
+ rollbackTest.transitionToSteadyStateOperations();
+
+ // Check globalIndexCreateUUID creation is rolled back and does not exist.
+ var res = node.getDB("system").runCommand(
+ {listCollections: 1, filter: {name: uuidToCollName(globalIndexCreateUUID)}});
+ assert.eq(res.cursor.firstBatch.length, 0);
+
+ // Check globalIndexDropUUID drop is rolled back and still exists.
+ res = node.getDB("system").runCommand(
+ {listCollections: 1, filter: {name: uuidToCollName(globalIndexDropUUID)}});
+ assert.eq(res.cursor.firstBatch.length, 1);
+
+ // Log calls out that the two commands have been rolled back.
+ assert(checkLog.checkContainsWithCountJson(
+ node,
+ 21656,
+ {
+ "oplogEntry": {
+ "op": "c",
+ "ns": "system.$cmd",
+ "ui": {"$uuid": extractUUIDFromObject(globalIndexDropUUID)},
+ "o": {"dropGlobalIndex": uuidToCollName(globalIndexDropUUID)},
+ "o2": {"numRecords": 0}
+ }
+ },
+ 1,
+ null,
+ true /*isRelaxed*/));
+ assert(checkLog.checkContainsWithCountJson(
+ node,
+ 21656,
+ {
+ "oplogEntry": {
+ "op": "c",
+ "ns": "system.$cmd",
+ "ui": {"$uuid": extractUUIDFromObject(globalIndexCreateUUID)},
+ "o": {"createGlobalIndex": uuidToCollName(globalIndexCreateUUID)}
+ }
+ },
+ 1,
+ null,
+ true /*isRelaxed*/));
+}
+
+// Rollback a single index key insert.
+function rollbackSingleKeyInsert(bulk) {
+ const node = rollbackTest.getPrimary();
+ const adminDB = node.getDB("admin");
+ const uuid = UUID();
+ jsTestLog("rollbackSingleKeyInsert uuid=" + uuid + ", bulk=" + bulk, ", primary=" + node);
+
+ const collName = uuidToCollName(uuid);
+ assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: uuid}));
+ assert.eq(0, node.getDB("system").getCollection(collName).find().itcount());
+
+ const keyMajorityCommitted = {key: {a: 0}, docKey: {sk: 0, _id: 0}};
+ const keyToRollback = {key: {a: 1}, docKey: {sk: 1, _id: 1}};
+
+ // Insert a key, majority-committed.
+ {
+ const session = node.startSession();
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("system").runCommand(
+ Object.extend({"_shardsvrInsertGlobalIndexKey": uuid}, keyMajorityCommitted)));
+ session.commitTransaction();
+ session.endSession();
+ }
+ assert.eq(1, node.getDB("system").getCollection(collName).find().itcount());
+ assert.eq(1,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyMajorityCommitted["docKey"]})
+ .itcount());
+
+ // Then insert a key that's not majority-committed.
+ rollbackTest.transitionToRollbackOperations();
+ {
+ const session = node.startSession();
+ session.startTransaction();
+ const stmts = [Object.extend({"_shardsvrInsertGlobalIndexKey": uuid}, keyToRollback)];
+ if (bulk) {
+ assert.commandWorked(session.getDatabase("system").runCommand(
+ {"_shardsvrWriteGlobalIndexKeys": 1, ops: stmts}));
+ } else {
+ for (let stmt of stmts) {
+ assert.commandWorked(session.getDatabase("system").runCommand(stmt));
+ }
+ }
+ session.commitTransaction();
+ session.endSession();
+ }
+ assert.eq(2, node.getDB("system").getCollection(collName).find().itcount());
+ assert.eq(1,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyMajorityCommitted["docKey"]})
+ .itcount());
+ assert.eq(1,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyToRollback["docKey"]})
+ .itcount());
+
+ // Perform the rollback.
+ rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
+ rollbackTest.transitionToSyncSourceOperationsDuringRollback();
+ rollbackTest.transitionToSteadyStateOperations();
+
+ // Only the majority-committed key is left.
+ assert.eq(1, node.getDB("system").getCollection(collName).find().itcount());
+ assert.eq(1,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyMajorityCommitted["docKey"]})
+ .itcount());
+ assert.eq(0,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyToRollback["docKey"]})
+ .itcount());
+
+ // Log calls out that the index key insert has been rolled back.
+ assert(
+ checkLog.checkContainsWithCountJson(node,
+ 6984700,
+ {"insertGlobalIndexKey": 1, "deleteGlobalIndexKey": 0},
+ 1,
+ null,
+ true /*isRelaxed*/));
+
+ // The rollback wrote the rolled-back index key insert to a file.
+ const replTest = rollbackTest.getTestFixture();
+ const expectedEntries = [Object.extend({_id: keyToRollback.docKey},
+ {"ik": BinData(0, "KwIE"), "tb": BinData(0, "AQ==")})];
+ checkRollbackFiles(replTest.getDbPath(node), "system." + collName, uuid, expectedEntries);
+}
+
+// Rollback a single index key delete.
+function rollbackSingleKeyDelete(bulk) {
+ const node = rollbackTest.getPrimary();
+ const adminDB = node.getDB("admin");
+ const uuid = UUID();
+ jsTestLog("rollbackSingleKeyDelete uuid=" + uuid + ", bulk=" + bulk, ", primary=" + node);
+
+ const collName = uuidToCollName(uuid);
+ assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: uuid}));
+ assert.eq(0, node.getDB("system").getCollection(collName).find().itcount());
+
+ const key = {key: {a: 0}, docKey: {sk: 0, _id: 0}};
+
+ // Insert a key, majority-committed.
+ {
+ const session = node.startSession();
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("system").runCommand(
+ Object.extend({"_shardsvrInsertGlobalIndexKey": uuid}, key)));
+ session.commitTransaction();
+ session.endSession();
+ }
+ assert.eq(1, node.getDB("system").getCollection(collName).find().itcount());
+ assert.eq(1, node.getDB("system").getCollection(collName).find({_id: key["docKey"]}).itcount());
+
+ // Then delete the key, not majority-committed.
+ rollbackTest.transitionToRollbackOperations();
+ {
+ const session = node.startSession();
+ session.startTransaction();
+ const stmts = [Object.extend({"_shardsvrDeleteGlobalIndexKey": uuid}, key)];
+ if (bulk) {
+ assert.commandWorked(session.getDatabase("system").runCommand(
+ {"_shardsvrWriteGlobalIndexKeys": 1, ops: stmts}));
+ } else {
+ for (let stmt of stmts) {
+ assert.commandWorked(session.getDatabase("system").runCommand(stmt));
+ }
+ }
+ session.commitTransaction();
+ session.endSession();
+ }
+ assert.eq(0, node.getDB("system").getCollection(collName).find().itcount());
+
+ // Perform the rollback.
+ rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
+ rollbackTest.transitionToSyncSourceOperationsDuringRollback();
+ rollbackTest.transitionToSteadyStateOperations();
+
+ // The key is still present, as its delete wasn't majority-committed.
+ assert.eq(1, node.getDB("system").getCollection(collName).find().itcount());
+ assert.eq(1, node.getDB("system").getCollection(collName).find({_id: key["docKey"]}).itcount());
+
+ // Log calls out that the index key delete has been rolled back.
+ assert(
+ checkLog.checkContainsWithCountJson(node,
+ 6984700,
+ {"insertGlobalIndexKey": 0, "deleteGlobalIndexKey": 1},
+ 1,
+ null,
+ true /*isRelaxed*/));
+}
+
+function rollbackOneKeyInsertTwoKeyDeletes(bulk) {
+ const node = rollbackTest.getPrimary();
+ const adminDB = node.getDB("admin");
+ const uuid = UUID();
+ jsTestLog("rollbackOneKeyInsertTwoKeyDeletes uuid=" + uuid + ", bulk=" + bulk,
+ ", primary=" + node);
+
+ const collName = uuidToCollName(uuid);
+ assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: uuid}));
+ assert.eq(0, node.getDB("system").getCollection(collName).find().itcount());
+
+ const keyMajorityCommitted = {key: {a: 0}, docKey: {sk: 0, _id: 0}};
+ const keyToRollback0 = {key: {a: 1}, docKey: {sk: 1, _id: 1}};
+ const keyToRollback1 = {key: {a: 2}, docKey: {sk: 2, _id: 2}};
+
+ // Insert a key, majority-committed.
+ {
+ const session = node.startSession();
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("system").runCommand(
+ Object.extend({"_shardsvrInsertGlobalIndexKey": uuid}, keyMajorityCommitted)));
+ session.commitTransaction();
+ session.endSession();
+ }
+ assert.eq(1, node.getDB("system").getCollection(collName).find().itcount());
+ assert.eq(1,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyMajorityCommitted["docKey"]})
+ .itcount());
+
+ // Then delete the key and insert two more keys. All these writes are not majority-committed.
+ rollbackTest.transitionToRollbackOperations();
+ {
+ const session = node.startSession();
+ session.startTransaction();
+ const stmts = [
+ Object.extend({"_shardsvrDeleteGlobalIndexKey": uuid}, keyMajorityCommitted),
+ Object.extend({"_shardsvrInsertGlobalIndexKey": uuid}, keyToRollback0),
+ Object.extend({"_shardsvrInsertGlobalIndexKey": uuid}, keyToRollback1)
+ ];
+ if (bulk) {
+ assert.commandWorked(session.getDatabase("system").runCommand(
+ {"_shardsvrWriteGlobalIndexKeys": 1, ops: stmts}));
+ } else {
+ for (let stmt of stmts) {
+ assert.commandWorked(session.getDatabase("system").runCommand(stmt));
+ }
+ }
+ session.commitTransaction();
+ session.endSession();
+ }
+ assert.eq(2, node.getDB("system").getCollection(collName).find().itcount());
+ assert.eq(1,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyToRollback0["docKey"]})
+ .itcount());
+ assert.eq(1,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyToRollback1["docKey"]})
+ .itcount());
+
+ // Perform the rollback.
+ rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
+ rollbackTest.transitionToSyncSourceOperationsDuringRollback();
+ rollbackTest.transitionToSteadyStateOperations();
+
+ // The only key that's present is the majority-committed one.
+ assert.eq(1, node.getDB("system").getCollection(collName).find().itcount());
+ assert.eq(1,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyMajorityCommitted["docKey"]})
+ .itcount());
+
+ // Log calls out that two index key inserts and one key delete have been rolled back.
+ assert(
+ checkLog.checkContainsWithCountJson(node,
+ 6984700,
+ {"insertGlobalIndexKey": 2, "deleteGlobalIndexKey": 1},
+ 1,
+ null,
+ true /*isRelaxed*/));
+
+ // The rollback wrote the two rolled-back index key inserts to a file.
+ const replTest = rollbackTest.getTestFixture();
+ const expectedEntries = [
+ Object.extend({_id: keyToRollback1.docKey},
+ {"ik": BinData(0, "KwQE"), "tb": BinData(0, "AQ==")}),
+ Object.extend({_id: keyToRollback0.docKey},
+ {"ik": BinData(0, "KwIE"), "tb": BinData(0, "AQ==")}),
+ ];
+ checkRollbackFiles(replTest.getDbPath(node), "system." + collName, uuid, expectedEntries);
+}
+
+function rollbackCreateWithCrud(bulk) {
+ const node = rollbackTest.getPrimary();
+ const adminDB = node.getDB("admin");
+ const uuid = UUID();
+ jsTestLog("rollbackCreateWithCrud uuid=" + uuid + ", bulk=" + bulk, ", primary=" + node);
+
+ const collName = uuidToCollName(uuid);
+
+ const keyToRollback0 = {key: {a: 1}, docKey: {sk: 1, _id: 1}};
+ const keyToRollback1 = {key: {a: 2}, docKey: {sk: 2, _id: 2}};
+ const keyToRollback2 = {key: {a: 3}, docKey: {sk: 3, _id: 3}};
+
+ // Create a container and insert keys to it. All these operations are not majority-committed.
+ rollbackTest.transitionToRollbackOperations();
+ {
+ assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: uuid}));
+
+ const session = node.startSession();
+ session.startTransaction();
+ const stmts = [
+ Object.extend({"_shardsvrInsertGlobalIndexKey": uuid}, keyToRollback0),
+ Object.extend({"_shardsvrInsertGlobalIndexKey": uuid}, keyToRollback1),
+ Object.extend({"_shardsvrInsertGlobalIndexKey": uuid}, keyToRollback2),
+ Object.extend({"_shardsvrDeleteGlobalIndexKey": uuid}, keyToRollback1),
+ ];
+ if (bulk) {
+ assert.commandWorked(session.getDatabase("system").runCommand(
+ {"_shardsvrWriteGlobalIndexKeys": 1, ops: stmts}));
+ } else {
+ for (let stmt of stmts) {
+ assert.commandWorked(session.getDatabase("system").runCommand(stmt));
+ }
+ }
+ session.commitTransaction();
+ session.endSession();
+ }
+ assert.eq(2, node.getDB("system").getCollection(collName).find().itcount());
+ assert.eq(1,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyToRollback0["docKey"]})
+ .itcount());
+ assert.eq(0,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyToRollback1["docKey"]})
+ .itcount());
+ assert.eq(1,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyToRollback2["docKey"]})
+ .itcount());
+
+ // Perform the rollback.
+ rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
+ rollbackTest.transitionToSyncSourceOperationsDuringRollback();
+ rollbackTest.transitionToSteadyStateOperations();
+
+ // The global index container doesn't exist.
+ const container =
+ node.getDB("system").runCommand({listCollections: 1, filter: {name: collName}});
+ assert.eq(container.cursor.firstBatch.length, 0);
+
+ // Log calls out that three index key inserts and one key delete have been rolled back.
+ assert(
+ checkLog.checkContainsWithCountJson(node,
+ 6984700,
+ {"insertGlobalIndexKey": 3, "deleteGlobalIndexKey": 1},
+ 1,
+ null,
+ true /*isRelaxed*/));
+
+ // The rollback wrote the two rolled-back index key inserts to a file.
+ const replTest = rollbackTest.getTestFixture();
+ const expectedEntries = [
+ Object.extend({_id: keyToRollback2.docKey},
+ {"ik": BinData(0, "KwYE"), "tb": BinData(0, "AQ==")}),
+ Object.extend({_id: keyToRollback0.docKey},
+ {"ik": BinData(0, "KwIE"), "tb": BinData(0, "AQ==")}),
+ ];
+ checkRollbackFiles(replTest.getDbPath(node), "system." + collName, uuid, expectedEntries);
+}
+
+function rollbackDropWithCrud(bulk) {
+ const node = rollbackTest.getPrimary();
+ const adminDB = node.getDB("admin");
+ const uuid = UUID();
+ jsTestLog("rollbackDropWithCrud uuid=" + uuid + ", bulk=" + bulk, ", primary=" + node);
+
+ const collName = uuidToCollName(uuid);
+
+ assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: uuid}));
+ const keyMajorityCommitted = {key: {a: 0}, docKey: {sk: 0, _id: 0}};
+
+ // Insert a key that will be majority-committed.
+ {
+ const session = node.startSession();
+ session.startTransaction();
+ assert.commandWorked(session.getDatabase("system").runCommand(
+ Object.extend({"_shardsvrInsertGlobalIndexKey": uuid}, keyMajorityCommitted)));
+ session.commitTransaction();
+ session.endSession();
+ }
+ assert.eq(1, node.getDB("system").getCollection(collName).find().itcount());
+ assert.eq(1,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyMajorityCommitted["docKey"]})
+ .itcount());
+
+ const keyToRollback0 = {key: {a: 1}, docKey: {sk: 1, _id: 1}};
+ const keyToRollback1 = {key: {a: 2}, docKey: {sk: 2, _id: 2}};
+ const keyToRollback2 = {key: {a: 3}, docKey: {sk: 3, _id: 3}};
+
+ // Write to the container and drop it. All these operations are not majority-committed.
+ rollbackTest.transitionToRollbackOperations();
+ {
+ assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: uuid}));
+
+ const session = node.startSession();
+ session.startTransaction();
+ const stmts = [
+ Object.extend({"_shardsvrInsertGlobalIndexKey": uuid}, keyToRollback1),
+ Object.extend({"_shardsvrInsertGlobalIndexKey": uuid}, keyToRollback2),
+ Object.extend({"_shardsvrDeleteGlobalIndexKey": uuid}, keyToRollback1),
+ Object.extend({"_shardsvrInsertGlobalIndexKey": uuid}, keyToRollback0),
+ Object.extend({"_shardsvrDeleteGlobalIndexKey": uuid}, keyToRollback2),
+ ];
+ if (bulk) {
+ assert.commandWorked(session.getDatabase("system").runCommand(
+ {"_shardsvrWriteGlobalIndexKeys": 1, ops: stmts}));
+ } else {
+ for (let stmt of stmts) {
+ assert.commandWorked(session.getDatabase("system").runCommand(stmt));
+ }
+ }
+ session.commitTransaction();
+ session.endSession();
+ assert.commandWorked(adminDB.runCommand({_shardsvrDropGlobalIndex: uuid}));
+ }
+ // The global index container doesn't exist.
+ const containerBeforeRollback =
+ node.getDB("system").runCommand({listCollections: 1, filter: {name: collName}});
+ assert.eq(containerBeforeRollback.cursor.firstBatch.length, 0);
+
+ // Perform the rollback.
+ rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
+ rollbackTest.transitionToSyncSourceOperationsDuringRollback();
+ rollbackTest.transitionToSteadyStateOperations();
+
+ // The global index exists, with the single majority-committed key.
+ const containerAfterRollback =
+ node.getDB("system").runCommand({listCollections: 1, filter: {name: collName}});
+ assert.eq(containerAfterRollback.cursor.firstBatch.length, 1);
+
+ assert.eq(1, node.getDB("system").getCollection(collName).find().itcount());
+ assert.eq(1,
+ node.getDB("system")
+ .getCollection(collName)
+ .find({_id: keyMajorityCommitted["docKey"]})
+ .itcount());
+
+ // Log calls out that three index key inserts and two key deletes have been rolled back.
+ assert(
+ checkLog.checkContainsWithCountJson(node,
+ 6984700,
+ {"insertGlobalIndexKey": 3, "deleteGlobalIndexKey": 2},
+ 1,
+ null,
+ true /*isRelaxed*/));
+
+ // We've reset the original fast count rather than doing an expensive collection scan.
+ assert(checkLog.checkContainsWithCountJson(node, 21602, undefined, 0));
+
+ // Log states that we're not going to write a rollback file for a collection whose drop was
+ // rolled back.
+ assert(checkLog.checkContainsWithCountJson(
+ node,
+ 21608,
+ {"uuid": {"uuid": {"$uuid": extractUUIDFromObject(uuid)}}},
+ 1,
+ null,
+ true /*isRelaxed*/));
+}
+
+rollbackDDLOps();
+for (let bulk of [false, true]) {
+ rollbackSingleKeyInsert(bulk);
+ rollbackSingleKeyDelete(bulk);
+ rollbackOneKeyInsertTwoKeyDeletes(bulk);
+ rollbackCreateWithCrud(bulk);
+ rollbackDropWithCrud(bulk);
+}
+
+rollbackTest.stop();
+})();
diff --git a/jstests/replsets/shardsvr_global_index_crud_rollback.js b/jstests/replsets/shardsvr_global_index_crud_rollback.js
deleted file mode 100644
index 7e4349c8945..00000000000
--- a/jstests/replsets/shardsvr_global_index_crud_rollback.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * Tests that global index key insert and delete are properly rolled back.
- *
- * @tags: [
- * featureFlagGlobalIndexes,
- * requires_fcv_62,
- * requires_replication,
- * ]
- */
-(function() {
-'use strict';
-
-load('jstests/replsets/libs/rollback_test.js');
-
-const rollbackTest = new RollbackTest(jsTestName());
-
-const primary = rollbackTest.getPrimary();
-const adminDB = primary.getDB("admin");
-const globalIndexUUID = UUID();
-const [_, uuidString] = globalIndexUUID.toString().match(/"((?:\\.|[^"\\])*)"/);
-const collName = "globalIndexes." + uuidString;
-
-assert.commandWorked(adminDB.runCommand({_shardsvrCreateGlobalIndex: globalIndexUUID}));
-
-// We start on a clean slate: the global index container is empty.
-assert.eq(0, primary.getDB("system").getCollection(collName).find().itcount());
-
-const docKeyToInsert = {
- sk: 1,
- _id: 1
-};
-const docKeyToDelete = {
- sk: 1,
- _id: 5
-};
-// Add key to delete during rollback ops phase.
-{
- const session = primary.startSession();
- session.startTransaction();
- assert.commandWorked(session.getDatabase("system").runCommand(
- {"_shardsvrInsertGlobalIndexKey": globalIndexUUID, key: {a: 5}, docKey: docKeyToDelete}));
- session.commitTransaction();
- session.endSession();
-}
-
-rollbackTest.transitionToRollbackOperations();
-
-// Insert an index key to be rolled back.
-{
- const session = primary.startSession();
- session.startTransaction();
- assert.commandWorked(session.getDatabase("system").runCommand(
- {"_shardsvrInsertGlobalIndexKey": globalIndexUUID, key: {a: 1}, docKey: docKeyToInsert}));
- assert.commandWorked(session.getDatabase("system").runCommand(
- {"_shardsvrDeleteGlobalIndexKey": globalIndexUUID, key: {a: 5}, docKey: docKeyToDelete}));
- session.commitTransaction();
- session.endSession();
-}
-
-// The inserted index key is present on the primary.
-assert.eq(1, primary.getDB("system").getCollection(collName).find({_id: docKeyToInsert}).itcount());
-// The deleted index key is not present on the primary.
-assert.eq(0, primary.getDB("system").getCollection(collName).find({_id: docKeyToDelete}).itcount());
-
-// Perform the rollback.
-rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
-rollbackTest.transitionToSyncSourceOperationsDuringRollback();
-rollbackTest.transitionToSteadyStateOperations();
-
-// Verify both global index key insert and delete have been rolled back. The container has exactly
-// one entry, and is the one inserted before transitionToRollbackOperations.
-rollbackTest.getTestFixture().nodes.forEach(function(node) {
- const nodeDB = node.getDB("system");
- const found = nodeDB.getCollection(collName).find();
- const elArr = found.toArray();
- assert.eq(1, elArr.length);
- assert.eq(elArr[0]["_id"], docKeyToDelete);
-});
-
-// TODO (SERVER-69847): fast count is not updated properly for global index CRUD ops after rollback.
-// Current test implementation makes fastcount valid due to rolling back both a delete and an
-// insert. After fixing fast count, we should make this test fail if fast count is not working
-// properly.
-// TODO (SERVER-69847): add a rollback test for _shardsvrWriteGlobalIndexKeys too.
-
-rollbackTest.stop();
-})();
diff --git a/src/mongo/db/auth/auth_op_observer.h b/src/mongo/db/auth/auth_op_observer.h
index c2ee0a02ad8..4b4186db6af 100644
--- a/src/mongo/db/auth/auth_op_observer.h
+++ b/src/mongo/db/auth/auth_op_observer.h
@@ -56,7 +56,8 @@ public:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/db/free_mon/free_mon_op_observer.h b/src/mongo/db/free_mon/free_mon_op_observer.h
index 4cd70414b94..60f3d4ed5b7 100644
--- a/src/mongo/db/free_mon/free_mon_op_observer.h
+++ b/src/mongo/db/free_mon/free_mon_op_observer.h
@@ -56,7 +56,8 @@ public:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/db/global_index.cpp b/src/mongo/db/global_index.cpp
index 13852a11eda..ed702f76aac 100644
--- a/src/mongo/db/global_index.cpp
+++ b/src/mongo/db/global_index.cpp
@@ -52,8 +52,6 @@ namespace mongo::global_index {
namespace { // Anonymous namespace for private functions.
-constexpr StringData kIndexKeyIndexName = "ik_1"_sd;
-
// Build an index entry to insert.
BSONObj buildIndexEntry(const BSONObj& key, const BSONObj& docKey) {
// Generate the KeyString representation of the index key.
@@ -72,12 +70,14 @@ BSONObj buildIndexEntry(const BSONObj& key, const BSONObj& docKey) {
// - 'tb': the index key's TypeBits. Only present if non-zero.
BSONObjBuilder indexEntryBuilder;
- indexEntryBuilder.append("_id", docKey);
+ indexEntryBuilder.append(kContainerIndexDocKeyFieldName, docKey);
indexEntryBuilder.append(
- "ik", BSONBinData(ks.getBuffer(), ks.getSize(), BinDataType::BinDataGeneral));
+ kContainerIndexKeyFieldName,
+ BSONBinData(ks.getBuffer(), ks.getSize(), BinDataType::BinDataGeneral));
if (!indexTB.isAllZeros()) {
indexEntryBuilder.append(
- "tb", BSONBinData(indexTB.getBuffer(), indexTB.getSize(), BinDataType::BinDataGeneral));
+ kContainerIndexKeyTypeBitsFieldName,
+ BSONBinData(indexTB.getBuffer(), indexTB.getSize(), BinDataType::BinDataGeneral));
}
return indexEntryBuilder.obj();
}
@@ -120,8 +120,9 @@ void createContainer(OperationContext* opCtx, const UUID& indexUUID) {
// Create the container.
return writeConflictRetry(opCtx, "createGlobalIndexContainer", nss.ns(), [&]() {
- const auto indexKeySpec = BSON("v" << 2 << "name" << kIndexKeyIndexName << "key"
- << BSON("ik" << 1) << "unique" << true);
+ const auto indexKeySpec =
+ BSON("v" << 2 << "name" << kContainerIndexKeyFieldName.toString() + "_1"
+ << "key" << BSON(kContainerIndexKeyFieldName << 1) << "unique" << true);
WriteUnitOfWork wuow(opCtx);
@@ -153,13 +154,15 @@ void createContainer(OperationContext* opCtx, const UUID& indexUUID) {
str::stream() << "Collection with UUID " << indexUUID
<< " already exists but it's not clustered.",
autoColl->getCollectionOptions().clusteredIndex);
- tassert(
- 6789205,
- str::stream() << "Collection with UUID " << indexUUID
- << " already exists but it's missing a unique index on "
- "'ik'.",
- autoColl->getIndexCatalog()->findIndexByKeyPatternAndOptions(
- opCtx, BSON("ik" << 1), indexKeySpec, IndexCatalog::InclusionPolicy::kReady));
+ tassert(6789205,
+ str::stream() << "Collection with UUID " << indexUUID
+ << " already exists but it's missing a unique index on "
+ << kContainerIndexKeyFieldName << ".",
+ autoColl->getIndexCatalog()->findIndexByKeyPatternAndOptions(
+ opCtx,
+ BSON(kContainerIndexKeyFieldName << 1),
+ indexKeySpec,
+ IndexCatalog::InclusionPolicy::kReady));
tassert(6789206,
str::stream() << "Collection with namespace " << nss.ns()
<< " already exists but it has inconsistent UUID "
@@ -191,6 +194,8 @@ void dropContainer(OperationContext* opCtx, const UUID& indexUUID) {
return;
}
+ const auto numKeys = autoColl->numRecords(opCtx);
+
WriteUnitOfWork wuow(opCtx);
{
repl::UnreplicatedWritesBlock unreplicatedWrites(opCtx);
@@ -198,7 +203,7 @@ void dropContainer(OperationContext* opCtx, const UUID& indexUUID) {
}
auto opObserver = opCtx->getServiceContext()->getOpObserver();
- opObserver->onDropGlobalIndex(opCtx, nss, indexUUID);
+ opObserver->onDropGlobalIndex(opCtx, nss, indexUUID, numKeys);
wuow.commit();
return;
diff --git a/src/mongo/db/global_index.h b/src/mongo/db/global_index.h
index 73d82da4a03..b93fb439547 100644
--- a/src/mongo/db/global_index.h
+++ b/src/mongo/db/global_index.h
@@ -35,6 +35,17 @@
namespace mongo::global_index {
+// The container (collection) fields of an index key. The document key is stored as a BSON object.
+// The index key is stored in its KeyString representation, hence the need to store TypeBits.
+constexpr auto kContainerIndexDocKeyFieldName = "_id"_sd;
+constexpr auto kContainerIndexKeyFieldName = "ik"_sd;
+constexpr auto kContainerIndexKeyTypeBitsFieldName = "tb"_sd;
+
+// The oplog entry fields representing an insert or delete of an index key. The index key and the
+// document key are BSON objects.
+constexpr auto kOplogEntryDocKeyFieldName = "dk"_sd;
+constexpr auto kOplogEntryIndexKeyFieldName = kContainerIndexKeyFieldName;
+
/**
* Creates the internal collection implements the global index container with the given UUID on the
* shard. Replicates as a 'createGlobalIndex' command. This container-backing collection:
diff --git a/src/mongo/db/global_index_test.cpp b/src/mongo/db/global_index_test.cpp
index 83cb975c990..1730d272b43 100644
--- a/src/mongo/db/global_index_test.cpp
+++ b/src/mongo/db/global_index_test.cpp
@@ -98,16 +98,21 @@ void verifyStoredKeyMatchesIndexKey(const BSONObj& key,
// 'tb' field stores the BinData(TypeBits(key)). The 'tb' field is not present if there are
// no TypeBits.
- auto entryIndexKeySize = indexEntry["ik"].size();
- const auto entryIndexKeyBinData = indexEntry["ik"].binData(entryIndexKeySize);
+ auto entryIndexKeySize = indexEntry[global_index::kContainerIndexKeyFieldName].size();
+ const auto entryIndexKeyBinData =
+ indexEntry[global_index::kContainerIndexKeyFieldName].binData(entryIndexKeySize);
- const auto hasTypeBits = indexEntry.hasElement("tb");
+ const auto hasTypeBits =
+ indexEntry.hasElement(global_index::kContainerIndexKeyTypeBitsFieldName);
ASSERT_EQ(expectTypeBits, hasTypeBits);
auto tb = KeyString::TypeBits(KeyString::Version::V1);
if (hasTypeBits) {
- auto entryTypeBitsSize = indexEntry["tb"].size();
- auto entryTypeBitsBinData = indexEntry["tb"].binData(entryTypeBitsSize);
+ auto entryTypeBitsSize =
+ indexEntry[global_index::kContainerIndexKeyTypeBitsFieldName].size();
+ auto entryTypeBitsBinData =
+ indexEntry[global_index::kContainerIndexKeyTypeBitsFieldName].binData(
+ entryTypeBitsSize);
auto entryTypeBitsReader = BufReader(entryTypeBitsBinData, entryTypeBitsSize);
tb = KeyString::TypeBits::fromBuffer(KeyString::Version::V1, &entryTypeBitsReader);
ASSERT(!tb.isAllZeros());
@@ -134,12 +139,14 @@ TEST_F(GlobalIndexTest, StorageFormat) {
const auto key = BSON(""
<< "hola");
const auto docKey = BSON("shk0" << 0 << "shk1" << 0 << "_id" << 0);
- const auto entryId = BSON("_id" << docKey);
+ const auto entryId = BSON(global_index::kContainerIndexDocKeyFieldName << docKey);
global_index::insertKey(operationContext(), uuid, key, docKey);
// Validate that the document key is stored in the index entry's _id field.
- StatusWith<BSONObj> status = storageInterface()->findById(
- operationContext(), NamespaceString::makeGlobalIndexNSS(uuid), entryId["_id"]);
+ StatusWith<BSONObj> status =
+ storageInterface()->findById(operationContext(),
+ NamespaceString::makeGlobalIndexNSS(uuid),
+ entryId[global_index::kContainerIndexDocKeyFieldName]);
ASSERT_OK(status.getStatus());
const auto indexEntry = status.getValue();
@@ -153,12 +160,14 @@ TEST_F(GlobalIndexTest, StorageFormat) {
<< "hola"
<< "" << 1);
const auto docKey = BSON("shk0" << 1 << "shk1" << 1 << "_id" << 1);
- const auto entryId = BSON("_id" << docKey);
+ const auto entryId = BSON(global_index::kContainerIndexDocKeyFieldName << docKey);
global_index::insertKey(operationContext(), uuid, key, docKey);
// Validate that the document key is stored in the index entry's _id field.
- StatusWith<BSONObj> status = storageInterface()->findById(
- operationContext(), NamespaceString::makeGlobalIndexNSS(uuid), entryId["_id"]);
+ StatusWith<BSONObj> status =
+ storageInterface()->findById(operationContext(),
+ NamespaceString::makeGlobalIndexNSS(uuid),
+ entryId[global_index::kContainerIndexDocKeyFieldName]);
ASSERT_OK(status.getStatus());
const auto indexEntry = status.getValue();
@@ -172,12 +181,14 @@ TEST_F(GlobalIndexTest, StorageFormat) {
<< "hola"
<< "" << 2LL);
const auto docKey = BSON("shk0" << 2 << "shk1" << 2 << "_id" << 2);
- const auto entryId = BSON("_id" << docKey);
+ const auto entryId = BSON(global_index::kContainerIndexDocKeyFieldName << docKey);
global_index::insertKey(operationContext(), uuid, key, docKey);
// Validate that the document key is stored in the index entry's _id field.
- StatusWith<BSONObj> status = storageInterface()->findById(
- operationContext(), NamespaceString::makeGlobalIndexNSS(uuid), entryId["_id"]);
+ StatusWith<BSONObj> status =
+ storageInterface()->findById(operationContext(),
+ NamespaceString::makeGlobalIndexNSS(uuid),
+ entryId[global_index::kContainerIndexDocKeyFieldName]);
ASSERT_OK(status.getStatus());
const auto indexEntry = status.getValue();
@@ -191,12 +202,14 @@ TEST_F(GlobalIndexTest, StorageFormat) {
<< "hola"
<< "" << 3.0);
const auto docKey = BSON("shk0" << 2 << "shk1" << 3 << "_id" << 3);
- const auto entryId = BSON("_id" << docKey);
+ const auto entryId = BSON(global_index::kContainerIndexDocKeyFieldName << docKey);
global_index::insertKey(operationContext(), uuid, key, docKey);
// Validate that the document key is stored in the index entry's _id field.
- StatusWith<BSONObj> status = storageInterface()->findById(
- operationContext(), NamespaceString::makeGlobalIndexNSS(uuid), entryId["_id"]);
+ StatusWith<BSONObj> status =
+ storageInterface()->findById(operationContext(),
+ NamespaceString::makeGlobalIndexNSS(uuid),
+ entryId[global_index::kContainerIndexDocKeyFieldName]);
ASSERT_OK(status.getStatus());
const auto indexEntry = status.getValue();
@@ -258,7 +271,7 @@ TEST_F(GlobalIndexTest, DeleteKey) {
const auto insertAndVerifyDelete =
[this](const UUID& uuid, const BSONObj& key, const BSONObj& docKey) {
- const auto entryId = BSON("_id" << docKey);
+ const auto entryId = BSON(global_index::kContainerIndexDocKeyFieldName << docKey);
const auto nss = NamespaceString::makeGlobalIndexNSS(uuid);
// Inserts already tested in StorageFormat case.
@@ -266,7 +279,8 @@ TEST_F(GlobalIndexTest, DeleteKey) {
// Delete and validate that the key is not found.
global_index::deleteKey(operationContext(), uuid, key, docKey);
- ASSERT_NOT_OK(storageInterface()->findById(operationContext(), nss, entryId["_id"]));
+ ASSERT_NOT_OK(storageInterface()->findById(
+ operationContext(), nss, entryId[global_index::kContainerIndexDocKeyFieldName]));
};
const auto docKey = BSON("shk0" << 0 << "shk1" << 0 << "_id" << 0);
@@ -332,7 +346,7 @@ void _assertDocumentsInGlobalIndexById(OperationContext* opCtx,
BSONObj obj;
for (auto& id : ids) {
ASSERT_EQUALS(exec->getNext(&obj, nullptr), PlanExecutor::ADVANCED);
- ASSERT_BSONOBJ_EQ(id, obj.getObjectField("_id"));
+ ASSERT_BSONOBJ_EQ(id, obj.getObjectField(global_index::kContainerIndexDocKeyFieldName));
}
ASSERT_EQUALS(exec->getNext(&obj, nullptr), PlanExecutor::IS_EOF);
}
diff --git a/src/mongo/db/op_observer/fcv_op_observer.h b/src/mongo/db/op_observer/fcv_op_observer.h
index d9040c52c44..c7641fd6d9a 100644
--- a/src/mongo/db/op_observer/fcv_op_observer.h
+++ b/src/mongo/db/op_observer/fcv_op_observer.h
@@ -86,7 +86,8 @@ public:
BSONObj indexDoc) final {}
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/db/op_observer/op_observer.h b/src/mongo/db/op_observer/op_observer.h
index a17706c33dd..f8bfd52e235 100644
--- a/src/mongo/db/op_observer/op_observer.h
+++ b/src/mongo/db/op_observer/op_observer.h
@@ -133,7 +133,8 @@ public:
virtual void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) = 0;
+ const UUID& globalIndexUUID,
+ long long numKeys) = 0;
virtual void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/db/op_observer/op_observer_impl.cpp b/src/mongo/db/op_observer/op_observer_impl.cpp
index 5bbc8e86d1b..d8f128e3c0a 100644
--- a/src/mongo/db/op_observer/op_observer_impl.cpp
+++ b/src/mongo/db/op_observer/op_observer_impl.cpp
@@ -304,6 +304,7 @@ void logGlobalIndexDDLOperation(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
const UUID& globalIndexUUID,
const StringData commandString,
+ boost::optional<long long> numKeys,
OplogWriter* oplogWriter) {
invariant(!opCtx->inMultiDocumentTransaction());
@@ -314,6 +315,16 @@ void logGlobalIndexDDLOperation(OperationContext* opCtx,
MutableOplogEntry oplogEntry;
oplogEntry.setOpType(repl::OpTypeEnum::kCommand);
oplogEntry.setObject(builder.done());
+
+ // On global index drops, persist the number of records into the 'o2' field similar to a
+ // collection drop. This allows for efficiently restoring the index keys count after rollback
+ // without forcing a collection scan.
+ invariant((numKeys && commandString == "dropGlobalIndex") ||
+ (!numKeys && commandString == "createGlobalIndex"));
+ if (numKeys) {
+ oplogEntry.setObject2(makeObject2ForDropOrRename(*numKeys));
+ }
+
// The 'ns' field is technically redundant as it can be derived from the uuid, however it's a
// required oplog entry field.
oplogEntry.setNss(globalIndexNss.getCommandNS());
@@ -347,16 +358,21 @@ void OpObserverImpl::onCreateGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
const UUID& globalIndexUUID) {
constexpr StringData commandString = "createGlobalIndex"_sd;
- logGlobalIndexDDLOperation(
- opCtx, globalIndexNss, globalIndexUUID, commandString, _oplogWriter.get());
+ logGlobalIndexDDLOperation(opCtx,
+ globalIndexNss,
+ globalIndexUUID,
+ commandString,
+ boost::none /* numKeys */,
+ _oplogWriter.get());
}
void OpObserverImpl::onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) {
+ const UUID& globalIndexUUID,
+ long long numKeys) {
constexpr StringData commandString = "dropGlobalIndex"_sd;
logGlobalIndexDDLOperation(
- opCtx, globalIndexNss, globalIndexUUID, commandString, _oplogWriter.get());
+ opCtx, globalIndexNss, globalIndexUUID, commandString, numKeys, _oplogWriter.get());
}
void OpObserverImpl::onCreateIndex(OperationContext* opCtx,
diff --git a/src/mongo/db/op_observer/op_observer_impl.h b/src/mongo/db/op_observer/op_observer_impl.h
index 5a2b1493d70..bb704238a01 100644
--- a/src/mongo/db/op_observer/op_observer_impl.h
+++ b/src/mongo/db/op_observer/op_observer_impl.h
@@ -64,7 +64,8 @@ public:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final;
+ const UUID& globalIndexUUID,
+ long long numKeys) final;
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/db/op_observer/op_observer_noop.h b/src/mongo/db/op_observer/op_observer_noop.h
index 66344d8e686..8bea94e5578 100644
--- a/src/mongo/db/op_observer/op_observer_noop.h
+++ b/src/mongo/db/op_observer/op_observer_noop.h
@@ -46,7 +46,8 @@ public:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/db/op_observer/op_observer_registry.h b/src/mongo/db/op_observer/op_observer_registry.h
index 59e4b9eb83e..0ce554bfb9a 100644
--- a/src/mongo/db/op_observer/op_observer_registry.h
+++ b/src/mongo/db/op_observer/op_observer_registry.h
@@ -76,10 +76,11 @@ public:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final {
+ const UUID& globalIndexUUID,
+ long long numKeys) final {
ReservedTimes times{opCtx};
for (auto& o : _observers)
- o->onDropGlobalIndex(opCtx, globalIndexNss, globalIndexUUID);
+ o->onDropGlobalIndex(opCtx, globalIndexNss, globalIndexUUID, numKeys);
};
void onCreateIndex(OperationContext* const opCtx,
diff --git a/src/mongo/db/op_observer/user_write_block_mode_op_observer.h b/src/mongo/db/op_observer/user_write_block_mode_op_observer.h
index 1b03e37a316..40e39378e68 100644
--- a/src/mongo/db/op_observer/user_write_block_mode_op_observer.h
+++ b/src/mongo/db/op_observer/user_write_block_mode_op_observer.h
@@ -173,7 +173,8 @@ public:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
// Index builds committing can be left unchecked since we kill any active index builds before
// enabling write blocking. This means any index build which gets to the commit phase while
diff --git a/src/mongo/db/repl/oplog.cpp b/src/mongo/db/repl/oplog.cpp
index 83866de6c9d..016e9be97c1 100644
--- a/src/mongo/db/repl/oplog.cpp
+++ b/src/mongo/db/repl/oplog.cpp
@@ -1863,19 +1863,21 @@ Status applyOperation_inlock(OperationContext* opCtx,
case OpTypeEnum::kInsertGlobalIndexKey: {
invariant(op.getUuid());
- global_index::insertKey(opCtx,
- *op.getUuid(),
- op.getObject().getObjectField("ik"),
- op.getObject().getObjectField("dk"));
+ global_index::insertKey(
+ opCtx,
+ *op.getUuid(),
+ op.getObject().getObjectField(global_index::kOplogEntryIndexKeyFieldName),
+ op.getObject().getObjectField(global_index::kOplogEntryDocKeyFieldName));
break;
}
case OpTypeEnum::kDeleteGlobalIndexKey: {
invariant(op.getUuid());
- global_index::deleteKey(opCtx,
- *op.getUuid(),
- op.getObject().getObjectField("ik"),
- op.getObject().getObjectField("dk"));
+ global_index::deleteKey(
+ opCtx,
+ *op.getUuid(),
+ op.getObject().getObjectField(global_index::kOplogEntryIndexKeyFieldName),
+ op.getObject().getObjectField(global_index::kOplogEntryDocKeyFieldName));
break;
}
default: {
diff --git a/src/mongo/db/repl/oplog_entry.cpp b/src/mongo/db/repl/oplog_entry.cpp
index 219dcb5be1c..8c71135dd72 100644
--- a/src/mongo/db/repl/oplog_entry.cpp
+++ b/src/mongo/db/repl/oplog_entry.cpp
@@ -32,6 +32,7 @@
#include "mongo/db/repl/oplog_entry.h"
+#include "mongo/db/global_index.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/server_feature_flags_gen.h"
@@ -135,7 +136,8 @@ ReplOperation makeGlobalIndexCrudOperation(const NamespaceString& indexNss,
// required oplog entry field.
op.setNss(indexNss.getCommandNS());
op.setUuid(indexUuid);
- op.setObject(BSON("ik" << key << "dk" << docKey));
+ op.setObject(BSON(global_index::kOplogEntryIndexKeyFieldName
+ << key << global_index::kOplogEntryDocKeyFieldName << docKey));
return op;
}
} // namespace
diff --git a/src/mongo/db/repl/primary_only_service_op_observer.h b/src/mongo/db/repl/primary_only_service_op_observer.h
index 54707c9ca25..391f612daa9 100644
--- a/src/mongo/db/repl/primary_only_service_op_observer.h
+++ b/src/mongo/db/repl/primary_only_service_op_observer.h
@@ -58,7 +58,8 @@ public:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/db/repl/rollback_impl.cpp b/src/mongo/db/repl/rollback_impl.cpp
index 2fff5e05a99..a1cec2f9309 100644
--- a/src/mongo/db/repl/rollback_impl.cpp
+++ b/src/mongo/db/repl/rollback_impl.cpp
@@ -44,6 +44,7 @@
#include "mongo/db/db_raii.h"
#include "mongo/db/dbdirectclient.h"
#include "mongo/db/dbhelpers.h"
+#include "mongo/db/global_index.h"
#include "mongo/db/index_builds_coordinator.h"
#include "mongo/db/logical_time_validator.h"
#include "mongo/db/operation_context.h"
@@ -91,6 +92,8 @@ RollbackImpl::Listener kNoopListener;
constexpr auto kInsertCmdName = "insert"_sd;
constexpr auto kUpdateCmdName = "update"_sd;
constexpr auto kDeleteCmdName = "delete"_sd;
+constexpr auto kInsertGlobalIndexKeyCmdName = "insertGlobalIndexKey"_sd;
+constexpr auto kDeleteGlobalIndexKeyCmdName = "deleteGlobalIndexKey"_sd;
constexpr auto kNumRecordsFieldName = "numRecords"_sd;
constexpr auto kToFieldName = "to"_sd;
constexpr auto kDropTargetFieldName = "dropTarget"_sd;
@@ -601,13 +604,13 @@ void RollbackImpl::_runPhaseFromAbortToReconstructPreparedTxns(
auto it = m.find(key);
return (it == m.end()) ? 0 : it->second;
};
- LOGV2(21599,
- "Rollback reverted {insert} insert operations, {update} update operations and {delete} "
- "delete operations.",
- "Rollback reverted command counts",
+ LOGV2(6984700,
+ "Operations reverted by rollback",
"insert"_attr = getCommandCount(kInsertCmdName),
"update"_attr = getCommandCount(kUpdateCmdName),
- "delete"_attr = getCommandCount(kDeleteCmdName));
+ "delete"_attr = getCommandCount(kDeleteCmdName),
+ "insertGlobalIndexKey"_attr = getCommandCount(kInsertGlobalIndexKeyCmdName),
+ "deleteGlobalIndexKey"_attr = getCommandCount(kDeleteGlobalIndexKeyCmdName));
// Retryable writes create derived updates to the transactions table which can be coalesced into
// one operation, so certain session operations history may be lost after restoring to the
@@ -919,12 +922,21 @@ Status RollbackImpl::_processRollbackOp(OperationContext* opCtx, const OplogEntr
// Keep track of the _ids of inserted and updated documents, as we may need to write them out to
// a rollback file.
- if (opType == OpTypeEnum::kInsert || opType == OpTypeEnum::kUpdate) {
+ if (opType == OpTypeEnum::kInsert || opType == OpTypeEnum::kUpdate ||
+ opType == OpTypeEnum::kInsertGlobalIndexKey) {
const auto uuid = oplogEntry.getUuid();
invariant(uuid,
str::stream() << "Oplog entry to roll back is unexpectedly missing a UUID: "
<< redact(oplogEntry.toBSONForLogging()));
- const auto idElem = oplogEntry.getIdElement();
+ const auto idElem = [&]() {
+ if (opType == OpTypeEnum::kInsertGlobalIndexKey) {
+ // As global indexes currently lack support for multi-key, a key can be uniquely
+ // identified by its document key, which maps the _id field in the global index
+ // container (collection).
+ return oplogEntry.getObject()[global_index::kOplogEntryDocKeyFieldName];
+ }
+ return oplogEntry.getIdElement();
+ }();
if (!idElem.eoo()) {
// We call BSONElement::wrap() on each _id element to create a new BSONObj with an owned
// buffer, as the underlying storage may be gone when we access this map to write
@@ -935,7 +947,7 @@ Status RollbackImpl::_processRollbackOp(OperationContext* opCtx, const OplogEntr
}
}
- if (opType == OpTypeEnum::kInsert) {
+ if (opType == OpTypeEnum::kInsert || opType == OpTypeEnum::kInsertGlobalIndexKey) {
auto idVal = oplogEntry.getObject().getStringField("_id");
if (serverGlobalParams.clusterRole == ClusterRole::ShardServer &&
opNss == NamespaceString::kServerConfigurationNamespace &&
@@ -959,11 +971,12 @@ Status RollbackImpl::_processRollbackOp(OperationContext* opCtx, const OplogEntr
// Rolling back an insert must decrement the count by 1.
_countDiffs[oplogEntry.getUuid().value()] -= 1;
- } else if (opType == OpTypeEnum::kDelete) {
+ } else if (opType == OpTypeEnum::kDelete || opType == OpTypeEnum::kDeleteGlobalIndexKey) {
// Rolling back a delete must increment the count by 1.
_countDiffs[oplogEntry.getUuid().value()] += 1;
} else if (opType == OpTypeEnum::kCommand) {
- if (oplogEntry.getCommandType() == OplogEntry::CommandType::kCreate) {
+ if (oplogEntry.getCommandType() == OplogEntry::CommandType::kCreate ||
+ oplogEntry.getCommandType() == OplogEntry::CommandType::kCreateGlobalIndex) {
// If we roll back a create, then we do not need to change the size of that uuid.
_countDiffs.erase(oplogEntry.getUuid().value());
_pendingDrops.erase(oplogEntry.getUuid().value());
@@ -984,13 +997,15 @@ Status RollbackImpl::_processRollbackOp(OperationContext* opCtx, const OplogEntr
_pendingDrops.erase(importTargetUUID);
_newCounts.erase(importTargetUUID);
}
- } else if (oplogEntry.getCommandType() == OplogEntry::CommandType::kDrop) {
- // If we roll back a collection drop, parse the o2 field for the collection count for
- // use later by _findRecordStoreCounts().
- // This will be used to reconcile collection counts in the case where the drop-pending
- // collection is managed by the storage engine and is not accessible through the UUID
- // catalog.
- // Adding a _newCounts entry ensures that the count will be set after the rollback.
+ } else if (oplogEntry.getCommandType() == OplogEntry::CommandType::kDrop ||
+ oplogEntry.getCommandType() == OplogEntry::CommandType::kDropGlobalIndex) {
+ // The collection count at collection drop time is op-logged in the 'o2' field.
+ // In the common case where the drop-pending collection is managed by the storage
+ // engine, the collection metadata - including the number of records at drop time -
+ // is not accessible through the catalog.
+ // Keep track of the record count stored in the 'o2' field via the _newCounts variable.
+ // This allows for cheaply restoring the collection count post rollback without an
+ // expensive collection scan.
const auto uuid = oplogEntry.getUuid().value();
invariant(_countDiffs.find(uuid) == _countDiffs.end(),
str::stream() << "Unexpected existing count diff for " << uuid.toString()
@@ -1060,6 +1075,12 @@ Status RollbackImpl::_processRollbackOp(OperationContext* opCtx, const OplogEntr
if (opType == OpTypeEnum::kDelete) {
++_observerInfo.rollbackCommandCounts[kDeleteCmdName];
}
+ if (opType == OpTypeEnum::kInsertGlobalIndexKey) {
+ ++_observerInfo.rollbackCommandCounts[kInsertGlobalIndexKeyCmdName];
+ }
+ if (opType == OpTypeEnum::kDeleteGlobalIndexKey) {
+ ++_observerInfo.rollbackCommandCounts[kDeleteGlobalIndexKeyCmdName];
+ }
return Status::OK();
}
diff --git a/src/mongo/db/repl/tenant_migration_donor_op_observer.h b/src/mongo/db/repl/tenant_migration_donor_op_observer.h
index 58354e9fae8..842a43926ab 100644
--- a/src/mongo/db/repl/tenant_migration_donor_op_observer.h
+++ b/src/mongo/db/repl/tenant_migration_donor_op_observer.h
@@ -56,7 +56,8 @@ public:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/db/repl/tenant_migration_recipient_op_observer.h b/src/mongo/db/repl/tenant_migration_recipient_op_observer.h
index 3c501df9c72..b62a276b86d 100644
--- a/src/mongo/db/repl/tenant_migration_recipient_op_observer.h
+++ b/src/mongo/db/repl/tenant_migration_recipient_op_observer.h
@@ -57,7 +57,8 @@ public:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/db/s/config_server_op_observer.h b/src/mongo/db/s/config_server_op_observer.h
index a25aa999584..64411e8c2a4 100644
--- a/src/mongo/db/s/config_server_op_observer.h
+++ b/src/mongo/db/s/config_server_op_observer.h
@@ -57,7 +57,8 @@ public:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/db/s/range_deleter_service_op_observer.h b/src/mongo/db/s/range_deleter_service_op_observer.h
index 10a4bfc9e1e..430535ceac5 100644
--- a/src/mongo/db/s/range_deleter_service_op_observer.h
+++ b/src/mongo/db/s/range_deleter_service_op_observer.h
@@ -88,7 +88,8 @@ private:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/db/s/resharding/resharding_op_observer.h b/src/mongo/db/s/resharding/resharding_op_observer.h
index 6759e335f5e..de3a84ded6b 100644
--- a/src/mongo/db/s/resharding/resharding_op_observer.h
+++ b/src/mongo/db/s/resharding/resharding_op_observer.h
@@ -72,7 +72,8 @@ public:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/db/s/shard_server_op_observer.h b/src/mongo/db/s/shard_server_op_observer.h
index 28c8337e477..3c1db831e7d 100644
--- a/src/mongo/db/s/shard_server_op_observer.h
+++ b/src/mongo/db/s/shard_server_op_observer.h
@@ -56,7 +56,8 @@ public:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/db/serverless/shard_split_donor_op_observer.h b/src/mongo/db/serverless/shard_split_donor_op_observer.h
index 3a9b056cce4..fba3471c7dd 100644
--- a/src/mongo/db/serverless/shard_split_donor_op_observer.h
+++ b/src/mongo/db/serverless/shard_split_donor_op_observer.h
@@ -55,7 +55,8 @@ public:
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,
diff --git a/src/mongo/idl/cluster_server_parameter_op_observer.h b/src/mongo/idl/cluster_server_parameter_op_observer.h
index 6d83e84658e..0de4a831616 100644
--- a/src/mongo/idl/cluster_server_parameter_op_observer.h
+++ b/src/mongo/idl/cluster_server_parameter_op_observer.h
@@ -108,7 +108,8 @@ public:
BSONObj indexDoc) final {}
void onDropGlobalIndex(OperationContext* opCtx,
const NamespaceString& globalIndexNss,
- const UUID& globalIndexUUID) final{};
+ const UUID& globalIndexUUID,
+ long long numKeys) final{};
void onCreateIndex(OperationContext* opCtx,
const NamespaceString& nss,