/** * Tests index usage with durable history across restarts. * * @tags: [ * requires_persistence, * requires_replication, * ] */ (function() { "use strict"; load("jstests/libs/fail_point_util.js"); load("jstests/noPassthrough/libs/index_build.js"); const replTest = new ReplSetTest({ nodes: 1, nodeOptions: { setParameter: { // To control durable history more predictably, disable the checkpoint thread. syncdelay: 0 } } }); replTest.startSet(); replTest.initiate(); const indexSpec = { a: 1 }; const primary = function() { return replTest.getPrimary(); }; const testDB = function() { return primary().getDB("test"); }; const coll = function() { return testDB()[jsTestName()]; }; const insert = function(document) { // The write concern guarantees the stable and oldest timestamp are bumped. return assert .commandWorked(testDB().runCommand( {insert: coll().getName(), documents: [document], writeConcern: {w: "majority"}})) .operationTime; }; const findWithIndex = function(atClusterTime, shouldSucceed) { let res = {}; if (atClusterTime == undefined) { res = testDB().runCommand({find: jsTestName(), hint: indexSpec}); } else { res = testDB().runCommand({ find: jsTestName(), hint: indexSpec, readConcern: {level: "snapshot", atClusterTime: atClusterTime} }); } if (shouldSucceed) { return assert.commandWorked(res); } assert.commandFailedWithCode(res, [ErrorCodes.BadValue, ErrorCodes.SnapshotUnavailable]); }; const oldestTS = insert({a: 0}); jsTestLog("Oldest timestamp: " + tojson(oldestTS)); // The index does not exist yet. findWithIndex(undefined, /*shouldSucceed=*/false); findWithIndex(oldestTS, /*shouldSucceed=*/false); const createIndexTS = assert.commandWorked(coll().createIndex(indexSpec)).operationTime; jsTestLog("Initial index creation timestamp: " + tojson(createIndexTS)); // The index is only available for use as of the 'createIndexTS'. findWithIndex(oldestTS, /*shouldSucceed=*/false); assert.eq(1, findWithIndex(createIndexTS, /*shouldSucceed=*/true)["cursor"]["firstBatch"].length); assert.eq(1, findWithIndex(undefined, /*shouldSucceed=*/true)["cursor"]["firstBatch"].length); // Rebuild the index without finishing it. assert.commandWorked(coll().dropIndex(indexSpec)); const fp = configureFailPoint(primary(), "hangAfterSettingUpIndexBuildUnlocked"); const awaitCreateIndex = IndexBuildTest.startIndexBuild(primary(), coll().getFullName(), indexSpec); fp.wait(); // Get a timestamp after starting the index build but before the commit timestamp of the index // build. const preIndexCommitTS = insert({a: 1}); assert.commandWorked(testDB().adminCommand({fsync: 1})); replTest.stop(0, 9, {allowedExitCode: MongoRunner.EXIT_SIGKILL}, {forRestart: true}); awaitCreateIndex({checkExitSuccess: false}); replTest.start( 0, { setParameter: { "failpoint.hangAfterSettingUpIndexBuildUnlocked": tojson({mode: "alwaysOn"}), // To control durable history more predictably, disable the checkpoint thread. syncdelay: 0 } }, true /* restart */); const checkLogs = function() { // The index build was not yet completed at the recovery timestamp, it will be dropped and // rebuilt. checkLog.containsJson(primary(), 6361201, { index: "a_1", namespace: coll().getFullName(), }); // Index build restarting. checkLog.containsJson(primary(), 20660); }; checkLogs(); // The index is being re-created. findWithIndex(oldestTS, /*shouldSucceed=*/false); findWithIndex(createIndexTS, /*shouldSucceed=*/false); findWithIndex(preIndexCommitTS, /*shouldSucceed=*/false); findWithIndex(undefined, /*shouldSucceed=*/false); const restartInsertTS = insert({a: 2}); // Take a checkpoint to persist the new catalog entry of the index being rebuilt. assert.commandWorked(testDB().adminCommand({fsync: 1})); replTest.stop(0, 9, {allowedExitCode: MongoRunner.EXIT_SIGKILL}, {forRestart: true}); replTest.start( 0, { setParameter: { // To control durable history more predictably, disable the checkpoint thread. syncdelay: 0 } }, true /* restart */); checkLogs(); // Startup recovery will rebuild the index in the background. Wait for the index build to finish. checkLog.containsJson(primary(), 20663, { namespace: coll().getFullName(), indexesBuilt: ["a_1"], numIndexesAfter: 2, }); IndexBuildTest.assertIndexes(coll(), 2, ["_id_", "a_1"]); // We can't use the index at earlier times than the index builds commit timestamp. findWithIndex(oldestTS, /*shouldSucceed=*/false); findWithIndex(createIndexTS, /*shouldSucceed=*/false); findWithIndex(preIndexCommitTS, /*shouldSucceed=*/false); findWithIndex(restartInsertTS, /*shouldSucceed=*/false); assert.eq(3, findWithIndex(undefined, /*shouldSucceed=*/true)["cursor"]["firstBatch"].length); const finalInsertTS = insert({a: 3}); assert.eq(4, findWithIndex(finalInsertTS, /*shouldSucceed=*/true)["cursor"]["firstBatch"].length); assert.eq(4, findWithIndex(undefined, /*shouldSucceed=*/true)["cursor"]["firstBatch"].length); // Demonstrate that durable history can be used across a restart with a finished index. assert.commandWorked(testDB().adminCommand({fsync: 1})); replTest.restart(primary()); assert.eq(4, findWithIndex(undefined, /*shouldSucceed=*/true)["cursor"]["firstBatch"].length); assert.eq(5, findWithIndex(insert({a: 4}), /*shouldSucceed=*/true)["cursor"]["firstBatch"].length); assert.eq(5, findWithIndex(undefined, /*shouldSucceed=*/true)["cursor"]["firstBatch"].length); findWithIndex(oldestTS, /*shouldSucceed=*/false); findWithIndex(createIndexTS, /*shouldSucceed=*/false); findWithIndex(preIndexCommitTS, /*shouldSucceed=*/false); findWithIndex(restartInsertTS, /*shouldSucceed=*/false); assert.eq(4, findWithIndex(finalInsertTS, /*shouldSucceed=*/true)["cursor"]["firstBatch"].length); replTest.stopSet(); })();