summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorLouis Williams <louis.williams@mongodb.com>2021-01-28 15:36:38 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-01-29 00:01:41 +0000
commit0d6199bb52dcae5978551816c6ac4ad98bda165b (patch)
tree0f1a707ead6ea117e4937414695fe4593d748dc6 /src/mongo
parent603f25ced3e7917ce94cf95265e7ffa0893b7fa6 (diff)
downloadmongo-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.cpp8
-rw-r--r--src/mongo/db/catalog/collection_validation.h17
-rw-r--r--src/mongo/db/catalog/index_catalog_entry.h13
-rw-r--r--src/mongo/db/catalog/index_catalog_entry_impl.cpp30
-rw-r--r--src/mongo/db/catalog/index_catalog_entry_impl.h5
-rw-r--r--src/mongo/db/catalog/index_consistency.cpp14
-rw-r--r--src/mongo/db/catalog/index_consistency.h10
-rw-r--r--src/mongo/db/catalog/validate_adaptor.cpp93
-rw-r--r--src/mongo/db/catalog/validate_state.cpp6
-rw-r--r--src/mongo/db/catalog/validate_state.h8
-rw-r--r--src/mongo/db/commands/validate.cpp21
-rw-r--r--src/mongo/db/repair.cpp2
-rw-r--r--src/mongo/db/storage/durable_catalog.h20
-rw-r--r--src/mongo/db/storage/durable_catalog_impl.cpp25
-rw-r--r--src/mongo/db/storage/durable_catalog_impl.h6
-rw-r--r--src/mongo/dbtests/validate_tests.cpp168
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>();
}
};