'use strict'; /** * create_index_background.js * * Create an index in the background while performing CRUD operations at the same time. * The command to create a background index completes in the shell once the * index has completed and the test no longer needs to execute more transitions. * The first thread (tid = 0) will be the one that creates the background index. * * @tags: [ * assumes_balancer_off, * creates_background_indexes * ] */ load('jstests/concurrency/fsm_workload_helpers/server_types.js'); // for isMongos var $config = (function() { var data = { nDocumentsToSeed: 1000, nDocumentsToCreate: 200, nDocumentsToRead: 100, nDocumentsToUpdate: 50, nDocumentsToDelete: 50, getHighestX: function getHighestX(coll, tid) { // Find highest value of x. var highest = 0; var cursor = coll.find({tid: tid}).sort({x: -1}).limit(-1); assertWhenOwnColl(function() { highest = cursor.next().x; }); return highest; }, getPartialFilterExpression: function getPartialFilterExpression() { return undefined; }, getIndexSpec: function getIndexSpec() { return {x: 1}; }, extendDocument: function getDocument(originalDocument) { // Only relevant for extended workloads. return originalDocument; }, extendUpdateExpr: function extendUpdateExpr(update) { // Only relevant for extended workloads. return update; } }; var states = (function() { function init(db, collName) { // Add thread-specific documents var bulk = db[collName].initializeUnorderedBulkOp(); for (var i = 0; i < this.nDocumentsToSeed; ++i) { const doc = {x: i, tid: this.tid}; bulk.insert(this.extendDocument(doc)); } var res = bulk.execute(); assertAlways.commandWorked(res); assertAlways.eq(this.nDocumentsToSeed, res.nInserted, tojson(res)); // In the first thread create the background index. if (this.tid === 0) { var coll = db[collName]; // Before creating the background index make sure insert or update // CRUD operations are active. assertWhenOwnColl.soon(function() { return coll.find({crud: {$exists: true}}).itcount() > 0; }, 'No documents with "crud" field have been inserted or updated', 60 * 1000); let createOptions = {background: true}; let filter = this.getPartialFilterExpression(); if (filter !== undefined) { createOptions['partialFilterExpression'] = filter; } res = coll.createIndex(this.getIndexSpec(), createOptions); assertAlways.commandWorked(res, tojson(res)); } } function createDocs(db, collName) { // Insert documents with an increasing value of index x. var coll = db[collName]; var res; var count = coll.find({tid: this.tid}).itcount(); var highest = this.getHighestX(coll, this.tid); for (var i = 0; i < this.nDocumentsToCreate; ++i) { const doc = {x: i + highest + 1, tid: this.tid, crud: 1}; res = coll.insert(this.extendDocument(doc)); assertAlways.commandWorked(res); assertAlways.eq(res.nInserted, 1, tojson(res)); } assertWhenOwnColl.eq(coll.find({tid: this.tid}).itcount(), this.nDocumentsToCreate + count, 'createDocs itcount mismatch'); } function readDocs(db, collName) { // Read random documents from the collection on index x. var coll = db[collName]; var res; var count = coll.find({tid: this.tid}).itcount(); assertWhenOwnColl.gte( count, this.nDocumentsToRead, 'readDocs not enough documents for tid ' + this.tid); var highest = this.getHighestX(coll, this.tid); for (var i = 0; i < this.nDocumentsToRead; ++i) { // Do randomized reads on index x. A document is not guaranteed // to match the randomized 'x' predicate. res = coll.find({x: Random.randInt(highest), tid: this.tid}).itcount(); assertWhenOwnColl.contains(res, [0, 1], tojson(res)); } assertWhenOwnColl.eq( coll.find({tid: this.tid}).itcount(), count, 'readDocs itcount mismatch'); } function updateDocs(db, collName) { // Update random documents from the collection on index x. // Since an update requires a shard key, do not run in a sharded cluster. if (!isMongos(db)) { var coll = db[collName]; var res; var count = coll.find({tid: this.tid}).itcount(); assertWhenOwnColl.gte(count, this.nDocumentsToUpdate, 'updateDocs not enough documents for tid ' + this.tid); var highest = this.getHighestX(coll, this.tid); for (var i = 0; i < this.nDocumentsToUpdate; ++i) { // Do randomized updates on index x. A document is not guaranteed // to match the randomized 'x' predicate. let updateExpr = {$inc: {crud: 1}}; updateExpr = this.extendUpdateExpr(updateExpr); res = coll.update({x: Random.randInt(highest), tid: this.tid}, updateExpr); assertAlways.commandWorked(res); assertWhenOwnColl.contains(res.nModified, [0, 1], tojson(res)); assertWhenOwnColl.contains(res.nMatched, [0, 1], tojson(res)); assertWhenOwnColl.eq(res.nUpserted, 0, tojson(res)); } assertWhenOwnColl.eq( coll.find({tid: this.tid}).itcount(), count, 'updateDocs itcount mismatch'); } } function deleteDocs(db, collName) { // Remove random documents from the collection on index x. var coll = db[collName]; var res; var count = coll.find({tid: this.tid}).itcount(); // Set the maximum number of documents we can delete to ensure that there // are documents to read or update after deleteDocs completes. // Return from this state if there are not enough documents in the collection // with this.tid. var minDocsToKeep = Math.max(this.nDocumentsToRead, this.nDocumentsToUpdate); // nDeleteDocs is the number of documents that can be deleted during this state. var nDeleteDocs = Math.min(count - minDocsToKeep, this.nDocumentsToDelete); if (nDeleteDocs < 0) { return; } var highest = this.getHighestX(coll, this.tid); var nActualDeletes = 0; for (var i = 0; i < nDeleteDocs; ++i) { // Do randomized deletes on index x. A document is not guaranteed // to match the randomized 'x' predicate. res = coll.remove({x: Random.randInt(highest), tid: this.tid}); assertAlways.commandWorked(res); assertWhenOwnColl.contains(res.nRemoved, [0, 1], tojson(res)); nActualDeletes += res.nRemoved; } assertWhenOwnColl.eq(coll.find({tid: this.tid}).itcount(), count - nActualDeletes, 'deleteDocs itcount mismatch'); } return { init: init, createDocs: createDocs, readDocs: readDocs, updateDocs: updateDocs, deleteDocs: deleteDocs }; })(); var transitions = { init: {createDocs: 1}, createDocs: {createDocs: 0.25, readDocs: 0.25, updateDocs: 0.25, deleteDocs: 0.25}, readDocs: {createDocs: 0.25, readDocs: 0.25, updateDocs: 0.25, deleteDocs: 0.25}, updateDocs: {createDocs: 0.25, readDocs: 0.25, updateDocs: 0.25, deleteDocs: 0.25}, deleteDocs: {createDocs: 0.25, readDocs: 0.25, updateDocs: 0.25, deleteDocs: 0.25}, }; var internalQueryExecYieldIterations; var internalQueryExecYieldPeriodMS; function setup(db, collName, cluster) { var nSetupDocs = this.nDocumentsToSeed * 200; var coll = db[collName]; var res = coll.createIndex({tid: 1}); assertAlways.commandWorked(res, tojson(res)); var bulk = coll.initializeUnorderedBulkOp(); for (var i = 0; i < nSetupDocs; ++i) { bulk.insert({x: i}); } res = bulk.execute(); assertAlways.commandWorked(res); assertAlways.eq(nSetupDocs, res.nInserted, tojson(res)); // Increase the following parameters to reduce the number of yields. cluster.executeOnMongodNodes(function(db) { var res; res = db.adminCommand({setParameter: 1, internalQueryExecYieldIterations: 100000}); assertAlways.commandWorked(res); internalQueryExecYieldIterations = res.was; res = db.adminCommand({setParameter: 1, internalQueryExecYieldPeriodMS: 10000}); assertAlways.commandWorked(res); internalQueryExecYieldPeriodMS = res.was; }); } function teardown(db, collName, cluster) { cluster.executeOnMongodNodes(function(db) { assertAlways.commandWorked(db.adminCommand({ setParameter: 1, internalQueryExecYieldIterations: internalQueryExecYieldIterations })); assertAlways.commandWorked(db.adminCommand( {setParameter: 1, internalQueryExecYieldPeriodMS: internalQueryExecYieldPeriodMS})); }); } return { threadCount: 5, iterations: 3, data: data, states: states, setup: setup, teardown: teardown, transitions: transitions, }; })();