summaryrefslogtreecommitdiff
path: root/src/mongo/db/catalog/collection_validation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/catalog/collection_validation.cpp')
-rw-r--r--src/mongo/db/catalog/collection_validation.cpp216
1 files changed, 86 insertions, 130 deletions
diff --git a/src/mongo/db/catalog/collection_validation.cpp b/src/mongo/db/catalog/collection_validation.cpp
index 9d0c487b41a..42c5ac18dc7 100644
--- a/src/mongo/db/catalog/collection_validation.cpp
+++ b/src/mongo/db/catalog/collection_validation.cpp
@@ -78,7 +78,8 @@ void _validateIndexesInternalStructure(OperationContext* opCtx,
// Need to use the IndexCatalog here because the 'validateState->indexes' object hasn't been
// constructed yet. It must be initialized to ensure we're validating all indexes.
const IndexCatalog* indexCatalog = validateState->getCollection()->getIndexCatalog();
- const auto it = indexCatalog->getIndexIterator(opCtx, IndexCatalog::InclusionPolicy::kReady);
+ const std::unique_ptr<IndexCatalog::IndexIterator> it =
+ indexCatalog->getIndexIterator(opCtx, false);
// Validate Indexes Internal Structure, checking if index files have been compromised or
// corrupted.
@@ -97,11 +98,14 @@ void _validateIndexesInternalStructure(OperationContext* opCtx,
auto& curIndexResults = (results->indexResultsMap)[descriptor->indexName()];
- iam->validate(opCtx, nullptr, &curIndexResults);
+ int64_t numValidated;
+ iam->validate(opCtx, &numValidated, &curIndexResults);
if (!curIndexResults.valid) {
results->valid = false;
}
+
+ curIndexResults.keysTraversedFromFullValidate = numValidated;
}
}
@@ -133,6 +137,33 @@ void _validateIndexes(OperationContext* opCtx,
auto& curIndexResults = (results->indexResultsMap)[descriptor->indexName()];
curIndexResults.keysTraversed = numTraversedKeys;
+ // If we are performing a full index validation, we have information on the number of index
+ // keys validated in _validateIndexesInternalStructure (when we validated the internal
+ // structure of the index). Check if this is consistent with 'numTraversedKeys' from
+ // traverseIndex above.
+ if (validateState->isFullIndexValidation()) {
+ invariant(opCtx->lockState()->isCollectionLockedForMode(validateState->nss(), MODE_X));
+
+ // The number of keys counted in _validateIndexesInternalStructure, when checking the
+ // internal structure of the index.
+ const int64_t numIndexKeys = curIndexResults.keysTraversedFromFullValidate;
+
+ // Check if currIndexResults is valid to ensure that this index is not corrupted or
+ // comprised (which was set in _validateIndexesInternalStructure). If the index is
+ // corrupted, there is no use in checking if the traversal yielded the same key count.
+ if (curIndexResults.valid) {
+ if (numIndexKeys != numTraversedKeys) {
+ curIndexResults.valid = false;
+ string msg = str::stream()
+ << "number of traversed index entries (" << numTraversedKeys
+ << ") does not match the number of expected index entries (" << numIndexKeys
+ << ")";
+ results->errors.push_back(msg);
+ results->valid = false;
+ }
+ }
+ }
+
if (!curIndexResults.valid) {
results->valid = false;
}
@@ -162,8 +193,7 @@ void _gatherIndexEntryErrors(OperationContext* opCtx,
ValidateResults tempValidateResults;
BSONObjBuilder tempBuilder;
- indexValidator->traverseRecordStore(
- opCtx, &tempValidateResults, &tempBuilder, validateState->validationVersion());
+ indexValidator->traverseRecordStore(opCtx, &tempValidateResults, &tempBuilder);
}
LOGV2_OPTIONS(
@@ -200,7 +230,7 @@ void _gatherIndexEntryErrors(OperationContext* opCtx,
LOGV2_OPTIONS(20301, {LogComponent::kIndex}, "Finished traversing through all the indexes");
- indexConsistency->addIndexEntryErrors(opCtx, result);
+ indexConsistency->addIndexEntryErrors(result);
}
void _validateIndexKeyCount(OperationContext* opCtx,
@@ -217,94 +247,6 @@ void _validateIndexKeyCount(OperationContext* opCtx,
}
}
-void _printIndexSpec(const ValidateState* validateState, StringData indexName) {
- auto& indexes = validateState->getIndexes();
- auto indexEntry =
- std::find_if(indexes.begin(),
- indexes.end(),
- [&](const std::shared_ptr<const IndexCatalogEntry> indexEntry) -> bool {
- return indexEntry->descriptor()->indexName() == indexName;
- });
- if (indexEntry != indexes.end()) {
- auto indexSpec = (*indexEntry)->descriptor()->infoObj();
- LOGV2_ERROR(7463100, "Index failed validation", "spec"_attr = indexSpec);
- }
-}
-
-/**
- * Logs oplog entries related to corrupted records/indexes in validation results.
- */
-void _logOplogEntriesForInvalidResults(OperationContext* opCtx, ValidateResults* results) {
- if (results->recordTimestamps.empty()) {
- return;
- }
-
- LOGV2(
- 7464200,
- "Validation failed: oplog timestamps referenced by corrupted collection and index entries",
- "numTimestamps"_attr = results->recordTimestamps.size());
-
- // Set up read on oplog collection.
- try {
- AutoGetOplog oplogRead(opCtx, OplogAccessMode::kRead);
- const auto& oplogCollection = oplogRead.getCollection();
-
- if (!oplogCollection) {
- for (auto it = results->recordTimestamps.rbegin();
- it != results->recordTimestamps.rend();
- it++) {
- const auto& timestamp = *it;
- LOGV2(8080900,
- " Validation failed: Oplog entry timestamp for corrupted collection and "
- "index entry",
- "timestamp"_attr = timestamp);
- }
- return;
- }
-
- // Log oplog entries in reverse from most recent timestamp to oldest.
- // Due to oplog truncation, if we fail to find any oplog entry for a particular timestamp,
- // we can stop searching for oplog entries with earlier timestamps.
- auto recordStore = oplogCollection->getRecordStore();
- uassert(ErrorCodes::InternalError,
- "Validation failed: Unable to get oplog record store for corrupted collection and "
- "index entries",
- recordStore);
-
- auto cursor = recordStore->getCursor(opCtx, /*forward=*/false);
- uassert(ErrorCodes::CursorNotFound,
- "Validation failed: Unable to get cursor to oplog collection.",
- cursor);
-
- for (auto it = results->recordTimestamps.rbegin(); it != results->recordTimestamps.rend();
- it++) {
- const auto& timestamp = *it;
-
- // A record id in the oplog collection is equivalent to the document's timestamp field.
- RecordId recordId(timestamp.asULL());
- auto record = cursor->seekExact(recordId);
- if (!record) {
- LOGV2(7464201,
- " Validation failed: Stopping oplog entry search for corrupted collection "
- "and index entries.",
- "timestamp"_attr = timestamp);
- break;
- }
-
- LOGV2(
- 7464202,
- " Validation failed: Oplog entry found for corrupted collection and index entry",
- "timestamp"_attr = timestamp,
- "oplogEntryDoc"_attr = redact(record->data.toBson()));
- }
- } catch (DBException& ex) {
- LOGV2_ERROR(7464203,
- "Validation failed: Unable to fetch entries from oplog collection for "
- "corrupted collection and index entries",
- "ex"_attr = ex);
- }
-}
-
void _reportValidationResults(OperationContext* opCtx,
ValidateState* validateState,
ValidateResults* results,
@@ -321,19 +263,17 @@ void _reportValidationResults(OperationContext* opCtx,
// Report detailed index validation results gathered when using {full: true} for validated
// indexes.
- int nIndexes = results->indexResultsMap.size();
- for (const auto& [indexName, vr] : results->indexResultsMap) {
- if (!vr.valid) {
- results->valid = false;
- _printIndexSpec(validateState, indexName);
+ for (const auto& index : validateState->getIndexes()) {
+ const std::string indexName = index->descriptor()->indexName();
+ auto& indexResultsMap = results->indexResultsMap;
+ if (indexResultsMap.find(indexName) == indexResultsMap.end()) {
+ continue;
}
- if (validateState->getSkippedIndexes().contains(indexName)) {
- // Index internal state was checked and cleared, so it was reported in indexResultsMap,
- // but we did not verify the index contents against the collection, so we should exclude
- // it from this report.
- --nIndexes;
- continue;
+ auto& vr = indexResultsMap.at(indexName);
+
+ if (!vr.valid) {
+ results->valid = false;
}
BSONObjBuilder bob(indexDetails.subobjStart(indexName));
@@ -354,7 +294,7 @@ void _reportValidationResults(OperationContext* opCtx,
results->errors.insert(results->errors.end(), vr.errors.begin(), vr.errors.end());
}
- output->append("nIndexes", nIndexes);
+ output->append("nIndexes", static_cast<int>(validateState->getIndexes().size()));
output->append("keysPerIndex", keysPerIndex.done());
output->append("indexDetails", indexDetails.done());
}
@@ -364,7 +304,6 @@ void _reportInvalidResults(OperationContext* opCtx,
ValidateResults* results,
BSONObjBuilder* output) {
_reportValidationResults(opCtx, validateState, results, output);
- _logOplogEntriesForInvalidResults(opCtx, results);
LOGV2_OPTIONS(20302,
{LogComponent::kIndex},
"Validation complete -- Corruption found",
@@ -402,6 +341,35 @@ void addErrorIfUnequal(boost::optional<ValidationActionEnum> stored,
results);
}
+std::string multikeyPathsToString(MultikeyPaths paths) {
+ str::stream builder;
+ builder << "[";
+ auto pathIt = paths.begin();
+ while (true) {
+ builder << "{";
+
+ auto pathSet = *pathIt;
+ auto setIt = pathSet.begin();
+ while (true) {
+ builder << *setIt++;
+ if (setIt == pathSet.end()) {
+ break;
+ } else {
+ builder << ",";
+ }
+ }
+ builder << "}";
+
+ if (++pathIt == paths.end()) {
+ break;
+ } else {
+ builder << ",";
+ }
+ }
+ builder << "]";
+ return builder;
+}
+
void _validateCatalogEntry(OperationContext* opCtx,
ValidateState* validateState,
ValidateResults* results) {
@@ -442,23 +410,20 @@ void _validateCatalogEntry(OperationContext* opCtx,
}
const auto& indexCatalog = collection->getIndexCatalog();
- auto indexIt = indexCatalog->getIndexIterator(opCtx,
- IndexCatalog::InclusionPolicy::kReady |
- IndexCatalog::InclusionPolicy::kUnfinished |
- IndexCatalog::InclusionPolicy::kFrozen);
+ auto indexIt = indexCatalog->getIndexIterator(opCtx, /*includeUnfinishedIndexes=*/true);
while (indexIt->more()) {
const IndexCatalogEntry* indexEntry = indexIt->next();
const std::string indexName = indexEntry->descriptor()->indexName();
Status status =
- index_key_validate::validateIndexSpec(opCtx, indexEntry->descriptor()->infoObj())
- .getStatus();
+ index_key_validate::validateIndexSpecFieldNames(indexEntry->descriptor()->infoObj());
if (!status.isOK()) {
- results->warnings.push_back(
- fmt::format("The index specification for index '{}' contains invalid fields. {}. "
- "Run the 'collMod' command on the collection without any arguments "
- "to fix the invalid index options",
+ results->valid = false;
+ results->errors.push_back(
+ fmt::format("The index specification for index '{}' contains invalid field names. "
+ "{}. Run the 'collMod' command on the collection without any arguments "
+ "to remove the invalid index options",
indexName,
status.reason()));
}
@@ -594,15 +559,14 @@ Status validate(OperationContext* opCtx,
const NamespaceString& nss,
ValidateMode mode,
RepairMode repairMode,
- const AdditionalOptions& additionalOptions,
ValidateResults* results,
BSONObjBuilder* output,
- bool logDiagnostics) {
+ bool turnOnExtraLoggingForTest) {
invariant(!opCtx->lockState()->isLocked() || storageGlobalParams.repair);
// This is deliberately outside of the try-catch block, so that any errors thrown in the
// constructor fail the cmd, as opposed to returning OK with valid:false.
- ValidateState validateState(opCtx, nss, mode, repairMode, additionalOptions, logDiagnostics);
+ ValidateState validateState(opCtx, nss, mode, repairMode, turnOnExtraLoggingForTest);
const auto replCoord = repl::ReplicationCoordinator::get(opCtx);
// Check whether we are allowed to read from this node after acquiring our locks. If we are
@@ -621,14 +585,6 @@ Status validate(OperationContext* opCtx,
opCtx->recoveryUnit()->abandonSnapshot();
opCtx->recoveryUnit()->setPrepareConflictBehavior(oldPrepareConflictBehavior);
});
-
- // Relax corruption detection so that we log and continue scanning instead of failing early.
- auto oldDataCorruptionMode = opCtx->recoveryUnit()->getDataCorruptionDetectionMode();
- opCtx->recoveryUnit()->setDataCorruptionDetectionMode(
- DataCorruptionDetectionMode::kLogAndContinue);
- ON_BLOCK_EXIT(
- [&] { opCtx->recoveryUnit()->setDataCorruptionDetectionMode(oldDataCorruptionMode); });
-
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
@@ -705,8 +661,7 @@ Status validate(OperationContext* opCtx,
// the collection. For clustered collections, the validator also verifies that the
// record key (RecordId) matches the cluster key field in the record value (document's
// cluster key).
- indexValidator.traverseRecordStore(
- opCtx, results, output, additionalOptions.validationVersion);
+ indexValidator.traverseRecordStore(opCtx, results, output);
// Pause collection validation while a lock is held and between collection and index data
// validation.
@@ -780,7 +735,8 @@ Status validate(OperationContext* opCtx,
return e.toStatus();
}
string err = str::stream() << "exception during collection validation: " << e.toString();
- results->warnings.push_back(err);
+ results->errors.push_back(err);
+ results->valid = false;
LOGV2_OPTIONS(5160302,
{LogComponent::kIndex},
"Validation failed due to exception",