diff options
author | Dianna Hohensee <dianna.hohensee@mongodb.com> | 2020-10-13 17:52:55 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-10-14 18:23:38 +0000 |
commit | 5a9dd0cbec1f4293e966321885cccb549c77e59c (patch) | |
tree | 5277c50be4f1035a813907ebb98598fabf6e6fdc | |
parent | 2667abf5f988c9cf9b548e23db961680a24c7a1b (diff) | |
download | mongo-5a9dd0cbec1f4293e966321885cccb549c77e59c.tar.gz |
SERVER-50677 Make a lock-free read version of AutoGetCollectionForRead and open a storage snapshot corresponding to the acquired Collection instance
-rw-r--r-- | src/mongo/db/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/catalog_raii.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/catalog_raii.h | 34 | ||||
-rw-r--r-- | src/mongo/db/db_raii.cpp | 139 | ||||
-rw-r--r-- | src/mongo/db/db_raii.h | 161 | ||||
-rw-r--r-- | src/mongo/db/storage/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/embedded/replication_coordinator_embedded.cpp | 2 |
7 files changed, 319 insertions, 23 deletions
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index 5f86b4cbd21..826cafe408a 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -1254,7 +1254,6 @@ env.Library( 'stats/serveronly_stats', 'storage/oplog_hack', 'storage/remove_saver', - 'storage/snapshot_helper', 'storage/storage_options', 'update/update_driver', ], @@ -1263,6 +1262,7 @@ env.Library( 'commands/server_status_core', 'kill_sessions', 'stats/resource_consumption_metrics', + 'storage/snapshot_helper', ], ) diff --git a/src/mongo/db/catalog_raii.cpp b/src/mongo/db/catalog_raii.cpp index f3b9e5aa039..e8bb4e48e74 100644 --- a/src/mongo/db/catalog_raii.cpp +++ b/src/mongo/db/catalog_raii.cpp @@ -95,8 +95,7 @@ AutoGetCollection::AutoGetCollection(OperationContext* opCtx, Database* const db = _autoDb.getDb(); invariant(!nsOrUUID.uuid() || db, str::stream() << "Database for " << _resolvedNss.ns() - << " disappeared after successufully resolving " - << nsOrUUID.toString()); + << " disappeared after successfully resolving " << nsOrUUID.toString()); // In most cases we expect modifications for system.views to upgrade MODE_IX to MODE_X before // taking the lock. One exception is a query by UUID of system.views in a transaction. Usual diff --git a/src/mongo/db/catalog_raii.h b/src/mongo/db/catalog_raii.h index d5c499c4bc7..4b0ddc7bf0c 100644 --- a/src/mongo/db/catalog_raii.h +++ b/src/mongo/db/catalog_raii.h @@ -123,11 +123,8 @@ public: return getCollection().get(); } - /** - * Dereference operator, returns a lvalue reference to the collection. - */ - const Collection& operator*() const { - return *getCollection().get(); + const CollectionPtr& operator*() const { + return getCollection(); } /** @@ -146,6 +143,8 @@ public: /** * Returns nullptr if the collection didn't exist. + * + * Deprecated in favor of the new ->(), *() and bool() accessors above! */ const CollectionPtr& getCollection() const { return _coll; @@ -218,14 +217,39 @@ public: Date_t deadline = Date_t::max()); /** + * Same constructor as above except it accepts a fifth unused LockMode parameter in order to + * parallel AutoGetCollection and meet the templated AutoGetCollectionForReadBase class' + * type structure expectations. + */ + AutoGetCollectionLockFree( + OperationContext* opCtx, + const NamespaceStringOrUUID& nsOrUUID, + LockMode unused, // unused + AutoGetCollectionViewMode viewMode = AutoGetCollectionViewMode::kViewsForbidden, + Date_t deadline = Date_t::max()) + : AutoGetCollectionLockFree(opCtx, nsOrUUID, viewMode, deadline) {} + + explicit operator bool() const { + // Use the CollectionPtr because it is updated if it yields whereas _collection is not until + // restore. + return static_cast<bool>(_collectionPtr); + } + + /** * AutoGetCollectionLockFree can be used as a Collection pointer with the -> operator. */ const Collection* operator->() const { return getCollection().get(); } + const CollectionPtr& operator*() const { + return getCollection(); + } + /** * Returns nullptr if the collection didn't exist. + * + * Deprecated in favor of the new ->(), *() and bool() accessors above! */ const CollectionPtr& getCollection() const { return _collectionPtr; diff --git a/src/mongo/db/db_raii.cpp b/src/mongo/db/db_raii.cpp index 7ffe40349ab..600a5b861c4 100644 --- a/src/mongo/db/db_raii.cpp +++ b/src/mongo/db/db_raii.cpp @@ -51,6 +51,16 @@ const boost::optional<int> kDoNotChangeProfilingLevel = boost::none; const auto allowSecondaryReadsDuringBatchApplication_DONT_USE = OperationContext::declareDecoration<boost::optional<bool>>(); +/** + * Performs some checks to determine whether the operation is compatible with a lock-free read. + * The DBDirectClient and multi-doc transactions are not supported. + */ +bool supportsLockFreeRead(OperationContext* opCtx) { + // Lock-free reads are only supported for external queries, not internal via DBDirectClient. + // Lock-free reads are not supported in multi-document transactions. + return !opCtx->getClient()->isInDirectClient() && !opCtx->inMultiDocumentTransaction(); +} + } // namespace AutoStatsTracker::AutoStatsTracker(OperationContext* opCtx, @@ -98,7 +108,10 @@ AutoGetCollectionForReadBase<AutoGetCollectionType>::AutoGetCollectionForReadBas opCtx->getServiceContext()->getStorageEngine()->supportsReadConcernSnapshot()) { _shouldNotConflictWithSecondaryBatchApplicationBlock.emplace(opCtx->lockState()); } + + // Multi-document transactions need MODE_IX locks, otherwise MODE_IS. const auto collectionLockMode = getLockModeForQuery(opCtx, nsOrUUID.nss()); + _autoColl.emplace(opCtx, nsOrUUID, collectionLockMode, viewMode, deadline); repl::ReplicationCoordinator* const replCoord = repl::ReplicationCoordinator::get(opCtx); @@ -243,6 +256,93 @@ AutoGetCollectionForReadBase<AutoGetCollectionType>::AutoGetCollectionForReadBas } } +AutoGetCollectionForReadLockFree::AutoGetCollectionForReadLockFree( + OperationContext* opCtx, + const NamespaceStringOrUUID& nsOrUUID, + AutoGetCollectionViewMode viewMode, + Date_t deadline) { + // Supported lock-free reads should never have an open storage snapshot prior to calling + // this helper. The storage snapshot and in-memory state fetched here must be consistent. + invariant(supportsLockFreeRead(opCtx) && !opCtx->recoveryUnit()->inActiveTxn()); + + while (true) { + // AutoGetCollectionForReadBase can choose a read source based on the current replication + // state. Therefore we must fetch the repl state beforehand, to compare with afterwards. + long long replTerm = repl::ReplicationCoordinator::get(opCtx)->getTerm(); + + _autoGetCollectionForReadBase.emplace(opCtx, nsOrUUID, viewMode, deadline); + + // A lock request does not always find a collection to lock. + if (!_autoGetCollectionForReadBase.get()) { + break; + } + + // We must open a storage snapshot consistent with the fetched in-memory Collection instance + // and chosen read source based on replication state. The Collection instance and + // replication state after opening a snapshot will be compared with the previously acquired + // state. If either does not match, then this loop will retry lock acquisition and read + // source selection until there is a match. + // + // Note: AutoGetCollectionForReadBase may open a snapshot for PIT reads, so + // preallocateSnapshot() may be a no-op, but that is OK because the snapshot is established + // by _autoGetCollectionForReadBase after it fetches a Collection instance. + + opCtx->recoveryUnit()->preallocateSnapshot(); + + auto newCollection = CollectionCatalog::get(opCtx).lookupCollectionByUUIDForRead( + opCtx, _autoGetCollectionForReadBase.get()->uuid()); + + if (_autoGetCollectionForReadBase.get()->getMinimumVisibleSnapshot() == + newCollection->getMinimumVisibleSnapshot() && + replTerm == repl::ReplicationCoordinator::get(opCtx)->getTerm()) { + break; + } + + LOGV2_DEBUG(5067701, + 3, + "Retrying acquiring state for lock-free read because collection or replication " + "state changed."); + _autoGetCollectionForReadBase.reset(); + opCtx->recoveryUnit()->abandonSnapshot(); + } +} + +AutoGetCollectionForReadMaybeLockFree::AutoGetCollectionForReadMaybeLockFree( + OperationContext* opCtx, + const NamespaceStringOrUUID& nsOrUUID, + AutoGetCollectionViewMode viewMode, + Date_t deadline) { + if (supportsLockFreeRead(opCtx)) { + _autoGetLockFree.emplace(opCtx, nsOrUUID, viewMode, deadline); + } else { + _autoGet.emplace(opCtx, nsOrUUID, viewMode, deadline); + } +} + +ViewDefinition* AutoGetCollectionForReadMaybeLockFree::getView() const { + if (_autoGet) { + return _autoGet->getView(); + } else { + return _autoGetLockFree->getView(); + } +} + +const NamespaceString& AutoGetCollectionForReadMaybeLockFree::getNss() const { + if (_autoGet) { + return _autoGet->getNss(); + } else { + return _autoGetLockFree->getNss(); + } +} + +const CollectionPtr& AutoGetCollectionForReadMaybeLockFree::getCollection() const { + if (_autoGet) { + return _autoGet->getCollection(); + } else { + return _autoGetLockFree->getCollection(); + } +} + template <typename AutoGetCollectionForReadType> AutoGetCollectionForReadCommandBase<AutoGetCollectionForReadType>:: AutoGetCollectionForReadCommandBase(OperationContext* opCtx, @@ -293,6 +393,43 @@ OldClientContext::OldClientContext(OperationContext* opCtx, const std::string& n CollectionCatalog::get(opCtx).getDatabaseProfileLevel(_db->name())); } +AutoGetCollectionForReadCommandMaybeLockFree::AutoGetCollectionForReadCommandMaybeLockFree( + OperationContext* opCtx, + const NamespaceStringOrUUID& nsOrUUID, + AutoGetCollectionViewMode viewMode, + Date_t deadline, + AutoStatsTracker::LogMode logMode) { + if (supportsLockFreeRead(opCtx)) { + _autoGetLockFree.emplace(opCtx, nsOrUUID, viewMode, deadline, logMode); + } else { + _autoGet.emplace(opCtx, nsOrUUID, viewMode, deadline, logMode); + } +} + +const CollectionPtr& AutoGetCollectionForReadCommandMaybeLockFree::getCollection() const { + if (_autoGet) { + return _autoGet->getCollection(); + } else { + return _autoGetLockFree->getCollection(); + } +} + +ViewDefinition* AutoGetCollectionForReadCommandMaybeLockFree::getView() const { + if (_autoGet) { + return _autoGet->getView(); + } else { + return _autoGetLockFree->getView(); + } +} + +const NamespaceString& AutoGetCollectionForReadCommandMaybeLockFree::getNss() const { + if (_autoGet) { + return _autoGet->getNss(); + } else { + return _autoGetLockFree->getNss(); + } +} + OldClientContext::~OldClientContext() { // If in an interrupt, don't record any stats. // It is possible to have no lock after saving the lock state and being interrupted while @@ -342,5 +479,7 @@ BlockSecondaryReadsDuringBatchApplication_DONT_USE:: template class AutoGetCollectionForReadBase<AutoGetCollection>; template class AutoGetCollectionForReadCommandBase<AutoGetCollectionForRead>; +template class AutoGetCollectionForReadBase<AutoGetCollectionLockFree>; +template class AutoGetCollectionForReadCommandBase<AutoGetCollectionForReadLockFree>; } // namespace mongo diff --git a/src/mongo/db/db_raii.h b/src/mongo/db/db_raii.h index dbe6b1c1b70..b62a010832f 100644 --- a/src/mongo/db/db_raii.h +++ b/src/mongo/db/db_raii.h @@ -104,8 +104,8 @@ public: return getCollection().get(); } - Database* getDb() const { - return _autoColl->getDb(); + const CollectionPtr& operator*() const { + return getCollection(); } const CollectionPtr& getCollection() const { @@ -120,7 +120,7 @@ public: return _autoColl->getNss(); } -private: +protected: // If this field is set, the reader will not take the ParallelBatchWriterMode lock and conflict // with secondary batch application. This stays in scope with the _autoColl so that locks are // taken and released in the right order. @@ -142,7 +142,7 @@ private: * status of CurrentOp, or add a Top entry. * * NOTE: Must not be used with any locks held, because it needs to block waiting on the committed - * snapshot to become available. + * snapshot to become available, and can potentially release and reacquire locks. */ class AutoGetCollectionForRead : public AutoGetCollectionForReadBase<AutoGetCollection> { public: @@ -152,6 +152,85 @@ public: AutoGetCollectionViewMode viewMode = AutoGetCollectionViewMode::kViewsForbidden, Date_t deadline = Date_t::max()) : AutoGetCollectionForReadBase(opCtx, nsOrUUID, viewMode, deadline) {} + + Database* getDb() const { + return _autoColl->getDb(); + } +}; + +/** + * Same as AutoGetCollectionForRead above except does not take collection, database or rstl locks. + * Takes the global lock and may take the PBWM, same as AutoGetCollectionForRead. Ensures a + * consistent in-memory and on-disk view of the collection. + */ +class AutoGetCollectionForReadLockFree { +public: + AutoGetCollectionForReadLockFree( + OperationContext* opCtx, + const NamespaceStringOrUUID& nsOrUUID, + AutoGetCollectionViewMode viewMode = AutoGetCollectionViewMode::kViewsForbidden, + Date_t deadline = Date_t::max()); + + explicit operator bool() const { + return static_cast<bool>(getCollection()); + } + + const Collection* operator->() const { + return getCollection().get(); + } + + const CollectionPtr& operator*() const { + return getCollection(); + } + + const CollectionPtr& getCollection() const { + return _autoGetCollectionForReadBase->getCollection(); + } + + ViewDefinition* getView() const { + return _autoGetCollectionForReadBase->getView(); + } + + const NamespaceString& getNss() const { + return _autoGetCollectionForReadBase->getNss(); + } + +private: + boost::optional<AutoGetCollectionForReadBase<AutoGetCollectionLockFree>> + _autoGetCollectionForReadBase; +}; + +/** + * Creates either an AutoGetCollectionForRead or AutoGetCollectionForReadLockFree depending on + * whether a lock-free read is supported. + */ +class AutoGetCollectionForReadMaybeLockFree { +public: + AutoGetCollectionForReadMaybeLockFree( + OperationContext* opCtx, + const NamespaceStringOrUUID& nsOrUUID, + AutoGetCollectionViewMode viewMode = AutoGetCollectionViewMode::kViewsForbidden, + Date_t deadline = Date_t::max()); + + /** + * Passthrough functions to either _autoGet or _autoGetLockFree. + */ + explicit operator bool() const { + return static_cast<bool>(getCollection()); + } + const Collection* operator->() const { + return getCollection().get(); + } + const CollectionPtr& operator*() const { + return getCollection(); + } + const CollectionPtr& getCollection() const; + ViewDefinition* getView() const; + const NamespaceString& getNss() const; + +private: + boost::optional<AutoGetCollectionForRead> _autoGet; + boost::optional<AutoGetCollectionForReadLockFree> _autoGetLockFree; }; template <typename AutoGetCollectionForReadType> @@ -161,13 +240,6 @@ class AutoGetCollectionForReadCommandBase { delete; public: - AutoGetCollectionForReadCommandBase( - OperationContext* opCtx, - const NamespaceStringOrUUID& nsOrUUID, - AutoGetCollectionViewMode viewMode = AutoGetCollectionViewMode::kViewsForbidden, - Date_t deadline = Date_t::max(), - AutoStatsTracker::LogMode logMode = AutoStatsTracker::LogMode::kUpdateTopAndCurOp); - explicit operator bool() const { return static_cast<bool>(getCollection()); } @@ -176,8 +248,8 @@ public: return getCollection().get(); } - Database* getDb() const { - return _autoCollForRead.getDb(); + const CollectionPtr& operator*() const { + return getCollection(); } const CollectionPtr& getCollection() const { @@ -192,7 +264,14 @@ public: return _autoCollForRead.getNss(); } -private: +protected: + AutoGetCollectionForReadCommandBase( + OperationContext* opCtx, + const NamespaceStringOrUUID& nsOrUUID, + AutoGetCollectionViewMode viewMode = AutoGetCollectionViewMode::kViewsForbidden, + Date_t deadline = Date_t::max(), + AutoStatsTracker::LogMode logMode = AutoStatsTracker::LogMode::kUpdateTopAndCurOp); + AutoGetCollectionForReadType _autoCollForRead; AutoStatsTracker _statsTracker; }; @@ -211,6 +290,60 @@ public: Date_t deadline = Date_t::max(), AutoStatsTracker::LogMode logMode = AutoStatsTracker::LogMode::kUpdateTopAndCurOp) : AutoGetCollectionForReadCommandBase(opCtx, nsOrUUID, viewMode, deadline, logMode) {} + + Database* getDb() const { + return _autoCollForRead.getDb(); + } +}; + +/** + * Same as AutoGetCollectionForReadCommand except no collection, database or RSTL lock is taken. + */ +class AutoGetCollectionForReadCommandLockFree + : public AutoGetCollectionForReadCommandBase<AutoGetCollectionForReadLockFree> { +public: + AutoGetCollectionForReadCommandLockFree( + OperationContext* opCtx, + const NamespaceStringOrUUID& nsOrUUID, + AutoGetCollectionViewMode viewMode = AutoGetCollectionViewMode::kViewsForbidden, + Date_t deadline = Date_t::max(), + AutoStatsTracker::LogMode logMode = AutoStatsTracker::LogMode::kUpdateTopAndCurOp) + : AutoGetCollectionForReadCommandBase(opCtx, nsOrUUID, viewMode, deadline, logMode) {} +}; + +/** + * Creates either an AutoGetCollectionForReadCommand or AutoGetCollectionForReadCommandLockFree + * depending on whether a lock-free read is supported in the situation per the results of + * supportsLockFreeRead(). + */ +class AutoGetCollectionForReadCommandMaybeLockFree { +public: + AutoGetCollectionForReadCommandMaybeLockFree( + OperationContext* opCtx, + const NamespaceStringOrUUID& nsOrUUID, + AutoGetCollectionViewMode viewMode = AutoGetCollectionViewMode::kViewsForbidden, + Date_t deadline = Date_t::max(), + AutoStatsTracker::LogMode logMode = AutoStatsTracker::LogMode::kUpdateTopAndCurOp); + + /** + * Passthrough function to either _autoGet or _autoGetLockFree. + */ + explicit operator bool() const { + return static_cast<bool>(getCollection()); + } + const Collection* operator->() const { + return getCollection().get(); + } + const CollectionPtr& operator*() const { + return getCollection(); + } + const CollectionPtr& getCollection() const; + ViewDefinition* getView() const; + const NamespaceString& getNss() const; + +private: + boost::optional<AutoGetCollectionForReadCommand> _autoGet; + boost::optional<AutoGetCollectionForReadCommandLockFree> _autoGetLockFree; }; /** diff --git a/src/mongo/db/storage/SConscript b/src/mongo/db/storage/SConscript index d8fc600d89b..46dfc0ecc9b 100644 --- a/src/mongo/db/storage/SConscript +++ b/src/mongo/db/storage/SConscript @@ -38,6 +38,7 @@ env.Library( '$BUILD_DIR/mongo/db/namespace_string', ], LIBDEPS_PRIVATE=[ + '$BUILD_DIR/mongo/db/catalog/collection_catalog', '$BUILD_DIR/mongo/db/concurrency/lock_manager', '$BUILD_DIR/mongo/db/repl/read_concern_args', '$BUILD_DIR/mongo/db/repl/repl_coordinator_interface', diff --git a/src/mongo/embedded/replication_coordinator_embedded.cpp b/src/mongo/embedded/replication_coordinator_embedded.cpp index e8e43f17bdb..72b956887ca 100644 --- a/src/mongo/embedded/replication_coordinator_embedded.cpp +++ b/src/mongo/embedded/replication_coordinator_embedded.cpp @@ -459,7 +459,7 @@ Status ReplicationCoordinatorEmbedded::processHeartbeatV1(const ReplSetHeartbeat } long long ReplicationCoordinatorEmbedded::getTerm() const { - UASSERT_NOT_IMPLEMENTED; + return 3; // arbitrary constant number } Status ReplicationCoordinatorEmbedded::updateTerm(OperationContext*, long long) { |