mirror of https://github.com/mongodb/mongo
SERVER-83215 Add generic logic to support fast paths that bypass the optimizer
This commit is contained in:
parent
dd8ecb087a
commit
7ecd6bd0fa
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* This test verifies that the optimizer fast path is not used for queries that would otherwise be
|
||||
* eligible but contain operations the current fast path implementations don't support (e.g. a
|
||||
* projection).
|
||||
*/
|
||||
import {planHasStage} from "jstests/libs/analyze_plan.js";
|
||||
import {
|
||||
checkCascadesOptimizerEnabled,
|
||||
checkFastPathEnabled,
|
||||
} from "jstests/libs/optimizer_utils.js";
|
||||
|
||||
if (!checkCascadesOptimizerEnabled(db)) {
|
||||
jsTestLog("Skipping test because the optimizer is not enabled");
|
||||
quit();
|
||||
}
|
||||
|
||||
if (!checkFastPathEnabled(db)) {
|
||||
jsTestLog("Skipping test because fast paths are not enabled");
|
||||
quit();
|
||||
}
|
||||
|
||||
function assertNotUsingFastPath(explainCmd) {
|
||||
const explain = assert.commandWorked(explainCmd);
|
||||
assert(!planHasStage(db, explain, "FASTPATH"));
|
||||
}
|
||||
|
||||
const coll = db.non_eligible_queries;
|
||||
coll.drop();
|
||||
|
||||
{
|
||||
// Empty find with a projection should not use fast path.
|
||||
const explain = coll.explain().find({}, {b: 1}).finish();
|
||||
assertNotUsingFastPath(explain);
|
||||
}
|
||||
{
|
||||
// Empty find with a sort spec should not use fast path.
|
||||
const explain = coll.explain().find({}).sort({b: 1}).finish();
|
||||
assertNotUsingFastPath(explain);
|
||||
}
|
||||
{
|
||||
// Empty find with limit should not use fast path.
|
||||
const explain = coll.explain().find({}).limit(3).finish();
|
||||
assertNotUsingFastPath(explain);
|
||||
}
|
||||
{
|
||||
// Empty find with skip should not use fast path.
|
||||
const explain = coll.explain().find({}).skip(3).finish();
|
||||
assertNotUsingFastPath(explain);
|
||||
}
|
||||
{
|
||||
// Pipeline with $project should not use fast path.
|
||||
let explain = coll.explain().aggregate([{$match: {}}, {$project: {a: 1}}]);
|
||||
assertNotUsingFastPath(explain);
|
||||
|
||||
explain = coll.explain().aggregate([{$project: {a: 1}}]);
|
||||
assertNotUsingFastPath(explain);
|
||||
}
|
||||
{
|
||||
// Pipeline with $sort should not use fast path.
|
||||
let explain = coll.explain().aggregate([{$match: {}}, {$sort: {a: 1}}]);
|
||||
assertNotUsingFastPath(explain);
|
||||
|
||||
explain = coll.explain().aggregate([{$sort: {a: 1}}]);
|
||||
assertNotUsingFastPath(explain);
|
||||
}
|
||||
{
|
||||
// Pipeline with $limit should not use fast path.
|
||||
let explain = coll.explain().aggregate([{$match: {}}, {$limit: 3}]);
|
||||
assertNotUsingFastPath(explain);
|
||||
|
||||
explain = coll.explain().aggregate([{$limit: 3}]);
|
||||
assertNotUsingFastPath(explain);
|
||||
}
|
||||
{
|
||||
// Pipeline with $skip should not use fast path.
|
||||
let explain = coll.explain().aggregate([{$match: {}}, {$skip: 3}]);
|
||||
assertNotUsingFastPath(explain);
|
||||
|
||||
explain = coll.explain().aggregate([{$skip: 3}]);
|
||||
assertNotUsingFastPath(explain);
|
||||
}
|
||||
|
|
@ -15,6 +15,13 @@ export function checkPlanCacheParameterization(theDB) {
|
|||
return false;
|
||||
}
|
||||
|
||||
export function checkFastPathEnabled(theDB) {
|
||||
const isDisabled =
|
||||
theDB.adminCommand({getParameter: 1, internalCascadesOptimizerDisableFastPath: 1})
|
||||
.internalCascadesOptimizerDisableFastPath;
|
||||
return !isDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for checking if the experimental Cascades optimizer code path is enabled (checks
|
||||
* framework control for M4+).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* This test verifies that the optimizer fast path is not used for queries against sharded
|
||||
* collections.
|
||||
*/
|
||||
import {planHasStage} from "jstests/libs/analyze_plan.js";
|
||||
|
||||
const bonsaiSettings = {
|
||||
internalQueryFrameworkControl: "tryBonsai",
|
||||
featureFlagCommonQueryFramework: true,
|
||||
// TODO SERVER-80582: Uncomment this setting. Some sharding code calls empty find on an
|
||||
// unsharded collection internally, which causes this test to fail as that fast path is not
|
||||
// implemented yet. internalCascadesOptimizerDisableFastPath: false,
|
||||
};
|
||||
|
||||
const st = new ShardingTest({
|
||||
shards: 2,
|
||||
mongos: 1,
|
||||
other: {
|
||||
shardOptions: {
|
||||
setParameter: {
|
||||
...bonsaiSettings,
|
||||
"failpoint.enableExplainInBonsai": tojson({mode: "alwaysOn"}),
|
||||
}
|
||||
},
|
||||
mongosOptions: {setParameter: {...bonsaiSettings}},
|
||||
}
|
||||
});
|
||||
|
||||
const db = st.getDB("test");
|
||||
|
||||
const coll = db[jsTestName()];
|
||||
coll.drop();
|
||||
|
||||
assert.commandWorked(coll.insertMany([...Array(100).keys()].map(i => {
|
||||
return {_id: i, a: 1};
|
||||
})));
|
||||
|
||||
st.shardColl(coll.getName(), {_id: 1}, {_id: 50}, {_id: 51});
|
||||
|
||||
function assertNotUsingFastPath(explainCmd) {
|
||||
const explain = assert.commandWorked(explainCmd);
|
||||
assert(!planHasStage(db, explain, "FASTPATH"));
|
||||
}
|
||||
|
||||
{
|
||||
// Empty find on a sharded collection should not use fast path.
|
||||
const explain = coll.explain().find().finish();
|
||||
assertNotUsingFastPath(explain);
|
||||
}
|
||||
{
|
||||
// Pipeline with empty match on a sharded collection should not use fast path.
|
||||
const explain = coll.explain().aggregate([{$match: {}}]);
|
||||
assertNotUsingFastPath(explain);
|
||||
}
|
||||
{
|
||||
// Empty aggregate on a sharded collection should not use fast path.
|
||||
const explain = coll.explain().aggregate([]);
|
||||
assertNotUsingFastPath(explain);
|
||||
}
|
||||
|
||||
st.stop();
|
||||
|
|
@ -1576,6 +1576,8 @@ execEnv.Library(
|
|||
'query/classic_stage_builder.cpp',
|
||||
'query/cost_model/on_coefficients_change_updater_impl.cpp',
|
||||
'query/cqf_command_utils.cpp',
|
||||
'query/cqf_fast_paths.cpp',
|
||||
'query/cqf_fast_paths_utils.cpp',
|
||||
'query/cqf_get_executor.cpp',
|
||||
'query/explain.cpp',
|
||||
'query/find.cpp',
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@
|
|||
#include "mongo/db/query/collation/collator_interface.h"
|
||||
#include "mongo/db/query/collection_query_info.h"
|
||||
#include "mongo/db/query/cqf_command_utils.h"
|
||||
#include "mongo/db/query/cqf_fast_paths.h"
|
||||
#include "mongo/db/query/cqf_get_executor.h"
|
||||
#include "mongo/db/query/cursor_response.h"
|
||||
#include "mongo/db/query/explain.h"
|
||||
|
|
@ -1436,13 +1437,28 @@ Status runAggregate(OperationContext* opCtx,
|
|||
optimizer::QueryHints queryHints = getHintsFromQueryKnobs();
|
||||
const bool fastIndexNullHandling = queryHints._fastIndexNullHandling;
|
||||
auto timeBegin = Date_t::now();
|
||||
auto maybeExec = getSBEExecutorViaCascadesOptimizer(opCtx,
|
||||
expCtx,
|
||||
nss,
|
||||
collections,
|
||||
std::move(queryHints),
|
||||
request.getHint(),
|
||||
pipeline.get());
|
||||
auto maybeExec = [&] {
|
||||
// If the query is eligible for a fast path, use the fast path plan instead of
|
||||
// invoking the optimizer.
|
||||
if (auto fastPathExec = optimizer::fast_path::tryGetSBEExecutorViaFastPath(
|
||||
opCtx,
|
||||
expCtx,
|
||||
nss,
|
||||
collections,
|
||||
request.getExplain().has_value(),
|
||||
request.getHint().has_value(),
|
||||
pipeline.get())) {
|
||||
return fastPathExec;
|
||||
}
|
||||
|
||||
return getSBEExecutorViaCascadesOptimizer(opCtx,
|
||||
expCtx,
|
||||
nss,
|
||||
collections,
|
||||
std::move(queryHints),
|
||||
request.getHint(),
|
||||
pipeline.get());
|
||||
}();
|
||||
if (maybeExec) {
|
||||
// Pass ownership of the pipeline to the executor. This is done to allow binding of
|
||||
// parameters to use views onto the constants living in the MatchExpression (in the
|
||||
|
|
|
|||
|
|
@ -449,6 +449,7 @@ env.CppUnitTest(
|
|||
"ce_mode_parameter_test.cpp",
|
||||
"classic_stage_builder_test.cpp",
|
||||
"count_command_test.cpp",
|
||||
"cqf_fast_paths_test.cpp",
|
||||
"cursor_response_test.cpp",
|
||||
"query_shape/distinct_cmd_shape_test.cpp",
|
||||
"query_shape/find_cmd_shape_test.cpp",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,265 @@
|
|||
/**
|
||||
* Copyright (C) 2023-present MongoDB, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the Server Side Public License in all respects for
|
||||
* all of the code used other than as permitted herein. If you modify file(s)
|
||||
* with this exception, you may extend this exception to your version of the
|
||||
* file(s), but you are not obligated to do so. If you do not wish to do so,
|
||||
* delete this exception statement from your version. If you delete this
|
||||
* exception statement from all source files in the program, then also delete
|
||||
* it in the license file.
|
||||
*/
|
||||
|
||||
#include "mongo/db/query/cqf_fast_paths.h"
|
||||
#include "mongo/db/query/cqf_command_utils.h"
|
||||
#include "mongo/db/query/cqf_fast_paths_utils.h"
|
||||
#include "mongo/db/query/plan_yield_policy_sbe.h"
|
||||
#include "mongo/db/query/query_planner_params.h"
|
||||
#include "mongo/util/assert_util.h"
|
||||
|
||||
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQuery
|
||||
|
||||
namespace mongo::optimizer::fast_path {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Holds all information required to construct SBE plans for queries that have a fast path
|
||||
* implementation.
|
||||
*/
|
||||
struct ExecTreeGeneratorParams {
|
||||
const UUID collectionUuid;
|
||||
PlanYieldPolicy* yieldPolicy;
|
||||
const BSONObj& filter;
|
||||
};
|
||||
|
||||
using ExecTreeResult = std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData>;
|
||||
|
||||
/**
|
||||
* Interface for implementing a fast path for a simple query of certain shape. Responsible for SBE
|
||||
* plan generation.
|
||||
*/
|
||||
class ExecTreeGenerator {
|
||||
public:
|
||||
virtual ~ExecTreeGenerator() = default;
|
||||
|
||||
virtual ExecTreeResult generateExecTree(const ExecTreeGeneratorParams& params) const = 0;
|
||||
|
||||
virtual BSONObj generateExplain() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* ABTPrinter implementation that passes on the given explain BSON. Used for implementing
|
||||
* explain for fast paths.
|
||||
*/
|
||||
class FastPathPrinter : public AbstractABTPrinter {
|
||||
public:
|
||||
FastPathPrinter(BSONObj fastPathExplain) : _explainBSON(std::move(fastPathExplain)) {}
|
||||
|
||||
BSONObj explainBSON() const override final {
|
||||
return _explainBSON;
|
||||
}
|
||||
|
||||
std::string getPlanSummary() const override final {
|
||||
return "COLLSCAN";
|
||||
}
|
||||
|
||||
private:
|
||||
const BSONObj _explainBSON;
|
||||
};
|
||||
|
||||
// We can use a BSON object representing the filter for fast and simple comparison.
|
||||
using FastPathMap = FilterComparator::Map<std::unique_ptr<ExecTreeGenerator>>;
|
||||
|
||||
FastPathMap fastPathMap{FilterComparator::kInstance.makeLessThan()};
|
||||
|
||||
/**
|
||||
* Do not call this method directly. Instead, use the REGISTER_FAST_PATH macro defined in this
|
||||
* file.
|
||||
*/
|
||||
void registerExecTreeGenerator(BSONObj shape,
|
||||
std::unique_ptr<ExecTreeGenerator> execTreeGenerator) {
|
||||
tassert(8321506,
|
||||
"Did not expect 'shape' to contain '_id' field or a dotted path",
|
||||
!containsSpecialField(shape));
|
||||
fastPathMap.insert({shape, std::move(execTreeGenerator)});
|
||||
}
|
||||
|
||||
bool canUseFastPath(const bool hasIndexHint,
|
||||
const MultipleCollectionAccessor& collections,
|
||||
const CanonicalQuery* canonicalQuery,
|
||||
const Pipeline* pipeline) {
|
||||
if (internalCascadesOptimizerDisableFastPath.load()) {
|
||||
return false;
|
||||
}
|
||||
if (internalQueryDefaultDOP.load() > 1) {
|
||||
// The current fast path implementations don't support parallel scan plans.
|
||||
return false;
|
||||
}
|
||||
if (hasIndexHint) {
|
||||
// The current fast path implementations only deal with collection scans.
|
||||
return false;
|
||||
}
|
||||
if (canonicalQuery) {
|
||||
const auto& findRequest = canonicalQuery->getFindCommandRequest();
|
||||
if (canonicalQuery->getProj() || canonicalQuery->getSortPattern() ||
|
||||
findRequest.getLimit() || findRequest.getSkip()) {
|
||||
// The current fast path implementations don't support
|
||||
// projections or sorting.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (pipeline) {
|
||||
const auto& sources = pipeline->getSources();
|
||||
if (sources.size() > 1 ||
|
||||
(sources.size() == 1 &&
|
||||
dynamic_cast<DocumentSourceMatch*>(sources.front().get()) == nullptr)) {
|
||||
// The current fast path implementations only support queries containing a single filter
|
||||
// with a simple predicate.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!collections.getMainCollection()) {
|
||||
// TODO SERVER-83267: Enable once we have a fast path for non-existent collections.
|
||||
return false;
|
||||
}
|
||||
const bool isSharded = collections.isAcquisition()
|
||||
? collections.getMainAcquisition().getShardingDescription().isSharded()
|
||||
: collections.getMainCollection().isSharded_DEPRECATED();
|
||||
if (isSharded) {
|
||||
// The current fast path implementations don't support shard filtering.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BSONObj extractQueryFilter(const CanonicalQuery* canonicalQuery, const Pipeline* pipeline) {
|
||||
if (canonicalQuery) {
|
||||
return canonicalQuery->getQueryObj();
|
||||
}
|
||||
if (pipeline) {
|
||||
return pipeline->getInitialQuery();
|
||||
}
|
||||
tasserted(8217100, "Expected canonicalQuery or pipeline.");
|
||||
}
|
||||
|
||||
const ExecTreeGenerator* getFastPathExecTreeGenerator(const BSONObj& filter) {
|
||||
auto generatorIt = fastPathMap.find(filter);
|
||||
if (generatorIt == fastPathMap.end()) {
|
||||
OPTIMIZER_DEBUG_LOG(
|
||||
8321501, 5, "Query not eligible for a fast path.", "query"_attr = filter.toString());
|
||||
return {};
|
||||
}
|
||||
|
||||
OPTIMIZER_DEBUG_LOG(
|
||||
8321502, 5, "Using a fast path for query", "query"_attr = filter.toString());
|
||||
|
||||
return generatorIt->second.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements fast path SBE plan generation for an empty query.
|
||||
*/
|
||||
class EmptyQueryExecTreeGenerator : public ExecTreeGenerator {
|
||||
public:
|
||||
ExecTreeResult generateExecTree(const ExecTreeGeneratorParams& params) const override {
|
||||
// TODO SERVER-80582
|
||||
MONGO_UNIMPLEMENTED_TASSERT(8321504);
|
||||
}
|
||||
|
||||
virtual BSONObj generateExplain() const override {
|
||||
// TODO SERVER-80582
|
||||
MONGO_UNIMPLEMENTED_TASSERT(8321505);
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_FAST_PATH_EXEC_TREE_GENERATOR(Empty, {}, std::make_unique<EmptyQueryExecTreeGenerator>());
|
||||
|
||||
} // namespace
|
||||
|
||||
boost::optional<ExecParams> tryGetSBEExecutorViaFastPath(
|
||||
OperationContext* opCtx,
|
||||
boost::intrusive_ptr<ExpressionContext> expCtx,
|
||||
const NamespaceString& nss,
|
||||
const MultipleCollectionAccessor& collections,
|
||||
const bool hasExplain,
|
||||
const bool hasIndexHint,
|
||||
const Pipeline* pipeline,
|
||||
const CanonicalQuery* canonicalQuery) {
|
||||
if (!canUseFastPath(hasIndexHint, collections, canonicalQuery, pipeline)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto filter = extractQueryFilter(canonicalQuery, pipeline);
|
||||
|
||||
auto generator = getFastPathExecTreeGenerator(filter);
|
||||
if (!generator) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<PlanYieldPolicySBE> sbeYieldPolicy =
|
||||
PlanYieldPolicySBE::make(opCtx, PlanYieldPolicy::YieldPolicy::YIELD_AUTO, collections, nss);
|
||||
|
||||
ExecTreeGeneratorParams params{
|
||||
collections.getMainCollection()->uuid(), sbeYieldPolicy.get(), filter};
|
||||
|
||||
auto [sbePlan, data] = generator->generateExecTree(params);
|
||||
|
||||
{
|
||||
sbe::DebugPrinter p;
|
||||
OPTIMIZER_DEBUG_LOG(6264802, 5, "Lowered SBE plan", "plan"_attr = p.print(*sbePlan.get()));
|
||||
}
|
||||
|
||||
sbePlan->attachToOperationContext(opCtx);
|
||||
if (expCtx->mayDbProfile) {
|
||||
sbePlan->markShouldCollectTimingInfo();
|
||||
}
|
||||
|
||||
auto explain = hasExplain ? generator->generateExplain() : BSONObj{};
|
||||
|
||||
sbePlan->prepare(data.env.ctx);
|
||||
CurOp::get(opCtx)->stopQueryPlanningTimer();
|
||||
|
||||
return {{opCtx,
|
||||
nullptr /*solution*/,
|
||||
{std::move(sbePlan), std::move(data)},
|
||||
std::make_unique<FastPathPrinter>(std::move(explain)),
|
||||
QueryPlannerParams::Options::DEFAULT,
|
||||
nss,
|
||||
std::move(sbeYieldPolicy),
|
||||
false /*isFromPlanCache*/,
|
||||
true /* generatedByBonsai */,
|
||||
nullptr /*pipelineMatchExpr*/}};
|
||||
}
|
||||
|
||||
boost::optional<ExecParams> tryGetSBEExecutorViaFastPath(
|
||||
const MultipleCollectionAccessor& collections, const CanonicalQuery* query) {
|
||||
auto hasIndexHint = !query->getFindCommandRequest().getHint().isEmpty();
|
||||
|
||||
return tryGetSBEExecutorViaFastPath(query->getOpCtx(),
|
||||
query->getExpCtx(),
|
||||
query->nss(),
|
||||
collections,
|
||||
query->getExplain(),
|
||||
hasIndexHint,
|
||||
nullptr /*pipeline*/,
|
||||
query);
|
||||
}
|
||||
|
||||
} // namespace mongo::optimizer::fast_path
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Copyright (C) 2023-present MongoDB, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the Server Side Public License in all respects for
|
||||
* all of the code used other than as permitted herein. If you modify file(s)
|
||||
* with this exception, you may extend this exception to your version of the
|
||||
* file(s), but you are not obligated to do so. If you do not wish to do so,
|
||||
* delete this exception statement from your version. If you delete this
|
||||
* exception statement from all source files in the program, then also delete
|
||||
* it in the license file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
||||
#include <cstddef>
|
||||
|
||||
#include "mongo/base/init.h"
|
||||
#include "mongo/db/namespace_string.h"
|
||||
#include "mongo/db/operation_context.h"
|
||||
#include "mongo/db/pipeline/expression_context.h"
|
||||
#include "mongo/db/pipeline/pipeline.h"
|
||||
#include "mongo/db/query/canonical_query.h"
|
||||
#include "mongo/db/query/cqf_get_executor.h"
|
||||
#include "mongo/db/query/multiple_collection_accessor.h"
|
||||
|
||||
|
||||
namespace mongo::optimizer::fast_path {
|
||||
|
||||
#define REGISTER_FAST_PATH_EXEC_TREE_GENERATOR(key, pattern, generator) \
|
||||
MONGO_INITIALIZER(addFastPath_##key) \
|
||||
(InitializerContext*) { \
|
||||
registerExecTreeGenerator(pattern, generator); \
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the arguments to create a PlanExecutor for the given CanonicalQuery.
|
||||
*/
|
||||
boost::optional<ExecParams> tryGetSBEExecutorViaFastPath(
|
||||
const MultipleCollectionAccessor& collections, const CanonicalQuery* query);
|
||||
|
||||
/**
|
||||
* Returns the arguments to create a PlanExecutor for the given Pipeline.
|
||||
*
|
||||
* The CanonicalQuery parameter allows for code reuse between functions in this file and should not
|
||||
* be set by callers.
|
||||
*/
|
||||
boost::optional<ExecParams> tryGetSBEExecutorViaFastPath(
|
||||
OperationContext* opCtx,
|
||||
boost::intrusive_ptr<ExpressionContext> expCtx,
|
||||
const NamespaceString& nss,
|
||||
const MultipleCollectionAccessor& collections,
|
||||
bool hasExplain,
|
||||
bool hasIndexHint,
|
||||
const Pipeline* pipeline,
|
||||
const CanonicalQuery* canonicalQuery = nullptr);
|
||||
} // namespace mongo::optimizer::fast_path
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* Copyright (C) 2023-present MongoDB, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the Server Side Public License in all respects for
|
||||
* all of the code used other than as permitted herein. If you modify file(s)
|
||||
* with this exception, you may extend this exception to your version of the
|
||||
* file(s), but you are not obligated to do so. If you do not wish to do so,
|
||||
* delete this exception statement from your version. If you delete this
|
||||
* exception statement from all source files in the program, then also delete
|
||||
* it in the license file.
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "mongo/bson/bsonmisc.h"
|
||||
#include "mongo/bson/bsonobjbuilder.h"
|
||||
#include "mongo/bson/json.h"
|
||||
#include "mongo/db/query/cqf_fast_paths_utils.h"
|
||||
#include "mongo/unittest/assert.h"
|
||||
#include "mongo/unittest/framework.h"
|
||||
|
||||
namespace mongo::optimizer::fast_path {
|
||||
namespace {
|
||||
|
||||
bool filterMatchesPattern(BSONObj& filter, BSONObj& pattern) {
|
||||
return FilterComparator::kInstance.compare(filter, pattern) == 0;
|
||||
}
|
||||
|
||||
TEST(CqfFastPathsTest, FilterComparatorMatchesEqOnTopLevelField) {
|
||||
auto filter = fromjson("{a: \"123\"}");
|
||||
auto pattern = BSON("ignored" << 0);
|
||||
ASSERT_TRUE(filterMatchesPattern(filter, pattern));
|
||||
|
||||
filter = fromjson("{a: {\"$eq\": \"123\"}}");
|
||||
pattern = BSON("ignored" << BSON("$eq" << 0));
|
||||
ASSERT_TRUE(filterMatchesPattern(filter, pattern));
|
||||
}
|
||||
|
||||
TEST(CqfFastPathsTest, FilterComparatorMatchesEqObj) {
|
||||
auto filter = fromjson("{a: {\"$eq\": {b: \"123\", c: 123}}}");
|
||||
auto pattern = BSON("ignored" << BSON("$eq" << 0));
|
||||
ASSERT_TRUE(filterMatchesPattern(filter, pattern));
|
||||
}
|
||||
|
||||
TEST(CqfFastPathsTest, FilterComparatorDoesNotMatchEqFiltersOfDifferentShapes) {
|
||||
auto filter = fromjson("{a: \"123\"}");
|
||||
auto pattern = BSON("ignored" << BSON("$eq" << 0));
|
||||
ASSERT_FALSE(filterMatchesPattern(filter, pattern));
|
||||
|
||||
filter = fromjson("{a: {\"$eq\": \"123\"}}");
|
||||
pattern = BSON("ignored" << 0);
|
||||
ASSERT_FALSE(filterMatchesPattern(filter, pattern));
|
||||
}
|
||||
|
||||
TEST(CqfFastPathsTest, FilterComparatorMatchesLtOnTopLevelField) {
|
||||
auto filter = fromjson("{a: {\"$lt\": \"123\"}}");
|
||||
auto pattern = BSON("ignored" << BSON("$lt" << 0));
|
||||
ASSERT_TRUE(filterMatchesPattern(filter, pattern));
|
||||
}
|
||||
|
||||
TEST(CqfFastPathsTest, FilterComparatorDoesNotMatchLtOnNestedField) {
|
||||
auto filter = fromjson("{\"a.b.c\": {\"$lt\": \"123\"}}");
|
||||
auto pattern = BSON("ignored" << BSON("$lt" << 0));
|
||||
ASSERT_FALSE(filterMatchesPattern(filter, pattern));
|
||||
|
||||
filter = fromjson("{a: {b: {c: {\"$lt\": \"123\"}}}}");
|
||||
ASSERT_FALSE(filterMatchesPattern(filter, pattern));
|
||||
}
|
||||
|
||||
TEST(CqfFastPathsTest, FilterComparatorDoesNotMatchDifferentOps) {
|
||||
auto filter = fromjson("{a: {\"$eq\": \"123\"}}");
|
||||
auto pattern = BSON("ignored" << BSON("$lt" << 0));
|
||||
ASSERT_FALSE(filterMatchesPattern(filter, pattern));
|
||||
|
||||
filter = fromjson("{a: {\"$gt\": \"123\"}}");
|
||||
ASSERT_FALSE(filterMatchesPattern(filter, pattern));
|
||||
}
|
||||
|
||||
TEST(CqfFastPathsTest, FilterComparatorDoesNotMatchSameOpsWithDifferentSubExprs) {
|
||||
auto filter = fromjson(R"({a: {$eq: {$concat: ["1", "2"]}}})");
|
||||
auto pattern = BSON("ignored" << BSON("$eq" << 0));
|
||||
ASSERT_FALSE(filterMatchesPattern(filter, pattern));
|
||||
|
||||
filter = fromjson(R"({a: {$eq: "$field"}})");
|
||||
ASSERT_FALSE(filterMatchesPattern(filter, pattern));
|
||||
}
|
||||
|
||||
TEST(CqfFastPathsTest, FilterComparatorDoesNotMatchSingleOpWithConjunction) {
|
||||
auto filter = fromjson(R"({ignored: {$lt: 5}, b: {$gt: 5}})");
|
||||
auto pattern = BSON("ignored" << BSON("$lt" << 0));
|
||||
ASSERT_FALSE(filterMatchesPattern(filter, pattern));
|
||||
}
|
||||
|
||||
TEST(CqfFastPathsTest, FilterComparatorMatchesEmptyWithEmpty) {
|
||||
BSONObj filter{};
|
||||
BSONObj pattern{};
|
||||
ASSERT_TRUE(filterMatchesPattern(filter, pattern));
|
||||
}
|
||||
|
||||
TEST(CqfFastPathsTest, FilterComparatorDoesNotMatchEmptyWithNonEmpty) {
|
||||
BSONObj filter{};
|
||||
auto pattern = BSON("ignored" << 0);
|
||||
ASSERT_FALSE(filterMatchesPattern(filter, pattern));
|
||||
}
|
||||
|
||||
TEST(CqfFastPathsTest, FilterComparatorDoesNotMatchEquivalentConjunctionsInDifferentOrder) {
|
||||
// This test illustrates a limitation of the comparator where the ordering of predicates
|
||||
// matters.
|
||||
auto filter = fromjson(R"({f1: {$gt: 5}, f2: {$lt: 10}})");
|
||||
auto pattern = fromjson(R"({ignore: {$lt: 10}, ignore: {$gt: 5}})");
|
||||
ASSERT_FALSE(filterMatchesPattern(filter, pattern));
|
||||
|
||||
filter = fromjson(R"({ignore: {$lt: 1, $gt: 1}})");
|
||||
pattern = fromjson(R"({f1: {$gt: 1, $lt: 1}})");
|
||||
ASSERT_FALSE(filterMatchesPattern(filter, pattern));
|
||||
}
|
||||
|
||||
TEST(CqfFastPathsTest, FilterComparatorMatchesConstantContainingSpecialFields) {
|
||||
auto filter = fromjson(R"({ignored: {$eq: {_id: 1, "a.b.c": 2}}})");
|
||||
auto pattern = BSON("ignored" << BSON("$eq" << 0));
|
||||
ASSERT_TRUE(filterMatchesPattern(filter, pattern));
|
||||
}
|
||||
|
||||
TEST(CqfFastPathsTest, FilterComparatorMatchesConjunctionWithDifferentConstants) {
|
||||
auto filter = fromjson(R"({a: {$lt: {x: 10}, $gt: {y: 50}}})");
|
||||
auto pattern = fromjson(R"({ignored: {$lt: 0, $gt: 0}})");
|
||||
ASSERT_TRUE(filterMatchesPattern(filter, pattern));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mongo::optimizer::fast_path
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* Copyright (C) 2023-present MongoDB, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the Server Side Public License in all respects for
|
||||
* all of the code used other than as permitted herein. If you modify file(s)
|
||||
* with this exception, you may extend this exception to your version of the
|
||||
* file(s), but you are not obligated to do so. If you do not wish to do so,
|
||||
* delete this exception statement from your version. If you delete this
|
||||
* exception statement from all source files in the program, then also delete
|
||||
* it in the license file.
|
||||
*/
|
||||
|
||||
#include "mongo/db/query/cqf_fast_paths_utils.h"
|
||||
|
||||
namespace mongo::optimizer::fast_path {
|
||||
namespace {
|
||||
|
||||
bool isSpecialField(const StringData& field) {
|
||||
return field == "_id" || field.find(".") != std::string::npos;
|
||||
}
|
||||
|
||||
bool isExpression(const StringData& field) {
|
||||
return !field.empty() && field[0] == '$';
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const FilterComparator FilterComparator::kInstance{};
|
||||
|
||||
int FilterComparator::compare(const BSONObj& lhs, const BSONObj& rhs) const {
|
||||
for (BSONObjIterator lhsIt{lhs}, rhsIt{rhs};;) {
|
||||
const auto& lhsChild = lhsIt.next();
|
||||
const auto& rhsChild = rhsIt.next();
|
||||
|
||||
if (lhsChild.eoo() && rhsChild.eoo()) {
|
||||
return 0;
|
||||
}
|
||||
if (lhsChild.eoo()) {
|
||||
return -1;
|
||||
}
|
||||
if (rhsChild.eoo()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const auto& lhsField = lhsChild.fieldNameStringData();
|
||||
const auto& rhsField = rhsChild.fieldNameStringData();
|
||||
|
||||
if (isSpecialField(lhsField) || isSpecialField(rhsField)) {
|
||||
// We currently don't want to match predicates on the '_id' field or a dotted field
|
||||
// (e.g. '{"a.b.c": 1}').
|
||||
return lhsChild.woCompare(rhsChild);
|
||||
}
|
||||
|
||||
if (isExpression(lhsField) || isExpression(rhsField)) {
|
||||
// Ensure we have the same expression on both sides.
|
||||
if (auto diff = lhsField.compare(rhsField)) {
|
||||
return diff;
|
||||
}
|
||||
if (containsExpression(lhsChild) || containsExpression(rhsChild)) {
|
||||
// We have nested expressions, do a strict comparison. Note that while we don't
|
||||
// currently want to match nested expressions, the comparator still needs to
|
||||
// implement a strict weak ordering between predicates.
|
||||
return lhsChild.woCompare(rhsChild);
|
||||
}
|
||||
} else if (lhsChild.isABSONObj() && rhsChild.isABSONObj()) {
|
||||
if (auto diff = compare(lhsChild.Obj(), rhsChild.Obj())) {
|
||||
return diff;
|
||||
}
|
||||
} else if (lhsChild.isABSONObj() || rhsChild.isABSONObj()) {
|
||||
// Different shape before reaching an expression.
|
||||
return lhsChild.woCompare(rhsChild);
|
||||
}
|
||||
// Non-object constants are treated as equal.
|
||||
}
|
||||
|
||||
MONGO_UNREACHABLE_TASSERT(8321500);
|
||||
}
|
||||
|
||||
bool FilterComparator::containsExpression(const BSONObj& obj) const {
|
||||
for (auto&& elem : obj) {
|
||||
if (isExpression(elem.fieldNameStringData())) {
|
||||
return true;
|
||||
}
|
||||
if (containsExpression(elem)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FilterComparator::containsExpression(const BSONElement& elem) const {
|
||||
return elem.valueStringDataSafe().startsWith("$") ||
|
||||
(elem.isABSONObj() && containsExpression(elem.Obj()));
|
||||
}
|
||||
|
||||
bool containsSpecialField(const BSONObj& obj) {
|
||||
for (auto&& elem : obj) {
|
||||
if (isSpecialField(elem.fieldNameStringData())) {
|
||||
return true;
|
||||
}
|
||||
if (elem.isABSONObj() && containsSpecialField(elem.Obj())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace mongo::optimizer::fast_path
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Copyright (C) 2023-present MongoDB, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the Server Side Public License in all respects for
|
||||
* all of the code used other than as permitted herein. If you modify file(s)
|
||||
* with this exception, you may extend this exception to your version of the
|
||||
* file(s), but you are not obligated to do so. If you do not wish to do so,
|
||||
* delete this exception statement from your version. If you delete this
|
||||
* exception statement from all source files in the program, then also delete
|
||||
* it in the license file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mongo/base/string_data.h"
|
||||
#include "mongo/bson/bsonelement.h"
|
||||
#include "mongo/bson/bsonobj.h"
|
||||
#include "mongo/bson/bsonobj_comparator_interface.h"
|
||||
#include "mongo/util/assert_util.h"
|
||||
|
||||
namespace mongo::optimizer::fast_path {
|
||||
|
||||
/**
|
||||
* Custom comparator for checking if a query is eligible for a fast path.
|
||||
*
|
||||
* Compares shape while ignoring field names until an expression is reached. After reaching an
|
||||
* expression reached, all constant values are considered equal.
|
||||
*
|
||||
* Note that this is not a generic comparator. We are currently disregarding the following
|
||||
* limitations intentionally because we only need to match queries with at most a single predicate
|
||||
* on a top-level field:
|
||||
* - The ordering of the predicates matters.
|
||||
* - Nested expressions always result in a strict comparison.
|
||||
* - Predicates on the '_id' field or a dotted path always result in a strict comparison.
|
||||
*/
|
||||
struct FilterComparator final : public BSONObj::ComparatorInterface {
|
||||
static const FilterComparator kInstance;
|
||||
|
||||
int compare(const BSONObj& left, const BSONObj& right) const final;
|
||||
|
||||
void hash_combine(size_t& seed, const BSONObj& toHash) const final {
|
||||
MONGO_UNREACHABLE_TASSERT(8321503);
|
||||
}
|
||||
|
||||
private:
|
||||
bool containsExpression(const BSONObj& obj) const;
|
||||
|
||||
bool containsExpression(const BSONElement& elem) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the given BSONObj contains the '_id' field or a dotted path.
|
||||
*/
|
||||
bool containsSpecialField(const BSONObj& obj);
|
||||
|
||||
} // namespace mongo::optimizer::fast_path
|
||||
|
|
@ -113,6 +113,7 @@
|
|||
#include "mongo/db/query/collation/collator_interface.h"
|
||||
#include "mongo/db/query/collection_query_info.h"
|
||||
#include "mongo/db/query/cqf_command_utils.h"
|
||||
#include "mongo/db/query/cqf_fast_paths.h"
|
||||
#include "mongo/db/query/cqf_get_executor.h"
|
||||
#include "mongo/db/query/find_command.h"
|
||||
#include "mongo/db/query/index_bounds.h"
|
||||
|
|
@ -1719,8 +1720,18 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutor(
|
|||
if (isEligibleForBonsai(*canonicalQuery, opCtx, mainColl)) {
|
||||
optimizer::QueryHints queryHints = getHintsFromQueryKnobs();
|
||||
const bool fastIndexNullHandling = queryHints._fastIndexNullHandling;
|
||||
auto maybeExec = getSBEExecutorViaCascadesOptimizer(
|
||||
collections, std::move(queryHints), canonicalQuery.get());
|
||||
|
||||
auto maybeExec = [&] {
|
||||
// If the query is eligible for a fast path, use the fast path plan instead of
|
||||
// invoking the optimizer.
|
||||
if (auto fastPathExec = optimizer::fast_path::tryGetSBEExecutorViaFastPath(
|
||||
collections, canonicalQuery.get())) {
|
||||
return fastPathExec;
|
||||
}
|
||||
|
||||
return getSBEExecutorViaCascadesOptimizer(
|
||||
collections, std::move(queryHints), canonicalQuery.get());
|
||||
}();
|
||||
if (maybeExec) {
|
||||
auto exec = uassertStatusOK(makeExecFromParams(
|
||||
std::move(canonicalQuery), nullptr /*pipeline*/, std::move(*maybeExec)));
|
||||
|
|
|
|||
|
|
@ -955,6 +955,14 @@ server_parameters:
|
|||
cpp_varname: "internalCascadesOptimizerSamplingCEFallBackForFilterNode"
|
||||
cpp_vartype: AtomicWord<bool>
|
||||
default: true
|
||||
|
||||
internalCascadesOptimizerDisableFastPath:
|
||||
description: "Disables fast paths that shortcut the optimizer for some trivial queries."
|
||||
set_at: [ startup, runtime ]
|
||||
cpp_varname: "internalCascadesOptimizerDisableFastPath"
|
||||
cpp_vartype: AtomicWord<bool>
|
||||
default: true
|
||||
test_only: true
|
||||
|
||||
internalQueryFrameworkControl:
|
||||
description: "Knob to control the optimizer/execution engine to use."
|
||||
|
|
|
|||
Loading…
Reference in New Issue