summaryrefslogtreecommitdiff
path: root/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp')
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp172
1 files changed, 149 insertions, 23 deletions
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp
index 09b1603361e..0a498ead248 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp
@@ -38,12 +38,16 @@
#include <set>
#include "mongo/base/checked_cast.h"
+#include "mongo/base/string_data.h"
+#include "mongo/db/catalog/health_log.h"
+#include "mongo/db/catalog/health_log_gen.h"
#include "mongo/db/catalog/index_catalog_entry.h"
#include "mongo/db/catalog/validate_results.h"
#include "mongo/db/concurrency/write_conflict_exception.h"
#include "mongo/db/global_settings.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/json.h"
+#include "mongo/db/namespace_string.h"
#include "mongo/db/repl/repl_settings.h"
#include "mongo/db/service_context.h"
#include "mongo/db/stats/resource_consumption_metrics.h"
@@ -59,6 +63,7 @@
#include "mongo/util/assert_util.h"
#include "mongo/util/fail_point.h"
#include "mongo/util/hex.h"
+#include "mongo/util/stacktrace.h"
#include "mongo/util/str.h"
#include "mongo/util/testing_proctor.h"
@@ -84,8 +89,53 @@ namespace {
MONGO_FAIL_POINT_DEFINE(WTCompactIndexEBUSY);
MONGO_FAIL_POINT_DEFINE(WTIndexPauseAfterSearchNear);
MONGO_FAIL_POINT_DEFINE(WTValidateIndexStructuralDamage);
+MONGO_FAIL_POINT_DEFINE(WTIndexUassertDuplicateRecordForKeyOnIdUnindex);
static const WiredTigerItem emptyItem(nullptr, 0);
+
+/**
+ * Add a data corruption entry to the health log.
+ */
+void addDataCorruptionEntryToHealthLog(OperationContext* opCtx,
+ const NamespaceString& nss,
+ StringData operation,
+ StringData message,
+ const BSONObj& key,
+ StringData indexName,
+ StringData uri) {
+ HealthLogEntry entry;
+ entry.setNss(nss);
+ entry.setTimestamp(Date_t::now());
+ entry.setSeverity(SeverityEnum::Error);
+ entry.setScope(ScopeEnum::Index);
+ entry.setOperation(operation);
+ entry.setMsg(message);
+
+ BSONObjBuilder bob;
+ bob.append("key", key);
+ bob.append("indexName", indexName);
+ bob.append("uri", uri);
+ bob.appendElements(getStackTrace().getBSONRepresentation());
+ entry.setData(bob.obj());
+
+ HealthLog::get(opCtx)->log(entry);
+}
+
+/**
+ * Returns the logv2::LogOptions controlling the behaviour after logging a data corruption
+ * error. When the TestingProctor is enabled we will fatally assert. When the testing proctor is
+ * disabled or when 'forceUassert' is specified (for instance because a failpoint is enabled),
+ * we should log and throw DataCorruptionDetected.
+ */
+logv2::LogOptions getLogOptionsForDataCorruption(RecoveryUnit& ru, bool forceUassert = false) {
+ if (ru.getDataCorruptionDetectionMode() == DataCorruptionDetectionMode::kThrow ||
+ MONGO_unlikely(forceUassert)) {
+ return logv2::LogOptions{logv2::UserAssertAfterLog(ErrorCodes::DataCorruptionDetected)};
+ } else {
+ return logv2::LogOptions(logv2::LogComponent::kAutomaticDetermination);
+ }
+}
+
} // namespace
void WiredTigerIndex::setKey(WT_CURSOR* cursor, const WT_ITEM* item) {
@@ -240,7 +290,7 @@ WiredTigerIndex::WiredTigerIndex(OperationContext* ctx,
bool isLogged,
bool isReadOnly)
: SortedDataInterface(ident,
- _handleVersionInfo(ctx, uri, desc, isLogged, isReadOnly),
+ _handleVersionInfo(ctx, uri, ident, desc, isLogged, isReadOnly),
Ordering::make(desc->keyPattern()),
rsKeyFormat),
_uri(uri),
@@ -711,8 +761,45 @@ StatusWith<bool> WiredTigerIndex::_checkDups(OperationContext* opCtx,
_collation);
}
+void WiredTigerIndex::_repairDataFormatVersion(OperationContext* opCtx,
+ const std::string& uri,
+ StringData ident,
+ const IndexDescriptor* desc) {
+ auto indexVersion = desc->version();
+ auto isIndexVersion1 = indexVersion == IndexDescriptor::IndexVersion::kV1;
+ auto isIndexVersion2 = indexVersion == IndexDescriptor::IndexVersion::kV2;
+ auto isDataFormat6 = _dataFormatVersion == kDataFormatV1KeyStringV0IndexVersionV1;
+ auto isDataFormat8 = _dataFormatVersion == kDataFormatV2KeyStringV1IndexVersionV2;
+ auto isDataFormat13 = _dataFormatVersion == kDataFormatV5KeyStringV0UniqueIndexVersionV1;
+ auto isDataFormat14 = _dataFormatVersion == kDataFormatV6KeyStringV1UniqueIndexVersionV2;
+ // Only fixes the index data format when it could be from an edge case when converting the
+ // uniqueness of the index. Specifically:
+ // * The index is a secondary unique index, but the data format version is 6 (v1) or 8 (v2).
+ // * The index is a non-unique index, but the data format version is 13 (v1) or 14 (v2).
+ if ((!desc->isIdIndex() && desc->unique() &&
+ ((isIndexVersion1 && isDataFormat6) || (isIndexVersion2 && isDataFormat8))) ||
+ (!desc->unique() &&
+ ((isIndexVersion1 && isDataFormat13) || (isIndexVersion2 && isDataFormat14)))) {
+ auto engine = opCtx->getServiceContext()->getStorageEngine();
+ engine->getEngine()->alterIdentMetadata(
+ opCtx, ident, desc, /* isForceUpdateMetadata */ false);
+ auto prevVersion = _dataFormatVersion;
+ // The updated data format is guaranteed to be within the supported version range.
+ _dataFormatVersion = WiredTigerUtil::checkApplicationMetadataFormatVersion(
+ opCtx, uri, kMinimumIndexVersion, kMaximumIndexVersion)
+ .getValue();
+ LOGV2_WARNING(6818600,
+ "Fixing index metadata data format version",
+ "namespace"_attr = desc->getEntry()->getNSSFromCatalog(opCtx),
+ "indexName"_attr = desc->indexName(),
+ "prevVersion"_attr = prevVersion,
+ "newVersion"_attr = _dataFormatVersion);
+ }
+}
+
KeyString::Version WiredTigerIndex::_handleVersionInfo(OperationContext* ctx,
const std::string& uri,
+ StringData ident,
const IndexDescriptor* desc,
bool isLogged,
bool isReadOnly) {
@@ -730,6 +817,8 @@ KeyString::Version WiredTigerIndex::_handleVersionInfo(OperationContext* ctx,
}
_dataFormatVersion = version.getValue();
+ _repairDataFormatVersion(ctx, uri, ident, desc);
+
if (!desc->isIdIndex() && desc->unique() &&
(_dataFormatVersion < kDataFormatV3KeyStringV0UniqueIndexVersionV1 ||
_dataFormatVersion > kDataFormatV6KeyStringV1UniqueIndexVersionV2)) {
@@ -1510,14 +1599,24 @@ private:
_typeBits.resetFromBuffer(&br);
if (!br.atEof()) {
- LOGV2_FATAL(28608,
- "Unique index cursor seeing multiple records for key {key} in index "
- "{index} ({uri}) belonging to collection {collection}",
- "Unique index cursor seeing multiple records for key in index",
- "key"_attr = redact(curr(kWantKey)->key),
- "index"_attr = _idx.indexName(),
- "uri"_attr = _idx.uri(),
- "collection"_attr = _idx.getCollectionNamespace(_opCtx));
+ const auto bsonKey = redact(curr(kWantKey)->key);
+ const auto collectionNamespace = _idx.getCollectionNamespace(_opCtx);
+ addDataCorruptionEntryToHealthLog(
+ _opCtx,
+ collectionNamespace,
+ "WiredTigerIndexUniqueCursor::_updateIdAndTypeBitsFromValue",
+ "Unique index cursor seeing multiple records for key in index",
+ bsonKey,
+ _idx.indexName(),
+ _idx.uri());
+
+ LOGV2_ERROR_OPTIONS(7623202,
+ getLogOptionsForDataCorruption(*_opCtx->recoveryUnit()),
+ "Unique index cursor seeing multiple records for key in index",
+ "key"_attr = bsonKey,
+ "index"_attr = _idx.indexName(),
+ "uri"_attr = _idx.uri(),
+ logAttrs(collectionNamespace));
}
}
};
@@ -1545,12 +1644,25 @@ public:
_typeBits.resetFromBuffer(&br);
if (!br.atEof()) {
- LOGV2_FATAL(5176200,
- "Index cursor seeing multiple records for key in _id index",
- "key"_attr = redact(curr(kWantKey)->key),
- "index"_attr = _idx.indexName(),
- "uri"_attr = _idx.uri(),
- "collection"_attr = _idx.getCollectionNamespace(_opCtx));
+ const auto bsonKey = redact(curr(kWantKey)->key);
+ const auto collectionNamespace = _idx.getCollectionNamespace(_opCtx);
+
+ addDataCorruptionEntryToHealthLog(
+ _opCtx,
+ collectionNamespace,
+ "WiredTigerIdIndexCursor::updateIdAndTypeBits",
+ "Index cursor seeing multiple records for key in _id index",
+ bsonKey,
+ _idx.indexName(),
+ _idx.uri());
+
+ LOGV2_ERROR_OPTIONS(5176200,
+ getLogOptionsForDataCorruption(*_opCtx->recoveryUnit()),
+ "Index cursor seeing multiple records for key in _id index",
+ "key"_attr = bsonKey,
+ "index"_attr = _idx.indexName(),
+ "uri"_attr = _idx.uri(),
+ logAttrs(collectionNamespace));
}
}
};
@@ -1773,9 +1885,11 @@ void WiredTigerIdIndex::_unindex(OperationContext* opCtx,
WiredTigerItem keyItem(keyString.getBuffer(), sizeWithoutRecordId);
setKey(c, keyItem.Get());
+ const auto failWithDataCorruptionForTest =
+ WTIndexUassertDuplicateRecordForKeyOnIdUnindex.shouldFail();
// On the _id index, the RecordId is stored in the value of the index entry. If the dupsAllowed
// flag is not set, we blindly delete using only the key without checking the RecordId.
- if (!dupsAllowed) {
+ if (!dupsAllowed && MONGO_likely(!failWithDataCorruptionForTest)) {
int ret = WT_OP_CHECK(wiredTigerCursorRemove(opCtx, c));
if (ret == WT_NOTFOUND) {
return;
@@ -1807,14 +1921,26 @@ void WiredTigerIdIndex::_unindex(OperationContext* opCtx,
RecordId idInIndex = KeyString::decodeRecordIdLong(&br);
KeyString::TypeBits typeBits = KeyString::TypeBits::fromBuffer(getKeyStringVersion(), &br);
- if (!br.atEof()) {
+ if (!br.atEof() || MONGO_unlikely(failWithDataCorruptionForTest)) {
auto bsonKey = KeyString::toBson(keyString, _ordering);
- LOGV2_FATAL(5176201,
- "Un-index seeing multiple records for key",
- "key"_attr = bsonKey,
- "index"_attr = _desc->indexName(),
- "uri"_attr = _uri,
- "collection"_attr = getCollectionNamespace(opCtx));
+ const auto collectionNamespace = getCollectionNamespace(opCtx);
+
+ addDataCorruptionEntryToHealthLog(opCtx,
+ collectionNamespace,
+ "WiredTigerIdIndex::_unindex",
+ "Un-index seeing multiple records for key",
+ bsonKey,
+ _indexName,
+ _uri);
+
+ LOGV2_ERROR_OPTIONS(
+ 5176201,
+ getLogOptionsForDataCorruption(*opCtx->recoveryUnit(), failWithDataCorruptionForTest),
+ "Un-index seeing multiple records for key",
+ "key"_attr = bsonKey,
+ "index"_attr = _indexName,
+ "uri"_attr = _uri,
+ logAttrs(collectionNamespace));
}
// The RecordId matches, so remove the entry.