summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorJustin Seyster <justin.seyster@mongodb.com>2020-01-10 16:52:07 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-01-17 22:06:40 +0000
commit8e810c9ff48db40e63b3201f8bd4d0bf4fdea8ce (patch)
tree0c005cbcd82f7a3e167fdb1e704b89b3c69e0054 /src/mongo/db
parent91d9714e6ddd83f6a215c7a159b15dacc68a8701 (diff)
downloadmongo-8e810c9ff48db40e63b3201f8bd4d0bf4fdea8ce.tar.gz
SERVER-40620 uassert and log when fetching dangling index entry
If a FETCH_STAGE encounters a record id that does not reference any existing documents, that means either the document was deleted since query execution encountered the index entry or their is corruption somewhere. If the snapshot id indicates that the query has not yielded since the time that the index entry was loaded, that leaves corruption as the only possibility. We return and error and write to the log with instructions on how to address potentially inconsistent data. create mode 100644 jstests/noPassthrough/query_yields_catch_index_corruption.js
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/catalog/index_catalog_impl.cpp11
-rw-r--r--src/mongo/db/exec/fetch.cpp2
-rw-r--r--src/mongo/db/exec/idhack.cpp3
-rw-r--r--src/mongo/db/exec/text_or.cpp3
-rw-r--r--src/mongo/db/exec/working_set_common.cpp46
-rw-r--r--src/mongo/db/exec/working_set_common.h4
-rw-r--r--src/mongo/db/exec/write_stage_common.cpp2
7 files changed, 65 insertions, 6 deletions
diff --git a/src/mongo/db/catalog/index_catalog_impl.cpp b/src/mongo/db/catalog/index_catalog_impl.cpp
index 4ad0a76d98d..10fa568a735 100644
--- a/src/mongo/db/catalog/index_catalog_impl.cpp
+++ b/src/mongo/db/catalog/index_catalog_impl.cpp
@@ -79,6 +79,8 @@
namespace mongo {
+MONGO_FAIL_POINT_DEFINE(skipUnindexingDocumentWhenDeleted);
+
using std::endl;
using std::string;
using std::unique_ptr;
@@ -1475,6 +1477,15 @@ void IndexCatalogImpl::_unindexRecord(OperationContext* opCtx,
nullptr,
loc);
+ // Tests can enable this failpoint to produce index corruption scenarios where an index has
+ // extra keys.
+ if (auto failpoint = skipUnindexingDocumentWhenDeleted.scoped();
+ MONGO_unlikely(failpoint.isActive())) {
+ auto indexName = failpoint.getData()["indexName"].valueStringDataSafe();
+ if (indexName == entry->descriptor()->indexName()) {
+ return;
+ }
+ }
_unindexKeys(opCtx, entry, {keys.begin(), keys.end()}, obj, loc, logIfError, keysDeletedOut);
}
diff --git a/src/mongo/db/exec/fetch.cpp b/src/mongo/db/exec/fetch.cpp
index 6e9eb4fbeb6..153ed5a878e 100644
--- a/src/mongo/db/exec/fetch.cpp
+++ b/src/mongo/db/exec/fetch.cpp
@@ -103,7 +103,7 @@ PlanStage::StageState FetchStage::doWork(WorkingSetID* out) {
if (!_cursor)
_cursor = collection()->getCursor(getOpCtx());
- if (!WorkingSetCommon::fetch(getOpCtx(), _ws, id, _cursor)) {
+ if (!WorkingSetCommon::fetch(getOpCtx(), _ws, id, _cursor, collection()->ns())) {
_ws->free(id);
return NEED_TIME;
}
diff --git a/src/mongo/db/exec/idhack.cpp b/src/mongo/db/exec/idhack.cpp
index 64848cb50e6..c3378824a58 100644
--- a/src/mongo/db/exec/idhack.cpp
+++ b/src/mongo/db/exec/idhack.cpp
@@ -103,7 +103,8 @@ PlanStage::StageState IDHackStage::doWork(WorkingSetID* out) {
_recordCursor = collection()->getCursor(getOpCtx());
// Find the document associated with 'id' in the collection's record store.
- if (!WorkingSetCommon::fetch(getOpCtx(), _workingSet, id, _recordCursor)) {
+ if (!WorkingSetCommon::fetch(
+ getOpCtx(), _workingSet, id, _recordCursor, collection()->ns())) {
// We didn't find a document with RecordId 'id'.
_workingSet->free(id);
_commonStats.isEOF = true;
diff --git a/src/mongo/db/exec/text_or.cpp b/src/mongo/db/exec/text_or.cpp
index 3bd267271da..8202ad623c7 100644
--- a/src/mongo/db/exec/text_or.cpp
+++ b/src/mongo/db/exec/text_or.cpp
@@ -268,7 +268,8 @@ PlanStage::StageState TextOrStage::addTerm(WorkingSetID wsid, WorkingSetID* out)
// Our parent expects RID_AND_OBJ members, so we fetch the document here if we haven't
// already.
try {
- if (!WorkingSetCommon::fetch(getOpCtx(), _ws, wsid, _recordCursor)) {
+ if (!WorkingSetCommon::fetch(
+ getOpCtx(), _ws, wsid, _recordCursor, collection()->ns())) {
_ws->free(wsid);
textRecordData->score = -1;
return NEED_TIME;
diff --git a/src/mongo/db/exec/working_set_common.cpp b/src/mongo/db/exec/working_set_common.cpp
index c8c9c3c00a5..b95dccf286b 100644
--- a/src/mongo/db/exec/working_set_common.cpp
+++ b/src/mongo/db/exec/working_set_common.cpp
@@ -27,6 +27,8 @@
* it in the license file.
*/
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery
+
#include "mongo/platform/basic.h"
#include "mongo/db/exec/working_set_common.h"
@@ -37,13 +39,34 @@
#include "mongo/db/index/index_access_method.h"
#include "mongo/db/query/canonical_query.h"
#include "mongo/db/service_context.h"
+#include "mongo/util/log.h"
namespace mongo {
+namespace {
+std::string indexKeyVectorDebugString(const std::vector<IndexKeyDatum>& keyData) {
+ StringBuilder sb;
+ sb << "[";
+ if (keyData.size() > 0) {
+ auto it = keyData.begin();
+ sb << "(key: " << redact(it->keyData) << ", index key pattern: " << it->indexKeyPattern
+ << ")";
+ while (++it != keyData.end()) {
+ sb << ", (key: " << redact(it->keyData)
+ << ", index key pattern: " << it->indexKeyPattern << ")";
+ }
+ }
+ sb << "]";
+ return sb.str();
+}
+} // namespace
+
+// static
bool WorkingSetCommon::fetch(OperationContext* opCtx,
WorkingSet* workingSet,
WorkingSetID id,
- unowned_ptr<SeekableRecordCursor> cursor) {
+ unowned_ptr<SeekableRecordCursor> cursor,
+ const NamespaceString& ns) {
WorkingSetMember* member = workingSet->get(id);
// We should have a RecordId but need to retrieve the obj. Get the obj now and reset all WSM
@@ -52,6 +75,27 @@ bool WorkingSetCommon::fetch(OperationContext* opCtx,
auto record = cursor->seekExact(member->recordId);
if (!record) {
+ // The record referenced by this index entry is gone. If the query yielded some time after
+ // we first examined the index entry, then it's likely that the record was deleted while we
+ // were yielding. However, if the snapshot id hasn't changed since the index lookup, then
+ // there could not have been a yield, and the only explanation is corruption.
+ std::vector<IndexKeyDatum>::iterator keyDataIt;
+ if (member->getState() == WorkingSetMember::RID_AND_IDX &&
+ (keyDataIt = std::find_if(member->keyData.begin(),
+ member->keyData.end(),
+ [currentSnapshotId = opCtx->recoveryUnit()->getSnapshotId()](
+ const auto& keyDatum) {
+ return keyDatum.snapshotId == currentSnapshotId;
+ })) != member->keyData.end()) {
+ std::stringstream ss;
+ ss << "Erroneous index key found with reference to non-existent record id "
+ << member->recordId << ": " << indexKeyVectorDebugString(member->keyData)
+ << ". Consider dropping and then re-creating the index with key pattern "
+ << keyDataIt->indexKeyPattern << " and then running the validate command on the "
+ << ns << " collection.";
+ error() << ss.str();
+ uasserted(ErrorCodes::DataCorruptionDetected, ss.str());
+ }
return false;
}
diff --git a/src/mongo/db/exec/working_set_common.h b/src/mongo/db/exec/working_set_common.h
index 0ebeb2ae65c..f62d861142f 100644
--- a/src/mongo/db/exec/working_set_common.h
+++ b/src/mongo/db/exec/working_set_common.h
@@ -30,6 +30,7 @@
#pragma once
#include "mongo/db/exec/working_set.h"
+#include "mongo/db/namespace_string.h"
#include "mongo/util/unowned_ptr.h"
namespace mongo {
@@ -53,7 +54,8 @@ public:
static bool fetch(OperationContext* opCtx,
WorkingSet* workingSet,
WorkingSetID id,
- unowned_ptr<SeekableRecordCursor> cursor);
+ unowned_ptr<SeekableRecordCursor> cursor,
+ const NamespaceString& ns);
/**
* Build a Document which represents a Status to return in a WorkingSet.
diff --git a/src/mongo/db/exec/write_stage_common.cpp b/src/mongo/db/exec/write_stage_common.cpp
index 74a3d846a2a..301ced6f3a5 100644
--- a/src/mongo/db/exec/write_stage_common.cpp
+++ b/src/mongo/db/exec/write_stage_common.cpp
@@ -50,7 +50,7 @@ bool ensureStillMatches(const Collection* collection,
if (opCtx->recoveryUnit()->getSnapshotId() != member->doc.snapshotId()) {
std::unique_ptr<SeekableRecordCursor> cursor(collection->getCursor(opCtx));
- if (!WorkingSetCommon::fetch(opCtx, ws, id, cursor)) {
+ if (!WorkingSetCommon::fetch(opCtx, ws, id, cursor, collection->ns())) {
// Doc is already deleted.
return false;
}