SERVER-94613: Find on collections with a multikey wildcard index handles metadata inconsistently (#43689)

GitOrigin-RevId: c7a208a6c7eb8c584915490d63a025af3aa8b464
This commit is contained in:
Matt Olma 2025-12-08 17:13:07 -05:00 committed by MongoDB Bot
parent ae781b036a
commit 934f9ddb71
3 changed files with 77 additions and 63 deletions

View File

@ -10,15 +10,6 @@ import {checkSbeFullyEnabled} from "jstests/libs/query/sbe_util.js";
const conn = MongoRunner.runMongod();
const testDB = conn.getDB("test");
// TODO SERVER-94613: Reenable this test once the stale reference issue is fixed. Early exiting
// in the meantime.
const debugBuild = testDB.adminCommand("buildInfo").debug;
if (debugBuild) {
jsTest.log("Skipping test as debug builds are susceptible to a consistent collection bug");
MongoRunner.stopMongod(conn);
quit();
}
if (checkSbeFullyEnabled(testDB)) {
jsTestLog("Skipping test as SBE is not resilient to WCEs");
MongoRunner.stopMongod(conn);

View File

@ -101,6 +101,7 @@
#include "mongo/db/s/query_analysis_writer.h"
#include "mongo/db/server_feature_flags_gen.h"
#include "mongo/db/service_context.h"
#include "mongo/db/shard_role/lock_manager/exception_util.h"
#include "mongo/db/shard_role/shard_catalog/catalog_raii.h"
#include "mongo/db/shard_role/shard_catalog/collection.h"
#include "mongo/db/shard_role/shard_catalog/collection_options.h"
@ -428,9 +429,26 @@ public:
}
}
/**
* Entry point for execution of find explain command.
* Wraps a 'explainFind' invocation while ensuring that for any read queries will retry
* execution if the query throws a WriteConflictException.
* WriteConflict error may appear in a read path mostly due to storage failures.
* A known location such error may occur is during access of wildcard index multikeyness
* information in 'getExecutorFind' invocation.
*/
void explain(OperationContext* opCtx,
ExplainOptions::Verbosity verbosity,
rpc::ReplyBuilderInterface* replyBuilder) override {
writeConflictRetry(
opCtx, "find explain cmd WriteConflictException loop", _ns, [&]() -> void {
this->explainFind(opCtx, verbosity, replyBuilder);
});
}
void explainFind(OperationContext* opCtx,
ExplainOptions::Verbosity verbosity,
rpc::ReplyBuilderInterface* replyBuilder) {
// We want to start the query planning timer right after parsing. In the explain code
// path, we have already parsed the FindCommandRequest, so start timing here.
CurOp::get(opCtx)->beginQueryPlanningTimer();
@ -603,6 +621,20 @@ public:
return numResults;
}
/**
* Entry point for execution of find command.
* Wraps a 'runFind' invocation while ensuring that for any read queries will retry
* execution if the query throws a WriteConflictException.
* WriteConflictException may appear in a read path mostly due to storage failures.
* A known location such error may occur is during access of wildcard index multikeyness
* information in 'getExecutorFind' invocation.
*/
void run(OperationContext* opCtx, rpc::ReplyBuilderInterface* replyBuilder) override {
writeConflictRetry(opCtx, "find cmd WriteConflictException loop", _ns, [&]() -> void {
this->runFind(opCtx, replyBuilder);
});
}
/**
* Runs a query using the following steps:
* --Parsing.
@ -612,7 +644,7 @@ public:
* --Save state for getMore, transferring ownership of the executor to a ClientCursor.
* --Generate response to send to the client.
*/
void run(OperationContext* opCtx, rpc::ReplyBuilderInterface* replyBuilder) override {
void runFind(OperationContext* opCtx, rpc::ReplyBuilderInterface* replyBuilder) {
CommandHelpers::handleMarkKillOnClientDisconnect(opCtx);
const BSONObj& cmdObj = _request.body;

View File

@ -171,64 +171,55 @@ static std::set<FieldRef> getWildcardMultikeyPathSetHelper(OperationContext* opC
MultikeyMetadataAccessStats* stats) {
const WildcardAccessMethod* wam =
static_cast<const WildcardAccessMethod*>(index->accessMethod());
return writeConflictRetry(
opCtx,
"wildcard multikey path retrieval",
NamespaceString::kEmpty,
[&]() -> std::set<FieldRef> {
stats->numSeeks = 0;
stats->keysExamined = 0;
auto& ru = *shard_role_details::getRecoveryUnit(opCtx);
auto cursor = wam->newCursor(opCtx, ru);
stats->numSeeks = 0;
stats->keysExamined = 0;
auto& ru = *shard_role_details::getRecoveryUnit(opCtx);
auto cursor = wam->newCursor(opCtx, ru);
constexpr int kForward = 1;
const auto keyPattern = buildIndexBoundsKeyPattern(index->descriptor()->keyPattern());
IndexBoundsChecker checker(&indexBounds, keyPattern, kForward);
IndexSeekPoint seekPoint;
if (!checker.getStartSeekPoint(&seekPoint)) {
return {};
constexpr int kForward = 1;
const auto keyPattern = buildIndexBoundsKeyPattern(index->descriptor()->keyPattern());
IndexBoundsChecker checker(&indexBounds, keyPattern, kForward);
IndexSeekPoint seekPoint;
if (!checker.getStartSeekPoint(&seekPoint)) {
return {};
}
std::set<FieldRef> multikeyPaths{};
key_string::Builder builder(wam->getSortedDataInterface()->getKeyStringVersion(),
wam->getSortedDataInterface()->getOrdering());
auto entry = cursor->seek(
ru, IndexEntryComparison::makeKeyStringFromSeekPointForSeek(seekPoint, kForward, builder));
++stats->numSeeks;
while (entry) {
++stats->keysExamined;
switch (checker.checkKey(entry->key, &seekPoint)) {
case IndexBoundsChecker::VALID:
multikeyPaths.emplace(extractMultikeyPathFromIndexKey(*entry));
entry = cursor->next(ru);
break;
case IndexBoundsChecker::MUST_ADVANCE: {
++stats->numSeeks;
key_string::Builder builder(wam->getSortedDataInterface()->getKeyStringVersion(),
wam->getSortedDataInterface()->getOrdering());
entry = cursor->seek(ru,
IndexEntryComparison::makeKeyStringFromSeekPointForSeek(
seekPoint, kForward, builder));
break;
}
std::set<FieldRef> multikeyPaths{};
key_string::Builder builder(wam->getSortedDataInterface()->getKeyStringVersion(),
wam->getSortedDataInterface()->getOrdering());
auto entry = cursor->seek(ru,
IndexEntryComparison::makeKeyStringFromSeekPointForSeek(
seekPoint, kForward, builder));
case IndexBoundsChecker::DONE:
entry = boost::none;
break;
++stats->numSeeks;
while (entry) {
++stats->keysExamined;
default:
MONGO_UNREACHABLE;
}
}
switch (checker.checkKey(entry->key, &seekPoint)) {
case IndexBoundsChecker::VALID:
multikeyPaths.emplace(extractMultikeyPathFromIndexKey(*entry));
entry = cursor->next(ru);
break;
case IndexBoundsChecker::MUST_ADVANCE: {
++stats->numSeeks;
key_string::Builder builder(
wam->getSortedDataInterface()->getKeyStringVersion(),
wam->getSortedDataInterface()->getOrdering());
entry =
cursor->seek(ru,
IndexEntryComparison::makeKeyStringFromSeekPointForSeek(
seekPoint, kForward, builder));
break;
}
case IndexBoundsChecker::DONE:
entry = boost::none;
break;
default:
MONGO_UNREACHABLE;
}
}
return multikeyPaths;
});
return multikeyPaths;
}
std::vector<Interval> getMultikeyPathIndexIntervalsForField(FieldRef field) {