From ac6d01aeea9a7ee989b7d1bc879d25e18c5b32a6 Mon Sep 17 00:00:00 2001 From: Dianna Hohensee Date: Thu, 1 Nov 2018 13:41:57 -0400 Subject: SERVER-37636 Establish an index builds interface through which to access and affect index builds --- src/mongo/SConscript | 1 + src/mongo/db/SConscript | 26 + src/mongo/db/catalog/SConscript | 22 + src/mongo/db/catalog/index_builds_manager.cpp | 174 ++++++ src/mongo/db/catalog/index_builds_manager.h | 179 +++++++ src/mongo/db/catalog/index_builds_manager_test.cpp | 101 ++++ src/mongo/db/catalog/multi_index_block.h | 1 + src/mongo/db/catalog/multi_index_block_impl.h | 3 - src/mongo/db/collection_index_builds_tracker.cpp | 109 ++++ src/mongo/db/collection_index_builds_tracker.h | 112 ++++ src/mongo/db/database_index_builds_tracker.cpp | 78 +++ src/mongo/db/database_index_builds_tracker.h | 102 ++++ src/mongo/db/db.cpp | 6 + src/mongo/db/index_builds_coordinator.cpp | 585 +++++++++++++++++++++ src/mongo/db/index_builds_coordinator.h | 438 +++++++++++++++ src/mongo/db/index_builds_coordinator_test.cpp | 323 ++++++++++++ src/mongo/db/repl_index_build_state.h | 123 +++++ 17 files changed, 2380 insertions(+), 3 deletions(-) create mode 100644 src/mongo/db/catalog/index_builds_manager.cpp create mode 100644 src/mongo/db/catalog/index_builds_manager.h create mode 100644 src/mongo/db/catalog/index_builds_manager_test.cpp create mode 100644 src/mongo/db/collection_index_builds_tracker.cpp create mode 100644 src/mongo/db/collection_index_builds_tracker.h create mode 100644 src/mongo/db/database_index_builds_tracker.cpp create mode 100644 src/mongo/db/database_index_builds_tracker.h create mode 100644 src/mongo/db/index_builds_coordinator.cpp create mode 100644 src/mongo/db/index_builds_coordinator.h create mode 100644 src/mongo/db/index_builds_coordinator_test.cpp create mode 100644 src/mongo/db/repl_index_build_state.h diff --git a/src/mongo/SConscript b/src/mongo/SConscript index 8dff7dd8bc4..9998f435060 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -341,6 +341,7 @@ mongod = env.Program( 'db/ftdc/ftdc_mongod', 'db/fts/ftsmongod', 'db/index/index_access_method', + 'db/index_builds_coordinator', 'db/index/index_descriptor', 'db/index_d', 'db/initialize_snmp', diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index 8a71699022b..ec14f2c6113 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -891,6 +891,32 @@ env.Library( ], ) +env.Library( + target="index_builds_coordinator", + source=[ + "collection_index_builds_tracker.cpp", + "database_index_builds_tracker.cpp", + "index_builds_coordinator.cpp", + ], + LIBDEPS=[ + "$BUILD_DIR/mongo/base", + '$BUILD_DIR/mongo/db/catalog_raii', + "$BUILD_DIR/mongo/db/catalog/index_builds_manager", + "$BUILD_DIR/mongo/util/concurrency/thread_pool", + ], +) + +env.CppUnitTest( + target="index_builds_coordinator_test", + source=[ + "index_builds_coordinator_test.cpp", + ], + LIBDEPS=[ + "$BUILD_DIR/mongo/db/catalog/catalog_test_fixture", + "index_builds_coordinator", + ] +) + env.Library( target="cloner", source=[ diff --git a/src/mongo/db/catalog/SConscript b/src/mongo/db/catalog/SConscript index 06ef36e5bce..2e3fb91c150 100644 --- a/src/mongo/db/catalog/SConscript +++ b/src/mongo/db/catalog/SConscript @@ -102,6 +102,28 @@ env.Library( ], ); +env.Library( + target='index_builds_manager', + source=[ + 'index_builds_manager.cpp', + ], + LIBDEPS=[ + 'multi_index_block', + '$BUILD_DIR/mongo/base', + ], +) + +env.CppUnitTest( + target='index_builds_manager_test', + source=[ + 'index_builds_manager_test.cpp', + ], + LIBDEPS=[ + 'catalog_test_fixture', + 'index_builds_manager', + ] +) + env.Library( target='index_key_validate', source=[ diff --git a/src/mongo/db/catalog/index_builds_manager.cpp b/src/mongo/db/catalog/index_builds_manager.cpp new file mode 100644 index 00000000000..15138cae0df --- /dev/null +++ b/src/mongo/db/catalog/index_builds_manager.cpp @@ -0,0 +1,174 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kStorage + +#include "mongo/platform/basic.h" + +#include "mongo/db/catalog/index_builds_manager.h" + +#include "mongo/db/catalog/collection.h" +#include "mongo/db/catalog/multi_index_block.h" +#include "mongo/db/catalog/multi_index_block_impl.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/service_context.h" +#include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + +using std::shared_ptr; + +IndexBuildsManager::~IndexBuildsManager() { + invariant(_builders.empty()); +} + +Status IndexBuildsManager::setUpIndexBuild(OperationContext* opCtx, + Collection* collection, + const NamespaceString& nss, + const std::vector& specs, + const UUID& buildUUID) { + _registerIndexBuild(opCtx, collection, buildUUID); + + // TODO: Not yet implemented. + + return Status::OK(); +} + +StatusWith IndexBuildsManager::recoverIndexBuild( + const NamespaceString& nss, const UUID& buildUUID, std::vector indexNames) { + + // TODO: Not yet implemented. + + return IndexBuildRecoveryState::Building; +} + +Status IndexBuildsManager::startBuildingIndex(const UUID& buildUUID) { + auto multiIndexBlockPtr = _getBuilder(buildUUID); + // TODO: verify that the index builder is in the expected state. + + // TODO: Not yet implemented. + + return Status::OK(); +} + +Status IndexBuildsManager::finishbBuildingPhase(const UUID& buildUUID) { + auto multiIndexBlockPtr = _getBuilder(buildUUID); + // TODO: verify that the index builder is in the expected state. + + // TODO: Not yet implemented. + + return Status::OK(); +} + +Status IndexBuildsManager::checkIndexConstraintViolations(const UUID& buildUUID) { + auto multiIndexBlockPtr = _getBuilder(buildUUID); + // TODO: verify that the index builder is in the expected state. + + // TODO: Not yet implemented. + + return Status::OK(); +} + +Status IndexBuildsManager::finishConstraintPhase(const UUID& buildUUID) { + auto multiIndexBlockPtr = _getBuilder(buildUUID); + // TODO: verify that the index builder is in the expected state. + + // TODO: Not yet implemented. + + return Status::OK(); +} + +Status IndexBuildsManager::commitIndexBuild(const UUID& buildUUID) { + auto multiIndexBlockPtr = _getBuilder(buildUUID); + // TODO: verify that the index builder is in the expected state. + + // TODO: Not yet implemented. + + return Status::OK(); +} + +bool IndexBuildsManager::abortIndexBuild(const UUID& buildUUID, const std::string& reason) { + stdx::unique_lock lk(_mutex); + + auto builderIt = _builders.find(buildUUID); + if (builderIt == _builders.end()) { + return false; + } + builderIt->second->abort(reason); + return true; +} + +bool IndexBuildsManager::interruptIndexBuild(const UUID& buildUUID, const std::string& reason) { + stdx::unique_lock lk(_mutex); + + auto builderIt = _builders.find(buildUUID); + if (builderIt == _builders.end()) { + return false; + } + + // TODO: Not yet implemented. + return true; +} + +void IndexBuildsManager::tearDownIndexBuild(const UUID& buildUUID) { + // TODO verify that the index builder is in a finished state before allowing its destruction. + _unregisterIndexBuild(buildUUID); +} + +void IndexBuildsManager::verifyNoIndexBuilds_forTestOnly() { + invariant(_builders.empty()); +} + +void IndexBuildsManager::_registerIndexBuild(OperationContext* opCtx, + Collection* collection, + UUID buildUUID) { + stdx::unique_lock lk(_mutex); + + std::shared_ptr mib = + std::make_shared(opCtx, collection); + invariant(_builders.insert(std::make_pair(buildUUID, mib)).second); +} + +void IndexBuildsManager::_unregisterIndexBuild(const UUID& buildUUID) { + stdx::unique_lock lk(_mutex); + + auto builderIt = _builders.find(buildUUID); + invariant(builderIt != _builders.end()); + _builders.erase(builderIt); +} + +std::shared_ptr IndexBuildsManager::_getBuilder(const UUID& buildUUID) { + stdx::unique_lock lk(_mutex); + auto builderIt = _builders.find(buildUUID); + invariant(builderIt != _builders.end()); + return builderIt->second; +} + +} // namespace mongo diff --git a/src/mongo/db/catalog/index_builds_manager.h b/src/mongo/db/catalog/index_builds_manager.h new file mode 100644 index 00000000000..fed73c8fe85 --- /dev/null +++ b/src/mongo/db/catalog/index_builds_manager.h @@ -0,0 +1,179 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include +#include +#include + +#include "mongo/base/disallow_copying.h" +#include "mongo/db/namespace_string.h" +#include "mongo/stdx/mutex.h" + +namespace mongo { + +class Collection; +class MultiIndexBlock; +class OperationContext; +class ServiceContext; + +enum IndexBuildRecoveryState { Building, Verifying, Committing }; + +/** + * This is the interface through which to act on index builders. Index builder life times are + * managed here, and all actions taken on index builders pass through this interface. Index builder + * state is set up and then cleaned up by this class. + */ +class IndexBuildsManager { + MONGO_DISALLOW_COPYING(IndexBuildsManager); + +public: + IndexBuildsManager() = default; + ~IndexBuildsManager(); + + /** + * Sets up the index build state and registers it in the manager. + * + * TODO: Not yet implemented. Only instantiates and registers a builder in the manager. Does not + * set up index build state. + */ + Status setUpIndexBuild(OperationContext* opCtx, + Collection* collection, + const NamespaceString& nss, + const std::vector& specs, + const UUID& buildUUID); + + /** + * Recovers the index build from its persisted state and sets it up to run again. + * + * Returns an enum reflecting the point up to which the build was recovered, so the caller knows + * where to recommence. + * + * TODO: Not yet implemented. + */ + StatusWith recoverIndexBuild(const NamespaceString& nss, + const UUID& buildUUID, + std::vector indexNames); + + /** + * Runs the scanning/insertion phase of the index build.. + * + * TODO: Not yet implemented. + */ + Status startBuildingIndex(const UUID& buildUUID); + + /** + * Persists information in the index catalog entry to reflect the successful completion of the + * scanning/insertion phase. + * + * TODO: Not yet implemented. + */ + Status finishbBuildingPhase(const UUID& buildUUID); + + /** + * Runs the index constraint violation checking phase of the index build.. + * + * TODO: Not yet implemented. + */ + Status checkIndexConstraintViolations(const UUID& buildUUID); + + /** + * Persists information in the index catalog entry to reflect the successful completion of the + * index constraint violation checking phase.. + * + * TODO: Not yet implemented. + */ + Status finishConstraintPhase(const UUID& buildUUID); + + /** + * Persists information in the index catalog entry that the index is ready for use, as well as + * updating the in-memory index catalog entry for this index to ready. + * + * TODO: Not yet implemented. + */ + Status commitIndexBuild(const UUID& buildUUID); + + /** + * Signals the index build to be aborted and returns without waiting for completion. + * + * Returns true if a build existed to be signaled, as opposed to having already finished and + * been cleared away, or not having yet started.. + * + * TODO: Not yet fully implemented. The MultiIndexBlock::abort function that is called is + * not yet implemented. + */ + bool abortIndexBuild(const UUID& buildUUID, const std::string& reason); + + /** + * Signals the index build to be interrupted and returns without waiting for it to stop. Does + * nothing if the index build has already been cleared away. + * + * Returns true if a build existed to be signaled, as opposed to having already finished and + * been cleared away, or not having yet started.. + * + * TODO: Not yet implemented. + */ + bool interruptIndexBuild(const UUID& buildUUID, const std::string& reason); + + /** + * Cleans up the index build state and unregisters it from the manager. + */ + void tearDownIndexBuild(const UUID& buildUUID); + + /** + * Checks via invariant that the manager has no index builds presently. + */ + void verifyNoIndexBuilds_forTestOnly(); + +private: + /** + * Creates and registers a new builder in the _builders map, mapped by the provided buildUUID. + */ + void _registerIndexBuild(OperationContext* opCtx, Collection* collection, UUID buildUUID); + + /** + * Unregisters the builder associcated with the given buildUUID from the _builders map. + */ + void _unregisterIndexBuild(const UUID& buildUUID); + + /** + * Returns a shared pointer to the builder. Invariants if the builder does not exist. + */ + std::shared_ptr _getBuilder(const UUID& buildUUID); + + // Protects the map data structures below. + mutable stdx::mutex _mutex; + + // Map of index builders by build UUID. Allows access to the builders so that actions can be + // taken on and information passed to and from index builds. + std::map> _builders; +}; + +} // namespace mongo diff --git a/src/mongo/db/catalog/index_builds_manager_test.cpp b/src/mongo/db/catalog/index_builds_manager_test.cpp new file mode 100644 index 00000000000..90f78e013c7 --- /dev/null +++ b/src/mongo/db/catalog/index_builds_manager_test.cpp @@ -0,0 +1,101 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/catalog/index_builds_manager.h" + +#include "mongo/db/catalog/catalog_test_fixture.h" +#include "mongo/db/catalog/multi_index_block.h" +#include "mongo/db/catalog/multi_index_block_impl.h" +#include "mongo/db/catalog_raii.h" +#include "mongo/db/namespace_string.h" +#include "mongo/db/operation_context.h" +#include "mongo/util/uuid.h" + +namespace mongo { + +using unittest::log; + +namespace { + +class IndexBuildsManagerTest : public CatalogTestFixture { +private: + void setUp() override; + void tearDown() override; + +public: + void createCollection(const NamespaceString& nss); + + const UUID _buildUUID = UUID::gen(); + const NamespaceString _nss = NamespaceString("test.foo"); + IndexBuildsManager _indexBuildsManager; +}; + +void IndexBuildsManagerTest::setUp() { + CatalogTestFixture::setUp(); + createCollection(_nss); +} + +void IndexBuildsManagerTest::tearDown() { + _indexBuildsManager.verifyNoIndexBuilds_forTestOnly(); + // All databases are dropped during tear down. + CatalogTestFixture::tearDown(); +} + +void IndexBuildsManagerTest::createCollection(const NamespaceString& nss) { + ASSERT_OK(storageInterface()->createCollection(operationContext(), nss, CollectionOptions())); +} + +std::vector makeSpecs(const NamespaceString& nss, std::vector keys) { + ASSERT(keys.size()); + std::vector indexSpecs; + for (auto keyName : keys) { + indexSpecs.push_back(BSON("ns" << nss.toString() << "v" << 2 << "key" << BSON(keyName << 1) + << "name" + << (keyName + "_1"))); + } + return indexSpecs; +} + +TEST_F(IndexBuildsManagerTest, IndexBuildsManagerSetUpAndTearDown) { + AutoGetCollection autoColl(operationContext(), _nss, MODE_X); + + ASSERT_OK(_indexBuildsManager.setUpIndexBuild(operationContext(), + autoColl.getCollection(), + _nss, + makeSpecs(_nss, {"a", "b"}), + _buildUUID)); + + _indexBuildsManager.tearDownIndexBuild(_buildUUID); +} + +} // namespace + +} // namespace mongo diff --git a/src/mongo/db/catalog/multi_index_block.h b/src/mongo/db/catalog/multi_index_block.h index 70d2d4f0ce9..b2468a15c9b 100644 --- a/src/mongo/db/catalog/multi_index_block.h +++ b/src/mongo/db/catalog/multi_index_block.h @@ -214,4 +214,5 @@ public: virtual bool getBuildInBackground() const = 0; }; + } // namespace mongo diff --git a/src/mongo/db/catalog/multi_index_block_impl.h b/src/mongo/db/catalog/multi_index_block_impl.h index a1c7f726cba..ab008f9692c 100644 --- a/src/mongo/db/catalog/multi_index_block_impl.h +++ b/src/mongo/db/catalog/multi_index_block_impl.h @@ -56,9 +56,6 @@ class MultiIndexBlockImpl : public MultiIndexBlock { MONGO_DISALLOW_COPYING(MultiIndexBlockImpl); public: - /** - * Neither pointer is owned. - */ MultiIndexBlockImpl(OperationContext* opCtx, Collection* collection); ~MultiIndexBlockImpl() override; diff --git a/src/mongo/db/collection_index_builds_tracker.cpp b/src/mongo/db/collection_index_builds_tracker.cpp new file mode 100644 index 00000000000..58b3fef7b1b --- /dev/null +++ b/src/mongo/db/collection_index_builds_tracker.cpp @@ -0,0 +1,109 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/collection_index_builds_tracker.h" + +#include "mongo/db/catalog/index_builds_manager.h" + +namespace mongo { + +CollectionIndexBuildsTracker::~CollectionIndexBuildsTracker() { + invariant(_buildStateByBuildUUID.empty()); + invariant(_buildStateByIndexName.empty()); +} + +void CollectionIndexBuildsTracker::addIndexBuild( + WithLock, std::shared_ptr replIndexBuildState) { + // Ensure that a new entry is added. + invariant( + _buildStateByBuildUUID.emplace(replIndexBuildState->buildUUID, replIndexBuildState).second); + + invariant(replIndexBuildState->indexNames.size()); + for (auto& indexName : replIndexBuildState->indexNames) { + // Ensure that a new entry is added. + invariant(_buildStateByIndexName.emplace(indexName, replIndexBuildState).second); + } +} + +void CollectionIndexBuildsTracker::removeIndexBuild( + WithLock, std::shared_ptr replIndexBuildState) { + invariant(_buildStateByBuildUUID.find(replIndexBuildState->buildUUID) != + _buildStateByBuildUUID.end()); + _buildStateByBuildUUID.erase(replIndexBuildState->buildUUID); + + for (const auto& indexName : replIndexBuildState->indexNames) { + invariant(_buildStateByIndexName.find(indexName) != _buildStateByIndexName.end()); + _buildStateByIndexName.erase(indexName); + } + + if (_buildStateByBuildUUID.empty()) { + _noIndexBuildsRemainCondVar.notify_all(); + } +} + +std::shared_ptr CollectionIndexBuildsTracker::getIndexBuildState( + WithLock, StringData indexName) const { + auto it = _buildStateByIndexName.find(indexName.toString()); + invariant(it != _buildStateByIndexName.end()); + return it->second; +} + +bool CollectionIndexBuildsTracker::hasIndexBuildState(WithLock, StringData indexName) const { + auto it = _buildStateByIndexName.find(indexName.toString()); + if (it == _buildStateByIndexName.end()) { + return false; + } + return true; +} + +void CollectionIndexBuildsTracker::runOperationOnAllBuilds( + WithLock lk, + IndexBuildsManager* indexBuildsManager, + std::function replIndexBuildState, + const std::string& reason)> func, + const std::string& reason) noexcept { + for (auto it = _buildStateByBuildUUID.begin(); it != _buildStateByBuildUUID.end(); ++it) { + func(lk, indexBuildsManager, it->second, reason); + } +} + +int CollectionIndexBuildsTracker::getNumberOfIndexBuilds(WithLock) const { + return _buildStateByBuildUUID.size(); +} + +void CollectionIndexBuildsTracker::waitUntilNoIndexBuildsRemain( + stdx::unique_lock& lk) { + _noIndexBuildsRemainCondVar.wait(lk, [&] { return _buildStateByBuildUUID.empty(); }); +} + +} // namespace mongo diff --git a/src/mongo/db/collection_index_builds_tracker.h b/src/mongo/db/collection_index_builds_tracker.h new file mode 100644 index 00000000000..10b4f833f7c --- /dev/null +++ b/src/mongo/db/collection_index_builds_tracker.h @@ -0,0 +1,112 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include +#include + +#include "mongo/base/disallow_copying.h" +#include "mongo/db/repl_index_build_state.h" +#include "mongo/stdx/condition_variable.h" +#include "mongo/util/concurrency/with_lock.h" +#include "mongo/util/uuid.h" + +namespace mongo { + +class IndexBuildsManager; + +/** + * Tracks index builds for a particular collection. Provides access to index build information by + * collection and index name. Can be used to act on all index builds for a collection, wait upon the + * completion of all index builds, and provide collection level index build information. + * + * The owner of a CollectionIndexBuildsTracker instance must instantiate a mutex to use along with + * the data structure to ensure it remains consistent across single or multiple function accesses. + * + * This is intended to only be used by the IndexBuildsCoordinator class. + */ +class CollectionIndexBuildsTracker { + MONGO_DISALLOW_COPYING(CollectionIndexBuildsTracker); + +public: + CollectionIndexBuildsTracker() = default; + ~CollectionIndexBuildsTracker(); + + /** + * Starts tracking the specified index build on the collection. + */ + void addIndexBuild(WithLock, std::shared_ptr buildInfo); + + /** + * Stops tracking the specified index build on the collection. + */ + void removeIndexBuild(WithLock, std::shared_ptr buildInfo); + + std::shared_ptr getIndexBuildState(WithLock, StringData indexName) const; + + bool hasIndexBuildState(WithLock, StringData indexName) const; + + /** + * Runs the provided function operation on all this collection's index builds + * + * 'func' returns void and must not throw. It should not have failure state to return. + */ + void runOperationOnAllBuilds( + WithLock, + IndexBuildsManager* indexBuildsManager, + std::function replIndexBuildState, + const std::string& reason)> func, + const std::string& reason) noexcept; + + /** + * Note that this is the number of index builders, and that each index builder can be building + * several indexes. + */ + int getNumberOfIndexBuilds(WithLock) const; + + /** + * Returns when no index builds remain on this collection. + */ + void waitUntilNoIndexBuildsRemain(stdx::unique_lock& lk); + +private: + // Maps of index build states on the collection, by build UUID and index name. + stdx::unordered_map, UUID::Hash> + _buildStateByBuildUUID; + stdx::unordered_map> _buildStateByIndexName; + + // Condition variable that is signaled when there are no active index builds remaining on the + // collection. + stdx::condition_variable _noIndexBuildsRemainCondVar; +}; + +} // namespace mongo diff --git a/src/mongo/db/database_index_builds_tracker.cpp b/src/mongo/db/database_index_builds_tracker.cpp new file mode 100644 index 00000000000..6e31c8ffeff --- /dev/null +++ b/src/mongo/db/database_index_builds_tracker.cpp @@ -0,0 +1,78 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/database_index_builds_tracker.h" + +#include "mongo/db/catalog/index_builds_manager.h" + +namespace mongo { + +DatabaseIndexBuildsTracker::~DatabaseIndexBuildsTracker() { + invariant(_allIndexBuilds.empty()); +} + +void DatabaseIndexBuildsTracker::addIndexBuild( + WithLock, std::shared_ptr replIndexBuildState) { + invariant(_allIndexBuilds.insert({replIndexBuildState->buildUUID, replIndexBuildState}).second); +} + +void DatabaseIndexBuildsTracker::removeIndexBuild(WithLock, const UUID& buildUUID) { + auto it = _allIndexBuilds.find(buildUUID); + invariant(it != _allIndexBuilds.end()); + _allIndexBuilds.erase(it); + + if (_allIndexBuilds.empty()) { + _noIndexBuildsRemainCondVar.notify_all(); + } +} + +void DatabaseIndexBuildsTracker::runOperationOnAllBuilds( + WithLock lk, + IndexBuildsManager* indexBuildsManager, + std::function replIndexBuildState, + const std::string& reason)> func, + const std::string& reason) { + for (auto it = _allIndexBuilds.begin(); it != _allIndexBuilds.end(); ++it) { + func(lk, indexBuildsManager, it->second, reason); + } +} + +int DatabaseIndexBuildsTracker::getNumberOfIndexBuilds(WithLock) const { + return _allIndexBuilds.size(); +} + +void DatabaseIndexBuildsTracker::waitUntilNoIndexBuildsRemain(stdx::unique_lock& lk) { + _noIndexBuildsRemainCondVar.wait(lk, [&] { return _allIndexBuilds.empty(); }); +} + +} // namespace mongo diff --git a/src/mongo/db/database_index_builds_tracker.h b/src/mongo/db/database_index_builds_tracker.h new file mode 100644 index 00000000000..000a58d9bde --- /dev/null +++ b/src/mongo/db/database_index_builds_tracker.h @@ -0,0 +1,102 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include +#include + +#include "mongo/base/disallow_copying.h" +#include "mongo/db/repl_index_build_state.h" +#include "mongo/stdx/condition_variable.h" +#include "mongo/util/concurrency/with_lock.h" +#include "mongo/util/uuid.h" + +namespace mongo { + +class IndexBuildsManager; + +/** + * Tracks index builds for a particular database. Can be used to act on all index builds in the + * database, wait upon the completion of all index build for the database, and provide database + * level index build information. + * + * The owner of a DatabaseIndexBuildsTracker instance must instantiate a mutex to use along with the + * data structure to ensure it remains consistent across single or multiple function accesses. + * + * This is intended to only be used by the IndexBuildsCoordinator class. + */ +class DatabaseIndexBuildsTracker { +public: + DatabaseIndexBuildsTracker() = default; + ~DatabaseIndexBuildsTracker(); + + /** + * Starts tracking the specified index build on the database. + */ + void addIndexBuild(WithLock, std::shared_ptr buildInfo); + + /** + * Stops tracking the specified index build on the database. + */ + void removeIndexBuild(WithLock, const UUID& buildUUID); + + /** + * Runs the provided function operation on all this database's index builds. + */ + void runOperationOnAllBuilds( + WithLock, + IndexBuildsManager* indexBuildsManager, + std::function replIndexBuildState, + const std::string& reason)> func, + const std::string& reason); + + /** + * Note that this is the number of index builders, and that each index builder can be building + * several indexes. + */ + int getNumberOfIndexBuilds(WithLock) const; + + /** + * Returns when no index builds remain on this database. + */ + void waitUntilNoIndexBuildsRemain(stdx::unique_lock& lk); + +private: + // Map of index build states on the database, by build UUID. + stdx::unordered_map, UUID::Hash> _allIndexBuilds; + + // Condition variable that is signaled when there are no active index builds remaining on the + // database. + stdx::condition_variable _noIndexBuildsRemainCondVar; +}; + +} // namespace mongo diff --git a/src/mongo/db/db.cpp b/src/mongo/db/db.cpp index edaa8416ab8..69471e9e65b 100644 --- a/src/mongo/db/db.cpp +++ b/src/mongo/db/db.cpp @@ -72,6 +72,7 @@ #include "mongo/db/free_mon/free_mon_mongod.h" #include "mongo/db/ftdc/ftdc_mongod.h" #include "mongo/db/global_settings.h" +#include "mongo/db/index_builds_coordinator.h" #include "mongo/db/index_names.h" #include "mongo/db/index_rebuilder.h" #include "mongo/db/initialize_server_global_state.h" @@ -908,6 +909,11 @@ void shutdownTask() { killSessionsLocalShutdownAllTransactions(opCtx); } + // Interrupts all index builds, leaving the state intact to be recovered when the server + // restarts. This should be done after replication oplog application finishes, so foreground + // index builds begun by replication on secondaries do not invariant. + IndexBuildsCoordinator::get(serviceContext)->shutdown(); + serviceContext->setKillAllOperations(); ReplicaSetMonitor::shutdown(); diff --git a/src/mongo/db/index_builds_coordinator.cpp b/src/mongo/db/index_builds_coordinator.cpp new file mode 100644 index 00000000000..624efe9c34f --- /dev/null +++ b/src/mongo/db/index_builds_coordinator.cpp @@ -0,0 +1,585 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kStorage + +#include "mongo/platform/basic.h" + +#include "mongo/db/index_builds_coordinator.h" + +#include "mongo/db/catalog/uuid_catalog.h" +#include "mongo/db/catalog_raii.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/service_context.h" +#include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + +namespace { + +/** + * Constructs the options for the loader thread pool. + */ +ThreadPool::Options makeDefaultThreadPoolOptions() { + ThreadPool::Options options; + options.poolName = "IndexBuildsCoordinator"; + options.minThreads = 0; + options.maxThreads = 10; + + // Ensure all threads have a client. + options.onCreateThread = [](const std::string& threadName) { + Client::initThread(threadName.c_str()); + }; + + return options; +} + +/** + * Returns the collection UUID for the given 'nss', or a NamespaceNotFound error. + * + * Momentarily takes the collection IS lock for 'nss' to access the collection UUID. + */ +StatusWith getCollectionUUID(OperationContext* opCtx, const NamespaceString& nss) { + try { + AutoGetCollection autoColl(opCtx, nss, MODE_IS); + return autoColl.getCollection()->uuid().get(); + } catch (const DBException& ex) { + invariant(ex.toStatus().code() == ErrorCodes::NamespaceNotFound); + return ex.toStatus(); + } +} + +/** + * Aborts the index build identified by the provided 'replIndexBuildState'. + * + * Sets a signal on the coordinator's repl index build state if the builder does not yet exist in + * the manager. + */ +void abortIndexBuild(WithLock lk, + IndexBuildsManager* indexBuildsManager, + std::shared_ptr replIndexBuildState, + const std::string& reason) { + bool res = indexBuildsManager->abortIndexBuild(replIndexBuildState->buildUUID, reason); + if (res) { + return; + } + // The index builder was not found in the manager, so it only exists in the coordinator. In this + // case, set the abort signal on the coordinator index build state. + replIndexBuildState->aborted = true; + replIndexBuildState->abortReason = reason; +} + +} // namespace + +const auto getIndexBuildsCoord = ServiceContext::declareDecoration(); + +IndexBuildsCoordinator* IndexBuildsCoordinator::get(ServiceContext* serviceContext) { + return &getIndexBuildsCoord(serviceContext); +} + +IndexBuildsCoordinator* IndexBuildsCoordinator::get(OperationContext* operationContext) { + return get(operationContext->getServiceContext()); +} + +IndexBuildsCoordinator::IndexBuildsCoordinator() : _threadPool(makeDefaultThreadPoolOptions()) { + _threadPool.startup(); +} + +IndexBuildsCoordinator::~IndexBuildsCoordinator() { + invariant(_databaseIndexBuilds.empty()); + invariant(_disallowedDbs.empty()); + invariant(_disallowedCollections.empty()); + invariant(_collectionIndexBuilds.empty()); +} + +void IndexBuildsCoordinator::shutdown() { + // Stop new scheduling. + _threadPool.shutdown(); + + // Signal active builds to stop and wait for them to stop. + interruptAllIndexBuilds("Index build interrupted due to shutdown."); + + // Wait for active threads to finish. + _threadPool.join(); +} + +StatusWith> IndexBuildsCoordinator::buildIndex(OperationContext* opCtx, + const NamespaceString& nss, + const std::vector& specs, + const UUID& buildUUID) { + std::vector indexNames; + for (auto& spec : specs) { + std::string name = spec.getStringField(IndexDescriptor::kIndexNameFieldName); + if (name.empty()) { + return Status( + ErrorCodes::CannotCreateIndex, + str::stream() << "Cannot create an index for a spec '" << spec + << "' without a non-empty string value for the 'name' field"); + } + indexNames.push_back(name); + } + + UUID collectionUUID = [&] { + AutoGetCollection autoColl(opCtx, nss, MODE_IS); + return autoColl.getCollection()->uuid().get(); + }(); + + auto pf = makePromiseFuture(); + + auto replIndexBuildState = std::make_shared( + buildUUID, collectionUUID, indexNames, specs, std::move(pf.promise)); + + Status status = _registerIndexBuild(opCtx, replIndexBuildState); + if (!status.isOK()) { + return status; + } + + status = _threadPool.schedule([ this, buildUUID ]() noexcept { + auto opCtx = Client::getCurrent()->makeOperationContext(); + + // Sets up and runs the index build. Sets result and cleans up index build. + _runIndexBuild(opCtx.get(), buildUUID); + }); + + // Clean up the index build if we failed to schedule it. + if (!status.isOK()) { + stdx::unique_lock lk(_mutex); + + // Unregister the index build before setting the promises, so callers do not see the build + // again. + _unregisterIndexBuild(lk, opCtx, replIndexBuildState); + + // Set the promises in case another thread already joined the index build. + for (auto& promise : replIndexBuildState->promises) { + promise.setError(status); + } + + return status; + } + + return std::move(pf.future); +} + + +Future IndexBuildsCoordinator::joinIndexBuilds(const NamespaceString& nss, + const std::vector& indexSpecs) { + // TODO: implement. This code is just to make it compile. + auto pf = makePromiseFuture(); + auto promise = std::move(pf.promise); + return std::move(pf.future); +} + +void IndexBuildsCoordinator::interruptAllIndexBuilds(const std::string& reason) { + stdx::unique_lock lk(_mutex); + + // Signal all the index builds to stop. + for (auto& buildStateIt : _allIndexBuilds) { + _indexBuildsManager.interruptIndexBuild(buildStateIt.second->buildUUID, reason); + } + + // Wait for all the index builds to stop. + for (auto& dbIt : _databaseIndexBuilds) { + dbIt.second->waitUntilNoIndexBuildsRemain(lk); + } +} + +void IndexBuildsCoordinator::abortCollectionIndexBuilds(const UUID& collectionUUID, + const std::string& reason) { + stdx::unique_lock lk(_mutex); + + // Ensure the caller correctly stopped any new index builds on the collection. + auto it = _disallowedCollections.find(collectionUUID); + invariant(it != _disallowedCollections.end()); + + auto collIndexBuildsIt = _collectionIndexBuilds.find(collectionUUID); + if (collIndexBuildsIt == _collectionIndexBuilds.end()) { + return; + } + + collIndexBuildsIt->second->runOperationOnAllBuilds( + lk, &_indexBuildsManager, abortIndexBuild, reason); + collIndexBuildsIt->second->waitUntilNoIndexBuildsRemain(lk); +} + +void IndexBuildsCoordinator::abortDatabaseIndexBuilds(StringData db, const std::string& reason) { + stdx::unique_lock lk(_mutex); + + // Ensure the caller correctly stopped any new index builds on the database. + auto it = _disallowedDbs.find(db); + invariant(it != _disallowedDbs.end()); + + auto dbIndexBuilds = _databaseIndexBuilds[db]; + if (!dbIndexBuilds) { + return; + } + + dbIndexBuilds->runOperationOnAllBuilds(lk, &_indexBuildsManager, abortIndexBuild, reason); + dbIndexBuilds->waitUntilNoIndexBuildsRemain(lk); +} + +Future IndexBuildsCoordinator::abortIndexBuildByName( + const NamespaceString& nss, + const std::vector& indexNames, + const std::string& reason) { + // TODO: not yet implemented. Some code to make it compile. + auto pf = makePromiseFuture(); + auto promise = std::move(pf.promise); + return std::move(pf.future); +} + +Future IndexBuildsCoordinator::abortIndexBuildByUUID(const UUID& buildUUID, + const std::string& reason) { + // TODO: not yet implemented. Some code to make it compile. + auto pf = makePromiseFuture(); + auto promise = std::move(pf.promise); + return std::move(pf.future); +} + +void IndexBuildsCoordinator::signalChangeToPrimaryMode() { + stdx::unique_lock lk(_mutex); + _replMode = ReplState::Primary; +} + +void IndexBuildsCoordinator::signalChangeToSecondaryMode() { + stdx::unique_lock lk(_mutex); + _replMode = ReplState::Secondary; +} + +void IndexBuildsCoordinator::signalChangeToInitialSyncMode() { + stdx::unique_lock lk(_mutex); + _replMode = ReplState::InitialSync; +} + +void IndexBuildsCoordinator::voteCommitIndexBuild(const UUID& buildUUID, + const HostAndPort& hostAndPort) {} + +Status IndexBuildsCoordinator::setCommitQuorum(const NamespaceString& nss, + const std::vector& indexNames, + const BSONObj& newCommitQuorum) { + // TODO: not yet implemented. + return Status::OK(); +} + +void IndexBuildsCoordinator::recoverIndexBuilds() {} + +int IndexBuildsCoordinator::numInProgForDb(StringData db) const { + stdx::unique_lock lk(_mutex); + + auto dbIndexBuildsIt = _databaseIndexBuilds.find(db); + if (dbIndexBuildsIt == _databaseIndexBuilds.end()) { + return 0; + } + return dbIndexBuildsIt->second->getNumberOfIndexBuilds(lk); +} + +void IndexBuildsCoordinator::dump(std::ostream& ss) const { + stdx::unique_lock lk(_mutex); + + if (_collectionIndexBuilds.size()) { + ss << "\nBackground Jobs in Progress\n"; + // TODO: We should improve this to print index names per collection, not just collection + // names. + for (auto it = _collectionIndexBuilds.begin(); it != _collectionIndexBuilds.end(); ++it) { + ss << " " << it->first << '\n'; + } + } + + for (auto it = _databaseIndexBuilds.begin(); it != _databaseIndexBuilds.end(); ++it) { + ss << "database " << it->first << ": " << it->second->getNumberOfIndexBuilds(lk) << '\n'; + } +} + +bool IndexBuildsCoordinator::inProgForCollection(const UUID& collectionUUID) const { + stdx::unique_lock lk(_mutex); + return _collectionIndexBuilds.find(collectionUUID) != _collectionIndexBuilds.end(); +} + +bool IndexBuildsCoordinator::inProgForDb(StringData db) const { + stdx::unique_lock lk(_mutex); + return _databaseIndexBuilds.find(db) != _databaseIndexBuilds.end(); +} + +void IndexBuildsCoordinator::assertNoIndexBuildInProgForCollection( + const UUID& collectionUUID) const { + uassert(ErrorCodes::BackgroundOperationInProgressForNamespace, + mongoutils::str::stream() + << "cannot perform operation: an index build is currently running", + !inProgForCollection(collectionUUID)); +} + +void IndexBuildsCoordinator::assertNoBgOpInProgForDb(StringData db) const { + uassert(ErrorCodes::BackgroundOperationInProgressForDatabase, + mongoutils::str::stream() + << "cannot perform operation: an index build is currently running for " + "database " + << db, + !inProgForDb(db)); +} + +void IndexBuildsCoordinator::awaitNoBgOpInProgForNs(OperationContext* opCtx, StringData ns) const { + auto statusWithCollectionUUID = getCollectionUUID(opCtx, NamespaceString(ns)); + if (!statusWithCollectionUUID.isOK()) { + // The collection does not exist, so there are no index builds on it. + invariant(statusWithCollectionUUID.getStatus().code() == ErrorCodes::NamespaceNotFound); + return; + } + + stdx::unique_lock lk(_mutex); + + auto collIndexBuildsIt = _collectionIndexBuilds.find(statusWithCollectionUUID.getValue()); + if (collIndexBuildsIt == _collectionIndexBuilds.end()) { + return; + } + + collIndexBuildsIt->second->waitUntilNoIndexBuildsRemain(lk); +} + +void IndexBuildsCoordinator::awaitNoBgOpInProgForDb(StringData db) const { + stdx::unique_lock lk(_mutex); + + auto dbIndexBuildsIt = _databaseIndexBuilds.find(db); + if (dbIndexBuildsIt != _databaseIndexBuilds.end()) { + return; + } + + dbIndexBuildsIt->second->waitUntilNoIndexBuildsRemain(lk); +} + +void IndexBuildsCoordinator::sleepIndexBuilds_forTestOnly(bool sleep) { + stdx::unique_lock lk(_mutex); + _sleepForTest = sleep; +} + +void IndexBuildsCoordinator::verifyNoIndexBuilds_forTestOnly() { + invariant(_databaseIndexBuilds.empty()); + invariant(_disallowedDbs.empty()); + invariant(_disallowedCollections.empty()); + invariant(_collectionIndexBuilds.empty()); +} + +Status IndexBuildsCoordinator::_registerIndexBuild( + OperationContext* opCtx, std::shared_ptr replIndexBuildState) { + stdx::unique_lock lk(_mutex); + + NamespaceString nss = + UUIDCatalog::get(opCtx).lookupNSSByUUID(replIndexBuildState->collectionUUID); + if (!nss.isValid()) { + return Status(ErrorCodes::NamespaceNotFound, + "The collection has been dropped since the index build began."); + } + + auto itns = _disallowedCollections.find(replIndexBuildState->collectionUUID); + auto itdb = _disallowedDbs.find(nss.db()); + if (itns != _disallowedCollections.end() || itdb != _disallowedDbs.end()) { + return Status(ErrorCodes::CannotCreateIndex, + str::stream() << "Collection '" << nss.toString() + << "' is in the process of being dropped. New index builds are " + "not currently allowed."); + } + + // Check whether any indexes are already being built with the same index name(s). (Duplicate + // specs will be discovered by the index builder.) + auto collIndexBuildsIt = _collectionIndexBuilds.find(replIndexBuildState->collectionUUID); + if (collIndexBuildsIt != _collectionIndexBuilds.end()) { + for (const auto& name : replIndexBuildState->indexNames) { + if (collIndexBuildsIt->second->hasIndexBuildState(lk, name)) { + return Status(ErrorCodes::IndexKeySpecsConflict, + str::stream() << "There's already an index with name '" << name + << "' being built on the collection"); + } + } + } + + // Register the index build. + + auto dbIndexBuilds = _databaseIndexBuilds[nss.db()]; + if (!dbIndexBuilds) { + _databaseIndexBuilds[nss.db()] = std::make_shared(); + dbIndexBuilds = _databaseIndexBuilds[nss.db()]; + } + dbIndexBuilds->addIndexBuild(lk, replIndexBuildState); + + auto collIndexBuildsItAndRes = _collectionIndexBuilds.insert( + {replIndexBuildState->collectionUUID, std::make_shared()}); + collIndexBuildsItAndRes.first->second->addIndexBuild(lk, replIndexBuildState); + + invariant(_allIndexBuilds.emplace(replIndexBuildState->buildUUID, replIndexBuildState).second); + + return Status::OK(); +} + +void IndexBuildsCoordinator::_unregisterIndexBuild( + WithLock lk, + OperationContext* opCtx, + std::shared_ptr replIndexBuildState) { + NamespaceString nss = + UUIDCatalog::get(opCtx).lookupNSSByUUID(replIndexBuildState->collectionUUID); + invariant(!nss.isEmpty()); + + auto dbIndexBuilds = _databaseIndexBuilds[nss.db()]; + invariant(dbIndexBuilds); + dbIndexBuilds->removeIndexBuild(lk, replIndexBuildState->buildUUID); + if (dbIndexBuilds->getNumberOfIndexBuilds(lk) == 0) { + _databaseIndexBuilds.erase(nss.db()); + } + + auto collIndexBuildsIt = _collectionIndexBuilds.find(replIndexBuildState->collectionUUID); + invariant(collIndexBuildsIt != _collectionIndexBuilds.end()); + collIndexBuildsIt->second->removeIndexBuild(lk, replIndexBuildState); + if (collIndexBuildsIt->second->getNumberOfIndexBuilds(lk) == 0) { + _collectionIndexBuilds.erase(collIndexBuildsIt); + } + + invariant(_allIndexBuilds.erase(replIndexBuildState->buildUUID)); +} + +void IndexBuildsCoordinator::_runIndexBuild(OperationContext* opCtx, + const UUID& buildUUID) noexcept { + auto replState = [&] { + stdx::unique_lock lk(_mutex); + auto it = _allIndexBuilds.find(buildUUID); + invariant(it != _allIndexBuilds.end()); + return it->second; + }(); + + { + stdx::unique_lock lk(_mutex); + while (_sleepForTest) { + lk.unlock(); + sleepmillis(100); + lk.lock(); + } + } + + // TODO: create scoped object to create the index builder, then destroy the builder, set the + // promises and unregister the build. + + // TODO: implement. + + stdx::unique_lock lk(_mutex); + + _unregisterIndexBuild(lk, opCtx, replState); + + for (auto& promise : replState->promises) { + promise.emplaceValue(); + } + + return; +} + +Status IndexBuildsCoordinator::_finishScanningPhase() { + // TODO: implement. + return Status::OK(); +} + +Status IndexBuildsCoordinator::_finishVerificationPhase() { + // TODO: implement. + return Status::OK(); +} + +Status IndexBuildsCoordinator::_finishCommitPhase() { + // TODO: implement. + return Status::OK(); +} + +void IndexBuildsCoordinator::_stopIndexBuildsOnDatabase(StringData dbName) { + stdx::unique_lock lk(_mutex); + + auto it = _disallowedDbs.find(dbName); + if (it != _disallowedDbs.end()) { + ++(it->second); + return; + } + _disallowedDbs[dbName] = 1; +} + +void IndexBuildsCoordinator::_stopIndexBuildsOnCollection(const UUID& collectionUUID) { + stdx::unique_lock lk(_mutex); + + auto it = _disallowedCollections.find(collectionUUID); + if (it != _disallowedCollections.end()) { + ++(it->second); + return; + } + _disallowedCollections[collectionUUID] = 1; +} + +void IndexBuildsCoordinator::_allowIndexBuildsOnDatabase(StringData dbName) { + stdx::unique_lock lk(_mutex); + + auto it = _disallowedDbs.find(dbName); + invariant(it != _disallowedDbs.end()); + invariant(it->second); + if (--(it->second) == 0) { + _disallowedDbs.erase(it); + } +} + +void IndexBuildsCoordinator::_allowIndexBuildsOnCollection(const UUID& collectionUUID) { + stdx::unique_lock lk(_mutex); + + auto it = _disallowedCollections.find(collectionUUID); + invariant(it != _disallowedCollections.end()); + invariant(it->second > 0); + if (--(it->second) == 0) { + _disallowedCollections.erase(it); + } +} + +StatusWith IndexBuildsCoordinator::_checkCommitQuorum( + const BSONObj& commitQuorum, const std::vector& confirmedMembers) { + // TODO: not yet implemented. + return false; +} + +void IndexBuildsCoordinator::_refreshReplStateFromPersisted(OperationContext* opCtx, + const UUID& buildUUID) {} + +ScopedStopNewDatabaseIndexBuilds::ScopedStopNewDatabaseIndexBuilds( + IndexBuildsCoordinator* indexBuildsCoordinator, StringData dbName) + : _indexBuildsCoordinatorPtr(indexBuildsCoordinator), _dbName(dbName.toString()) { + _indexBuildsCoordinatorPtr->_stopIndexBuildsOnDatabase(_dbName); +} + +ScopedStopNewDatabaseIndexBuilds::~ScopedStopNewDatabaseIndexBuilds() { + _indexBuildsCoordinatorPtr->_allowIndexBuildsOnDatabase(_dbName); +} + +ScopedStopNewCollectionIndexBuilds::ScopedStopNewCollectionIndexBuilds( + IndexBuildsCoordinator* indexBuildsCoordinator, const UUID& collectionUUID) + : _indexBuildsCoordinatorPtr(indexBuildsCoordinator), _collectionUUID(collectionUUID) { + _indexBuildsCoordinatorPtr->_stopIndexBuildsOnCollection(_collectionUUID); +} + +ScopedStopNewCollectionIndexBuilds::~ScopedStopNewCollectionIndexBuilds() { + _indexBuildsCoordinatorPtr->_allowIndexBuildsOnCollection(_collectionUUID); +} + +} // namespace mongo diff --git a/src/mongo/db/index_builds_coordinator.h b/src/mongo/db/index_builds_coordinator.h new file mode 100644 index 00000000000..8780c2e2a69 --- /dev/null +++ b/src/mongo/db/index_builds_coordinator.h @@ -0,0 +1,438 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include +#include +#include + +#include "mongo/base/disallow_copying.h" +#include "mongo/base/string_data.h" +#include "mongo/db/catalog/index_builds_manager.h" +#include "mongo/db/collection_index_builds_tracker.h" +#include "mongo/db/database_index_builds_tracker.h" +#include "mongo/db/namespace_string.h" +#include "mongo/db/repl_index_build_state.h" +#include "mongo/stdx/condition_variable.h" +#include "mongo/stdx/mutex.h" +#include "mongo/util/concurrency/thread_pool.h" +#include "mongo/util/concurrency/with_lock.h" +#include "mongo/util/future.h" +#include "mongo/util/net/hostandport.h" +#include "mongo/util/uuid.h" + +namespace mongo { + +class OperationContext; +class ServiceContext; + +/** + * This is a coordinator for all things index builds. It has a threadpool that runs index builds + * asynchronously, returning results to waiting callers via Futures and Promises. Index builds can + * be externally affected, notified, waited upon and aborted through this interface. The coordinator + * uses the cross replica set index build state to control index build progression. + * + * The IndexBuildsCoordinator is instantiated on the ServiceContext as a decoration, and is always + * accessible via the ServiceContext. It owns an IndexBuildsManager that manages the + * MultiIndexBlockImpl index builder instances. + */ +class IndexBuildsCoordinator { + MONGO_DISALLOW_COPYING(IndexBuildsCoordinator); + +public: + /** + * Sets up the thread pool. + */ + IndexBuildsCoordinator(); + + /** + * Invariants that there are no index builds in-progress. + */ + ~IndexBuildsCoordinator(); + + /** + * Shuts down the thread pool, signals interrupt to all index builds, then waits for all of the + * threads to finish. + */ + void shutdown(); + + static IndexBuildsCoordinator* get(ServiceContext* serviceContext); + static IndexBuildsCoordinator* get(OperationContext* operationContext); + + /** + * Sets up the in-memory and persisted state of the index build, then passes the build off to an + * asynchronous thread to run. A Future is returned to await the result of the asynchronous + * thread. + * + * Returns an error status if there are any errors setting up the index build. + */ + StatusWith> buildIndex(OperationContext* opCtx, + const NamespaceString& nss, + const std::vector& specs, + const UUID& buildUUID); + + /** + * TODO: not yet implemented. + */ + Future joinIndexBuilds(const NamespaceString& nss, + const std::vector& indexSpecs); + + /** + * Signals all the index builds to stop and then waits for them to finish. Leaves the index + * builds in a recoverable state. + * + * This should only be called when certain the server will not start any new index builds -- + * i.e. when the server is not accepting user requests and no internal operations are + * concurrently starting new index builds. + * + * TODO: not yet fully implemented. IndexBuildsManager::interruptIndexBuild is not yet + * implemented. + */ + void interruptAllIndexBuilds(const std::string& reason); + + /** + * Signals all of the index builds on the specified collection to abort and then waits until the + * index builds are no longer running. Must identify the collection with a UUID and the caller + * must continue to operate on the collection by UUID to protect against rename collection. The + * provided 'reason' will be used in the error message that the index builders return to their + * callers. + * + * First create a ScopedStopNewCollectionIndexBuilds to block further index builds on the + * collection before calling this and for the duration of the drop collection operation. + * + * { + * ScopedStopNewCollectionIndexBuilds scopedStop(collectionUUID); + * indexBuildsCoord->abortCollectionIndexBuilds(collectionUUID, "..."); + * AutoGetCollection autoColl(..., collectionUUID, ...); + * autoColl->dropCollection(...); + * } + * + * TODO: this is partially implemented. It calls IndexBuildsManager::abortIndexBuild that is not + * implemented. + */ + void abortCollectionIndexBuilds(const UUID& collectionUUID, const std::string& reason); + + /** + * Signals all of the index builds on the specified 'db' to abort and then waits until the index + * builds are no longer running. The provided 'reason' will be used in the error message that + * the index builders return to their callers. + * + * First create a ScopedStopNewDatabaseIndexBuilds to block further index builds on the + * specified + * database before calling this and for the duration of the drop database operation. + * + * { + * ScopedStopNewDatabaseIndexBuilds scopedStop(dbName); + * indexBuildsCoord->abortDatabaseIndexBuilds(dbName, "..."); + * AutoGetDb autoDb(...); + * autoDb->dropDatabase(...); + * } + * + * TODO: this is partially implemented. It calls IndexBuildsManager::abortIndexBuild that is not + * implemented. + */ + void abortDatabaseIndexBuilds(StringData db, const std::string& reason); + + /** + * Aborts a given index build by name on the given collection. + * + * TODO: This is not yet implemented. + */ + Future abortIndexBuildByName(const NamespaceString& nss, + const std::vector& indexNames, + const std::string& reason); + + /** + * Aborts a given index build by index build UUID. + * + * TODO: This is not yet implemented. + */ + Future abortIndexBuildByUUID(const UUID& buildUUID, const std::string& reason); + + void signalChangeToPrimaryMode(); + + void signalChangeToSecondaryMode(); + + void signalChangeToInitialSyncMode(); + + /** + * TODO: This is not yet implemented. + */ + void voteCommitIndexBuild(const UUID& buildUUID, const HostAndPort& hostAndPort); + + /** + * TODO: This is not yet implemented. (This will have to take a collection IS lock to look up + * the collection UUID.) + */ + Status setCommitQuorum(const NamespaceString& nss, + const std::vector& indexNames, + const BSONObj& newCommitQuorum); + + /** + * TODO: This is not yet implemented. + */ + void recoverIndexBuilds(); + + /** + * Returns the number of index builds that are running on the specified database. + */ + int numInProgForDb(StringData db) const; + + /** + * Prints out the names of collections on which index builds are running, and the number of + * index builds per database. + */ + void dump(std::ostream&) const; + + /** + * Returns true if an index build is in progress on the specified collection. + */ + bool inProgForCollection(const UUID& collectionUUID) const; + + /** + * Returns true if an index build is in progress on the specified database. + */ + bool inProgForDb(StringData db) const; + + /** + * Uasserts if any index builds is in progress on the specified collection. + */ + void assertNoIndexBuildInProgForCollection(const UUID& collectionUUID) const; + + /** + * Uasserts if any index builds is in progress on the specified database. + */ + void assertNoBgOpInProgForDb(StringData db) const; + + /** + * Waits for all index builds on a specified collection to finish. + * + * Momentarily takes the collection IS lock for 'ns', to fetch the collection UUID. + */ + void awaitNoBgOpInProgForNs(OperationContext* opCtx, StringData ns) const; + void awaitNoBgOpInProgForNs(OperationContext* opCtx, const NamespaceString& ns) const { + awaitNoBgOpInProgForNs(opCtx, ns.ns()); + } + + /** + * Waits for all index builds on a specified database to finish. + */ + void awaitNoBgOpInProgForDb(StringData db) const; + + void sleepIndexBuilds_forTestOnly(bool sleep); + + void verifyNoIndexBuilds_forTestOnly(); + +private: + // Friend classes in order to be the only allowed callers of + //_stopIndexBuildsOnCollection/Database and _allowIndexBuildsOnCollection/Database. + friend class ScopedStopNewDatabaseIndexBuilds; + friend class ScopedStopNewCollectionIndexBuilds; + + /** + * Keeps track of the relevant replica set member states. Index builds are managed differently + * depending on the state of the replica set member. + * + * These states follow the replica set member states, as maintained by MemberState in the + * ReplicationCoordinator. If not in Primary or InitialSync modes, then the default will be + * Secondary, with the expectation that a replica set member must always transition to Secondary + * before Primary. + */ + enum class ReplState { Primary, Secondary, InitialSync }; + + /** + * Registers an index build so that the rest of the system can discover it. + * + * If stopIndexBuildsOnNsOrDb has been called on the index build's collection or database, then + * an error will be returned. + */ + Status _registerIndexBuild(OperationContext* opCtx, + std::shared_ptr replIndexBuildState); + + /** + * Unregisters the index build. + */ + void _unregisterIndexBuild(WithLock lk, + OperationContext* opCtx, + std::shared_ptr replIndexBuildState); + + /** + * TODO: not yet implemented. + */ + void _runIndexBuild(OperationContext* opCtx, const UUID& buildUUID) noexcept; + + /** + * TODO: not yet implemented. + */ + Status _finishScanningPhase(); + + /** + * TODO: not yet implemented. + */ + Status _finishVerificationPhase(); + + /** + * TODO: not yet implemented. + */ + Status _finishCommitPhase(); + + /** + * Prevents new index builds being registered on the provided collection or database. + * + * It is safe to call this on the same collection/database concurrently in different threads. It + * will still behave correctly. + */ + void _stopIndexBuildsOnDatabase(StringData dbName); + void _stopIndexBuildsOnCollection(const UUID& collectionUUID); + + /** + * Allows new index builds to again be registered on the provided collection or database. Should + * only be called after calling stopIndexBuildsOnCollection or stopIndexBuildsOnDatabase on the + * same collection or database, respectively. + */ + void _allowIndexBuildsOnDatabase(StringData dbName); + void _allowIndexBuildsOnCollection(const UUID& collectionUUID); + + /** + * TODO: not yet implemented. + */ + StatusWith _checkCommitQuorum(const BSONObj& commitQuorum, + const std::vector& confirmedMembers); + + /** + * TODO: not yet implemented. + */ + void _refreshReplStateFromPersisted(OperationContext* opCtx, const UUID& buildUUID); + + // Protects the below state. + mutable stdx::mutex _mutex; + + // New index builds are not allowed on a collection or database if the collection or database is + // in either of these maps. These are used when concurrent operations need to abort index builds + // on a collection or database and must wait for the index builds to drain, without further + // index builds being allowed to begin. + StringMap _disallowedDbs; + stdx::unordered_map _disallowedCollections; + + // Maps database name to database information. Tracks and accesses index builds on a database + // level. Can be used to abort and wait upon the completion of all index builds for a database. + // + // Maps shared_ptrs so that DatabaseIndexBuildsTracker instances can outlive being erased from + // this map when there are no longer any builds remaining on the database. This is necessary + // when callers must wait for all index builds to cease. + StringMap> _databaseIndexBuilds; + + // Collection UUID to collection level index build information. Enables index build lookup and + // abort by collection UUID and index name, as well as collection level interruption. + // + // Maps shared_ptrs so that CollectionIndexBuildsTracker instances can outlive being erased from + // this map when there are no longer any builds remaining on the collection. This is necessary + // when callers must wait for and index build or all index builds to cease. + stdx::unordered_map, UUID::Hash> + _collectionIndexBuilds; + + // Build UUID to index build information map. + stdx::unordered_map, UUID::Hash> _allIndexBuilds; + + // Handles actually building the indexes. + IndexBuildsManager _indexBuildsManager; + + // Replication hooks will call into the Coordinator to update this on relevant state + // transitions. The Coordinator will then use the setting to inform how the index build is run. + // Index builds have different inter node communication responsibilities and error checking + // requirements depending on the replica set member's state. + ReplState _replMode = ReplState::Secondary; + + // Thread pool on which index builds are run. + ThreadPool _threadPool; + + bool _sleepForTest = false; +}; + +/** + * For this object's lifetime no new index builds will be allowed on the specified database. An + * error will be returned by the IndexBuildsCoordinator to any caller attempting to register a new + * index build on the blocked collection or database. + * + * This should be used by operations like drop database, where the active index builds must be + * signaled to abort, but it takes time for them to wrap up, during which time no further index + * builds should be scheduled. + */ +class ScopedStopNewDatabaseIndexBuilds { + MONGO_DISALLOW_COPYING(ScopedStopNewDatabaseIndexBuilds); + +public: + /** + * Takes either the full collection namespace or a database name and will block further index + * builds on that collection or database. + */ + ScopedStopNewDatabaseIndexBuilds(IndexBuildsCoordinator* indexBuildsCoordinator, + StringData dbName); + + /** + * Allows new index builds on the collection or database that were previously disallowed. + */ + ~ScopedStopNewDatabaseIndexBuilds(); + +private: + IndexBuildsCoordinator* _indexBuildsCoordinatorPtr; + std::string _dbName; +}; + +/** + * For this object's lifetime no new index builds will be allowed on the specified collection. An + * error will be returned by the IndexBuildsCoordinator to any caller attempting to register a new + * index build on the blocked collection. + * + * This should be used by operations like drop collection, where the active index builds must be + * signaled to abort, but it takes time for them to wrap up, during which time no further index + * builds should be scheduled. + */ +class ScopedStopNewCollectionIndexBuilds { + MONGO_DISALLOW_COPYING(ScopedStopNewCollectionIndexBuilds); + +public: + /** + * Blocks further index builds on the specified collection. + */ + ScopedStopNewCollectionIndexBuilds(IndexBuildsCoordinator* indexBuildsCoordinator, + const UUID& collectionUUID); + + /** + * Allows new index builds on the collection that were previously disallowed. + */ + ~ScopedStopNewCollectionIndexBuilds(); + +private: + IndexBuildsCoordinator* _indexBuildsCoordinatorPtr; + UUID _collectionUUID; +}; + +} // namespace mongo diff --git a/src/mongo/db/index_builds_coordinator_test.cpp b/src/mongo/db/index_builds_coordinator_test.cpp new file mode 100644 index 00000000000..bcea0dcc982 --- /dev/null +++ b/src/mongo/db/index_builds_coordinator_test.cpp @@ -0,0 +1,323 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/index_builds_coordinator.h" + +#include "mongo/db/catalog/catalog_test_fixture.h" +#include "mongo/db/catalog/multi_index_block.h" +#include "mongo/db/catalog/multi_index_block_impl.h" +#include "mongo/db/catalog_raii.h" +#include "mongo/db/namespace_string.h" +#include "mongo/db/operation_context.h" +#include "mongo/util/uuid.h" + +namespace mongo { + +using unittest::assertGet; +using unittest::log; + +namespace { + +class IndexBuildsCoordinatorTest : public CatalogTestFixture { +private: + void setUp() override; + void tearDown() override; + +public: + void createCollection(const NamespaceString& nss); + + const NamespaceString _testFooNss = NamespaceString("test.foo"); + const NamespaceString _testBarNss = NamespaceString("test.bar"); + const NamespaceString _othertestFooNss = NamespaceString("othertest.foo"); + std::unique_ptr _indexBuildsCoord; +}; + +void IndexBuildsCoordinatorTest::setUp() { + CatalogTestFixture::setUp(); + createCollection(_testFooNss); + createCollection(_testBarNss); + createCollection(_othertestFooNss); + _indexBuildsCoord = std::make_unique(); +} + +void IndexBuildsCoordinatorTest::tearDown() { + _indexBuildsCoord->verifyNoIndexBuilds_forTestOnly(); + _indexBuildsCoord.reset(); + // All databases are dropped during tear down. + CatalogTestFixture::tearDown(); +} + +void IndexBuildsCoordinatorTest::createCollection(const NamespaceString& nss) { + ASSERT_OK(storageInterface()->createCollection(operationContext(), nss, CollectionOptions())); +} + +UUID getCollectionUUID(OperationContext* opCtx, const NamespaceString& nss) { + AutoGetCollection autoColl(opCtx, nss, MODE_IS); + Collection* coll = autoColl.getCollection(); + ASSERT(coll); + return coll->uuid().get(); +} + +std::vector makeSpecs(const NamespaceString& nss, std::vector keys) { + invariant(keys.size()); + std::vector indexSpecs; + for (auto keyName : keys) { + indexSpecs.push_back(BSON("ns" << nss.toString() << "v" << 2 << "key" << BSON(keyName << 1) + << "name" + << (keyName + "_1"))); + } + return indexSpecs; +} + +TEST_F(IndexBuildsCoordinatorTest, CannotBuildIndexWithSameIndexName) { + _indexBuildsCoord->sleepIndexBuilds_forTestOnly(true); + + // Register an index build on _testFooNss. + Future testFoo1Future = assertGet(_indexBuildsCoord->buildIndex( + operationContext(), _testFooNss, makeSpecs(_testFooNss, {"a", "b"}), UUID::gen())); + + // Attempt and fail to register an index build on _testFooNss with the same index name, while + // the prior build is still running. + ASSERT_EQ(ErrorCodes::IndexKeySpecsConflict, + _indexBuildsCoord + ->buildIndex( + operationContext(), _testFooNss, makeSpecs(_testFooNss, {"b"}), UUID::gen()) + .getStatus()); + + _indexBuildsCoord->sleepIndexBuilds_forTestOnly(false); + ASSERT_OK(testFoo1Future.getNoThrow()); +} + +// Incrementally registering index builds and checking both that the registration was successful and +// that the access functions convey the expected state of the manager. +TEST_F(IndexBuildsCoordinatorTest, IndexBuildsCoordinatorRegistration) { + _indexBuildsCoord->sleepIndexBuilds_forTestOnly(true); + + // Register an index build on _testFooNss. + UUID testFooUUID = getCollectionUUID(operationContext(), _testFooNss); + Future testFoo1Future = assertGet(_indexBuildsCoord->buildIndex( + operationContext(), _testFooNss, makeSpecs(_testFooNss, {"a", "b"}), UUID::gen())); + + ASSERT_EQ(_indexBuildsCoord->numInProgForDb(_testFooNss.db()), 1); + ASSERT(_indexBuildsCoord->inProgForCollection(testFooUUID)); + ASSERT(_indexBuildsCoord->inProgForDb(_testFooNss.db())); + ASSERT_THROWS_CODE(_indexBuildsCoord->assertNoIndexBuildInProgForCollection(testFooUUID), + AssertionException, + ErrorCodes::BackgroundOperationInProgressForNamespace); + ASSERT_THROWS_CODE(_indexBuildsCoord->assertNoBgOpInProgForDb(_testFooNss.db()), + AssertionException, + ErrorCodes::BackgroundOperationInProgressForDatabase); + + // Register a second index build on _testFooNss. + Future testFoo2Future = assertGet(_indexBuildsCoord->buildIndex( + operationContext(), _testFooNss, makeSpecs(_testFooNss, {"c", "d"}), UUID::gen())); + + ASSERT_EQ(_indexBuildsCoord->numInProgForDb(_testFooNss.db()), 2); + ASSERT(_indexBuildsCoord->inProgForCollection(testFooUUID)); + ASSERT(_indexBuildsCoord->inProgForDb(_testFooNss.db())); + ASSERT_THROWS_CODE(_indexBuildsCoord->assertNoIndexBuildInProgForCollection(testFooUUID), + AssertionException, + ErrorCodes::BackgroundOperationInProgressForNamespace); + ASSERT_THROWS_CODE(_indexBuildsCoord->assertNoBgOpInProgForDb(_testFooNss.db()), + AssertionException, + ErrorCodes::BackgroundOperationInProgressForDatabase); + + // Register an index build on a different collection _testBarNss. + UUID testBarUUID = getCollectionUUID(operationContext(), _testBarNss); + Future testBarFuture = assertGet(_indexBuildsCoord->buildIndex( + operationContext(), _testBarNss, makeSpecs(_testBarNss, {"x", "y"}), UUID::gen())); + + ASSERT_EQ(_indexBuildsCoord->numInProgForDb(_testBarNss.db()), 3); + ASSERT(_indexBuildsCoord->inProgForCollection(testBarUUID)); + ASSERT(_indexBuildsCoord->inProgForDb(_testBarNss.db())); + ASSERT_THROWS_CODE(_indexBuildsCoord->assertNoIndexBuildInProgForCollection(testBarUUID), + AssertionException, + ErrorCodes::BackgroundOperationInProgressForNamespace); + ASSERT_THROWS_CODE(_indexBuildsCoord->assertNoBgOpInProgForDb(_testBarNss.db()), + AssertionException, + ErrorCodes::BackgroundOperationInProgressForDatabase); + + // Register an index build on a collection in a different database _othertestFoo. + UUID othertestFooUUID = getCollectionUUID(operationContext(), _othertestFooNss); + Future othertestFooFuture = + assertGet(_indexBuildsCoord->buildIndex(operationContext(), + _othertestFooNss, + makeSpecs(_othertestFooNss, {"r", "s"}), + UUID::gen())); + + ASSERT_EQ(_indexBuildsCoord->numInProgForDb(_othertestFooNss.db()), 1); + ASSERT(_indexBuildsCoord->inProgForCollection(othertestFooUUID)); + ASSERT(_indexBuildsCoord->inProgForDb(_othertestFooNss.db())); + ASSERT_THROWS_CODE(_indexBuildsCoord->assertNoIndexBuildInProgForCollection(othertestFooUUID), + AssertionException, + ErrorCodes::BackgroundOperationInProgressForNamespace); + ASSERT_THROWS_CODE(_indexBuildsCoord->assertNoBgOpInProgForDb(_othertestFooNss.db()), + AssertionException, + ErrorCodes::BackgroundOperationInProgressForDatabase); + + _indexBuildsCoord->sleepIndexBuilds_forTestOnly(false); + + ASSERT_OK(testFoo1Future.getNoThrow()); + ASSERT_OK(testFoo2Future.getNoThrow()); + ASSERT_OK(testBarFuture.getNoThrow()); + ASSERT_OK(othertestFooFuture.getNoThrow()); + + _indexBuildsCoord->assertNoIndexBuildInProgForCollection(testFooUUID); + _indexBuildsCoord->assertNoIndexBuildInProgForCollection(testBarUUID); + _indexBuildsCoord->assertNoIndexBuildInProgForCollection(othertestFooUUID); + + _indexBuildsCoord->assertNoBgOpInProgForDb(_testFooNss.db()); + _indexBuildsCoord->assertNoBgOpInProgForDb(_othertestFooNss.db()); + + ASSERT_NOT_EQUALS(_testFooNss, _testBarNss); + ASSERT_NOT_EQUALS(_testFooNss, _othertestFooNss); +} + +// Exercises the stopIndexBuildsOnCollection/Database() and allowIndexBuildsOnCollection/Database() +// functions, checking that they correctly disallow and allow index builds when +// ScopedStopNewCollectionIndexBuilds and ScopedStopNewDatabaseIndexBuilds are present on a +// collection or database name. +TEST_F(IndexBuildsCoordinatorTest, IndexBuildsCoordinatorDisallowNewBuildsOnNamespace) { + UUID testFooUUID = getCollectionUUID(operationContext(), _testFooNss); + + { + _indexBuildsCoord->sleepIndexBuilds_forTestOnly(true); + + // Create a scoped object to block new index builds ONLY on _testFooNss. + ScopedStopNewCollectionIndexBuilds scopedStop(_indexBuildsCoord.get(), testFooUUID); + + // Registering an index build on _testFooNss should fail. + ASSERT_EQ(ErrorCodes::CannotCreateIndex, + _indexBuildsCoord + ->buildIndex(operationContext(), + _testFooNss, + makeSpecs(_testFooNss, {"a", "b"}), + UUID::gen()) + .getStatus()); + + // Registering index builds on other collections and databases should still succeed. + Future testBarFuture = assertGet(_indexBuildsCoord->buildIndex( + operationContext(), _testBarNss, makeSpecs(_testBarNss, {"c", "d"}), UUID::gen())); + Future othertestFooFuture = + assertGet(_indexBuildsCoord->buildIndex(operationContext(), + _othertestFooNss, + makeSpecs(_othertestFooNss, {"e", "f"}), + UUID::gen())); + + _indexBuildsCoord->sleepIndexBuilds_forTestOnly(false); + + ASSERT_OK(testBarFuture.getNoThrow()); + ASSERT_OK(othertestFooFuture.getNoThrow()); + } + + { + // Check that the scoped object correctly cleared. + Future testFooFuture = assertGet(_indexBuildsCoord->buildIndex( + operationContext(), _testFooNss, makeSpecs(_testFooNss, {"a", "b"}), UUID::gen())); + ASSERT_OK(testFooFuture.getNoThrow()); + } + + { + _indexBuildsCoord->sleepIndexBuilds_forTestOnly(true); + + // Create a scoped object to block new index builds on the 'test' database. + ScopedStopNewDatabaseIndexBuilds scopedStop(_indexBuildsCoord.get(), _testFooNss.db()); + + // Registering an index build on any collection in the 'test' database should fail. + ASSERT_EQ(ErrorCodes::CannotCreateIndex, + _indexBuildsCoord + ->buildIndex(operationContext(), + _testFooNss, + makeSpecs(_testFooNss, {"a", "b"}), + UUID::gen()) + .getStatus()); + ASSERT_EQ(ErrorCodes::CannotCreateIndex, + _indexBuildsCoord + ->buildIndex(operationContext(), + _testBarNss, + makeSpecs(_testBarNss, {"c", "d"}), + UUID::gen()) + .getStatus()); + + // Registering index builds on another database should still succeed. + Future othertestFooFuture = + assertGet(_indexBuildsCoord->buildIndex(operationContext(), + _othertestFooNss, + makeSpecs(_othertestFooNss, {"e", "f"}), + UUID::gen())); + + _indexBuildsCoord->sleepIndexBuilds_forTestOnly(false); + + ASSERT_OK(othertestFooFuture.getNoThrow()); + } + + { + // Check that the scoped object correctly cleared. + Future testFooFuture = assertGet(_indexBuildsCoord->buildIndex( + operationContext(), _testFooNss, makeSpecs(_testFooNss, {"a", "b"}), UUID::gen())); + ASSERT_OK(testFooFuture.getNoThrow()); + } + + { + // Test concurrency of multiple scoped objects to block an index builds. + + ScopedStopNewCollectionIndexBuilds scopedStop(_indexBuildsCoord.get(), testFooUUID); + { + ScopedStopNewCollectionIndexBuilds scopedStop(_indexBuildsCoord.get(), testFooUUID); + + ASSERT_EQ(ErrorCodes::CannotCreateIndex, + _indexBuildsCoord + ->buildIndex(operationContext(), + _testFooNss, + makeSpecs(_testFooNss, {"a", "b"}), + UUID::gen()) + .getStatus()); + } + ASSERT_EQ(ErrorCodes::CannotCreateIndex, + _indexBuildsCoord + ->buildIndex(operationContext(), + _testFooNss, + makeSpecs(_testFooNss, {"a", "b"}), + UUID::gen()) + .getStatus()); + } + + { + // Check that the scoped object correctly cleared. + Future testFooFuture = assertGet(_indexBuildsCoord->buildIndex( + operationContext(), _testFooNss, makeSpecs(_testFooNss, {"a", "b"}), UUID::gen())); + ASSERT_OK(testFooFuture.getNoThrow()); + } +} + +} // namespace + +} // namespace mongo diff --git a/src/mongo/db/repl_index_build_state.h b/src/mongo/db/repl_index_build_state.h new file mode 100644 index 00000000000..b2473ccb934 --- /dev/null +++ b/src/mongo/db/repl_index_build_state.h @@ -0,0 +1,123 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include +#include +#include +#include + +#include "mongo/bson/bsonobj.h" +#include "mongo/db/index/index_descriptor.h" +#include "mongo/db/namespace_string.h" +#include "mongo/db/write_concern_options.h" +#include "mongo/stdx/condition_variable.h" +#include "mongo/util/future.h" +#include "mongo/util/net/hostandport.h" +#include "mongo/util/uuid.h" + +namespace mongo { + +/** + * Tracks the cross replica set progress of a particular index build identified by a build UUID. + * + * This is intended to only be used by the IndexBuildsCoordinator class. + * + * TODO: pass in commit quorum setting and FCV to decide the twoPhaseIndexBuild setting. + */ +struct ReplIndexBuildState { + ReplIndexBuildState(const UUID& indexBuildUUID, + const UUID& collUUID, + const std::vector names, + const std::vector& specs, + Promise promise) + : buildUUID(indexBuildUUID), + collectionUUID(collUUID), + indexNames(names), + indexSpecs(specs) { + promises.emplace_back(std::move(promise)); + + // Verify that the given index names and index specs match. + invariant(names.size() == specs.size()); + for (auto& spec : specs) { + std::string name = spec.getStringField(IndexDescriptor::kIndexNameFieldName); + invariant(std::find(names.begin(), names.end(), name) != names.end()); + } + } + + // Uniquely identifies this index build across replica set members. + const UUID buildUUID; + + // Identifies the collection for which the index is being built. Collections can be renamed, so + // the collection UUID is used to maintain correct association. + const UUID collectionUUID; + + // The names of the indexes being built. + const std::vector indexNames; + + // The specs of the index(es) being built. Facilitates new callers joining an active index + // build. + const std::vector indexSpecs; + + // Whether to do a two phase index build or a single phase index build like in v4.0. The FCV + // at the start of the index build will determine this setting. + bool twoPhaseIndexBuild = false; + + // Protects the state below. + mutable stdx::mutex mutex; + + // The quorum required of commit ready replica set members before the index build will be + // allowed to commit. + WriteConcernOptions commitQuorum; + + // Whether or not the primary replica set member has signaled that it is okay to go ahead and + // verify index constraint violations have gone away. + bool prepareIndexBuild = false; + + // Tracks the members of the replica set that have finished building the index(es) and are ready + // to commit the index(es). + std::vector commitReadyMembers; + + // Communicates the final outcome of the index build to any callers waiting upon the associated + // Future(s). + std::vector> promises; + + // There is a period of time where the index build is registered on the coordinator, but an + // index builder does not yet exist. Since a signal cannot be set on the index builder at that + // time, it must be saved here. + bool aborted = false; + std::string abortReason = ""; + + // The coordinator for the index build will wait upon this when awaiting an external signal, + // such as commit or commit readiness signals. + stdx::condition_variable condVar; +}; + +} // namespace mongo -- cgit v1.2.1