diff options
Diffstat (limited to 'src/mongo/db/commands/run_aggregate.cpp')
| -rw-r--r-- | src/mongo/db/commands/run_aggregate.cpp | 217 |
1 files changed, 163 insertions, 54 deletions
diff --git a/src/mongo/db/commands/run_aggregate.cpp b/src/mongo/db/commands/run_aggregate.cpp index b47645610f7..66d68f4413c 100644 --- a/src/mongo/db/commands/run_aggregate.cpp +++ b/src/mongo/db/commands/run_aggregate.cpp @@ -74,6 +74,9 @@ #include "mongo/db/query/query_feature_flags_gen.h" #include "mongo/db/query/query_knobs_gen.h" #include "mongo/db/query/query_planner_common.h" +#include "mongo/db/query/query_stats/agg_key.h" +#include "mongo/db/query/query_stats/key.h" +#include "mongo/db/query/query_stats/query_stats.h" #include "mongo/db/read_concern.h" #include "mongo/db/repl/oplog.h" #include "mongo/db/repl/read_concern_args.h" @@ -627,7 +630,6 @@ std::vector<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> createLegacyEx getSearchHelpers(expCtx->opCtx->getServiceContext()) ->injectSearchShardFiltererIfNeeded(pipeline.get()); - // Complete creation of the initial $cursor stage, if needed. PipelineD::attachInnerQueryExecutorToPipeline(collections, attachExecutorCallback.first, @@ -640,7 +642,6 @@ std::vector<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> createLegacyEx // There are separate ExpressionContexts for each exchange pipeline, so make sure to // pass the pipeline's ExpressionContext to the plan executor factory. auto pipelineExpCtx = pipelineIt->getContext(); - execs.emplace_back( plan_executor_factory::make(std::move(pipelineExpCtx), std::move(pipelineIt), @@ -664,15 +665,11 @@ Status runAggregateOnView(OperationContext* opCtx, const MultipleCollectionAccessor& collections, boost::optional<std::unique_ptr<CollatorInterface>> collatorToUse, const ViewDefinition* view, - const boost::intrusive_ptr<ExpressionContext>& expCtx, std::shared_ptr<const CollectionCatalog> catalog, const PrivilegeVector& privileges, - CurOp* curOp, rpc::ReplyBuilderInterface* result, const std::function<void(void)>& resetContextFn) { auto nss = request.getNamespace(); - checkCollectionUUIDMismatch( - opCtx, nss, collections.getMainCollection(), request.getCollectionUUID()); uassert(ErrorCodes::CommandNotSupportedOnView, "mapReduce on a view is not supported", @@ -719,7 +716,7 @@ Status runAggregateOnView(OperationContext* opCtx, auto status{Status::OK()}; try { - status = runAggregate(opCtx, origNss, newRequest, newCmd, privileges, result); + status = runAggregate(opCtx, newRequest, newCmd, privileges, result, resolvedView, request); } catch (const ExceptionForCat<ErrorCategory::StaleShardVersionError>& ex) { // Since we expect the view to be UNSHARDED, if we reached to this point there are // two possibilities: @@ -739,31 +736,145 @@ Status runAggregateOnView(OperationContext* opCtx, // Set the namespace of the curop back to the view namespace so ctx records // stats on this view namespace on destruction. stdx::lock_guard<Client> lk(*opCtx->getClient()); - curOp->setNS_inlock(nss.ns()); + CurOp::get(opCtx)->setNS_inlock(nss.ns()); } return status; } +/** + * Determines the collection type of the query by precedence of various configurations. The order + * of these checks is critical since there may be overlap (e.g., a view over a virtual collection + * is classified as a view). + */ +query_shape::CollectionType determineCollectionType( + const boost::optional<AutoGetCollectionForReadCommandMaybeLockFree>& ctx, + boost::optional<const ResolvedView&> resolvedView, + bool hasChangeStream, + bool isCollectionless) { + if (resolvedView.has_value()) { + if (resolvedView->timeseries()) { + return query_shape::CollectionType::kTimeseries; + } + return query_shape::CollectionType::kView; + } + if (isCollectionless) { + return query_shape::CollectionType::kVirtual; + } + if (hasChangeStream) { + return query_shape::CollectionType::kChangeStream; + } + return ctx ? ctx->getCollectionType() : query_shape::CollectionType::kUnknown; +} + +std::unique_ptr<Pipeline, PipelineDeleter> parsePipelineAndRegisterQueryStats( + OperationContext* opCtx, + const NamespaceString& origNss, + const AggregateCommandRequest& request, + const boost::optional<AutoGetCollectionForReadCommandMaybeLockFree>& ctx, + std::unique_ptr<CollatorInterface> collator, + boost::optional<UUID> uuid, + ExpressionContext::CollationMatchesDefault collationMatchesDefault, + const MultipleCollectionAccessor& collections, + stdx::unordered_set<NamespaceString> pipelineInvolvedNamespaces, + const LiteParsedPipeline& liteParsedPipeline, + bool isCollectionless, + boost::optional<const ResolvedView&> resolvedView, + boost::optional<const AggregateCommandRequest&> origRequest) { + // If we're operating over a view, we first parse just the original user-given request + // for the sake of registering query stats. Then, we'll parse the view pipeline and stitch + // the two pipelines together below. + auto expCtx = + makeExpressionContext(opCtx, request, std::move(collator), uuid, collationMatchesDefault); + // If any involved collection contains extended-range data, set a flag which individual + // DocumentSource parsers can check. + collections.forEach([&](const CollectionPtr& coll) { + if (coll->getRequiresTimeseriesExtendedRangeSupport()) + expCtx->setRequiresTimeseriesExtendedRangeSupport(true); + }); + + const bool hasChangeStream = liteParsedPipeline.hasChangeStream(); + // A pipeline with $changeStreamSplitLargeEvent requires the use of resume token format + // v2, since the 'fragmentNum' field only exists in this version and later. + if (hasChangeStream && liteParsedPipeline.endsWithChangeStreamSplitLargeEvent()) { + expCtx->changeStreamTokenVersion = 2; + } + + auto requestForQueryStats = origRequest.has_value() ? *origRequest : request; + expCtx->startExpressionCounters(); + auto pipeline = Pipeline::parse(requestForQueryStats.getPipeline(), expCtx); + expCtx->stopExpressionCounters(); + + // Register query stats with the pre-optimized pipeline. Exclude queries against collections + // with encrypted fields. We still collect query stats on collection-less aggregations. + bool hasEncryptedFields = ctx && ctx->getCollection() && + ctx->getCollection()->getCollectionOptions().encryptedFieldConfig; + if (!hasEncryptedFields) { + // If this is a query over a resolved view, we want to register query stats with the + // original user-given request and pipeline, rather than the new request generated when + // resolving the view. + auto collectionType = + determineCollectionType(ctx, resolvedView, hasChangeStream, isCollectionless); + + query_stats::registerRequest(opCtx, + origNss, + [&]() { + return std::make_unique<query_stats::AggKey>( + requestForQueryStats, + *pipeline, + expCtx, + pipelineInvolvedNamespaces, + origNss, + collectionType); + }, + hasChangeStream); + } + + if (resolvedView.has_value()) { + expCtx->startExpressionCounters(); + + if (resolvedView->timeseries()) { + // For timeseries, there may have been rewrites done on the raw BSON pipeline + // during view resolution. We must parse the request's full resolved pipeline + // which will account for those rewrites. + // TODO SERVER-82101 Re-organize timeseries rewrites so timeseries can follow the + // same pattern here as other views + pipeline = Pipeline::parse(request.getPipeline(), expCtx); + } else { + // Parse the view pipeline, then stitch the user pipeline and view pipeline together + // to build the total aggregation pipeline. + auto userPipeline = std::move(pipeline); + pipeline = Pipeline::parse(resolvedView->getPipeline(), expCtx); + pipeline->appendPipeline(std::move(userPipeline)); + } + + expCtx->stopExpressionCounters(); + } + + return pipeline; +} } // namespace Status runAggregate(OperationContext* opCtx, - const NamespaceString& nss, AggregateCommandRequest& request, const BSONObj& cmdObj, const PrivilegeVector& privileges, - rpc::ReplyBuilderInterface* result) { - return runAggregate(opCtx, nss, request, {request}, cmdObj, privileges, result); + rpc::ReplyBuilderInterface* result, + boost::optional<const ResolvedView&> resolvedView, + boost::optional<const AggregateCommandRequest&> origRequest) { + return runAggregate( + opCtx, request, {request}, cmdObj, privileges, result, resolvedView, origRequest); } Status runAggregate(OperationContext* opCtx, - const NamespaceString& origNss, AggregateCommandRequest& request, const LiteParsedPipeline& liteParsedPipeline, const BSONObj& cmdObj, const PrivilegeVector& privileges, - rpc::ReplyBuilderInterface* result) { - + rpc::ReplyBuilderInterface* result, + boost::optional<const ResolvedView&> resolvedView, + boost::optional<const AggregateCommandRequest&> origRequest) { + auto origNss = origRequest.has_value() ? origRequest->getNamespace() : request.getNamespace(); // Perform some validations on the LiteParsedPipeline and request before continuing with the // aggregation command. performValidationChecks(opCtx, request, liteParsedPipeline); @@ -848,7 +959,6 @@ Status runAggregate(OperationContext* opCtx, // Raise an error if 'origNss' is a view. We do not need to check this if we are opening // a stream on an entire db or across the cluster. - const TenantDatabaseName origTenantDbName(boost::none, origNss.db()); if (!origNss.isCollectionlessAggregateNS()) { auto view = catalog->lookupView(opCtx, origNss); uassert(ErrorCodes::CommandNotSupportedOnView, @@ -882,7 +992,7 @@ Status runAggregate(OperationContext* opCtx, nss, Top::LockType::NotLocked, AutoStatsTracker::LogMode::kUpdateTopAndCurOp, - 0); + catalog->getDatabaseProfileLevel(nss.db())); auto [collator, match] = PipelineD::resolveCollator( opCtx, request.getCollation().get_value_or(BSONObj()), nullptr); collatorToUse.emplace(std::move(collator)); @@ -904,6 +1014,13 @@ Status runAggregate(OperationContext* opCtx, } } + // If collectionUUID was provided, verify the collection exists and has the expected UUID. + checkCollectionUUIDMismatch(opCtx, + nss, + collections.getMainCollection(), + request.getCollectionUUID(), + false /* checkFeatureFlag */); + // If this is a view, resolve it by finding the underlying collection and stitching view // pipelines and this request's pipeline together. We then release our locks before // recursively calling runAggregate(), which will re-acquire locks on the underlying @@ -921,41 +1038,29 @@ Status runAggregate(OperationContext* opCtx, collections, std::move(collatorToUse), ctx->getView(), - expCtx, catalog, privileges, - curOp, result, resetContext); } - // If collectionUUID was provided, verify the collection exists and has the expected UUID. - checkCollectionUUIDMismatch(opCtx, - nss, - collections.getMainCollection(), - request.getCollectionUUID(), - false /* checkFeatureFlag */); - invariant(collatorToUse); - expCtx = makeExpressionContext( - opCtx, request, std::move(*collatorToUse), uuid, collatorToUseMatchesDefault); - - // If any involved collection contains extended-range data, set a flag which individual - // DocumentSource parsers can check. - collections.forEach([&](const CollectionPtr& coll) { - if (coll->getRequiresTimeseriesExtendedRangeSupport()) - expCtx->setRequiresTimeseriesExtendedRangeSupport(true); - }); - - // A pipeline with $changeStreamSplitLargeEvent requires the use of resume token format v2, - // since the 'fragmentNum' field only exists in this version and later. - if (hasChangeStream && liteParsedPipeline.endsWithChangeStreamSplitLargeEvent()) { - expCtx->changeStreamTokenVersion = 2; - } - - expCtx->startExpressionCounters(); - auto pipeline = Pipeline::parse(request.getPipeline(), expCtx); - expCtx->stopExpressionCounters(); + auto pipeline = parsePipelineAndRegisterQueryStats(opCtx, + origNss, + request, + ctx, + std::move(*collatorToUse), + uuid, + collatorToUseMatchesDefault, + collections, + pipelineInvolvedNamespaces, + liteParsedPipeline, + nss.isCollectionlessAggregateNS(), + resolvedView, + origRequest); + expCtx = pipeline->getContext(); + + CurOp::get(opCtx)->beginQueryPlanningTimer(); if (!request.getAllowDiskUse().value_or(true)) { allowDiskUseFalseCounter.increment(); @@ -1034,6 +1139,7 @@ Status runAggregate(OperationContext* opCtx, } }); for (auto&& exec : execs) { + // TODO SERVER-79373: Do not create a cursor if results can fit in a single batch. ClientCursorParams cursorParams( std::move(exec), origNss, @@ -1077,6 +1183,7 @@ Status runAggregate(OperationContext* opCtx, cmdObj, &bodyBuilder); } + collectQueryStatsMongod(opCtx, std::move(curOp->debug().queryStatsInfo.key)); } else { // Cursor must be specified, if explain is not. const bool keepCursor = handleCursorCommand( @@ -1089,13 +1196,15 @@ Status runAggregate(OperationContext* opCtx, PlanSummaryStats stats; planExplainer.getSummaryStats(&stats); curOp->debug().setPlanSummaryMetrics(stats); - curOp->debug().nreturned = stats.nReturned; + curOp->setEndOfOpMetrics(stats.nReturned); + + collectQueryStatsMongod(opCtx, pins[0]); - // For an optimized away pipeline, signal the cache that a query operation has completed. - // For normal pipelines this is done in DocumentSourceCursor. + // For an optimized away pipeline, signal the cache that a query operation has + // completed. For normal pipelines this is done in DocumentSourceCursor. if (ctx) { - // Due to yielding, the collection pointers saved in MultipleCollectionAccessor might - // have become invalid. We will need to refresh them here. + // Due to yielding, the collection pointers saved in MultipleCollectionAccessor + // might have become invalid. We will need to refresh them here. collections = MultipleCollectionAccessor(opCtx, &ctx->getCollection(), ctx->getNss(), @@ -1118,10 +1227,11 @@ Status runAggregate(OperationContext* opCtx, } } - // The aggregation pipeline may change the namespace of the curop and we need to set it back to - // the original namespace to correctly report command stats. One example when the namespace can - // be changed is when the pipeline contains an $out stage, which executes an internal command to - // create a temp collection, changing the curop namespace to the name of this temp collection. + // The aggregation pipeline may change the namespace of the curop and we need to set it back + // to the original namespace to correctly report command stats. One example when the + // namespace can be changed is when the pipeline contains an $out stage, which executes an + // internal command to create a temp collection, changing the curop namespace to the name of + // this temp collection. { stdx::lock_guard<Client> lk(*opCtx->getClient()); curOp->setNS_inlock(origNss.ns()); @@ -1129,5 +1239,4 @@ Status runAggregate(OperationContext* opCtx, return Status::OK(); } - } // namespace mongo |
