diff options
Diffstat (limited to 'src/mongo/db/exec/plan_cache_util.h')
-rw-r--r-- | src/mongo/db/exec/plan_cache_util.h | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/src/mongo/db/exec/plan_cache_util.h b/src/mongo/db/exec/plan_cache_util.h new file mode 100644 index 00000000000..3bb25315724 --- /dev/null +++ b/src/mongo/db/exec/plan_cache_util.h @@ -0,0 +1,168 @@ +/** + * Copyright (C) 2020-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/db/exec/plan_stats.h" +#include "mongo/db/query/canonical_query.h" +#include "mongo/db/query/collection_query_info.h" +#include "mongo/db/query/sbe_plan_ranker.h" + +namespace mongo { +/** + * Specifies how the multi-planner should interact with the plan cache. + */ +enum class PlanCachingMode { + // Always write a cache entry for the winning plan to the plan cache, overwriting any + // previously existing cache entry for the query shape. + AlwaysCache, + + // Write a cache entry for the query shape *unless* we encounter one of the following edge + // cases: + // - Two or more plans tied for the win. + // - The winning plan returned zero query results during the plan ranking trial period. + SometimesCache, + + // Do not write to the plan cache. + NeverCache, +}; + +namespace plan_cache_util { +// The logging facility enforces the rule that logging should not be done in a header file. Since +// the template classes and functions below must be defined in the header file and since they do use +// the logging facility, we have to define the helper functions below to perform the actual logging +// operation from template code. +namespace log_detail { +void logTieForBest(std::string&& query, + double winnerScore, + double runnerUpScore, + std::string winnerPlanSummary, + std::string runnerUpPlanSummary); +void logNotCachingZeroResults(std::string&& query, double score, std::string winnerPlanSummary); +void logNotCachingNoData(std::string&& solution); +} // namespace log_detail + +/** + * Caches the best candidate plan, chosen from the given 'candidates' based on the 'ranking' + * decision, if the 'query' is of a type that can be cached. Otherwise, does nothing. + * + * The 'cachingMode' specifies whether the query should be: + * * Always cached. + * * Never cached. + * * Cached, except in certain special cases. + */ +template <typename PlanStageType, typename ResultType, typename Data> +void updatePlanCache( + OperationContext* opCtx, + const Collection* collection, + PlanCachingMode cachingMode, + const CanonicalQuery& query, + std::unique_ptr<plan_ranker::PlanRankingDecision> ranking, + const std::vector<plan_ranker::BaseCandidatePlan<PlanStageType, ResultType, Data>>& + candidates) { + auto winnerIdx = ranking->candidateOrder[0]; + invariant(winnerIdx >= 0 && winnerIdx < candidates.size()); + + // Even if the query is of a cacheable shape, the caller might have indicated that we shouldn't + // write to the plan cache. + // + // TODO: We can remove this if we introduce replanning logic to the SubplanStage. + bool canCache = (cachingMode == PlanCachingMode::AlwaysCache); + if (cachingMode == PlanCachingMode::SometimesCache) { + // In "sometimes cache" mode, we cache unless we hit one of the special cases below. + canCache = true; + + if (ranking->tieForBest) { + // The winning plan tied with the runner-up and we're using "sometimes cache" mode. We + // will not write a plan cache entry. + canCache = false; + + // These arrays having two or more entries is implied by 'tieForBest'. + invariant(ranking->scores.size() > 1U); + invariant(ranking->candidateOrder.size() > 1U); + + size_t winnerIdx = ranking->candidateOrder[0]; + size_t runnerUpIdx = ranking->candidateOrder[1]; + + log_detail::logTieForBest(query.toStringShort(), + ranking->scores[0], + ranking->scores[1], + Explain::getPlanSummary(&*candidates[winnerIdx].root), + Explain::getPlanSummary(&*candidates[runnerUpIdx].root)); + } + + if (candidates[winnerIdx].results.empty()) { + // We're using the "sometimes cache" mode, and the winning plan produced no results + // during the plan ranking trial period. We will not write a plan cache entry. + canCache = false; + log_detail::logNotCachingZeroResults( + query.toStringShort(), + ranking->scores[0], + Explain::getPlanSummary(&*candidates[winnerIdx].root)); + } + } + + // Store the choice we just made in the cache, if the query is of a type that is safe to + // cache. + if (PlanCache::shouldCacheQuery(query) && canCache) { + // Create list of candidate solutions for the cache with the best solution at the front. + std::vector<QuerySolution*> solutions; + + // Generate solutions and ranking decisions sorted by score. + for (auto&& ix : ranking->candidateOrder) { + solutions.push_back(candidates[ix].solution.get()); + } + // Insert the failed plans in the back. + for (auto&& ix : ranking->failedCandidates) { + solutions.push_back(candidates[ix].solution.get()); + } + + // Check solution cache data. Do not add to cache if we have any invalid SolutionCacheData + // data. One known example is 2D queries. + bool validSolutions = true; + for (size_t ix = 0; ix < solutions.size(); ++ix) { + if (nullptr == solutions[ix]->cacheData.get()) { + log_detail::logNotCachingNoData(solutions[ix]->toString()); + validSolutions = false; + break; + } + } + + if (validSolutions) { + uassertStatusOK(CollectionQueryInfo::get(collection) + .getPlanCache() + ->set(query, + solutions, + std::move(ranking), + opCtx->getServiceContext()->getPreciseClockSource()->now())); + } + } +} +} // namespace plan_cache_util +} // namespace mongo |