mirror of https://github.com/mongodb/mongo
SERVER-94613: Find on collections with a multikey wildcard index handles metadata inconsistently (#43689)
GitOrigin-RevId: c7a208a6c7eb8c584915490d63a025af3aa8b464
This commit is contained in:
parent
ae781b036a
commit
934f9ddb71
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue