diff options
author | ADAM David Alan Martin <adam.martin@10gen.com> | 2017-03-28 13:15:16 -0400 |
---|---|---|
committer | ADAM David Alan Martin <adam.martin@10gen.com> | 2017-03-28 13:15:16 -0400 |
commit | 8a988f7dfee2eaba686043cee9d7bca3366e0e8e (patch) | |
tree | 9010cfa47d3e77ce7da4c1b0b6f13c83a4e580c8 /src/mongo/db/catalog | |
parent | 00ee4f5156348477b9dd3f71b747104794f766c0 (diff) | |
download | mongo-8a988f7dfee2eaba686043cee9d7bca3366e0e8e.tar.gz |
SERVER-28025 Sever `index_access_method`'s catalog dependency.
Slice this dependency by inserting an `IndexCatalogEntry` proxy class
with a pure vtable, and exposing an inline version of `Collection::docFor`.
Diffstat (limited to 'src/mongo/db/catalog')
-rw-r--r-- | src/mongo/db/catalog/SConscript | 14 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection.h | 5 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_catalog.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_catalog_entry.cpp | 283 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_catalog_entry.h | 242 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_catalog_entry_impl.cpp | 326 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_catalog_entry_impl.h | 219 |
8 files changed, 722 insertions, 373 deletions
diff --git a/src/mongo/db/catalog/SConscript b/src/mongo/db/catalog/SConscript index 506750b65a2..7f1d7bb965b 100644 --- a/src/mongo/db/catalog/SConscript +++ b/src/mongo/db/catalog/SConscript @@ -54,7 +54,16 @@ env.CppUnitTest( LIBDEPS=[ '$BUILD_DIR/mongo/db/query/query_test_service_context', 'index_key_validate', - ] + ], +) + +env.Library( + target='index_catalog_entry', + source=[ + "index_catalog_entry.cpp", + ], + LIBDEPS=[ + ], ) env.Library( @@ -74,12 +83,13 @@ env.Library( "drop_database.cpp", "drop_indexes.cpp", "index_catalog.cpp", - "index_catalog_entry.cpp", + "index_catalog_entry_impl.cpp", "index_create.cpp", "rename_collection.cpp", ], LIBDEPS=[ 'collection_options', + 'index_catalog_entry', 'index_key_validate', '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/db/concurrency/lock_manager', diff --git a/src/mongo/db/catalog/collection.cpp b/src/mongo/db/catalog/collection.cpp index de7d36b205d..be824048daa 100644 --- a/src/mongo/db/catalog/collection.cpp +++ b/src/mongo/db/catalog/collection.cpp @@ -280,10 +280,6 @@ vector<std::unique_ptr<RecordCursor>> Collection::getManyCursors(OperationContex return _recordStore->getManyCursors(opCtx); } -Snapshotted<BSONObj> Collection::docFor(OperationContext* opCtx, const RecordId& loc) const { - return Snapshotted<BSONObj>(opCtx->recoveryUnit()->getSnapshotId(), - _recordStore->dataFor(opCtx, loc).releaseToBson()); -} bool Collection::findDoc(OperationContext* opCtx, const RecordId& loc, diff --git a/src/mongo/db/catalog/collection.h b/src/mongo/db/catalog/collection.h index 0d04abc5c76..16394710cca 100644 --- a/src/mongo/db/catalog/collection.h +++ b/src/mongo/db/catalog/collection.h @@ -228,7 +228,10 @@ public: bool requiresIdIndex() const; - Snapshotted<BSONObj> docFor(OperationContext* opCtx, const RecordId& loc) const; + Snapshotted<BSONObj> docFor(OperationContext* opCtx, const RecordId& loc) const { + return Snapshotted<BSONObj>(opCtx->recoveryUnit()->getSnapshotId(), + _recordStore->dataFor(opCtx, loc).releaseToBson()); + } /** * @param out - contents set to the right docs if exists, or nothing. diff --git a/src/mongo/db/catalog/index_catalog.cpp b/src/mongo/db/catalog/index_catalog.cpp index 9ff5f4e1027..faf7ad134e1 100644 --- a/src/mongo/db/catalog/index_catalog.cpp +++ b/src/mongo/db/catalog/index_catalog.cpp @@ -147,7 +147,7 @@ IndexCatalogEntry* IndexCatalog::_setupInMemoryStructures(OperationContext* opCt auto entry = stdx::make_unique<IndexCatalogEntry>(opCtx, _collection->ns().ns(), _collection->getCatalogEntry(), - descriptorCleanup.release(), + std::move(descriptorCleanup), _collection->infoCache()); std::unique_ptr<IndexAccessMethod> accessMethod( _collection->_dbce->getIndex(opCtx, _collection->getCatalogEntry(), entry.get())); diff --git a/src/mongo/db/catalog/index_catalog_entry.cpp b/src/mongo/db/catalog/index_catalog_entry.cpp index ae22ff4d633..94a05fde7ac 100644 --- a/src/mongo/db/catalog/index_catalog_entry.cpp +++ b/src/mongo/db/catalog/index_catalog_entry.cpp @@ -1,7 +1,5 @@ -// index_catalog_entry.cpp - /** -* Copyright (C) 2013 10gen Inc. +* Copyright (C) 2017 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, @@ -34,278 +32,40 @@ #include "mongo/db/catalog/index_catalog_entry.h" -#include <algorithm> - -#include "mongo/db/catalog/collection_catalog_entry.h" -#include "mongo/db/catalog/head_manager.h" -#include "mongo/db/concurrency/d_concurrency.h" -#include "mongo/db/concurrency/write_conflict_exception.h" #include "mongo/db/index/index_access_method.h" #include "mongo/db/index/index_descriptor.h" -#include "mongo/db/matcher/expression.h" -#include "mongo/db/matcher/expression_parser.h" -#include "mongo/db/matcher/extensions_callback_disallow_extensions.h" -#include "mongo/db/operation_context.h" -#include "mongo/db/query/collation/collator_factory_interface.h" -#include "mongo/db/service_context.h" -#include "mongo/util/log.h" -#include "mongo/util/scopeguard.h" namespace mongo { +IndexCatalogEntry::Impl::~Impl() = default; -using std::string; - -class HeadManagerImpl : public HeadManager { -public: - HeadManagerImpl(IndexCatalogEntry* ice) : _catalogEntry(ice) {} - virtual ~HeadManagerImpl() {} +namespace { +stdx::function<IndexCatalogEntry::factory_function_type> factory; +} // namespace - const RecordId getHead(OperationContext* opCtx) const { - return _catalogEntry->head(opCtx); - } +void IndexCatalogEntry::registerFactory(decltype(factory) newFactory) { + factory = std::move(newFactory); +} - void setHead(OperationContext* opCtx, const RecordId newHead) { - _catalogEntry->setHead(opCtx, newHead); - } +auto IndexCatalogEntry::makeImpl(IndexCatalogEntry* const this_, + OperationContext* const opCtx, + const StringData ns, + CollectionCatalogEntry* const collection, + std::unique_ptr<IndexDescriptor> descriptor, + CollectionInfoCache* const infoCache) -> std::unique_ptr<Impl> { + return factory(this_, opCtx, ns, collection, std::move(descriptor), infoCache); +} -private: - // Not owned here. - IndexCatalogEntry* _catalogEntry; -}; +void IndexCatalogEntry::TUHook::hook() noexcept {} IndexCatalogEntry::IndexCatalogEntry(OperationContext* opCtx, StringData ns, CollectionCatalogEntry* collection, - IndexDescriptor* descriptor, + std::unique_ptr<IndexDescriptor> descriptor, CollectionInfoCache* infoCache) - : _ns(ns.toString()), - _collection(collection), - _descriptor(descriptor), - _infoCache(infoCache), - _headManager(new HeadManagerImpl(this)), - _ordering(Ordering::make(descriptor->keyPattern())), - _isReady(false) { - _descriptor->_cachedEntry = this; - - _isReady = _catalogIsReady(opCtx); - _head = _catalogHead(opCtx); - - { - stdx::lock_guard<stdx::mutex> lk(_indexMultikeyPathsMutex); - _isMultikey.store(_catalogIsMultikey(opCtx, &_indexMultikeyPaths)); - _indexTracksPathLevelMultikeyInfo = !_indexMultikeyPaths.empty(); - } - - if (BSONElement collationElement = _descriptor->getInfoElement("collation")) { - invariant(collationElement.isABSONObj()); - BSONObj collation = collationElement.Obj(); - auto statusWithCollator = - CollatorFactoryInterface::get(opCtx->getServiceContext())->makeFromBSON(collation); - - // Index spec should have already been validated. - invariantOK(statusWithCollator.getStatus()); - - _collator = std::move(statusWithCollator.getValue()); - } - - if (BSONElement filterElement = _descriptor->getInfoElement("partialFilterExpression")) { - invariant(filterElement.isABSONObj()); - BSONObj filter = filterElement.Obj(); - StatusWithMatchExpression statusWithMatcher = MatchExpressionParser::parse( - filter, ExtensionsCallbackDisallowExtensions(), _collator.get()); - // this should be checked in create, so can blow up here - invariantOK(statusWithMatcher.getStatus()); - _filterExpression = std::move(statusWithMatcher.getValue()); - LOG(2) << "have filter expression for " << _ns << " " << _descriptor->indexName() << " " - << redact(filter); - } -} - -IndexCatalogEntry::~IndexCatalogEntry() { - _descriptor->_cachedEntry = NULL; // defensive - - delete _headManager; - delete _descriptor; -} + : _pimpl(makeImpl(this, opCtx, ns, collection, std::move(descriptor), infoCache)) {} void IndexCatalogEntry::init(std::unique_ptr<IndexAccessMethod> accessMethod) { - invariant(!_accessMethod); - _accessMethod = std::move(accessMethod); -} - -const RecordId& IndexCatalogEntry::head(OperationContext* opCtx) const { - DEV invariant(_head == _catalogHead(opCtx)); - return _head; -} - -bool IndexCatalogEntry::isReady(OperationContext* opCtx) const { - DEV invariant(_isReady == _catalogIsReady(opCtx)); - return _isReady; -} - -bool IndexCatalogEntry::isMultikey() const { - return _isMultikey.load(); -} - -MultikeyPaths IndexCatalogEntry::getMultikeyPaths(OperationContext* opCtx) const { - stdx::lock_guard<stdx::mutex> lk(_indexMultikeyPathsMutex); - return _indexMultikeyPaths; -} - -// --- - -void IndexCatalogEntry::setIsReady(bool newIsReady) { - _isReady = newIsReady; -} - -class IndexCatalogEntry::SetHeadChange : public RecoveryUnit::Change { -public: - SetHeadChange(IndexCatalogEntry* ice, RecordId oldHead) : _ice(ice), _oldHead(oldHead) {} - - virtual void commit() {} - virtual void rollback() { - _ice->_head = _oldHead; - } - - IndexCatalogEntry* _ice; - const RecordId _oldHead; -}; - -void IndexCatalogEntry::setHead(OperationContext* opCtx, RecordId newHead) { - _collection->setIndexHead(opCtx, _descriptor->indexName(), newHead); - - opCtx->recoveryUnit()->registerChange(new SetHeadChange(this, _head)); - _head = newHead; -} - - -/** - * RAII class, which associates a new RecoveryUnit with an OperationContext for the purposes - * of simulating a side-transaction. Takes ownership of the new recovery unit and frees it at - * destruction time. - */ -class RecoveryUnitSwap { -public: - RecoveryUnitSwap(OperationContext* opCtx, RecoveryUnit* newRecoveryUnit) - : _opCtx(opCtx), - _oldRecoveryUnit(_opCtx->releaseRecoveryUnit()), - _oldRecoveryUnitState( - _opCtx->setRecoveryUnit(newRecoveryUnit, OperationContext::kNotInUnitOfWork)), - _newRecoveryUnit(newRecoveryUnit) {} - - ~RecoveryUnitSwap() { - _opCtx->releaseRecoveryUnit(); - _opCtx->setRecoveryUnit(_oldRecoveryUnit, _oldRecoveryUnitState); - } - -private: - // Not owned - OperationContext* const _opCtx; - - // Owned, but life-time is not controlled - RecoveryUnit* const _oldRecoveryUnit; - OperationContext::RecoveryUnitState const _oldRecoveryUnitState; - - // Owned and life-time is controlled - const std::unique_ptr<RecoveryUnit> _newRecoveryUnit; -}; - -void IndexCatalogEntry::setMultikey(OperationContext* opCtx, const MultikeyPaths& multikeyPaths) { - if (!_indexTracksPathLevelMultikeyInfo && isMultikey()) { - // If the index is already set as multikey and we don't have any path-level information to - // update, then there's nothing more for us to do. - return; - } - - if (_indexTracksPathLevelMultikeyInfo) { - stdx::lock_guard<stdx::mutex> lk(_indexMultikeyPathsMutex); - invariant(multikeyPaths.size() == _indexMultikeyPaths.size()); - - bool newPathIsMultikey = false; - for (size_t i = 0; i < multikeyPaths.size(); ++i) { - if (!std::includes(_indexMultikeyPaths[i].begin(), - _indexMultikeyPaths[i].end(), - multikeyPaths[i].begin(), - multikeyPaths[i].end())) { - // If 'multikeyPaths' contains a new path component that causes this index to be - // multikey, then we must update the index metadata in the CollectionCatalogEntry. - newPathIsMultikey = true; - break; - } - } - - if (!newPathIsMultikey) { - // Otherwise, if all the path components in 'multikeyPaths' are already tracked in - // '_indexMultikeyPaths', then there's nothing more for us to do. - return; - } - } - - { - // Only one thread should set the multi-key value per collection, because the metadata for a - // collection is one large document. - Lock::ResourceLock collMDLock( - opCtx->lockState(), ResourceId(RESOURCE_METADATA, _ns), MODE_X); - - if (!_indexTracksPathLevelMultikeyInfo && isMultikey()) { - // It's possible that we raced with another thread when acquiring the MD lock. If the - // index is already set as multikey and we don't have any path-level information to - // update, then there's nothing more for us to do. - return; - } - - // This effectively emulates a side-transaction off the main transaction, which invoked - // setMultikey. The reason we need is to avoid artificial WriteConflicts, which happen with - // snapshot isolation. - { - StorageEngine* storageEngine = getGlobalServiceContext()->getGlobalStorageEngine(); - RecoveryUnitSwap ruSwap(opCtx, storageEngine->newRecoveryUnit()); - - WriteUnitOfWork wuow(opCtx); - - // It's possible that the index type (e.g. ascending/descending index) supports tracking - // path-level multikey information, but this particular index doesn't. - // CollectionCatalogEntry::setIndexIsMultikey() requires that we discard the path-level - // multikey information in order to avoid unintentionally setting path-level multikey - // information on an index created before 3.4. - if (_collection->setIndexIsMultikey( - opCtx, - _descriptor->indexName(), - _indexTracksPathLevelMultikeyInfo ? multikeyPaths : MultikeyPaths{})) { - if (_infoCache) { - LOG(1) << _ns << ": clearing plan cache - index " << _descriptor->keyPattern() - << " set to multi key."; - _infoCache->clearQueryCache(); - } - } - - wuow.commit(); - } - } - - _isMultikey.store(true); - - if (_indexTracksPathLevelMultikeyInfo) { - stdx::lock_guard<stdx::mutex> lk(_indexMultikeyPathsMutex); - for (size_t i = 0; i < multikeyPaths.size(); ++i) { - _indexMultikeyPaths[i].insert(multikeyPaths[i].begin(), multikeyPaths[i].end()); - } - } -} - -// ---- - -bool IndexCatalogEntry::_catalogIsReady(OperationContext* opCtx) const { - return _collection->isIndexReady(opCtx, _descriptor->indexName()); -} - -RecordId IndexCatalogEntry::_catalogHead(OperationContext* opCtx) const { - return _collection->getIndexHead(opCtx, _descriptor->indexName()); -} - -bool IndexCatalogEntry::_catalogIsMultikey(OperationContext* opCtx, - MultikeyPaths* multikeyPaths) const { - return _collection->isIndexMultikey(opCtx, _descriptor->indexName(), multikeyPaths); + return this->_impl().init(std::move(accessMethod)); } // ------------------ @@ -334,7 +94,7 @@ IndexCatalogEntry* IndexCatalogEntryContainer::find(const IndexDescriptor* desc) return nullptr; } -IndexCatalogEntry* IndexCatalogEntryContainer::find(const string& name) { +IndexCatalogEntry* IndexCatalogEntryContainer::find(const std::string& name) { for (iterator i = begin(); i != end(); ++i) { IndexCatalogEntry* e = i->get(); if (e->descriptor()->indexName() == name) @@ -353,5 +113,4 @@ IndexCatalogEntry* IndexCatalogEntryContainer::release(const IndexDescriptor* de } return nullptr; } - } // namespace mongo diff --git a/src/mongo/db/catalog/index_catalog_entry.h b/src/mongo/db/catalog/index_catalog_entry.h index 6a36f455661..66eac041713 100644 --- a/src/mongo/db/catalog/index_catalog_entry.h +++ b/src/mongo/db/catalog/index_catalog_entry.h @@ -1,7 +1,5 @@ -// index_catalog_entry.h - /** -* Copyright (C) 2013 10gen Inc. +* Copyright (C) 2017 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, @@ -39,10 +37,11 @@ #include "mongo/db/record_id.h" #include "mongo/db/storage/snapshot_name.h" #include "mongo/platform/atomic_word.h" +#include "mongo/stdx/functional.h" #include "mongo/stdx/mutex.h" +#include "mongo/util/debug_util.h" namespace mongo { - class CollatorInterface; class CollectionCatalogEntry; class CollectionInfoCache; @@ -53,67 +52,157 @@ class MatchExpression; class OperationContext; class IndexCatalogEntry { - MONGO_DISALLOW_COPYING(IndexCatalogEntry); +public: + // This class represents the internal vtable for the (potentially polymorphic) implementation of + // the `IndexCatalogEntry` class. This allows us to expose an interface to this object without + // requiring a dependency upon the implementation's definition library. + class Impl { + public: + virtual ~Impl() = 0; + + virtual const std::string& ns() const = 0; + + virtual void init(std::unique_ptr<IndexAccessMethod> accessMethod) = 0; + + virtual IndexDescriptor* descriptor() = 0; + + virtual const IndexDescriptor* descriptor() const = 0; + + virtual IndexAccessMethod* accessMethod() = 0; + + virtual const IndexAccessMethod* accessMethod() const = 0; + + virtual const Ordering& ordering() const = 0; + + virtual const MatchExpression* getFilterExpression() const = 0; + + virtual const CollatorInterface* getCollator() const = 0; + + virtual const RecordId& head(OperationContext* opCtx) const = 0; + + virtual void setHead(OperationContext* opCtx, RecordId newHead) = 0; + + virtual void setIsReady(bool newIsReady) = 0; + + virtual HeadManager* headManager() const = 0; + + virtual bool isMultikey() const = 0; + + virtual MultikeyPaths getMultikeyPaths(OperationContext* opCtx) const = 0; + + virtual void setMultikey(OperationContext* opCtx, const MultikeyPaths& multikeyPaths) = 0; + + virtual bool isReady(OperationContext* opCtx) const = 0; + + virtual boost::optional<SnapshotName> getMinimumVisibleSnapshot() = 0; + + virtual void setMinimumVisibleSnapshot(SnapshotName name) = 0; + }; + +private: + std::unique_ptr<Impl> _pimpl; + struct TUHook { + static void hook() noexcept; + + explicit inline TUHook() noexcept { + if (kDebugBuild) + this->hook(); + } + }; + inline const Impl& _impl() const { + TUHook{}; + return *this->_pimpl; + } + inline Impl& _impl() { + TUHook{}; + return *this->_pimpl; + } + + static std::unique_ptr<Impl> makeImpl(IndexCatalogEntry* this_, + OperationContext* opCtx, + StringData ns, + CollectionCatalogEntry* collection, + std::unique_ptr<IndexDescriptor> descriptor, + CollectionInfoCache* infoCache); public: - IndexCatalogEntry(OperationContext* opCtx, - StringData ns, - CollectionCatalogEntry* collection, // not owned - IndexDescriptor* descriptor, // ownership passes to me - CollectionInfoCache* infoCache); // not owned, optional + using factory_function_type = decltype(makeImpl); + + static void registerFactory(stdx::function<factory_function_type> factory); - ~IndexCatalogEntry(); + explicit IndexCatalogEntry( + OperationContext* opCtx, + StringData ns, + CollectionCatalogEntry* collection, // not owned + std::unique_ptr<IndexDescriptor> descriptor, // ownership passes to me + CollectionInfoCache* infoCache); // not owned, optional - const std::string& ns() const { - return _ns; + // Do not call this function. It exists for use with test drivers that need to inject + // alternative implementations. + explicit IndexCatalogEntry(std::unique_ptr<Impl> impl) : _pimpl(std::move(impl)) {} + + inline ~IndexCatalogEntry() = default; + + inline const std::string& ns() const { + return this->_impl().ns(); } void init(std::unique_ptr<IndexAccessMethod> accessMethod); - IndexDescriptor* descriptor() { - return _descriptor; + inline IndexDescriptor* descriptor() { + return this->_impl().descriptor(); } - const IndexDescriptor* descriptor() const { - return _descriptor; + + inline const IndexDescriptor* descriptor() const { + return this->_impl().descriptor(); } - IndexAccessMethod* accessMethod() { - return _accessMethod.get(); + inline IndexAccessMethod* accessMethod() { + return this->_impl().accessMethod(); } - const IndexAccessMethod* accessMethod() const { - return _accessMethod.get(); + + inline const IndexAccessMethod* accessMethod() const { + return this->_impl().accessMethod(); } - const Ordering& ordering() const { - return _ordering; + inline const Ordering& ordering() const { + return this->_impl().ordering(); } - const MatchExpression* getFilterExpression() const { - return _filterExpression.get(); + inline const MatchExpression* getFilterExpression() const { + return this->_impl().getFilterExpression(); } - const CollatorInterface* getCollator() const { - return _collator.get(); + inline const CollatorInterface* getCollator() const { + return this->_impl().getCollator(); } /// --------------------- - const RecordId& head(OperationContext* opCtx) const; + inline const RecordId& head(OperationContext* const opCtx) const { + return this->_impl().head(opCtx); + } - void setHead(OperationContext* opCtx, RecordId newHead); + inline void setHead(OperationContext* const opCtx, const RecordId newHead) { + return this->_impl().setHead(opCtx, newHead); + } - void setIsReady(bool newIsReady); + inline void setIsReady(const bool newIsReady) { + return this->_impl().setIsReady(newIsReady); + } - HeadManager* headManager() const { - return _headManager; + inline HeadManager* headManager() const { + return this->_impl().headManager(); } // -- /** - * Returns true if this index is multikey, and returns false otherwise. + * Returns true if this index is multikey and false otherwise. */ - bool isMultikey() const; + inline bool isMultikey() const { + return this->_impl().isMultikey(); + } /** * Returns the path components that cause this index to be multikey if this index supports @@ -124,7 +213,9 @@ public: * returns a vector with size equal to the number of elements in the index key pattern where * each element in the vector is an empty set. */ - MultikeyPaths getMultikeyPaths(OperationContext* opCtx) const; + inline MultikeyPaths getMultikeyPaths(OperationContext* const opCtx) const { + return this->_impl().getMultikeyPaths(opCtx); + } /** * Sets this index to be multikey. Information regarding which newly detected path components @@ -136,84 +227,26 @@ public: * with size equal to the number of elements in the index key pattern. Additionally, at least * one path component of the indexed fields must cause this index to be multikey. */ - void setMultikey(OperationContext* opCtx, const MultikeyPaths& multikeyPaths); + void setMultikey(OperationContext* const opCtx, const MultikeyPaths& multikeyPaths) { + return this->_impl().setMultikey(opCtx, multikeyPaths); + } // if this ready is ready for queries - bool isReady(OperationContext* opCtx) const; + bool isReady(OperationContext* const opCtx) const { + return this->_impl().isReady(opCtx); + } /** * If return value is not boost::none, reads with majority read concern using an older snapshot * must treat this index as unfinished. */ boost::optional<SnapshotName> getMinimumVisibleSnapshot() { - return _minVisibleSnapshot; + return this->_impl().getMinimumVisibleSnapshot(); } - void setMinimumVisibleSnapshot(SnapshotName name) { - _minVisibleSnapshot = name; + void setMinimumVisibleSnapshot(const SnapshotName name) { + return this->_impl().setMinimumVisibleSnapshot(name); } - -private: - class SetMultikeyChange; - class SetHeadChange; - - bool _catalogIsReady(OperationContext* opCtx) const; - RecordId _catalogHead(OperationContext* opCtx) const; - - /** - * Retrieves the multikey information associated with this index from '_collection', - * - * See CollectionCatalogEntry::isIndexMultikey() for more details. - */ - bool _catalogIsMultikey(OperationContext* opCtx, MultikeyPaths* multikeyPaths) const; - - // ----- - - std::string _ns; - - CollectionCatalogEntry* _collection; // not owned here - - IndexDescriptor* _descriptor; // owned here - - CollectionInfoCache* _infoCache; // not owned here - - std::unique_ptr<IndexAccessMethod> _accessMethod; - - // Owned here. - HeadManager* _headManager; - std::unique_ptr<CollatorInterface> _collator; - std::unique_ptr<MatchExpression> _filterExpression; - - // cached stuff - - Ordering _ordering; // TODO: this might be b-tree specific - bool _isReady; // cache of NamespaceDetails info - RecordId _head; // cache of IndexDetails - - // Set to true if this index supports path-level multikey tracking. - // '_indexTracksPathLevelMultikeyInfo' is effectively const after IndexCatalogEntry::init() is - // called. - bool _indexTracksPathLevelMultikeyInfo = false; - - // Set to true if this index is multikey. '_isMultikey' serves as a cache of the information - // stored in the NamespaceDetails or KVCatalog. - AtomicWord<bool> _isMultikey; - - // Controls concurrent access to '_indexMultikeyPaths'. We acquire this mutex rather than the - // RESOURCE_METADATA lock as a performance optimization so that it is cheaper to detect whether - // there is actually any path-level multikey information to update or not. - mutable stdx::mutex _indexMultikeyPathsMutex; - - // Non-empty only if '_indexTracksPathLevelMultikeyInfo' is true. - // - // If non-empty, '_indexMultikeyPaths' is a vector with size equal to the number of elements - // in the index key pattern. Each element in the vector is an ordered set of positions (starting - // at 0) into the corresponding indexed field that represent what prefixes of the indexed field - // causes the index to be multikey. - MultikeyPaths _indexMultikeyPaths; - - // The earliest snapshot that is allowed to read this index. - boost::optional<SnapshotName> _minVisibleSnapshot; }; class IndexCatalogEntryContainer { @@ -224,6 +257,7 @@ public: const_iterator begin() const { return _entries.begin(); } + const_iterator end() const { return _entries.end(); } @@ -231,6 +265,7 @@ public: iterator begin() { return _entries.begin(); } + iterator end() { return _entries.end(); } @@ -246,6 +281,7 @@ public: unsigned size() const { return _entries.size(); } + // ----------------- /** @@ -267,4 +303,4 @@ public: private: std::vector<std::unique_ptr<IndexCatalogEntry>> _entries; }; -} +} // namespace mongo diff --git a/src/mongo/db/catalog/index_catalog_entry_impl.cpp b/src/mongo/db/catalog/index_catalog_entry_impl.cpp new file mode 100644 index 00000000000..7b0295d234b --- /dev/null +++ b/src/mongo/db/catalog/index_catalog_entry_impl.cpp @@ -0,0 +1,326 @@ +/** +* Copyright (C) 2013 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* 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 GNU Affero General 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::kIndex + +#include "mongo/platform/basic.h" + +#include "mongo/db/catalog/index_catalog_entry_impl.h" + +#include <algorithm> + +#include "mongo/base/init.h" +#include "mongo/db/catalog/collection_catalog_entry.h" +#include "mongo/db/catalog/head_manager.h" +#include "mongo/db/concurrency/d_concurrency.h" +#include "mongo/db/concurrency/write_conflict_exception.h" +#include "mongo/db/index/index_access_method.h" +#include "mongo/db/index/index_descriptor.h" +#include "mongo/db/matcher/expression.h" +#include "mongo/db/matcher/expression_parser.h" +#include "mongo/db/matcher/extensions_callback_disallow_extensions.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/query/collation/collator_factory_interface.h" +#include "mongo/db/service_context.h" +#include "mongo/stdx/memory.h" +#include "mongo/util/log.h" +#include "mongo/util/scopeguard.h" + +namespace mongo { +namespace { +MONGO_INITIALIZER(InitializeIndexCatalogEntryFactory)(InitializerContext* const) { + IndexCatalogEntry::registerFactory([](IndexCatalogEntry* const this_, + OperationContext* const opCtx, + const StringData ns, + CollectionCatalogEntry* const collection, + std::unique_ptr<IndexDescriptor> descriptor, + CollectionInfoCache* const infoCache) { + return stdx::make_unique<IndexCatalogEntryImpl>( + this_, opCtx, ns, collection, std::move(descriptor), infoCache); + }); + return Status::OK(); +} +} // namespace + +using std::string; + +class HeadManagerImpl : public HeadManager { +public: + HeadManagerImpl(IndexCatalogEntry* ice) : _catalogEntry(ice) {} + virtual ~HeadManagerImpl() {} + + const RecordId getHead(OperationContext* opCtx) const { + return _catalogEntry->head(opCtx); + } + + void setHead(OperationContext* opCtx, const RecordId newHead) { + _catalogEntry->setHead(opCtx, newHead); + } + +private: + // Not owned here. + IndexCatalogEntry* _catalogEntry; +}; + +IndexCatalogEntryImpl::IndexCatalogEntryImpl(IndexCatalogEntry* const this_, + OperationContext* const opCtx, + const StringData ns, + CollectionCatalogEntry* const collection, + std::unique_ptr<IndexDescriptor> descriptor, + CollectionInfoCache* const infoCache) + : _ns(ns.toString()), + _collection(collection), + _descriptor(std::move(descriptor)), + _infoCache(infoCache), + _headManager(stdx::make_unique<HeadManagerImpl>(this_)), + _ordering(Ordering::make(_descriptor->keyPattern())), + _isReady(false) { + _descriptor->_cachedEntry = this_; + + _isReady = _catalogIsReady(opCtx); + _head = _catalogHead(opCtx); + + { + stdx::lock_guard<stdx::mutex> lk(_indexMultikeyPathsMutex); + _isMultikey.store(_catalogIsMultikey(opCtx, &_indexMultikeyPaths)); + _indexTracksPathLevelMultikeyInfo = !_indexMultikeyPaths.empty(); + } + + if (BSONElement collationElement = _descriptor->getInfoElement("collation")) { + invariant(collationElement.isABSONObj()); + BSONObj collation = collationElement.Obj(); + auto statusWithCollator = + CollatorFactoryInterface::get(opCtx->getServiceContext())->makeFromBSON(collation); + + // Index spec should have already been validated. + invariantOK(statusWithCollator.getStatus()); + + _collator = std::move(statusWithCollator.getValue()); + } + + if (BSONElement filterElement = _descriptor->getInfoElement("partialFilterExpression")) { + invariant(filterElement.isABSONObj()); + BSONObj filter = filterElement.Obj(); + StatusWithMatchExpression statusWithMatcher = MatchExpressionParser::parse( + filter, ExtensionsCallbackDisallowExtensions(), _collator.get()); + // this should be checked in create, so can blow up here + invariantOK(statusWithMatcher.getStatus()); + _filterExpression = std::move(statusWithMatcher.getValue()); + LOG(2) << "have filter expression for " << _ns << " " << _descriptor->indexName() << " " + << redact(filter); + } +} + +IndexCatalogEntryImpl::~IndexCatalogEntryImpl() { + _descriptor->_cachedEntry = nullptr; // defensive + + _headManager.reset(); + _descriptor.reset(); +} + +void IndexCatalogEntryImpl::init(std::unique_ptr<IndexAccessMethod> accessMethod) { + invariant(!_accessMethod); + _accessMethod = std::move(accessMethod); +} + +const RecordId& IndexCatalogEntryImpl::head(OperationContext* opCtx) const { + DEV invariant(_head == _catalogHead(opCtx)); + return _head; +} + +bool IndexCatalogEntryImpl::isReady(OperationContext* opCtx) const { + DEV invariant(_isReady == _catalogIsReady(opCtx)); + return _isReady; +} + +bool IndexCatalogEntryImpl::isMultikey() const { + return _isMultikey.load(); +} + +MultikeyPaths IndexCatalogEntryImpl::getMultikeyPaths(OperationContext* opCtx) const { + stdx::lock_guard<stdx::mutex> lk(_indexMultikeyPathsMutex); + return _indexMultikeyPaths; +} + +// --- + +void IndexCatalogEntryImpl::setIsReady(bool newIsReady) { + _isReady = newIsReady; +} + +class IndexCatalogEntryImpl::SetHeadChange : public RecoveryUnit::Change { +public: + SetHeadChange(IndexCatalogEntryImpl* ice, RecordId oldHead) : _ice(ice), _oldHead(oldHead) {} + + virtual void commit() {} + virtual void rollback() { + _ice->_head = _oldHead; + } + + IndexCatalogEntryImpl* _ice; + const RecordId _oldHead; +}; + +void IndexCatalogEntryImpl::setHead(OperationContext* opCtx, RecordId newHead) { + _collection->setIndexHead(opCtx, _descriptor->indexName(), newHead); + + opCtx->recoveryUnit()->registerChange(new SetHeadChange(this, _head)); + _head = newHead; +} + + +/** + * RAII class, which associates a new RecoveryUnit with an OperationContext for the purposes + * of simulating a side-transaction. Takes ownership of the new recovery unit and frees it at + * destruction time. + */ +class RecoveryUnitSwap { +public: + RecoveryUnitSwap(OperationContext* opCtx, RecoveryUnit* newRecoveryUnit) + : _opCtx(opCtx), + _oldRecoveryUnit(_opCtx->releaseRecoveryUnit()), + _oldRecoveryUnitState( + _opCtx->setRecoveryUnit(newRecoveryUnit, OperationContext::kNotInUnitOfWork)), + _newRecoveryUnit(newRecoveryUnit) {} + + ~RecoveryUnitSwap() { + _opCtx->releaseRecoveryUnit(); + _opCtx->setRecoveryUnit(_oldRecoveryUnit, _oldRecoveryUnitState); + } + +private: + // Not owned + OperationContext* const _opCtx; + + // Owned, but life-time is not controlled + RecoveryUnit* const _oldRecoveryUnit; + OperationContext::RecoveryUnitState const _oldRecoveryUnitState; + + // Owned and life-time is controlled + const std::unique_ptr<RecoveryUnit> _newRecoveryUnit; +}; + +void IndexCatalogEntryImpl::setMultikey(OperationContext* opCtx, + const MultikeyPaths& multikeyPaths) { + if (!_indexTracksPathLevelMultikeyInfo && isMultikey()) { + // If the index is already set as multikey and we don't have any path-level information to + // update, then there's nothing more for us to do. + return; + } + + if (_indexTracksPathLevelMultikeyInfo) { + stdx::lock_guard<stdx::mutex> lk(_indexMultikeyPathsMutex); + invariant(multikeyPaths.size() == _indexMultikeyPaths.size()); + + bool newPathIsMultikey = false; + for (size_t i = 0; i < multikeyPaths.size(); ++i) { + if (!std::includes(_indexMultikeyPaths[i].begin(), + _indexMultikeyPaths[i].end(), + multikeyPaths[i].begin(), + multikeyPaths[i].end())) { + // If 'multikeyPaths' contains a new path component that causes this index to be + // multikey, then we must update the index metadata in the CollectionCatalogEntry. + newPathIsMultikey = true; + break; + } + } + + if (!newPathIsMultikey) { + // Otherwise, if all the path components in 'multikeyPaths' are already tracked in + // '_indexMultikeyPaths', then there's nothing more for us to do. + return; + } + } + + { + // Only one thread should set the multi-key value per collection, because the metadata for a + // collection is one large document. + Lock::ResourceLock collMDLock( + opCtx->lockState(), ResourceId(RESOURCE_METADATA, _ns), MODE_X); + + if (!_indexTracksPathLevelMultikeyInfo && isMultikey()) { + // It's possible that we raced with another thread when acquiring the MD lock. If the + // index is already set as multikey and we don't have any path-level information to + // update, then there's nothing more for us to do. + return; + } + + // This effectively emulates a side-transaction off the main transaction, which invoked + // setMultikey. The reason we need is to avoid artificial WriteConflicts, which happen with + // snapshot isolation. + { + StorageEngine* storageEngine = getGlobalServiceContext()->getGlobalStorageEngine(); + RecoveryUnitSwap ruSwap(opCtx, storageEngine->newRecoveryUnit()); + + WriteUnitOfWork wuow(opCtx); + + // It's possible that the index type (e.g. ascending/descending index) supports tracking + // path-level multikey information, but this particular index doesn't. + // CollectionCatalogEntry::setIndexIsMultikey() requires that we discard the path-level + // multikey information in order to avoid unintentionally setting path-level multikey + // information on an index created before 3.4. + if (_collection->setIndexIsMultikey( + opCtx, + _descriptor->indexName(), + _indexTracksPathLevelMultikeyInfo ? multikeyPaths : MultikeyPaths{})) { + if (_infoCache) { + LOG(1) << _ns << ": clearing plan cache - index " << _descriptor->keyPattern() + << " set to multi key."; + _infoCache->clearQueryCache(); + } + } + + wuow.commit(); + } + } + + _isMultikey.store(true); + + if (_indexTracksPathLevelMultikeyInfo) { + stdx::lock_guard<stdx::mutex> lk(_indexMultikeyPathsMutex); + for (size_t i = 0; i < multikeyPaths.size(); ++i) { + _indexMultikeyPaths[i].insert(multikeyPaths[i].begin(), multikeyPaths[i].end()); + } + } +} + +// ---- + +bool IndexCatalogEntryImpl::_catalogIsReady(OperationContext* opCtx) const { + return _collection->isIndexReady(opCtx, _descriptor->indexName()); +} + +RecordId IndexCatalogEntryImpl::_catalogHead(OperationContext* opCtx) const { + return _collection->getIndexHead(opCtx, _descriptor->indexName()); +} + +bool IndexCatalogEntryImpl::_catalogIsMultikey(OperationContext* opCtx, + MultikeyPaths* multikeyPaths) const { + return _collection->isIndexMultikey(opCtx, _descriptor->indexName(), multikeyPaths); +} +} // namespace mongo diff --git a/src/mongo/db/catalog/index_catalog_entry_impl.h b/src/mongo/db/catalog/index_catalog_entry_impl.h new file mode 100644 index 00000000000..593c74dc3f1 --- /dev/null +++ b/src/mongo/db/catalog/index_catalog_entry_impl.h @@ -0,0 +1,219 @@ +/** +* Copyright (C) 2013 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* 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 +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* 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 GNU Affero General 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 <boost/optional.hpp> +#include <string> + +#include "mongo/base/owned_pointer_vector.h" +#include "mongo/bson/ordering.h" +#include "mongo/db/catalog/index_catalog_entry.h" +#include "mongo/db/index/multikey_paths.h" +#include "mongo/db/record_id.h" +#include "mongo/db/storage/snapshot_name.h" +#include "mongo/platform/atomic_word.h" +#include "mongo/stdx/mutex.h" + +namespace mongo { + +class CollatorInterface; +class CollectionCatalogEntry; +class CollectionInfoCache; +class HeadManager; +class IndexAccessMethod; +class IndexDescriptor; +class MatchExpression; +class OperationContext; + +class IndexCatalogEntryImpl : public IndexCatalogEntry::Impl { + MONGO_DISALLOW_COPYING(IndexCatalogEntryImpl); + +public: + explicit IndexCatalogEntryImpl( + IndexCatalogEntry* this_, + OperationContext* opCtx, + StringData ns, + CollectionCatalogEntry* collection, // not owned + std::unique_ptr<IndexDescriptor> descriptor, // ownership passes to me + CollectionInfoCache* infoCache); // not owned, optional + + ~IndexCatalogEntryImpl() final; + + const std::string& ns() const final { + return _ns; + } + + void init(std::unique_ptr<IndexAccessMethod> accessMethod) final; + + IndexDescriptor* descriptor() final { + return _descriptor.get(); + } + const IndexDescriptor* descriptor() const final { + return _descriptor.get(); + } + + IndexAccessMethod* accessMethod() final { + return _accessMethod.get(); + } + const IndexAccessMethod* accessMethod() const final { + return _accessMethod.get(); + } + + const Ordering& ordering() const final { + return _ordering; + } + + const MatchExpression* getFilterExpression() const final { + return _filterExpression.get(); + } + + const CollatorInterface* getCollator() const final { + return _collator.get(); + } + + /// --------------------- + + const RecordId& head(OperationContext* opCtx) const final; + + void setHead(OperationContext* opCtx, RecordId newHead) final; + + void setIsReady(bool newIsReady) final; + + HeadManager* headManager() const final { + return _headManager.get(); + } + + // -- + + /** + * Returns true if this index is multikey, and returns false otherwise. + */ + bool isMultikey() const final; + + /** + * Returns the path components that cause this index to be multikey if this index supports + * path-level multikey tracking, and returns an empty vector if path-level multikey tracking + * isn't supported. + * + * If this index supports path-level multikey tracking but isn't multikey, then this function + * returns a vector with size equal to the number of elements in the index key pattern where + * each element in the vector is an empty set. + */ + MultikeyPaths getMultikeyPaths(OperationContext* opCtx) const final; + + /** + * Sets this index to be multikey. Information regarding which newly detected path components + * cause this index to be multikey can also be specified. + * + * If this index doesn't support path-level multikey tracking, then 'multikeyPaths' is ignored. + * + * If this index supports path-level multikey tracking, then 'multikeyPaths' must be a vector + * with size equal to the number of elements in the index key pattern. Additionally, at least + * one path component of the indexed fields must cause this index to be multikey. + */ + void setMultikey(OperationContext* opCtx, const MultikeyPaths& multikeyPaths) final; + + // if this ready is ready for queries + bool isReady(OperationContext* opCtx) const final; + + /** + * If return value is not boost::none, reads with majority read concern using an older snapshot + * must treat this index as unfinished. + */ + boost::optional<SnapshotName> getMinimumVisibleSnapshot() final { + return _minVisibleSnapshot; + } + + void setMinimumVisibleSnapshot(SnapshotName name) final { + _minVisibleSnapshot = name; + } + +private: + class SetMultikeyChange; + class SetHeadChange; + + bool _catalogIsReady(OperationContext* opCtx) const; + RecordId _catalogHead(OperationContext* opCtx) const; + + /** + * Retrieves the multikey information associated with this index from '_collection', + * + * See CollectionCatalogEntry::isIndexMultikey() for more details. + */ + bool _catalogIsMultikey(OperationContext* opCtx, MultikeyPaths* multikeyPaths) const; + + // ----- + + std::string _ns; + + CollectionCatalogEntry* _collection; // not owned here + + std::unique_ptr<IndexDescriptor> _descriptor; // owned here + + CollectionInfoCache* _infoCache; // not owned here + + std::unique_ptr<IndexAccessMethod> _accessMethod; + + // Owned here. + std::unique_ptr<HeadManager> _headManager; + std::unique_ptr<CollatorInterface> _collator; + std::unique_ptr<MatchExpression> _filterExpression; + + // cached stuff + + Ordering _ordering; // TODO: this might be b-tree specific + bool _isReady; // cache of NamespaceDetails info + RecordId _head; // cache of IndexDetails + + // Set to true if this index supports path-level multikey tracking. + // '_indexTracksPathLevelMultikeyInfo' is effectively const after IndexCatalogEntry::init() is + // called. + bool _indexTracksPathLevelMultikeyInfo = false; + + // Set to true if this index is multikey. '_isMultikey' serves as a cache of the information + // stored in the NamespaceDetails or KVCatalog. + AtomicWord<bool> _isMultikey; + + // Controls concurrent access to '_indexMultikeyPaths'. We acquire this mutex rather than the + // RESOURCE_METADATA lock as a performance optimization so that it is cheaper to detect whether + // there is actually any path-level multikey information to update or not. + mutable stdx::mutex _indexMultikeyPathsMutex; + + // Non-empty only if '_indexTracksPathLevelMultikeyInfo' is true. + // + // If non-empty, '_indexMultikeyPaths' is a vector with size equal to the number of elements + // in the index key pattern. Each element in the vector is an ordered set of positions (starting + // at 0) into the corresponding indexed field that represent what prefixes of the indexed field + // causes the index to be multikey. + MultikeyPaths _indexMultikeyPaths; + + // The earliest snapshot that is allowed to read this index. + boost::optional<SnapshotName> _minVisibleSnapshot; +}; +} // namespace mongo |