summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Storch <david.storch@mongodb.com>2021-01-07 17:23:11 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-01-15 20:52:42 +0000
commit8ccc099b1821a18a120a9ce483bf64dac1899ad1 (patch)
treeebe12b1d0209d72077221b5e12398392610273a4
parent601e94d2edd7a07ca5dd3a66bf3bebaaf7597b4a (diff)
downloadmongo-8ccc099b1821a18a120a9ce483bf64dac1899ad1.tar.gz
SERVER-53435 Make queries run with DBHelpers plus SBE circumvent 'checkCanServeReadsFor()'
Such queries may be run when a node is neither primary nor secondary, e.g. as part of executing an initial sync. This affects only the SBE engine, since SBE's internal lock policy requires it to have its own calls to 'checkCanServeReadsFor()'.
-rw-r--r--src/mongo/db/commands/index_filter_commands_test.cpp2
-rw-r--r--src/mongo/db/dbhelpers.cpp1
-rw-r--r--src/mongo/db/dbhelpers.h28
-rw-r--r--src/mongo/db/exec/sbe/parser/parser.cpp12
-rw-r--r--src/mongo/db/exec/sbe/stages/ix_scan.cpp22
-rw-r--r--src/mongo/db/exec/sbe/stages/ix_scan.h7
-rw-r--r--src/mongo/db/exec/sbe/stages/lock_acquisition_callback.h45
-rw-r--r--src/mongo/db/exec/sbe/stages/scan.cpp16
-rw-r--r--src/mongo/db/exec/sbe/stages/scan.h4
-rw-r--r--src/mongo/db/query/classic_stage_builder_test.cpp2
-rw-r--r--src/mongo/db/query/get_executor.cpp2
-rw-r--r--src/mongo/db/query/plan_cache_test.cpp7
-rw-r--r--src/mongo/db/query/planner_analysis.cpp2
-rw-r--r--src/mongo/db/query/query_planner.cpp3
-rw-r--r--src/mongo/db/query/query_planner_params.h7
-rw-r--r--src/mongo/db/query/query_solution.h14
-rw-r--r--src/mongo/db/query/query_solution_test.cpp2
-rw-r--r--src/mongo/db/query/sbe_stage_builder.cpp34
-rw-r--r--src/mongo/db/query/sbe_stage_builder.h5
-rw-r--r--src/mongo/db/query/sbe_stage_builder_coll_scan.cpp24
-rw-r--r--src/mongo/db/query/sbe_stage_builder_coll_scan.h4
-rw-r--r--src/mongo/db/query/sbe_stage_builder_index_scan.cpp36
-rw-r--r--src/mongo/db/query/sbe_stage_builder_index_scan.h7
-rw-r--r--src/mongo/db/query/sbe_stage_builder_test_fixture.cpp2
-rw-r--r--src/mongo/dbtests/query_stage_multiplan.cpp10
25 files changed, 219 insertions, 79 deletions
diff --git a/src/mongo/db/commands/index_filter_commands_test.cpp b/src/mongo/db/commands/index_filter_commands_test.cpp
index a503804ec64..b1b7e7b7e29 100644
--- a/src/mongo/db/commands/index_filter_commands_test.cpp
+++ b/src/mongo/db/commands/index_filter_commands_test.cpp
@@ -139,7 +139,7 @@ void addQueryShapeToPlanCache(OperationContext* opCtx,
ASSERT_OK(statusWithCQ.getStatus());
std::unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue());
- QuerySolution qs;
+ QuerySolution qs{QueryPlannerParams::Options::DEFAULT};
qs.cacheData.reset(new SolutionCacheData());
qs.cacheData->tree.reset(new PlanCacheIndexTree());
std::vector<QuerySolution*> solns;
diff --git a/src/mongo/db/dbhelpers.cpp b/src/mongo/db/dbhelpers.cpp
index cafe10dd59c..94d5ef3e3d3 100644
--- a/src/mongo/db/dbhelpers.cpp
+++ b/src/mongo/db/dbhelpers.cpp
@@ -107,6 +107,7 @@ RecordId Helpers::findOne(OperationContext* opCtx,
unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue());
size_t options = requireIndex ? QueryPlannerParams::NO_TABLE_SCAN : QueryPlannerParams::DEFAULT;
+ options = options | QueryPlannerParams::OMIT_REPL_STATE_PERMITS_READS_CHECK;
auto exec = uassertStatusOK(getExecutor(
opCtx, &collection, std::move(cq), PlanYieldPolicy::YieldPolicy::NO_YIELD, options));
diff --git a/src/mongo/db/dbhelpers.h b/src/mongo/db/dbhelpers.h
index 9ac3164f6e0..54fe7635ce4 100644
--- a/src/mongo/db/dbhelpers.h
+++ b/src/mongo/db/dbhelpers.h
@@ -48,29 +48,31 @@ class QueryRequest;
* all helpers assume locking is handled above them
*/
struct Helpers {
-
- /* fetch a single object from collection ns that matches query.
- set your db SavedContext first.
-
- @param query - the query to perform. note this is the low level portion of query so
- "orderby : ..." won't work.
-
- @param requireIndex if true, assert if no index for the query. a way to guard against
- writing a slow query.
-
- @return true if object found
- */
+ /**
+ * Executes the given match expression ('query') and returns true if there is at least one
+ * one matching document. The first found matching document is returned via the 'result' output
+ * parameter.
+ *
+ * If 'requireIndex' is true, then this forces the query system to choose an indexed plan. An
+ * exception is thrown if no 'requireIndex' is set to true but no indexed plan exists.
+ *
+ * Performs the read successfully regardless of a replica set node's state, meaning that the
+ * node does not need to be primary or secondary.
+ */
static bool findOne(OperationContext* opCtx,
const CollectionPtr& collection,
const BSONObj& query,
BSONObj& result,
bool requireIndex = false);
+ /**
+ * Similar to the 'findOne()' overload above, except returns the RecordId of the first matching
+ * document, or a null RecordId if no such document exists.
+ */
static RecordId findOne(OperationContext* opCtx,
const CollectionPtr& collection,
const BSONObj& query,
bool requireIndex);
-
static RecordId findOne(OperationContext* opCtx,
const CollectionPtr& collection,
std::unique_ptr<QueryRequest> qr,
diff --git a/src/mongo/db/exec/sbe/parser/parser.cpp b/src/mongo/db/exec/sbe/parser/parser.cpp
index 49bd40b3cbc..0c17e710f3d 100644
--- a/src/mongo/db/exec/sbe/parser/parser.cpp
+++ b/src/mongo/db/exec/sbe/parser/parser.cpp
@@ -574,7 +574,8 @@ void Parser::walkScan(AstQuery& ast) {
boost::none,
forward,
nullptr,
- getCurrentPlanNodeId());
+ getCurrentPlanNodeId(),
+ LockAcquisitionCallback{});
}
void Parser::walkParallelScan(AstQuery& ast) {
@@ -642,7 +643,8 @@ void Parser::walkSeek(AstQuery& ast) {
lookupSlot(ast.nodes[0]->identifier),
true /* forward */,
nullptr,
- getCurrentPlanNodeId());
+ getCurrentPlanNodeId(),
+ LockAcquisitionCallback{});
}
void Parser::walkIndexScan(AstQuery& ast) {
@@ -692,7 +694,8 @@ void Parser::walkIndexScan(AstQuery& ast) {
boost::none,
boost::none,
nullptr,
- getCurrentPlanNodeId());
+ getCurrentPlanNodeId(),
+ LockAcquisitionCallback{});
}
void Parser::walkIndexSeek(AstQuery& ast) {
@@ -742,7 +745,8 @@ void Parser::walkIndexSeek(AstQuery& ast) {
lookupSlot(ast.nodes[0]->identifier),
lookupSlot(ast.nodes[1]->identifier),
nullptr,
- getCurrentPlanNodeId());
+ getCurrentPlanNodeId(),
+ LockAcquisitionCallback{});
}
void Parser::walkProject(AstQuery& ast) {
diff --git a/src/mongo/db/exec/sbe/stages/ix_scan.cpp b/src/mongo/db/exec/sbe/stages/ix_scan.cpp
index a1d4fb3fc97..2fe95dc3980 100644
--- a/src/mongo/db/exec/sbe/stages/ix_scan.cpp
+++ b/src/mongo/db/exec/sbe/stages/ix_scan.cpp
@@ -36,7 +36,6 @@
#include "mongo/db/exec/sbe/values/bson.h"
#include "mongo/db/exec/trial_run_tracker.h"
#include "mongo/db/index/index_access_method.h"
-#include "mongo/db/repl/replication_coordinator.h"
namespace mongo::sbe {
IndexScanStage::IndexScanStage(const NamespaceStringOrUUID& name,
@@ -49,7 +48,8 @@ IndexScanStage::IndexScanStage(const NamespaceStringOrUUID& name,
boost::optional<value::SlotId> seekKeySlotLow,
boost::optional<value::SlotId> seekKeySlotHigh,
PlanYieldPolicy* yieldPolicy,
- PlanNodeId nodeId)
+ PlanNodeId nodeId,
+ LockAcquisitionCallback lockAcquisitionCallback)
: PlanStage(seekKeySlotLow ? "ixseek"_sd : "ixscan"_sd, yieldPolicy, nodeId),
_name(name),
_indexName(indexName),
@@ -59,7 +59,8 @@ IndexScanStage::IndexScanStage(const NamespaceStringOrUUID& name,
_indexKeysToInclude(indexKeysToInclude),
_vars(std::move(vars)),
_seekKeySlotLow(seekKeySlotLow),
- _seekKeySlotHigh(seekKeySlotHigh) {
+ _seekKeySlotHigh(seekKeySlotHigh),
+ _lockAcquisitionCallback(std::move(lockAcquisitionCallback)) {
// The valid state is when both boundaries, or none is set, or only low key is set.
invariant((_seekKeySlotLow && _seekKeySlotHigh) || (!_seekKeySlotLow && !_seekKeySlotHigh) ||
(_seekKeySlotLow && !_seekKeySlotHigh));
@@ -78,7 +79,8 @@ std::unique_ptr<PlanStage> IndexScanStage::clone() const {
_seekKeySlotLow,
_seekKeySlotHigh,
_yieldPolicy,
- _commonStats.nodeId);
+ _commonStats.nodeId,
+ _lockAcquisitionCallback);
}
void IndexScanStage::prepare(CompileCtx& ctx) {
@@ -138,9 +140,9 @@ void IndexScanStage::doRestoreState() {
}
_coll.emplace(_opCtx, _name);
-
- uassertStatusOK(repl::ReplicationCoordinator::get(_opCtx)->checkCanServeReadsFor(
- _opCtx, _coll->getNss(), true));
+ if (_lockAcquisitionCallback) {
+ _lockAcquisitionCallback(_opCtx, *_coll);
+ }
if (_cursor) {
_cursor->restore();
@@ -175,9 +177,9 @@ void IndexScanStage::open(bool reOpen) {
invariant(!_cursor);
invariant(!_coll);
_coll.emplace(_opCtx, _name);
-
- uassertStatusOK(repl::ReplicationCoordinator::get(_opCtx)->checkCanServeReadsFor(
- _opCtx, _coll->getNss(), true));
+ if (_lockAcquisitionCallback) {
+ _lockAcquisitionCallback(_opCtx, *_coll);
+ }
} else {
invariant(_cursor);
invariant(_coll);
diff --git a/src/mongo/db/exec/sbe/stages/ix_scan.h b/src/mongo/db/exec/sbe/stages/ix_scan.h
index 6023957a3d2..c3ec85ebb05 100644
--- a/src/mongo/db/exec/sbe/stages/ix_scan.h
+++ b/src/mongo/db/exec/sbe/stages/ix_scan.h
@@ -31,12 +31,12 @@
#include "mongo/bson/ordering.h"
#include "mongo/db/db_raii.h"
+#include "mongo/db/exec/sbe/stages/lock_acquisition_callback.h"
#include "mongo/db/exec/sbe/stages/stages.h"
#include "mongo/db/storage/record_store.h"
#include "mongo/db/storage/sorted_data_interface.h"
namespace mongo::sbe {
-
/**
* A stage that iterates the entries of a collection index, starting from a bound specified by the
* value in 'seekKeySlotLow' and ending (via IS_EOF) with the 'seekKeySlotHigh' bound. (An
@@ -69,7 +69,8 @@ public:
boost::optional<value::SlotId> seekKeySlotLow,
boost::optional<value::SlotId> seekKeySlotHigh,
PlanYieldPolicy* yieldPolicy,
- PlanNodeId nodeId);
+ PlanNodeId nodeId,
+ LockAcquisitionCallback lockAcquisitionCallback);
std::unique_ptr<PlanStage> clone() const final;
@@ -102,6 +103,8 @@ private:
const boost::optional<value::SlotId> _seekKeySlotLow;
const boost::optional<value::SlotId> _seekKeySlotHigh;
+ LockAcquisitionCallback _lockAcquisitionCallback;
+
std::unique_ptr<value::ViewOfValueAccessor> _recordAccessor;
std::unique_ptr<value::ViewOfValueAccessor> _recordIdAccessor;
diff --git a/src/mongo/db/exec/sbe/stages/lock_acquisition_callback.h b/src/mongo/db/exec/sbe/stages/lock_acquisition_callback.h
new file mode 100644
index 00000000000..6d5a22ae460
--- /dev/null
+++ b/src/mongo/db/exec/sbe/stages/lock_acquisition_callback.h
@@ -0,0 +1,45 @@
+/**
+ * Copyright (C) 2021-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 <functional>
+
+#include "mongo/db/db_raii.h"
+#include "mongo/db/operation_context.h"
+
+namespace mongo::sbe {
+/**
+ * A callback which gets called whenever a stage which accesses the storage engine (e.g. "scan",
+ * "seek", or "ixscan") obtains or re-obtains its AutoGet*.
+ */
+using LockAcquisitionCallback =
+ std::function<void(OperationContext*, const AutoGetCollectionForReadMaybeLockFree&)>;
+
+} // namespace mongo::sbe
diff --git a/src/mongo/db/exec/sbe/stages/scan.cpp b/src/mongo/db/exec/sbe/stages/scan.cpp
index 2b7b62240d9..69d35aa62bd 100644
--- a/src/mongo/db/exec/sbe/stages/scan.cpp
+++ b/src/mongo/db/exec/sbe/stages/scan.cpp
@@ -33,7 +33,6 @@
#include "mongo/db/exec/sbe/expressions/expression.h"
#include "mongo/db/exec/trial_run_tracker.h"
-#include "mongo/db/repl/replication_coordinator.h"
#include "mongo/util/str.h"
namespace mongo {
@@ -47,6 +46,7 @@ ScanStage::ScanStage(const NamespaceStringOrUUID& name,
bool forward,
PlanYieldPolicy* yieldPolicy,
PlanNodeId nodeId,
+ LockAcquisitionCallback lockAcquisitionCallback,
ScanOpenCallback openCallback)
: PlanStage(seekKeySlot ? "seek"_sd : "scan"_sd, yieldPolicy, nodeId),
_name(name),
@@ -56,6 +56,7 @@ ScanStage::ScanStage(const NamespaceStringOrUUID& name,
_vars(std::move(vars)),
_seekKeySlot(seekKeySlot),
_forward(forward),
+ _lockAcquisitionCallback(std::move(lockAcquisitionCallback)),
_openCallback(openCallback) {
invariant(_fields.size() == _vars.size());
invariant(!_seekKeySlot || _forward);
@@ -71,6 +72,7 @@ std::unique_ptr<PlanStage> ScanStage::clone() const {
_forward,
_yieldPolicy,
_commonStats.nodeId,
+ _lockAcquisitionCallback,
_openCallback);
}
@@ -130,9 +132,9 @@ void ScanStage::doRestoreState() {
}
_coll.emplace(_opCtx, _name);
-
- uassertStatusOK(repl::ReplicationCoordinator::get(_opCtx)->checkCanServeReadsFor(
- _opCtx, _coll->getNss(), true));
+ if (_lockAcquisitionCallback) {
+ _lockAcquisitionCallback(_opCtx, *_coll);
+ }
if (_cursor) {
const bool couldRestore = _cursor->restore();
@@ -170,9 +172,9 @@ void ScanStage::open(bool reOpen) {
invariant(!_cursor);
invariant(!_coll);
_coll.emplace(_opCtx, _name);
-
- uassertStatusOK(repl::ReplicationCoordinator::get(_opCtx)->checkCanServeReadsFor(
- _opCtx, _coll->getNss(), true));
+ if (_lockAcquisitionCallback) {
+ _lockAcquisitionCallback(_opCtx, *_coll);
+ }
} else {
invariant(_cursor);
invariant(_coll);
diff --git a/src/mongo/db/exec/sbe/stages/scan.h b/src/mongo/db/exec/sbe/stages/scan.h
index 71a3a8438e3..f4653f8f0c3 100644
--- a/src/mongo/db/exec/sbe/stages/scan.h
+++ b/src/mongo/db/exec/sbe/stages/scan.h
@@ -29,7 +29,7 @@
#pragma once
-#include "mongo/db/db_raii.h"
+#include "mongo/db/exec/sbe/stages/lock_acquisition_callback.h"
#include "mongo/db/exec/sbe/stages/stages.h"
#include "mongo/db/exec/sbe/values/bson.h"
#include "mongo/db/storage/record_store.h"
@@ -49,6 +49,7 @@ public:
bool forward,
PlanYieldPolicy* yieldPolicy,
PlanNodeId nodeId,
+ LockAcquisitionCallback lockAcquisitionCallback,
ScanOpenCallback openCallback = {});
std::unique_ptr<PlanStage> clone() const final;
@@ -84,6 +85,7 @@ private:
// run is complete, this pointer is reset to nullptr.
TrialRunTracker* _tracker{nullptr};
+ LockAcquisitionCallback _lockAcquisitionCallback;
ScanOpenCallback _openCallback;
std::unique_ptr<value::ViewOfValueAccessor> _recordAccessor;
diff --git a/src/mongo/db/query/classic_stage_builder_test.cpp b/src/mongo/db/query/classic_stage_builder_test.cpp
index 5d912b81d3d..64f00ebf4c2 100644
--- a/src/mongo/db/query/classic_stage_builder_test.cpp
+++ b/src/mongo/db/query/classic_stage_builder_test.cpp
@@ -59,7 +59,7 @@ public:
* Converts a 'QuerySolutionNode' to a 'QuerySolution'.
*/
std::unique_ptr<QuerySolution> makeQuerySolution(std::unique_ptr<QuerySolutionNode> root) {
- auto querySoln = std::make_unique<QuerySolution>();
+ auto querySoln = std::make_unique<QuerySolution>(QueryPlannerParams::Options::DEFAULT);
querySoln->setRoot(std::move(root));
return querySoln;
}
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index 884bbc9c311..444fc33f74f 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -550,7 +550,7 @@ public:
"namespace"_attr = _cq->ns(),
"canonicalQuery"_attr = redact(_cq->toStringShort()));
- auto solution = std::make_unique<QuerySolution>();
+ auto solution = std::make_unique<QuerySolution>(_plannerOptions);
solution->setRoot(std::make_unique<EofNode>());
auto root = buildExecutableTree(*solution);
diff --git a/src/mongo/db/query/plan_cache_test.cpp b/src/mongo/db/query/plan_cache_test.cpp
index c10a1d2a2cb..e465979baaa 100644
--- a/src/mongo/db/query/plan_cache_test.cpp
+++ b/src/mongo/db/query/plan_cache_test.cpp
@@ -286,7 +286,7 @@ std::pair<CoreIndexInfo, std::unique_ptr<WildcardProjection>> makeWildcardUpdate
*/
struct GenerateQuerySolution {
QuerySolution* operator()() const {
- unique_ptr<QuerySolution> qs(new QuerySolution());
+ unique_ptr<QuerySolution> qs(new QuerySolution(QueryPlannerParams::Options::DEFAULT));
qs->cacheData.reset(new SolutionCacheData());
qs->cacheData->solnType = SolutionCacheData::COLLSCAN_SOLN;
qs->cacheData->tree.reset(new PlanCacheIndexTree());
@@ -349,7 +349,8 @@ void assertShouldNotCacheQuery(const char* queryStr) {
}
std::unique_ptr<QuerySolution> getQuerySolutionForCaching() {
- std::unique_ptr<QuerySolution> qs = std::make_unique<QuerySolution>();
+ std::unique_ptr<QuerySolution> qs =
+ std::make_unique<QuerySolution>(QueryPlannerParams::Options::DEFAULT);
qs->cacheData = std::make_unique<SolutionCacheData>();
qs->cacheData->tree = std::make_unique<PlanCacheIndexTree>();
return qs;
@@ -1136,7 +1137,7 @@ protected:
// Create a CachedSolution the long way..
// QuerySolution -> PlanCacheEntry -> CachedSolution
- QuerySolution qs;
+ QuerySolution qs{QueryPlannerParams::Options::DEFAULT};
qs.cacheData = soln.cacheData->clone();
std::vector<QuerySolution*> solutions;
solutions.push_back(&qs);
diff --git a/src/mongo/db/query/planner_analysis.cpp b/src/mongo/db/query/planner_analysis.cpp
index 6c19619ba1c..f61f7fc3264 100644
--- a/src/mongo/db/query/planner_analysis.cpp
+++ b/src/mongo/db/query/planner_analysis.cpp
@@ -912,7 +912,7 @@ std::unique_ptr<QuerySolution> QueryPlannerAnalysis::analyzeDataAccess(
const CanonicalQuery& query,
const QueryPlannerParams& params,
std::unique_ptr<QuerySolutionNode> solnRoot) {
- auto soln = std::make_unique<QuerySolution>();
+ auto soln = std::make_unique<QuerySolution>(params.options);
soln->indexFilterApplied = params.indexFiltersApplied;
solnRoot->computeProperties();
diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp
index 2d30ca830c2..bfa853b37cc 100644
--- a/src/mongo/db/query/query_planner.cpp
+++ b/src/mongo/db/query/query_planner.cpp
@@ -179,6 +179,9 @@ string optionString(size_t options) {
case QueryPlannerParams::ENUMERATE_OR_CHILDREN_LOCKSTEP:
ss << "ENUMERATE_OR_CHILDREN_LOCKSTEP ";
break;
+ case QueryPlannerParams::OMIT_REPL_STATE_PERMITS_READS_CHECK:
+ ss << "OMIT_REPL_STATE_PERMITS_READS_CHECK";
+ break;
case QueryPlannerParams::DEFAULT:
MONGO_UNREACHABLE;
break;
diff --git a/src/mongo/db/query/query_planner_params.h b/src/mongo/db/query/query_planner_params.h
index 626062fe2ec..d40eb397ff8 100644
--- a/src/mongo/db/query/query_planner_params.h
+++ b/src/mongo/db/query/query_planner_params.h
@@ -118,6 +118,13 @@ struct QueryPlannerParams {
// is thought to be helpful in general, but particularly in cases where all children of the
// $or use the same fields and have the same indexes available, as in this example.
ENUMERATE_OR_CHILDREN_LOCKSTEP = 1 << 12,
+
+ // Instructs the planner to produce a plan which will *not* check at runtime that the node's
+ // replica set member state allows reads. Typically, replica set members will only serve
+ // reads to clients if thet are in parimary or secondary state. Client reads are disallowed
+ // in other states, e.g. during initial sync. Internal operations, on the other hand, can
+ // use this flag to exempt themselves from this repl set note state requirement.
+ OMIT_REPL_STATE_PERMITS_READS_CHECK = 1 << 13,
};
// See Options enum above.
diff --git a/src/mongo/db/query/query_solution.h b/src/mongo/db/query/query_solution.h
index 4d3c272f7fa..6445b7d8f2f 100644
--- a/src/mongo/db/query/query_solution.h
+++ b/src/mongo/db/query/query_solution.h
@@ -326,7 +326,7 @@ struct QuerySolutionNodeWithSortSet : public QuerySolutionNode {
*/
class QuerySolution {
public:
- QuerySolution() = default;
+ explicit QuerySolution(size_t plannerOptions) : plannerOptions(plannerOptions) {}
/**
* Return true if this solution tree contains a node of the given 'type'.
@@ -362,6 +362,18 @@ public:
*/
void setRoot(std::unique_ptr<QuerySolutionNode> root);
+ /**
+ * Returns true if the execution plan which is constructed from this QuerySolution should check
+ * that the node is eligible to serve reads prior to actually performing any reads.
+ */
+ bool shouldCheckCanServeReads() const {
+ return !(plannerOptions & QueryPlannerParams::OMIT_REPL_STATE_PERMITS_READS_CHECK);
+ }
+
+ // A bit vector of flags which clients to the QueryPlanner pass to control which plans are
+ // generated and their properties.
+ const size_t plannerOptions;
+
// There are two known scenarios in which a query solution might potentially block:
//
// Sort stage:
diff --git a/src/mongo/db/query/query_solution_test.cpp b/src/mongo/db/query/query_solution_test.cpp
index 8763b208fcd..71470e22c50 100644
--- a/src/mongo/db/query/query_solution_test.cpp
+++ b/src/mongo/db/query/query_solution_test.cpp
@@ -1061,7 +1061,7 @@ TEST(QuerySolutionTest, NodeIdsAssignedInPostOrderFashionStartingFromOne) {
ASSERT_EQ(orNode->children[0]->nodeId(), 0u);
ASSERT_EQ(orNode->children[1]->nodeId(), 0u);
- auto querySolution = std::make_unique<QuerySolution>();
+ auto querySolution = std::make_unique<QuerySolution>(QueryPlannerParams::Options::DEFAULT);
querySolution->setRoot(std::move(orNode));
auto root = querySolution->root();
diff --git a/src/mongo/db/query/sbe_stage_builder.cpp b/src/mongo/db/query/sbe_stage_builder.cpp
index 32ac09989f4..787e5746639 100644
--- a/src/mongo/db/query/sbe_stage_builder.cpp
+++ b/src/mongo/db/query/sbe_stage_builder.cpp
@@ -114,6 +114,18 @@ const QuerySolutionNode* getNodeByType(const QuerySolutionNode* root, StageType
return nullptr;
}
+
+sbe::LockAcquisitionCallback makeLockAcquisitionCallback(bool checkNodeCanServeReads) {
+ if (!checkNodeCanServeReads) {
+ return {};
+ }
+
+ return [](OperationContext* opCtx, const AutoGetCollectionForReadMaybeLockFree& coll) {
+ uassertStatusOK(repl::ReplicationCoordinator::get(opCtx)->checkCanServeReadsFor(
+ opCtx, coll.getNss(), true));
+ };
+}
+
} // namespace
SlotBasedStageBuilder::SlotBasedStageBuilder(OperationContext* opCtx,
@@ -125,7 +137,8 @@ SlotBasedStageBuilder::SlotBasedStageBuilder(OperationContext* opCtx,
: StageBuilder(opCtx, collection, cq, solution),
_yieldPolicy(yieldPolicy),
_data(makeRuntimeEnvironment(_opCtx, &_slotIdGenerator)),
- _shardFiltererFactory(shardFiltererFactory) {
+ _shardFiltererFactory(shardFiltererFactory),
+ _lockAcquisitionCallback(makeLockAcquisitionCallback(solution.shouldCheckCanServeReads())) {
// SERVER-52803: In the future if we need to gather more information from the QuerySolutionNode
// tree, rather than doing one-off scans for each piece of information, we should add a formal
// analysis pass here.
@@ -184,7 +197,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder
&_frameIdGenerator,
_yieldPolicy,
_data.env,
- reqs.getIsTailableCollScanResumeBranch());
+ reqs.getIsTailableCollScanResumeBranch(),
+ _lockAcquisitionCallback);
if (reqs.has(kReturnKey)) {
// Assign the 'returnKeySlot' to be the empty object.
@@ -245,8 +259,14 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder
// Index scans cannot produce an oplogTsSlot, so assert that the caller doesn't need it.
invariant(!reqs.has(kOplogTs));
- return generateIndexScan(
- _opCtx, _collection, ixn, reqs, &_slotIdGenerator, &_spoolIdGenerator, _yieldPolicy);
+ return generateIndexScan(_opCtx,
+ _collection,
+ ixn,
+ reqs,
+ &_slotIdGenerator,
+ &_spoolIdGenerator,
+ _yieldPolicy,
+ _lockAcquisitionCallback);
}
std::tuple<sbe::value::SlotId, sbe::value::SlotId, std::unique_ptr<sbe::PlanStage>>
@@ -267,7 +287,8 @@ SlotBasedStageBuilder::makeLoopJoinForFetch(std::unique_ptr<sbe::PlanStage> inpu
seekKeySlot,
true,
nullptr,
- planNodeId);
+ planNodeId,
+ _lockAcquisitionCallback);
// Get the recordIdSlot from the outer side (e.g., IXSCAN) and feed it to the inner side,
// limiting the result set to 1 row.
@@ -826,7 +847,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> SlotBasedStageBuilder
boost::none, // recordSlot
&_slotIdGenerator,
_yieldPolicy,
- root->nodeId());
+ root->nodeId(),
+ _lockAcquisitionCallback);
indexScanList.push_back(std::move(ixscan));
ixscanOutputSlots.push_back(sbe::makeSV(recordIdSlot));
}
diff --git a/src/mongo/db/query/sbe_stage_builder.h b/src/mongo/db/query/sbe_stage_builder.h
index 61b728f5d9b..2dd50579b76 100644
--- a/src/mongo/db/query/sbe_stage_builder.h
+++ b/src/mongo/db/query/sbe_stage_builder.h
@@ -30,6 +30,7 @@
#pragma once
#include "mongo/db/exec/sbe/expressions/expression.h"
+#include "mongo/db/exec/sbe/stages/lock_acquisition_callback.h"
#include "mongo/db/exec/sbe/values/slot.h"
#include "mongo/db/exec/sbe/values/value.h"
#include "mongo/db/exec/trial_period_utils.h"
@@ -337,5 +338,9 @@ private:
// A factory to construct shard filters.
ShardFiltererFactoryInterface* _shardFiltererFactory;
+
+ // A callback that should be installed on "scan" and "ixscan" nodes. It will get invoked when
+ // these data access stages acquire their AutoGet*.
+ const sbe::LockAcquisitionCallback _lockAcquisitionCallback;
};
} // namespace mongo::stage_builder
diff --git a/src/mongo/db/query/sbe_stage_builder_coll_scan.cpp b/src/mongo/db/query/sbe_stage_builder_coll_scan.cpp
index ef882219f37..832597adfc5 100644
--- a/src/mongo/db/query/sbe_stage_builder_coll_scan.cpp
+++ b/src/mongo/db/query/sbe_stage_builder_coll_scan.cpp
@@ -122,7 +122,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateOptimizedOplo
sbe::value::FrameIdGenerator* frameIdGenerator,
PlanYieldPolicy* yieldPolicy,
sbe::RuntimeEnvironment* env,
- bool isTailableResumeBranch) {
+ bool isTailableResumeBranch,
+ sbe::LockAcquisitionCallback lockAcquisitionCallback) {
invariant(collection->ns().isOplog());
// The minTs and maxTs optimizations are not compatible with resumeAfterRecordId and can only
// be done for a forward scan.
@@ -174,6 +175,7 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateOptimizedOplo
true /* forward */,
yieldPolicy,
csn->nodeId(),
+ std::move(lockAcquisitionCallback),
makeOpenCallbackIfNeeded(collection, csn));
// Start the scan from the seekRecordId if we can use the oplogStartHack.
@@ -276,7 +278,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateOptimizedOplo
seekRecordIdSlot,
true /* forward */,
yieldPolicy,
- csn->nodeId()),
+ csn->nodeId(),
+ std::move(lockAcquisitionCallback)),
sbe::makeSV(),
sbe::makeSV(*seekRecordIdSlot),
nullptr,
@@ -311,7 +314,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateGenericCollSc
sbe::value::FrameIdGenerator* frameIdGenerator,
PlanYieldPolicy* yieldPolicy,
sbe::RuntimeEnvironment* env,
- bool isTailableResumeBranch) {
+ bool isTailableResumeBranch,
+ sbe::LockAcquisitionCallback lockAcquisitionCallback) {
const auto forward = csn->direction == CollectionScanParams::FORWARD;
invariant(!csn->shouldTrackLatestOplogTimestamp || collection->ns().isOplog());
@@ -345,6 +349,7 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateGenericCollSc
forward,
yieldPolicy,
csn->nodeId(),
+ lockAcquisitionCallback,
makeOpenCallbackIfNeeded(collection, csn));
// Check if the scan should be started after the provided resume RecordId and construct a nested
@@ -376,8 +381,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateGenericCollSc
seekSlot,
forward,
yieldPolicy,
- csn->nodeId()),
-
+ csn->nodeId(),
+ lockAcquisitionCallback),
sbe::makeSV(seekSlot),
sbe::makeSV(seekSlot),
nullptr,
@@ -461,7 +466,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateCollScan(
sbe::value::FrameIdGenerator* frameIdGenerator,
PlanYieldPolicy* yieldPolicy,
sbe::RuntimeEnvironment* env,
- bool isTailableResumeBranch) {
+ bool isTailableResumeBranch,
+ sbe::LockAcquisitionCallback lockAcquisitionCallback) {
if (csn->minTs || csn->maxTs) {
return generateOptimizedOplogScan(opCtx,
collection,
@@ -470,7 +476,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateCollScan(
frameIdGenerator,
yieldPolicy,
env,
- isTailableResumeBranch);
+ isTailableResumeBranch,
+ std::move(lockAcquisitionCallback));
} else {
return generateGenericCollScan(opCtx,
collection,
@@ -479,7 +486,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateCollScan(
frameIdGenerator,
yieldPolicy,
env,
- isTailableResumeBranch);
+ isTailableResumeBranch,
+ std::move(lockAcquisitionCallback));
}
}
} // namespace mongo::stage_builder
diff --git a/src/mongo/db/query/sbe_stage_builder_coll_scan.h b/src/mongo/db/query/sbe_stage_builder_coll_scan.h
index 1a1683d986f..4d3a73f18fe 100644
--- a/src/mongo/db/query/sbe_stage_builder_coll_scan.h
+++ b/src/mongo/db/query/sbe_stage_builder_coll_scan.h
@@ -30,6 +30,7 @@
#pragma once
#include "mongo/db/exec/sbe/expressions/expression.h"
+#include "mongo/db/exec/sbe/stages/lock_acquisition_callback.h"
#include "mongo/db/exec/sbe/stages/stages.h"
#include "mongo/db/exec/sbe/values/value.h"
#include "mongo/db/query/query_solution.h"
@@ -58,6 +59,7 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateCollScan(
sbe::value::FrameIdGenerator* frameIdGenerator,
PlanYieldPolicy* yieldPolicy,
sbe::RuntimeEnvironment* env,
- bool isTailableResumeBranch);
+ bool isTailableResumeBranch,
+ sbe::LockAcquisitionCallback lockAcquisitionCallback);
} // namespace mongo::stage_builder
diff --git a/src/mongo/db/query/sbe_stage_builder_index_scan.cpp b/src/mongo/db/query/sbe_stage_builder_index_scan.cpp
index 3512a24296f..3a460cf03f8 100644
--- a/src/mongo/db/query/sbe_stage_builder_index_scan.cpp
+++ b/src/mongo/db/query/sbe_stage_builder_index_scan.cpp
@@ -277,7 +277,8 @@ generateOptimizedMultiIntervalIndexScan(
sbe::value::SlotVector indexKeySlots,
sbe::value::SlotIdGenerator* slotIdGenerator,
PlanYieldPolicy* yieldPolicy,
- PlanNodeId planNodeId) {
+ PlanNodeId planNodeId,
+ sbe::LockAcquisitionCallback lockAcquisitionCallback) {
using namespace std::literals;
auto recordIdSlot = slotIdGenerator->generate();
@@ -344,7 +345,8 @@ generateOptimizedMultiIntervalIndexScan(
lowKeySlot,
highKeySlot,
yieldPolicy,
- planNodeId);
+ planNodeId,
+ std::move(lockAcquisitionCallback));
// Finally, get the keys from the outer side and feed them to the inner side (ixscan).
return {recordIdSlot,
@@ -397,7 +399,8 @@ makeRecursiveBranchForGenericIndexScan(const CollectionPtr& collection,
sbe::value::SlotVector savedIndexKeySlots,
sbe::value::SlotIdGenerator* slotIdGenerator,
PlanYieldPolicy* yieldPolicy,
- PlanNodeId planNodeId) {
+ PlanNodeId planNodeId,
+ sbe::LockAcquisitionCallback lockAcquisitionCallback) {
auto resultSlot = slotIdGenerator->generate();
auto recordIdSlot = slotIdGenerator->generate();
@@ -425,7 +428,8 @@ makeRecursiveBranchForGenericIndexScan(const CollectionPtr& collection,
lowKeySlot,
boost::none,
yieldPolicy,
- planNodeId);
+ planNodeId,
+ std::move(lockAcquisitionCallback));
// Get the low key from the outer side and feed it to the inner side (ixscan).
auto nlj = sbe::makeS<sbe::LoopJoinStage>(std::move(project),
@@ -522,7 +526,8 @@ generateGenericMultiIntervalIndexScan(const CollectionPtr& collection,
sbe::value::SlotVector indexKeySlots,
sbe::value::SlotIdGenerator* slotIdGenerator,
sbe::value::SpoolIdGenerator* spoolIdGenerator,
- PlanYieldPolicy* yieldPolicy) {
+ PlanYieldPolicy* yieldPolicy,
+ sbe::LockAcquisitionCallback lockAcquisitionCallback) {
using namespace std::literals;
@@ -572,7 +577,8 @@ generateGenericMultiIntervalIndexScan(const CollectionPtr& collection,
savedIndexKeySlots,
slotIdGenerator,
yieldPolicy,
- ixn->nodeId());
+ ixn->nodeId(),
+ std::move(lockAcquisitionCallback));
// Construct a union stage from the two branches.
auto makeSlotVector = [](sbe::value::SlotId headSlot, const sbe::value::SlotVector& varSlots) {
@@ -624,7 +630,8 @@ std::pair<sbe::value::SlotId, std::unique_ptr<sbe::PlanStage>> generateSingleInt
boost::optional<sbe::value::SlotId> recordSlot,
sbe::value::SlotIdGenerator* slotIdGenerator,
PlanYieldPolicy* yieldPolicy,
- PlanNodeId planNodeId) {
+ PlanNodeId planNodeId,
+ sbe::LockAcquisitionCallback lockAcquisitionCallback) {
auto recordIdSlot = slotIdGenerator->generate();
auto lowKeySlot = slotIdGenerator->generate();
auto highKeySlot = slotIdGenerator->generate();
@@ -656,7 +663,8 @@ std::pair<sbe::value::SlotId, std::unique_ptr<sbe::PlanStage>> generateSingleInt
lowKeySlot,
highKeySlot,
yieldPolicy,
- planNodeId);
+ planNodeId,
+ std::move(lockAcquisitionCallback));
// Finally, get the keys from the outer side and feed them to the inner side.
return {recordIdSlot,
@@ -675,7 +683,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScan(
PlanStageReqs reqs,
sbe::value::SlotIdGenerator* slotIdGenerator,
sbe::value::SpoolIdGenerator* spoolIdGenerator,
- PlanYieldPolicy* yieldPolicy) {
+ PlanYieldPolicy* yieldPolicy,
+ sbe::LockAcquisitionCallback lockAcquisitionCallback) {
uassert(4822864, "Index scans with a filter are not supported in SBE", !ixn->filter);
auto descriptor =
@@ -737,7 +746,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScan(
boost::none, // recordSlot
slotIdGenerator,
yieldPolicy,
- ixn->nodeId());
+ ixn->nodeId(),
+ std::move(lockAcquisitionCallback));
outputs.set(PlanStageSlots::kRecordId, recordIdSlot);
} else if (intervals.size() > 1) {
@@ -753,7 +763,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScan(
indexKeySlots,
slotIdGenerator,
yieldPolicy,
- ixn->nodeId());
+ ixn->nodeId(),
+ std::move(lockAcquisitionCallback));
outputs.set(PlanStageSlots::kRecordId, recordIdSlot);
} else {
@@ -768,7 +779,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScan(
indexKeySlots,
slotIdGenerator,
spoolIdGenerator,
- yieldPolicy);
+ yieldPolicy,
+ std::move(lockAcquisitionCallback));
outputs.set(PlanStageSlots::kRecordId, recordIdSlot);
}
diff --git a/src/mongo/db/query/sbe_stage_builder_index_scan.h b/src/mongo/db/query/sbe_stage_builder_index_scan.h
index ab54dfa02cf..bdfa8661291 100644
--- a/src/mongo/db/query/sbe_stage_builder_index_scan.h
+++ b/src/mongo/db/query/sbe_stage_builder_index_scan.h
@@ -29,6 +29,7 @@
#pragma once
+#include "mongo/db/exec/sbe/stages/lock_acquisition_callback.h"
#include "mongo/db/exec/sbe/stages/stages.h"
#include "mongo/db/exec/sbe/values/value.h"
#include "mongo/db/query/query_solution.h"
@@ -55,7 +56,8 @@ std::pair<std::unique_ptr<sbe::PlanStage>, PlanStageSlots> generateIndexScan(
PlanStageReqs reqs,
sbe::value::SlotIdGenerator* slotIdGenerator,
sbe::value::SpoolIdGenerator* spoolIdGenerator,
- PlanYieldPolicy* yieldPolicy);
+ PlanYieldPolicy* yieldPolicy,
+ sbe::LockAcquisitionCallback lockAcquisitionCallback);
/**
* Constructs the most simple version of an index scan from the single interval index bounds. The
@@ -86,6 +88,7 @@ std::pair<sbe::value::SlotId, std::unique_ptr<sbe::PlanStage>> generateSingleInt
boost::optional<sbe::value::SlotId> recordSlot,
sbe::value::SlotIdGenerator* slotIdGenerator,
PlanYieldPolicy* yieldPolicy,
- PlanNodeId nodeId);
+ PlanNodeId nodeId,
+ sbe::LockAcquisitionCallback lockAcquisitionCallback);
} // namespace mongo::stage_builder
diff --git a/src/mongo/db/query/sbe_stage_builder_test_fixture.cpp b/src/mongo/db/query/sbe_stage_builder_test_fixture.cpp
index efb6c08639f..d832a3e30b6 100644
--- a/src/mongo/db/query/sbe_stage_builder_test_fixture.cpp
+++ b/src/mongo/db/query/sbe_stage_builder_test_fixture.cpp
@@ -39,7 +39,7 @@
namespace mongo {
std::unique_ptr<QuerySolution> SbeStageBuilderTestFixture::makeQuerySolution(
std::unique_ptr<QuerySolutionNode> root) {
- auto querySoln = std::make_unique<QuerySolution>();
+ auto querySoln = std::make_unique<QuerySolution>(QueryPlannerParams::Options::DEFAULT);
querySoln->setRoot(std::move(root));
return querySoln;
}
diff --git a/src/mongo/dbtests/query_stage_multiplan.cpp b/src/mongo/dbtests/query_stage_multiplan.cpp
index 3275bcaed17..7a2ee9aef72 100644
--- a/src/mongo/dbtests/query_stage_multiplan.cpp
+++ b/src/mongo/dbtests/query_stage_multiplan.cpp
@@ -74,7 +74,7 @@ using std::vector;
static const NamespaceString nss("unittests.QueryStageMultiPlan");
std::unique_ptr<QuerySolution> createQuerySolution() {
- auto soln = std::make_unique<QuerySolution>();
+ auto soln = std::make_unique<QuerySolution>(QueryPlannerParams::Options::DEFAULT);
soln->cacheData = std::make_unique<SolutionCacheData>();
soln->cacheData->solnType = SolutionCacheData::COLLSCAN_SOLN;
soln->cacheData->tree = std::make_unique<PlanCacheIndexTree>();
@@ -492,8 +492,12 @@ TEST_F(QueryStageMultiPlanTest, MPSExplainAllPlans) {
std::make_unique<MultiPlanStage>(_expCtx.get(), ctx.getCollection(), cq.get());
// Put each plan into the MultiPlanStage. Takes ownership of 'firstPlan' and 'secondPlan'.
- mps->addPlan(std::make_unique<QuerySolution>(), std::move(firstPlan), ws.get());
- mps->addPlan(std::make_unique<QuerySolution>(), std::move(secondPlan), ws.get());
+ mps->addPlan(std::make_unique<QuerySolution>(QueryPlannerParams::Options::DEFAULT),
+ std::move(firstPlan),
+ ws.get());
+ mps->addPlan(std::make_unique<QuerySolution>(QueryPlannerParams::Options::DEFAULT),
+ std::move(secondPlan),
+ ws.get());
// Making a PlanExecutor chooses the best plan.
auto exec =