summaryrefslogtreecommitdiff
path: root/src/mongo/db/catalog/drop_indexes.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/catalog/drop_indexes.cpp')
-rw-r--r--src/mongo/db/catalog/drop_indexes.cpp470
1 files changed, 360 insertions, 110 deletions
diff --git a/src/mongo/db/catalog/drop_indexes.cpp b/src/mongo/db/catalog/drop_indexes.cpp
index 1d55cd77d59..7eaf3d6bd4a 100644
--- a/src/mongo/db/catalog/drop_indexes.cpp
+++ b/src/mongo/db/catalog/drop_indexes.cpp
@@ -52,13 +52,109 @@
namespace mongo {
namespace {
-// Field name in dropIndexes command for indexes to drop. This field can contain one of:
-// 1) '*' - drop all indexes.
-// 2) <index name> - name of single index to drop.
-// 3) <index key pattern> - BSON document representing key pattern of index to drop.
-// 4) [<index name 1>, <index name 2>, ...] - array containing names of indexes to drop.
+// Field name in dropIndexes command for indexes to drop.
constexpr auto kIndexFieldName = "index"_sd;
+Status checkForViewOrMissingNS(OperationContext* opCtx,
+ const NamespaceString& nss,
+ Database* db,
+ Collection* collection) {
+ if (!collection) {
+ if (db && ViewCatalog::get(db)->lookup(opCtx, nss.ns())) {
+ return Status(ErrorCodes::CommandNotSupportedOnView,
+ str::stream() << "Cannot drop indexes on view " << nss);
+ }
+ return Status(ErrorCodes::NamespaceNotFound, str::stream() << "ns not found " << nss);
+ }
+ return Status::OK();
+}
+
+/**
+ * Validates the key pattern passed through the command.
+ */
+StatusWith<const IndexDescriptor*> getDescriptorByKeyPattern(OperationContext* opCtx,
+ IndexCatalog* indexCatalog,
+ const BSONElement& keyPattern) {
+ const bool includeUnfinished = true;
+ std::vector<const IndexDescriptor*> indexes;
+ indexCatalog->findIndexesByKeyPattern(
+ opCtx, keyPattern.embeddedObject(), includeUnfinished, &indexes);
+ if (indexes.empty()) {
+ return Status(ErrorCodes::IndexNotFound,
+ str::stream()
+ << "can't find index with key: " << keyPattern.embeddedObject());
+ } else if (indexes.size() > 1) {
+ return Status(ErrorCodes::AmbiguousIndexKeyPattern,
+ str::stream() << indexes.size() << " indexes found for key: "
+ << keyPattern.embeddedObject() << ", identify by name instead."
+ << " Conflicting indexes: " << indexes[0]->infoObj() << ", "
+ << indexes[1]->infoObj());
+ }
+
+ const IndexDescriptor* desc = indexes[0];
+ if (desc->isIdIndex()) {
+ return Status(ErrorCodes::InvalidOptions, "cannot drop _id index");
+ }
+
+ if (desc->indexName() == "*") {
+ // Dropping an index named '*' results in an drop-index oplog entry with a name of '*',
+ // which in 3.6 and later is interpreted by replication as meaning "drop all indexes on
+ // this collection".
+ return Status(ErrorCodes::InvalidOptions,
+ "cannot drop an index named '*' by key pattern. You must drop the "
+ "entire collection, drop all indexes on the collection by using an index "
+ "name of '*', or downgrade to 3.4 to drop only this index.");
+ }
+
+ return desc;
+}
+
+/**
+ * Returns a list of index names that the caller requested to abort/drop. Requires a collection lock
+ * to be held to look up the index name from the key pattern.
+ */
+StatusWith<std::vector<std::string>> getIndexNames(OperationContext* opCtx,
+ Collection* collection,
+ const BSONElement& indexElem) {
+ invariant(opCtx->lockState()->isCollectionLockedForMode(collection->ns(), MODE_IX));
+
+ std::vector<std::string> indexNames;
+ if (indexElem.type() == String) {
+ std::string indexToAbort = indexElem.valuestr();
+ indexNames.push_back(indexToAbort);
+ } else if (indexElem.type() == Object) {
+ auto swDescriptor =
+ getDescriptorByKeyPattern(opCtx, collection->getIndexCatalog(), indexElem);
+ if (!swDescriptor.isOK()) {
+ return swDescriptor.getStatus();
+ }
+ indexNames.push_back(swDescriptor.getValue()->indexName());
+ } else if (indexElem.type() == Array) {
+ for (auto indexNameElem : indexElem.Array()) {
+ invariant(indexNameElem.type() == String);
+ indexNames.push_back(indexNameElem.valuestr());
+ }
+ }
+
+ return indexNames;
+}
+
+/**
+ * Attempts to abort a single index builder that is responsible for all the index names passed in.
+ */
+std::vector<UUID> abortIndexBuildByIndexNamesNoWait(OperationContext* opCtx,
+ Collection* collection,
+ std::vector<std::string> indexNames) {
+
+ boost::optional<UUID> buildUUID =
+ IndexBuildsCoordinator::get(opCtx)->abortIndexBuildByIndexNamesNoWait(
+ opCtx, collection->uuid(), indexNames, Timestamp(), "dropIndexes command");
+ if (buildUUID) {
+ return {*buildUUID};
+ }
+ return {};
+}
+
/**
* Drops single index given a descriptor.
*/
@@ -98,128 +194,288 @@ Status dropIndexByDescriptor(OperationContext* opCtx,
return Status::OK();
}
-Status wrappedRun(OperationContext* opCtx,
- Collection* collection,
- const BSONObj& jsobj,
- BSONObjBuilder* anObjBuilder) {
+/**
+ * Aborts all the index builders on the collection if the first element in 'indexesToAbort' is "*",
+ * otherwise this attempts to abort a single index builder building the given index names.
+ */
+std::vector<UUID> abortActiveIndexBuilders(OperationContext* opCtx,
+ Collection* collection,
+ const std::vector<std::string>& indexNames) {
+ invariant(opCtx->lockState()->isCollectionLockedForMode(collection->ns(), MODE_IX));
- IndexCatalog* indexCatalog = collection->getIndexCatalog();
- anObjBuilder->appendNumber("nIndexesWas", indexCatalog->numIndexesTotal(opCtx));
+ if (indexNames.empty()) {
+ return {};
+ }
- BSONElement indexElem = jsobj.getField(kIndexFieldName);
- if (indexElem.type() == String) {
- std::string indexToDelete = indexElem.valuestr();
-
- if (indexToDelete == "*") {
- indexCatalog->dropAllIndexes(
- opCtx, false, [opCtx, collection](const IndexDescriptor* desc) {
- opCtx->getServiceContext()->getOpObserver()->onDropIndex(opCtx,
- collection->ns(),
- collection->uuid(),
- desc->indexName(),
- desc->infoObj());
- });
-
- anObjBuilder->append("msg", "non-_id indexes dropped for collection");
- return Status::OK();
- }
+ if (indexNames.front() == "*") {
+ return IndexBuildsCoordinator::get(opCtx)->abortCollectionIndexBuildsNoWait(
+ collection->uuid(), "dropIndexes command");
+ }
+
+ return abortIndexBuildByIndexNamesNoWait(opCtx, collection, indexNames);
+}
+
+Status dropReadyIndexes(OperationContext* opCtx,
+ Collection* collection,
+ const std::vector<std::string>& indexNames,
+ BSONObjBuilder* anObjBuilder) {
+ invariant(opCtx->lockState()->isCollectionLockedForMode(collection->ns(), MODE_X));
+
+ if (indexNames.empty()) {
+ return Status::OK();
+ }
- bool includeUnfinished = true;
- auto desc = indexCatalog->findIndexByName(opCtx, indexToDelete, includeUnfinished);
+ IndexCatalog* indexCatalog = collection->getIndexCatalog();
+ if (indexNames.front() == "*") {
+ indexCatalog->dropAllIndexes(
+ opCtx, false, [opCtx, collection](const IndexDescriptor* desc) {
+ opCtx->getServiceContext()->getOpObserver()->onDropIndex(opCtx,
+ collection->ns(),
+ collection->uuid(),
+ desc->indexName(),
+ desc->infoObj());
+ });
+
+ anObjBuilder->append("msg", "non-_id indexes dropped for collection");
+ return Status::OK();
+ }
+
+ bool includeUnfinished = true;
+ for (const auto& indexName : indexNames) {
+ auto desc = indexCatalog->findIndexByName(opCtx, indexName, includeUnfinished);
if (!desc) {
return Status(ErrorCodes::IndexNotFound,
- str::stream() << "index not found with name [" << indexToDelete << "]");
+ str::stream() << "index not found with name [" << indexName << "]");
+ }
+ Status status = dropIndexByDescriptor(opCtx, collection, indexCatalog, desc);
+ if (!status.isOK()) {
+ return status;
}
- return dropIndexByDescriptor(opCtx, collection, indexCatalog, desc);
}
+ return Status::OK();
+}
- if (indexElem.type() == Object) {
- const bool includeUnfinished = true;
- std::vector<const IndexDescriptor*> indexes;
- collection->getIndexCatalog()->findIndexesByKeyPattern(
- opCtx, indexElem.embeddedObject(), includeUnfinished, &indexes);
- if (indexes.empty()) {
- return Status(ErrorCodes::IndexNotFound,
- str::stream()
- << "can't find index with key: " << indexElem.embeddedObject());
- } else if (indexes.size() > 1) {
- return Status(ErrorCodes::AmbiguousIndexKeyPattern,
- str::stream() << indexes.size()
- << " indexes found for key: " << indexElem.embeddedObject()
- << ", identify by name instead."
- << " Conflicting indexes: " << indexes[0]->infoObj() << ", "
- << indexes[1]->infoObj());
- }
+} // namespace
- const IndexDescriptor* desc = indexes[0];
- if (desc->isIdIndex()) {
- return Status(ErrorCodes::InvalidOptions, "cannot drop _id index");
- }
+Status dropIndexes(OperationContext* opCtx,
+ const NamespaceString& nss,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) {
+ // Protects the command from replica set state changes during its execution.
+ Lock::GlobalLock globalLk(opCtx, MODE_IX);
- if (desc->indexName() == "*") {
- // Dropping an index named '*' results in an drop-index oplog entry with a name of '*',
- // which in 3.6 and later is interpreted by replication as meaning "drop all indexes on
- // this collection".
- return Status(ErrorCodes::InvalidOptions,
- "cannot drop an index named '*' by key pattern. You must drop the "
- "entire collection, drop all indexes on the collection by using an index "
- "name of '*', or downgrade to 3.4 to drop only this index.");
- }
+ // We only need to hold an intent lock to send abort signals to the active index builder(s) we
+ // intend to abort.
+ boost::optional<AutoGetCollection> autoColl;
+ autoColl.emplace(opCtx, nss, MODE_IX);
+
+ bool writesAreReplicatedAndNotPrimary = opCtx->writesAreReplicated() &&
+ !repl::ReplicationCoordinator::get(opCtx)->canAcceptWritesFor(opCtx, nss);
+
+ if (writesAreReplicatedAndNotPrimary) {
+ return Status(ErrorCodes::NotMaster,
+ str::stream() << "Not primary while dropping indexes in " << nss);
+ }
- return dropIndexByDescriptor(opCtx, collection, indexCatalog, desc);
+ Database* db = autoColl->getDb();
+ Collection* collection = autoColl->getCollection();
+ Status status = checkForViewOrMissingNS(opCtx, nss, db, collection);
+ if (!status.isOK()) {
+ return status;
}
- // The 'index' field contains a list of names of indexes to drop.
- // Drops all or none of the indexes due to the enclosing WriteUnitOfWork.
+ const UUID collectionUUID = collection->uuid();
+ const NamespaceStringOrUUID nssOrUUID = {nss.db().toString(), collectionUUID};
+
+ if (!serverGlobalParams.quiet.load()) {
+ LOGV2(51806,
+ "CMD: dropIndexes {nss}: {cmdObj_kIndexFieldName_false}",
+ "nss"_attr = nss,
+ "cmdObj_kIndexFieldName_false"_attr = cmdObj[kIndexFieldName].toString(false));
+ }
+
+ result->appendNumber("nIndexesWas", collection->getIndexCatalog()->numIndexesTotal(opCtx));
+
+ // Validate basic user input.
+ BSONElement indexElem = cmdObj.getField(kIndexFieldName);
+ const bool isWildcard = indexElem.type() == String && indexElem.String() == "*";
+
+ // If an Array was passed in, verify that all the elements are of type String.
if (indexElem.type() == Array) {
for (auto indexNameElem : indexElem.Array()) {
if (indexNameElem.type() != String) {
return Status(ErrorCodes::TypeMismatch,
str::stream()
- << "dropIndexes " << collection->ns() << " ("
- << collection->uuid() << ") failed to drop multiple indexes "
+ << "dropIndexes " << collection->ns() << " (" << collectionUUID
+ << ") failed to drop multiple indexes "
<< indexElem.toString(false) << ": index name must be a string");
}
+ }
+ }
- auto indexToDelete = indexNameElem.String();
- bool includeUnfinished = true;
- auto desc = indexCatalog->findIndexByName(opCtx, indexToDelete, includeUnfinished);
- if (!desc) {
- return Status(ErrorCodes::IndexNotFound,
- str::stream()
- << "index not found with name [" << indexToDelete << "]");
- }
- auto status = dropIndexByDescriptor(opCtx, collection, indexCatalog, desc);
- if (!status.isOK()) {
- return status.withContext(
- str::stream() << "dropIndexes " << collection->ns() << " ("
- << collection->uuid() << ") failed to drop multiple indexes "
- << indexElem.toString(false) << ": " << indexToDelete);
- }
+ IndexBuildsCoordinator* indexBuildsCoord = IndexBuildsCoordinator::get(opCtx);
+
+ // When releasing the collection lock to send the abort signal to the index builders, it's
+ // possible for new index builds to start. Keep aborting in-progress index builds if they
+ // satisfy the caller's input.
+ std::vector<UUID> abortedIndexBuilders;
+ std::vector<std::string> indexNames;
+ while (true) {
+ auto swIndexNames = getIndexNames(opCtx, collection, indexElem);
+ if (!swIndexNames.isOK()) {
+ return swIndexNames.getStatus();
}
- return Status::OK();
+ indexNames = swIndexNames.getValue();
+
+ // Send the abort signal to any index builders that match the users request.
+ abortedIndexBuilders = abortActiveIndexBuilders(opCtx, collection, indexNames);
+
+ // Now that the abort signals were sent to the intended index builders, release our lock
+ // temporarily to allow the index builders to process the abort signal. Holding a lock here
+ // will cause the index builders to block indefinitely.
+ autoColl = boost::none;
+ if (abortedIndexBuilders.size() == 1) {
+ indexBuildsCoord->awaitIndexBuildFinished(collectionUUID, abortedIndexBuilders.front());
+ } else if (abortedIndexBuilders.size() > 1) {
+ // Only the "*" wildcard can abort multiple index builders.
+ invariant(isWildcard);
+ indexBuildsCoord->awaitNoIndexBuildInProgressForCollection(collectionUUID);
+ }
+
+ // Take an exclusive lock on the collection now to be able to perform index catalog writes
+ // when removing ready indexes from disk.
+ autoColl.emplace(opCtx, nssOrUUID, MODE_X);
+
+ // Abandon the snapshot as the index catalog will compare the in-memory state to the disk
+ // state, which may have changed when we released the lock temporarily.
+ opCtx->recoveryUnit()->abandonSnapshot();
+
+ db = autoColl->getDb();
+ collection = autoColl->getCollection();
+ if (!collection) {
+ return {ErrorCodes::NamespaceNotFound,
+ str::stream() << "Collection not found on database " << nss.db()
+ << " with UUID " << collectionUUID};
+ }
+
+ // Check to see if a new index build was started that the caller requested to be aborted.
+ bool abortAgain = false;
+ if (isWildcard) {
+ abortAgain = indexBuildsCoord->inProgForCollection(collectionUUID);
+ } else {
+ abortAgain = indexBuildsCoord->hasIndexBuilder(opCtx, collectionUUID, indexNames);
+ }
+
+ if (!abortAgain) {
+ break;
+ }
+
+ // We only need to hold an intent lock to send abort signals to the active index
+ // builder(s) we intend to abort.
+ autoColl = boost::none;
+ autoColl.emplace(opCtx, nssOrUUID, MODE_IX);
+
+ // Abandon the snapshot as the index catalog will compare the in-memory state to the
+ // disk state, which may have changed when we released the lock temporarily.
+ opCtx->recoveryUnit()->abandonSnapshot();
+
+ db = autoColl->getDb();
+ collection = autoColl->getCollection();
+ if (!collection) {
+ return {ErrorCodes::NamespaceNotFound,
+ str::stream() << "Collection not found on database " << nss.db()
+ << " with UUID " << collectionUUID};
+ }
}
- return Status(ErrorCodes::IndexNotFound,
- str::stream() << "invalid index name spec: " << indexElem.toString(false));
-}
+ // If the "*" wildcard was not specified, verify that all the index names belonging to the
+ // index builder were aborted. If not, they must be ready, so we drop them.
+ if (!isWildcard && !abortedIndexBuilders.empty()) {
+ invariant(abortedIndexBuilders.size() == 1);
+
+ return writeConflictRetry(
+ opCtx, "dropIndexes", nss.db(), [opCtx, &collection, &indexNames, result] {
+ WriteUnitOfWork wunit(opCtx);
+
+ // This is necessary to check shard version.
+ OldClientContext ctx(opCtx, collection->ns().ns());
+
+ size_t numReady = 0;
+ const bool includeUnfinished = false;
+ IndexCatalog* indexCatalog = collection->getIndexCatalog();
+ for (const auto& indexName : indexNames) {
+ const IndexDescriptor* desc =
+ indexCatalog->findIndexByName(opCtx, indexName, includeUnfinished);
+ if (!desc) {
+ // The given index name was successfully aborted.
+ continue;
+ }
+
+ Status status = dropIndexByDescriptor(opCtx, collection, indexCatalog, desc);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ numReady++;
+ }
+
+ invariant(numReady == 0 || numReady == indexNames.size());
+
+ wunit.commit();
+ return Status::OK();
+ });
+ }
-} // namespace
+ if (!abortedIndexBuilders.empty()) {
+ // All the index builders were sent the abort signal, remove all the remaining indexes in
+ // the index catalog.
+ invariant(isWildcard);
+ invariant(indexNames.size() == 1);
+ invariant(indexNames.front() == "*");
+ invariant(collection->getIndexCatalog()->numIndexesInProgress(opCtx) == 0);
+ } else {
+ // The index catalog requires that no active index builders are running when dropping
+ // indexes.
+ BackgroundOperation::assertNoBgOpInProgForNs(collection->ns());
+ IndexBuildsCoordinator::get(opCtx)->assertNoIndexBuildInProgForCollection(collectionUUID);
+ }
-Status dropIndexes(OperationContext* opCtx,
- const NamespaceString& nss,
- const BSONObj& cmdObj,
- BSONObjBuilder* result) {
+ return writeConflictRetry(
+ opCtx, "dropIndexes", nss.db(), [opCtx, &collection, &indexNames, result] {
+ WriteUnitOfWork wunit(opCtx);
+
+ // This is necessary to check shard version.
+ OldClientContext ctx(opCtx, collection->ns().ns());
+
+ // Use an empty BSONObjBuilder to avoid duplicate appends to result on retry loops.
+ BSONObjBuilder tempObjBuilder;
+ Status status = dropReadyIndexes(opCtx, collection, indexNames, &tempObjBuilder);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ wunit.commit();
+
+ result->appendElementsUnique(
+ tempObjBuilder.done()); // This append will only happen once.
+ return Status::OK();
+ });
+}
+
+Status dropIndexesForApplyOps(OperationContext* opCtx,
+ const NamespaceString& nss,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) {
return writeConflictRetry(opCtx, "dropIndexes", nss.db(), [opCtx, &nss, &cmdObj, result] {
AutoGetCollection autoColl(opCtx, nss, MODE_X);
- bool userInitiatedWritesAndNotPrimary = opCtx->writesAreReplicated() &&
- !repl::ReplicationCoordinator::get(opCtx)->canAcceptWritesFor(opCtx, nss);
-
- if (userInitiatedWritesAndNotPrimary) {
- return Status(ErrorCodes::NotMaster,
- str::stream() << "Not primary while dropping indexes in " << nss);
+ // If db/collection does not exist, short circuit and return.
+ Database* db = autoColl.getDb();
+ Collection* collection = autoColl.getCollection();
+ Status status = checkForViewOrMissingNS(opCtx, nss, db, collection);
+ if (!status.isOK()) {
+ return status;
}
if (!serverGlobalParams.quiet.load()) {
@@ -229,22 +485,16 @@ Status dropIndexes(OperationContext* opCtx,
"cmdObj_kIndexFieldName_false"_attr = cmdObj[kIndexFieldName].toString(false));
}
- // If db/collection does not exist, short circuit and return.
- Database* db = autoColl.getDb();
- Collection* collection = autoColl.getCollection();
- if (!collection) {
- if (db && ViewCatalog::get(db)->lookup(opCtx, nss.ns())) {
- return Status(ErrorCodes::CommandNotSupportedOnView,
- str::stream() << "Cannot drop indexes on view " << nss);
- }
-
- return Status(ErrorCodes::NamespaceNotFound, "ns not found");
- }
-
BackgroundOperation::assertNoBgOpInProgForNs(nss);
IndexBuildsCoordinator::get(opCtx)->assertNoIndexBuildInProgForCollection(
collection->uuid());
+ BSONElement indexElem = cmdObj.getField(kIndexFieldName);
+ auto swIndexNames = getIndexNames(opCtx, collection, indexElem);
+ if (!swIndexNames.isOK()) {
+ return swIndexNames.getStatus();
+ }
+
WriteUnitOfWork wunit(opCtx);
// This is necessary to check shard version.
@@ -252,7 +502,7 @@ Status dropIndexes(OperationContext* opCtx,
// Use an empty BSONObjBuilder to avoid duplicate appends to result on retry loops.
BSONObjBuilder tempObjBuilder;
- Status status = wrappedRun(opCtx, collection, cmdObj, &tempObjBuilder);
+ status = dropReadyIndexes(opCtx, collection, swIndexNames.getValue(), &tempObjBuilder);
if (!status.isOK()) {
return status;
}