diff options
author | Louis Williams <louis.williams@mongodb.com> | 2021-01-28 15:36:38 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-01-29 00:01:41 +0000 |
commit | 0d6199bb52dcae5978551816c6ac4ad98bda165b (patch) | |
tree | 0f1a707ead6ea117e4937414695fe4593d748dc6 /src/mongo | |
parent | 603f25ced3e7917ce94cf95265e7ffa0893b7fa6 (diff) | |
download | mongo-0d6199bb52dcae5978551816c6ac4ad98bda165b.tar.gz |
SERVER-53675 Allow validate to fix up multikey metadata
This allows foreground validation to fix up the following multikey
metadata inconsistencies:
* An index is multikey but there are no multikey fields
* An index has multikeyPaths covering fields that are not multikey
* An index does not have multikeyPaths but there are multikey documents (for pre-3.4 indexes)
If any changes were made, a warning is included to the validate output
and the 'repaired' flag is set to true.
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/catalog/collection_validation.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_validation.h | 17 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_catalog_entry.h | 13 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_catalog_entry_impl.cpp | 30 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_catalog_entry_impl.h | 5 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_consistency.cpp | 14 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_consistency.h | 10 | ||||
-rw-r--r-- | src/mongo/db/catalog/validate_adaptor.cpp | 93 | ||||
-rw-r--r-- | src/mongo/db/catalog/validate_state.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/catalog/validate_state.h | 8 | ||||
-rw-r--r-- | src/mongo/db/commands/validate.cpp | 21 | ||||
-rw-r--r-- | src/mongo/db/repair.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/storage/durable_catalog.h | 20 | ||||
-rw-r--r-- | src/mongo/db/storage/durable_catalog_impl.cpp | 25 | ||||
-rw-r--r-- | src/mongo/db/storage/durable_catalog_impl.h | 6 | ||||
-rw-r--r-- | src/mongo/dbtests/validate_tests.cpp | 168 |
16 files changed, 408 insertions, 38 deletions
diff --git a/src/mongo/db/catalog/collection_validation.cpp b/src/mongo/db/catalog/collection_validation.cpp index f47813abef7..241f9fe4f05 100644 --- a/src/mongo/db/catalog/collection_validation.cpp +++ b/src/mongo/db/catalog/collection_validation.cpp @@ -220,7 +220,7 @@ void _gatherIndexEntryErrors(OperationContext* opCtx, << " extra index entries."); } - if (validateState->shouldRunRepair()) { + if (validateState->fixErrors()) { indexConsistency->repairMissingIndexEntries(opCtx, result); } @@ -460,15 +460,17 @@ Status validate(OperationContext* opCtx, opCtx->recoveryUnit()->abandonSnapshot(); opCtx->recoveryUnit()->setPrepareConflictBehavior(oldPrepareConflictBehavior); }); - if (validateState.shouldRunRepair()) { + if (validateState.fixErrors()) { // Note: cannot set PrepareConflictBehavior here, since the validate command with repair // needs kIngnoreConflictsAllowWrites, but validate repair at startup cannot set that here // due to an already active WriteUnitOfWork. The prepare conflict behavior for validate // command with repair is set in the command code prior to this point. invariant(!validateState.isBackground()); } else if (!validateState.isBackground()) { + // Foreground validation may perform writes to fix up inconsistencies that are not + // correctness errors. opCtx->recoveryUnit()->setPrepareConflictBehavior( - PrepareConflictBehavior::kIgnoreConflicts); + PrepareConflictBehavior::kIgnoreConflictsAllowWrites); } else { // isBackground(). invariant(oldPrepareConflictBehavior == PrepareConflictBehavior::kEnforce); diff --git a/src/mongo/db/catalog/collection_validation.h b/src/mongo/db/catalog/collection_validation.h index d8eca6226d6..4d36864db79 100644 --- a/src/mongo/db/catalog/collection_validation.h +++ b/src/mongo/db/catalog/collection_validation.h @@ -64,14 +64,21 @@ enum class ValidateMode { }; /** - * RepairMode indicates whether validate should repair the data inconsistencies it detects. When set - * to kRepair, if any repairs are made, the 'repaired' flag in ValidateResults will be set to true. - * If all errors are fixed, then 'valid' will also be set to true. kRepair is incompatible with the - * ValidateModes kBackground and kForegroundFullEnforceFastCount. + * RepairMode indicates whether validate should repair the data inconsistencies it detects. + * + * When set to kFixErrors, if any validation errors are detected, repairs are attempted and the + * 'repaired' flag in ValidateResults will be set to true. If all errors are fixed, then 'valid' + * will also be set to true. kFixErrors is incompatible with the ValidateModes kBackground and + * kForegroundFullEnforceFastCount. This implies kAdjustMultikey. + * + * When set to kAdjustMultikey, if any permissible, yet undesirable multikey inconsistencies are + * detected, then the multikey metadata will be adjusted. The 'repaired' flag will be set if any + * adjustments have been made. This is incompatible with background validation. */ enum class RepairMode { kNone, - kRepair, + kFixErrors, + kAdjustMultikey, }; /** diff --git a/src/mongo/db/catalog/index_catalog_entry.h b/src/mongo/db/catalog/index_catalog_entry.h index 16f2094d90d..59658f6db32 100644 --- a/src/mongo/db/catalog/index_catalog_entry.h +++ b/src/mongo/db/catalog/index_catalog_entry.h @@ -140,6 +140,19 @@ public: const MultikeyPaths& multikeyPaths) = 0; /** + * Sets the index to be multikey with the provided paths. This performs minimal validation of + * the inputs and is intended to be used internally to "correct" multikey metadata that drifts + * from the underlying data. + * + * This may also be used to allow indexes built before 3.4 to start tracking multikey path + * metadata in the catalog. + */ + virtual void forceSetMultikey(OperationContext* const opCtx, + const CollectionPtr& coll, + bool isMultikey, + const MultikeyPaths& multikeyPaths) = 0; + + /** * Returns whether this index is ready for queries. This is potentially unsafe in that it does * not consider whether the index is visible or ready in the current storage snapshot. For * that, use isReadyInMySnapshot() or isPresentInMySnapshot(). diff --git a/src/mongo/db/catalog/index_catalog_entry_impl.cpp b/src/mongo/db/catalog/index_catalog_entry_impl.cpp index 5aafcab9a1b..80a02584e37 100644 --- a/src/mongo/db/catalog/index_catalog_entry_impl.cpp +++ b/src/mongo/db/catalog/index_catalog_entry_impl.cpp @@ -253,6 +253,36 @@ void IndexCatalogEntryImpl::setMultikey(OperationContext* opCtx, } } +void IndexCatalogEntryImpl::forceSetMultikey(OperationContext* const opCtx, + const CollectionPtr& coll, + bool isMultikey, + const MultikeyPaths& multikeyPaths) { + invariant(opCtx->lockState()->isCollectionLockedForMode(coll->ns(), MODE_X)); + + // Don't check _indexTracksMultikeyPathsInCatalog because the caller may be intentionally trying + // to bypass this check. That is, pre-3.4 indexes may be 'stuck' in a state where they are not + // tracking multikey paths in the catalog (i.e. the multikeyPaths field is absent), but the + // caller wants to upgrade this index because it knows exactly which paths are multikey. We rely + // on the following function to make sure this upgrade only takes place on index types that + // currently support path-level multikey path tracking. + DurableCatalog::get(opCtx)->forceSetIndexIsMultikey( + opCtx, _catalogId, _descriptor.get(), isMultikey, multikeyPaths); + + // The prior call to set the multikey metadata in the catalog does some validation and clean up + // based on the inputs, so reset the multikey variables based on what is actually in the durable + // catalog entry. + { + stdx::lock_guard<Latch> lk(_indexMultikeyPathsMutex); + const bool isMultikey = _catalogIsMultikey(opCtx, &_indexMultikeyPaths); + _isMultikeyForRead.store(isMultikey); + _isMultikeyForWrite.store(isMultikey); + _indexTracksMultikeyPathsInCatalog = !_indexMultikeyPaths.empty(); + } + + // Since multikey metadata has changed, invalidate the query cache. + CollectionQueryInfo::get(coll).clearQueryCacheForSetMultikey(coll); +} + Status IndexCatalogEntryImpl::_setMultikeyInMultiDocumentTransaction( OperationContext* opCtx, const CollectionPtr& collection, const MultikeyPaths& multikeyPaths) { // If we are inside a multi-document transaction, we write the on-disk multikey update in a diff --git a/src/mongo/db/catalog/index_catalog_entry_impl.h b/src/mongo/db/catalog/index_catalog_entry_impl.h index a26f975189a..84e5b871db5 100644 --- a/src/mongo/db/catalog/index_catalog_entry_impl.h +++ b/src/mongo/db/catalog/index_catalog_entry_impl.h @@ -160,6 +160,11 @@ public: const KeyStringSet& multikeyMetadataKeys, const MultikeyPaths& multikeyPaths) final; + void forceSetMultikey(OperationContext* const opCtx, + const CollectionPtr& coll, + bool isMultikey, + const MultikeyPaths& multikeyPaths) final; + bool isReady(OperationContext* opCtx) const final; bool isFrozen() const final; diff --git a/src/mongo/db/catalog/index_consistency.cpp b/src/mongo/db/catalog/index_consistency.cpp index 276fab4daf1..634659d959e 100644 --- a/src/mongo/db/catalog/index_consistency.cpp +++ b/src/mongo/db/catalog/index_consistency.cpp @@ -41,6 +41,7 @@ #include "mongo/db/db_raii.h" #include "mongo/db/index/index_access_method.h" #include "mongo/db/index/index_descriptor.h" +#include "mongo/db/multi_key_path_tracker.h" #include "mongo/db/storage/storage_debug_util.h" #include "mongo/logv2/log.h" #include "mongo/util/string_map.h" @@ -282,6 +283,17 @@ void IndexConsistency::addIndexEntryErrors(ValidateResults* results) { } } +void IndexConsistency::addDocumentMultikeyPaths(IndexInfo* indexInfo, + const MultikeyPaths& newPaths) { + invariant(newPaths.size()); + if (indexInfo->docMultikeyPaths.size()) { + MultikeyPathTracker::mergeMultikeyPaths(&indexInfo->docMultikeyPaths, newPaths); + } else { + // Instantiate the multikey paths. Also indicates that this index uses multikeyPaths. + indexInfo->docMultikeyPaths = newPaths; + } +} + void IndexConsistency::addDocKey(OperationContext* opCtx, const KeyString::Value& ks, IndexInfo* indexInfo, @@ -359,7 +371,7 @@ void IndexConsistency::addIndexKey(OperationContext* opCtx, IndexKey key = _generateKeyForMap(*indexInfo, ks); if (_missingIndexEntries.count(key) == 0) { - if (_validateState->shouldRunRepair()) { + if (_validateState->fixErrors()) { // Removing extra index entries. InsertDeleteOptions options; options.dupsAllowed = !indexInfo->unique; diff --git a/src/mongo/db/catalog/index_consistency.h b/src/mongo/db/catalog/index_consistency.h index 313950fa25e..5c33a6e2e5b 100644 --- a/src/mongo/db/catalog/index_consistency.h +++ b/src/mongo/db/catalog/index_consistency.h @@ -57,6 +57,11 @@ struct IndexInfo { int64_t numRecords = 0; // A hashed set of indexed multikey paths (applies to $** indexes only). std::set<uint32_t> hashedMultikeyMetadataPaths; + // Indicates whether or not there are documents that make this index multikey. + bool multikeyDocs = false; + // The set of multikey paths generated from all documents. Only valid when multikeyDocs is also + // set and an index tracks path-level information. + MultikeyPaths docMultikeyPaths; // Indicates whether key entries must be unique. const bool unique; // Index access method pointer. @@ -118,6 +123,11 @@ public: ValidateResults* results); /** + * During the first phase of validation, tracks the multikey paths for every observed document. + */ + void addDocumentMultikeyPaths(IndexInfo* indexInfo, const MultikeyPaths& multikeyPaths); + + /** * To validate $** multikey metadata paths, we first scan the collection and add a hash of all * multikey paths encountered to a set. We then scan the index for multikey metadata path * entries and remove any path encountered. As we expect the index to contain a super-set of diff --git a/src/mongo/db/catalog/validate_adaptor.cpp b/src/mongo/db/catalog/validate_adaptor.cpp index e66f4f7dd0e..461968d68ff 100644 --- a/src/mongo/db/catalog/validate_adaptor.cpp +++ b/src/mongo/db/catalog/validate_adaptor.cpp @@ -110,12 +110,13 @@ Status ValidateAdaptor::validateRecord(OperationContext* opCtx, recordId, IndexAccessMethod::kNoopOnSuppressedErrorFn); - if (!index->isMultikey() && - iam->shouldMarkIndexAsMultikey( - documentKeySet->size(), - {multikeyMetadataKeys->begin(), multikeyMetadataKeys->end()}, - *documentMultikeyPaths)) { - if (_validateState->shouldRunRepair()) { + bool shouldBeMultikey = iam->shouldMarkIndexAsMultikey( + documentKeySet->size(), + {multikeyMetadataKeys->begin(), multikeyMetadataKeys->end()}, + *documentMultikeyPaths); + + if (!index->isMultikey() && shouldBeMultikey) { + if (_validateState->fixErrors()) { writeConflictRetry(opCtx, "setIndexAsMultikey", coll->ns().ns(), [&] { WriteUnitOfWork wuow(opCtx); coll->getIndexCatalog()->setMultikeyPaths( @@ -146,7 +147,7 @@ Status ValidateAdaptor::validateRecord(OperationContext* opCtx, if (index->isMultikey()) { const MultikeyPaths& indexPaths = index->getMultikeyPaths(opCtx); if (!MultikeyPathTracker::covers(indexPaths, *documentMultikeyPaths.get())) { - if (_validateState->shouldRunRepair()) { + if (_validateState->fixErrors()) { writeConflictRetry(opCtx, "increaseMultikeyPathCoverage", coll->ns().ns(), [&] { WriteUnitOfWork wuow(opCtx); coll->getIndexCatalog()->setMultikeyPaths( @@ -173,6 +174,16 @@ Status ValidateAdaptor::validateRecord(OperationContext* opCtx, } IndexInfo& indexInfo = _indexConsistency->getIndexInfo(descriptor->indexName()); + if (shouldBeMultikey) { + indexInfo.multikeyDocs = true; + } + + // An empty set of multikey paths indicates that this index does not track path-level + // multikey information and we should do no tracking. + if (shouldBeMultikey && documentMultikeyPaths->size()) { + _indexConsistency->addDocumentMultikeyPaths(&indexInfo, *documentMultikeyPaths); + } + for (const auto& keyString : *multikeyMetadataKeys) { try { _indexConsistency->addMultikeyMetadataPath(keyString, &indexInfo); @@ -328,6 +339,72 @@ void ValidateAdaptor::traverseIndex(OperationContext* opCtx, results->valid = false; } + // Adjust multikey metadata when allowed. These states are all allowed by the design of + // multikey. A collection should still be valid without these adjustments. + if (_validateState->adjustMultikey()) { + + // If this collection has documents that make this index multikey, then check whether those + // multikey paths match the index's metadata. + auto indexPaths = index->getMultikeyPaths(opCtx); + auto& documentPaths = indexInfo.docMultikeyPaths; + if (indexInfo.multikeyDocs && documentPaths != indexPaths) { + LOGV2(5367500, + "Index's multikey paths do not match those of its documents", + "index"_attr = descriptor->indexName(), + "indexPaths"_attr = MultikeyPathTracker::dumpMultikeyPaths(indexPaths), + "documentPaths"_attr = MultikeyPathTracker::dumpMultikeyPaths(documentPaths)); + + // Since we have the correct multikey path information for this index, we can tighten up + // its metadata to improve query performance. This may apply in two distinct scenarios: + // 1. Collection data has changed such that the current multikey paths on the index + // are too permissive and certain document paths are no longer multikey. + // 2. This index was built before 3.4, and there is no multikey path information for + // the index. We can effectively 'upgrade' the index so that it does not need to be + // rebuilt to update this information. + writeConflictRetry(opCtx, "updateMultikeyPaths", _validateState->nss().ns(), [&]() { + WriteUnitOfWork wuow(opCtx); + auto writeableIndex = const_cast<IndexCatalogEntry*>(index); + const bool isMultikey = true; + writeableIndex->forceSetMultikey( + opCtx, _validateState->getCollection(), isMultikey, documentPaths); + wuow.commit(); + }); + + if (results) { + results->warnings.push_back(str::stream() << "Updated index multikey metadata" + << ": " << descriptor->indexName()); + results->repaired = true; + } + } + + // If this index does not need to be multikey, then unset the flag. + if (index->isMultikey() && !indexInfo.multikeyDocs) { + invariant(!indexInfo.docMultikeyPaths.size()); + + LOGV2(5367501, + "Index is multikey but there are no multikey documents", + "index"_attr = descriptor->indexName()); + + // This makes an improvement in the case that no documents make the index multikey and + // the flag can be unset entirely. This may be due to a change in the data or historical + // multikey bugs that have persisted incorrect multikey infomation. + writeConflictRetry(opCtx, "unsetMultikeyPaths", _validateState->nss().ns(), [&]() { + WriteUnitOfWork wuow(opCtx); + auto writeableIndex = const_cast<IndexCatalogEntry*>(index); + const bool isMultikey = false; + writeableIndex->forceSetMultikey( + opCtx, _validateState->getCollection(), isMultikey, {}); + wuow.commit(); + }); + + if (results) { + results->warnings.push_back(str::stream() << "Unset index multikey metadata" + << ": " << descriptor->indexName()); + results->repaired = true; + } + } + } + if (numTraversedKeys) { *numTraversedKeys = numKeys; } @@ -397,7 +474,7 @@ void ValidateAdaptor::traverseRecordStore(OperationContext* opCtx, "recordBytes"_attr = dataSize); } - if (_validateState->shouldRunRepair()) { + if (_validateState->fixErrors()) { writeConflictRetry( opCtx, "corrupt record removal", _validateState->nss().ns(), [&] { WriteUnitOfWork wunit(opCtx); diff --git a/src/mongo/db/catalog/validate_state.cpp b/src/mongo/db/catalog/validate_state.cpp index 61a159b8c62..508577c20cb 100644 --- a/src/mongo/db/catalog/validate_state.cpp +++ b/src/mongo/db/catalog/validate_state.cpp @@ -91,11 +91,15 @@ ValidateState::ValidateState(OperationContext* opCtx, // RepairMode is incompatible with the ValidateModes kBackground and // kForegroundFullEnforceFastCount. - if (shouldRunRepair()) { + if (fixErrors()) { invariant(!isBackground()); invariant(!shouldEnforceFastCount()); } + if (adjustMultikey()) { + invariant(!isBackground()); + } + _uuid = _collection->uuid(); _catalogGeneration = opCtx->getServiceContext()->getCatalogGeneration(); } diff --git a/src/mongo/db/catalog/validate_state.h b/src/mongo/db/catalog/validate_state.h index 7c3ecbc021a..4b76f4c57dd 100644 --- a/src/mongo/db/catalog/validate_state.h +++ b/src/mongo/db/catalog/validate_state.h @@ -84,8 +84,12 @@ public: return isFullValidation() || _mode == ValidateMode::kForegroundFullIndexOnly; } - bool shouldRunRepair() const { - return _repairMode == RepairMode::kRepair; + bool fixErrors() const { + return _repairMode == RepairMode::kFixErrors; + } + + bool adjustMultikey() const { + return _repairMode == RepairMode::kFixErrors || _repairMode == RepairMode::kAdjustMultikey; } const UUID uuid() const { diff --git a/src/mongo/db/commands/validate.cpp b/src/mongo/db/commands/validate.cpp index c1d787a49bb..bdde2c3156a 100644 --- a/src/mongo/db/commands/validate.cpp +++ b/src/mongo/db/commands/validate.cpp @@ -212,8 +212,25 @@ public: return CollectionValidation::ValidateMode::kForeground; }(); - auto repairMode = repair ? CollectionValidation::RepairMode::kRepair - : CollectionValidation::RepairMode::kNone; + auto repairMode = [&] { + switch (mode) { + case CollectionValidation::ValidateMode::kForeground: + case CollectionValidation::ValidateMode::kForegroundFull: + case CollectionValidation::ValidateMode::kForegroundFullIndexOnly: + // Foreground validation may not repair data while running as a replica set node + // because we do not have timestamps that are required to perform writes. + if (replCoord->isReplEnabled()) { + return CollectionValidation::RepairMode::kNone; + } + if (repair) { + return CollectionValidation::RepairMode::kFixErrors; + } + // Foreground validation will adjust multikey metadata by default. + return CollectionValidation::RepairMode::kAdjustMultikey; + default: + return CollectionValidation::RepairMode::kNone; + } + }(); if (repair) { opCtx->recoveryUnit()->setPrepareConflictBehavior( diff --git a/src/mongo/db/repair.cpp b/src/mongo/db/repair.cpp index 44c79ad28b4..c58c300a234 100644 --- a/src/mongo/db/repair.cpp +++ b/src/mongo/db/repair.cpp @@ -236,7 +236,7 @@ Status repairCollection(OperationContext* opCtx, CollectionValidation::validate(opCtx, nss, CollectionValidation::ValidateMode::kForegroundFullIndexOnly, - CollectionValidation::RepairMode::kRepair, + CollectionValidation::RepairMode::kFixErrors, &validateResults, &output); if (!status.isOK()) { diff --git a/src/mongo/db/storage/durable_catalog.h b/src/mongo/db/storage/durable_catalog.h index bfe60fb2fc4..80447cf3de9 100644 --- a/src/mongo/db/storage/durable_catalog.h +++ b/src/mongo/db/storage/durable_catalog.h @@ -281,8 +281,8 @@ public: * Returns true if the index identified by 'indexName' is multikey, and returns false otherwise. * * If the 'multikeyPaths' pointer is non-null, then it must point to an empty vector. If this - * index supports tracking path-level multikey information, then this function sets - * 'multikeyPaths' as the path components that cause this index to be multikey. + * index type supports tracking path-level multikey information in the catalog, then this + * function sets 'multikeyPaths' as the path components that cause this index to be multikey. * * In particular, if this function returns false and the index supports tracking path-level * multikey information, then 'multikeyPaths' is initialized as a vector with size equal to the @@ -307,6 +307,22 @@ public: StringData indexName, const MultikeyPaths& multikeyPaths) = 0; + /** + * Sets the index to be multikey with the provided paths. This performs minimal validation of + * the inputs and is intended to be used internally to "correct" multikey metadata that drifts + * from the underlying collection data. + * + * When isMultikey is false, ignores multikeyPaths and resets the metadata appropriately based + * on the index descriptor. Otherwise, overwrites the existing multikeyPaths with the ones + * provided. This only writes multikey paths if the index type supports path-level tracking, and + * only sets the multikey boolean flag otherwise. + */ + virtual void forceSetIndexIsMultikey(OperationContext* opCtx, + RecordId catalogId, + const IndexDescriptor* desc, + bool isMultikey, + const MultikeyPaths& multikeyPaths) = 0; + virtual CollectionOptions getCollectionOptions(OperationContext* opCtx, RecordId catalogId) const = 0; diff --git a/src/mongo/db/storage/durable_catalog_impl.cpp b/src/mongo/db/storage/durable_catalog_impl.cpp index ea8df57a9d1..ad5515a7fb6 100644 --- a/src/mongo/db/storage/durable_catalog_impl.cpp +++ b/src/mongo/db/storage/durable_catalog_impl.cpp @@ -1258,6 +1258,31 @@ bool DurableCatalogImpl::setIndexIsMultikey(OperationContext* opCtx, return true; } +void DurableCatalogImpl::forceSetIndexIsMultikey(OperationContext* opCtx, + RecordId catalogId, + const IndexDescriptor* desc, + bool isMultikey, + const MultikeyPaths& multikeyPaths) { + BSONCollectionCatalogEntry::MetaData md = getMetaData(opCtx, catalogId); + + int offset = md.findIndexOffset(desc->indexName()); + invariant(offset >= 0, + str::stream() << "cannot set index " << desc->indexName() << " multikey state @ " + << catalogId << " : " << md.toBSON()); + + md.indexes[offset].multikey = isMultikey; + if (indexTypeSupportsPathLevelMultikeyTracking(desc->getAccessMethodName())) { + if (isMultikey) { + md.indexes[offset].multikeyPaths = multikeyPaths; + } else { + md.indexes[offset].multikeyPaths = + MultikeyPaths{static_cast<size_t>(desc->keyPattern().nFields())}; + } + } + putMetaData(opCtx, catalogId, md); +} + + CollectionOptions DurableCatalogImpl::getCollectionOptions(OperationContext* opCtx, RecordId catalogId) const { BSONCollectionCatalogEntry::MetaData md = getMetaData(opCtx, catalogId); diff --git a/src/mongo/db/storage/durable_catalog_impl.h b/src/mongo/db/storage/durable_catalog_impl.h index 51f84738c2a..70104abfafc 100644 --- a/src/mongo/db/storage/durable_catalog_impl.h +++ b/src/mongo/db/storage/durable_catalog_impl.h @@ -189,6 +189,12 @@ public: StringData indexName, const MultikeyPaths& multikeyPaths); + void forceSetIndexIsMultikey(OperationContext* opCtx, + RecordId catalogId, + const IndexDescriptor* desc, + bool isMultikey, + const MultikeyPaths& multikeyPaths); + CollectionOptions getCollectionOptions(OperationContext* opCtx, RecordId catalogId) const; int getTotalIndexCount(OperationContext* opCtx, RecordId catalogId) const; diff --git a/src/mongo/dbtests/validate_tests.cpp b/src/mongo/dbtests/validate_tests.cpp index e75eadbebf0..3448c1b22e9 100644 --- a/src/mongo/dbtests/validate_tests.cpp +++ b/src/mongo/dbtests/validate_tests.cpp @@ -41,6 +41,7 @@ #include "mongo/db/index/index_build_interceptor.h" #include "mongo/db/index/index_descriptor.h" #include "mongo/db/service_context.h" +#include "mongo/db/storage/durable_catalog.h" #include "mongo/db/storage/execution_context.h" #include "mongo/db/storage/storage_debug_util.h" #include "mongo/dbtests/dbtests.h" @@ -1558,7 +1559,7 @@ public: CollectionValidation::validate(&_opCtx, _nss, CollectionValidation::ValidateMode::kForegroundFull, - CollectionValidation::RepairMode::kRepair, + CollectionValidation::RepairMode::kFixErrors, &results, &output, kTurnOnExtraLoggingForTest)); @@ -1594,7 +1595,7 @@ public: CollectionValidation::validate(&_opCtx, _nss, CollectionValidation::ValidateMode::kForegroundFull, - CollectionValidation::RepairMode::kRepair, + CollectionValidation::RepairMode::kFixErrors, &results, &output, kTurnOnExtraLoggingForTest)); @@ -1742,7 +1743,7 @@ public: CollectionValidation::validate(&_opCtx, _nss, CollectionValidation::ValidateMode::kForegroundFull, - CollectionValidation::RepairMode::kRepair, + CollectionValidation::RepairMode::kFixErrors, &results, &output, kTurnOnExtraLoggingForTest)); @@ -1895,7 +1896,7 @@ public: CollectionValidation::validate(&_opCtx, _nss, CollectionValidation::ValidateMode::kForegroundFull, - CollectionValidation::RepairMode::kRepair, + CollectionValidation::RepairMode::kFixErrors, &results, &output, kTurnOnExtraLoggingForTest)); @@ -2143,7 +2144,7 @@ public: CollectionValidation::validate(&_opCtx, _nss, CollectionValidation::ValidateMode::kForegroundFull, - CollectionValidation::RepairMode::kRepair, + CollectionValidation::RepairMode::kFixErrors, &results, &output, kTurnOnExtraLoggingForTest)); @@ -2293,7 +2294,7 @@ public: CollectionValidation::validate(&_opCtx, _nss, CollectionValidation::ValidateMode::kForeground, - CollectionValidation::RepairMode::kRepair, + CollectionValidation::RepairMode::kFixErrors, &results, &output, kTurnOnExtraLoggingForTest)); @@ -2325,7 +2326,7 @@ public: CollectionValidation::validate(&_opCtx, _nss, CollectionValidation::ValidateMode::kForeground, - CollectionValidation::RepairMode::kRepair, + CollectionValidation::RepairMode::kFixErrors, &results, &output, kTurnOnExtraLoggingForTest)); @@ -2840,7 +2841,7 @@ public: CollectionValidation::validate(&_opCtx, _nss, CollectionValidation::ValidateMode::kForeground, - CollectionValidation::RepairMode::kRepair, + CollectionValidation::RepairMode::kFixErrors, &results, &output, kTurnOnExtraLoggingForTest)); @@ -2875,7 +2876,7 @@ public: CollectionValidation::validate(&_opCtx, _nss, CollectionValidation::ValidateMode::kForeground, - CollectionValidation::RepairMode::kRepair, + CollectionValidation::RepairMode::kFixErrors, &results, &output, kTurnOnExtraLoggingForTest)); @@ -3100,7 +3101,7 @@ public: CollectionValidation::validate(&_opCtx, _nss, CollectionValidation::ValidateMode::kForeground, - CollectionValidation::RepairMode::kRepair, + CollectionValidation::RepairMode::kFixErrors, &results, &output, kTurnOnExtraLoggingForTest)); @@ -3130,7 +3131,7 @@ public: CollectionValidation::validate(&_opCtx, _nss, CollectionValidation::ValidateMode::kForeground, - CollectionValidation::RepairMode::kRepair, + CollectionValidation::RepairMode::kFixErrors, &results, &output, kTurnOnExtraLoggingForTest)); @@ -3303,7 +3304,7 @@ public: CollectionValidation::validate(&_opCtx, _nss, CollectionValidation::ValidateMode::kForeground, - CollectionValidation::RepairMode::kRepair, + CollectionValidation::RepairMode::kFixErrors, &results, &output, kTurnOnExtraLoggingForTest)); @@ -3318,7 +3319,7 @@ public: ASSERT_EQ(static_cast<size_t>(0), results.errors.size()); ASSERT_EQ(static_cast<size_t>(0), results.extraIndexEntries.size()); ASSERT_EQ(static_cast<size_t>(0), results.missingIndexEntries.size()); - ASSERT_EQ(static_cast<size_t>(1), results.warnings.size()); + ASSERT_EQ(static_cast<size_t>(2), results.warnings.size()); dumpOnErrorGuard.dismiss(); } @@ -3354,6 +3355,145 @@ public: } }; +// Tests that multikey paths can be added to an index for the first time. +class ValidateAddNewMultikeyPaths : public ValidateBase { +public: + // No need to test with background validation as repair mode is not supported in background + // validation. + ValidateAddNewMultikeyPaths() : ValidateBase(/*full=*/false, /*background=*/false) {} + + void run() { + + // Create a new collection and create an index. + lockDb(MODE_X); + CollectionPtr coll; + { + WriteUnitOfWork wunit(&_opCtx); + ASSERT_OK(_db->dropCollection(&_opCtx, _nss)); + coll = _db->createCollection(&_opCtx, _nss); + wunit.commit(); + } + + const auto indexName = "mk_index"; + auto status = dbtests::createIndexFromSpec(&_opCtx, + coll->ns().ns(), + BSON("name" << indexName << "key" + << BSON("a" << 1 << "b" << 1) << "v" + << static_cast<int>(kIndexVersion))); + ASSERT_OK(status); + + // Remove the multikeyPaths from the index catalog entry. This simulates the catalog state + // of a pre-3.4 index. + { + WriteUnitOfWork wunit(&_opCtx); + auto collMetadata = + DurableCatalog::get(&_opCtx)->getMetaData(&_opCtx, coll->getCatalogId()); + int offset = collMetadata.findIndexOffset(indexName); + ASSERT_GTE(offset, 0); + + auto& indexMetadata = collMetadata.indexes[offset]; + indexMetadata.multikeyPaths = {}; + DurableCatalog::get(&_opCtx)->putMetaData(&_opCtx, coll->getCatalogId(), collMetadata); + wunit.commit(); + } + + // Reload the index from the modified catalog. + auto indexCatalog = coll->getIndexCatalog(); + auto descriptor = indexCatalog->findIndexByName(&_opCtx, indexName); + { + WriteUnitOfWork wunit(&_opCtx); + auto writableCatalog = const_cast<IndexCatalog*>(indexCatalog); + descriptor = writableCatalog->refreshEntry(&_opCtx, descriptor); + wunit.commit(); + } + + // Insert a multikey document. The multikeyPaths should not get updated in this old + // state. + RecordId id1; + BSONObj doc1 = BSON("_id" << 0 << "a" << BSON_ARRAY(1 << 2) << "b" << 1); + OpDebug* const nullOpDebug = nullptr; + { + WriteUnitOfWork wunit(&_opCtx); + ASSERT_OK(coll->insertDocument(&_opCtx, InsertStatement(doc1), nullOpDebug, true)); + id1 = coll->getCursor(&_opCtx)->next()->id; + wunit.commit(); + } + + auto catalogEntry = indexCatalog->getEntry(descriptor); + auto expectedPathsBefore = MultikeyPaths{}; + ASSERT(catalogEntry->isMultikey()); + ASSERT(catalogEntry->getMultikeyPaths(&_opCtx) == expectedPathsBefore); + + releaseDb(); + ensureValidateWorked(); + + // Confirm multikeyPaths are added by validate. + { + ValidateResults results; + BSONObjBuilder output; + + ASSERT_OK( + CollectionValidation::validate(&_opCtx, + _nss, + CollectionValidation::ValidateMode::kForeground, + CollectionValidation::RepairMode::kAdjustMultikey, + &results, + &output, + kTurnOnExtraLoggingForTest)); + + auto dumpOnErrorGuard = makeGuard([&] { + StorageDebugUtil::printValidateResults(results); + StorageDebugUtil::printCollectionAndIndexTableEntries(&_opCtx, coll->ns()); + }); + + ASSERT_EQ(true, results.valid); + ASSERT_EQ(true, results.repaired); + ASSERT_EQ(static_cast<size_t>(0), results.errors.size()); + ASSERT_EQ(static_cast<size_t>(0), results.extraIndexEntries.size()); + ASSERT_EQ(static_cast<size_t>(0), results.missingIndexEntries.size()); + ASSERT_EQ(static_cast<size_t>(1), results.warnings.size()); + + dumpOnErrorGuard.dismiss(); + } + + auto expectedPathsAfter = MultikeyPaths{{0}, {}}; + ASSERT(catalogEntry->isMultikey()); + ASSERT(catalogEntry->getMultikeyPaths(&_opCtx) == expectedPathsAfter); + + // Confirm validate does not make changes when run a second time. + { + ValidateResults results; + BSONObjBuilder output; + + ASSERT_OK( + CollectionValidation::validate(&_opCtx, + _nss, + CollectionValidation::ValidateMode::kForeground, + CollectionValidation::RepairMode::kAdjustMultikey, + &results, + &output, + kTurnOnExtraLoggingForTest)); + + auto dumpOnErrorGuard = makeGuard([&] { + StorageDebugUtil::printValidateResults(results); + StorageDebugUtil::printCollectionAndIndexTableEntries(&_opCtx, coll->ns()); + }); + + ASSERT_EQ(true, results.valid); + ASSERT_EQ(false, results.repaired); + ASSERT_EQ(static_cast<size_t>(0), results.errors.size()); + ASSERT_EQ(static_cast<size_t>(0), results.extraIndexEntries.size()); + ASSERT_EQ(static_cast<size_t>(0), results.missingIndexEntries.size()); + ASSERT_EQ(static_cast<size_t>(0), results.warnings.size()); + + dumpOnErrorGuard.dismiss(); + } + + ASSERT(catalogEntry->isMultikey()); + ASSERT(catalogEntry->getMultikeyPaths(&_opCtx) == expectedPathsAfter); + } +}; + class ValidateTests : public OldStyleSuiteSpecification { public: ValidateTests() : OldStyleSuiteSpecification("validate_tests") {} @@ -3414,6 +3554,8 @@ public: add<ValidateIndexWithMultikeyDocRepair>(); add<ValidateMultikeyPathCoverageRepair>(); + + add<ValidateAddNewMultikeyPaths>(); } }; |