diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/pipeline/pipeline_d.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/query/get_runner.cpp | 129 | ||||
-rw-r--r-- | src/mongo/db/query/get_runner.h | 15 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner.cpp | 20 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_params.h | 3 | ||||
-rw-r--r-- | src/mongo/dbtests/plan_ranking.cpp | 76 |
6 files changed, 168 insertions, 76 deletions
diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp index d60a3259a15..ae1b42ce887 100644 --- a/src/mongo/db/pipeline/pipeline_d.cpp +++ b/src/mongo/db/pipeline/pipeline_d.cpp @@ -147,7 +147,6 @@ namespace { // cursor. Either way, we can then apply other optimizations there // are tickets for, such as SERVER-4507. const size_t runnerOptions = QueryPlannerParams::DEFAULT - | QueryPlannerParams::INCLUDE_COLLSCAN | QueryPlannerParams::INCLUDE_SHARD_FILTER | QueryPlannerParams::NO_BLOCKING_SORT ; diff --git a/src/mongo/db/query/get_runner.cpp b/src/mongo/db/query/get_runner.cpp index 61b7934604d..439d5672dd4 100644 --- a/src/mongo/db/query/get_runner.cpp +++ b/src/mongo/db/query/get_runner.cpp @@ -139,6 +139,70 @@ namespace mongo { bool turnIxscanIntoCount(QuerySolution* soln); } // namespace + + void fillOutPlannerParams(Collection* collection, + CanonicalQuery* canonicalQuery, + QueryPlannerParams* plannerParams) { + // If it's not NULL, we may have indices. Access the catalog and fill out IndexEntry(s) + IndexCatalog::IndexIterator ii = collection->getIndexCatalog()->getIndexIterator(false); + while (ii.more()) { + const IndexDescriptor* desc = ii.next(); + plannerParams->indices.push_back(IndexEntry(desc->keyPattern(), + desc->getAccessMethodName(), + desc->isMultikey(), + desc->isSparse(), + desc->indexName(), + desc->infoObj())); + } + + // If query supports index filters, filter params.indices by indices in query settings. + QuerySettings* querySettings = collection->infoCache()->getQuerySettings(); + AllowedIndices* allowedIndicesRaw; + + // Filter index catalog if index filters are specified for query. + // Also, signal to planner that application hint should be ignored. + if (querySettings->getAllowedIndices(*canonicalQuery, &allowedIndicesRaw)) { + boost::scoped_ptr<AllowedIndices> allowedIndices(allowedIndicesRaw); + filterAllowedIndexEntries(*allowedIndices, &plannerParams->indices); + plannerParams->indexFiltersApplied = true; + } + + // We will not output collection scans unless there are no indexed solutions. NO_TABLE_SCAN + // overrides this behavior by not outputting a collscan even if there are no indexed + // solutions. + if (storageGlobalParams.noTableScan) { + const string& ns = canonicalQuery->ns(); + // There are certain cases where we ignore this restriction: + bool ignore = canonicalQuery->getQueryObj().isEmpty() + || (string::npos != ns.find(".system.")) + || (0 == ns.find("local.")); + if (!ignore) { + plannerParams->options |= QueryPlannerParams::NO_TABLE_SCAN; + } + } + + // If the caller wants a shard filter, make sure we're actually sharded. + if (plannerParams->options & QueryPlannerParams::INCLUDE_SHARD_FILTER) { + CollectionMetadataPtr collMetadata = + shardingState.getCollectionMetadata(canonicalQuery->ns()); + + if (collMetadata) { + plannerParams->shardKey = collMetadata->getKeyPattern(); + } + else { + // If there's no metadata don't bother w/the shard filter since we won't know what + // the key pattern is anyway... + plannerParams->options &= ~QueryPlannerParams::INCLUDE_SHARD_FILTER; + } + } + + if (enableIndexIntersection) { + plannerParams->options |= QueryPlannerParams::INDEX_INTERSECTION; + } + + plannerParams->options |= QueryPlannerParams::KEEP_MUTATIONS; + } + /** * For a given query, get a runner. The runner could be a SingleSolutionRunner, a * CachedQueryRunner, or a MultiPlanRunner, depending on the cache/query solver/etc. @@ -164,32 +228,6 @@ namespace mongo { return Status::OK(); } - // If it's not NULL, we may have indices. Access the catalog and fill out IndexEntry(s) - QueryPlannerParams plannerParams; - - IndexCatalog::IndexIterator ii = collection->getIndexCatalog()->getIndexIterator(false); - while (ii.more()) { - const IndexDescriptor* desc = ii.next(); - plannerParams.indices.push_back(IndexEntry(desc->keyPattern(), - desc->getAccessMethodName(), - desc->isMultikey(), - desc->isSparse(), - desc->indexName(), - desc->infoObj())); - } - - // If query supports index filters, filter params.indices by indices in query settings. - QuerySettings* querySettings = collection->infoCache()->getQuerySettings(); - AllowedIndices* allowedIndicesRaw; - - // Filter index catalog if index filters are specified for query. - // Also, signal to planner that application hint should be ignored. - if (querySettings->getAllowedIndices(*canonicalQuery, &allowedIndicesRaw)) { - boost::scoped_ptr<AllowedIndices> allowedIndices(allowedIndicesRaw); - filterAllowedIndexEntries(*allowedIndices, &plannerParams.indices); - plannerParams.indexFiltersApplied = true; - } - // Tailable: If the query requests tailable the collection must be capped. if (canonicalQuery->getParsed().hasOption(QueryOption_CursorTailable)) { if (!collection->isCapped()) { @@ -209,37 +247,10 @@ namespace mongo { } } - // Process the planning options. + // Fill out the planning params. We use these for both cached solutions and non-cached. + QueryPlannerParams plannerParams; plannerParams.options = plannerOptions; - if (storageGlobalParams.noTableScan) { - const string& ns = canonicalQuery->ns(); - // There are certain cases where we ignore this restriction: - bool ignore = canonicalQuery->getQueryObj().isEmpty() - || (string::npos != ns.find(".system.")) - || (0 == ns.find("local.")); - if (!ignore) { - plannerParams.options |= QueryPlannerParams::NO_TABLE_SCAN; - } - } - - if (!(plannerParams.options & QueryPlannerParams::NO_TABLE_SCAN)) { - plannerParams.options |= QueryPlannerParams::INCLUDE_COLLSCAN; - } - - // If the caller wants a shard filter, make sure we're actually sharded. - if (plannerParams.options & QueryPlannerParams::INCLUDE_SHARD_FILTER) { - CollectionMetadataPtr collMetadata = - shardingState.getCollectionMetadata(canonicalQuery->ns()); - - if (collMetadata) { - plannerParams.shardKey = collMetadata->getKeyPattern(); - } - else { - // If there's no metadata don't bother w/the shard filter since we won't know what - // the key pattern is anyway... - plannerParams.options &= ~QueryPlannerParams::INCLUDE_SHARD_FILTER; - } - } + fillOutPlannerParams(collection, rawCanonicalQuery, &plannerParams); // Try to look up a cached solution for the query. // @@ -310,12 +321,6 @@ namespace mongo { } } - if (enableIndexIntersection) { - plannerParams.options |= QueryPlannerParams::INDEX_INTERSECTION; - } - - plannerParams.options |= QueryPlannerParams::KEEP_MUTATIONS; - vector<QuerySolution*> solutions; Status status = QueryPlanner::plan(*canonicalQuery, plannerParams, &solutions); if (!status.isOK()) { diff --git a/src/mongo/db/query/get_runner.h b/src/mongo/db/query/get_runner.h index c5011d7639c..187ba543ad9 100644 --- a/src/mongo/db/query/get_runner.h +++ b/src/mongo/db/query/get_runner.h @@ -27,6 +27,7 @@ */ #include "mongo/db/query/canonical_query.h" +#include "mongo/db/query/query_planner_params.h" #include "mongo/db/query/query_settings.h" #include "mongo/db/query/runner.h" @@ -43,6 +44,13 @@ namespace mongo { void filterAllowedIndexEntries(const AllowedIndices& allowedIndices, std::vector<IndexEntry>* indexEntries); + /** + * Fill out the provided 'plannerParams' for the 'canonicalQuery' operating on the collection + * 'collection'. Exposed for testing. + */ + void fillOutPlannerParams(Collection* collection, + CanonicalQuery* canonicalQuery, + QueryPlannerParams* plannerParams); /** * Get a runner for a query. Takes ownership of rawCanonicalQuery. @@ -80,8 +88,11 @@ namespace mongo { * the returned runner. On failure, returns other status values, and '*outRunner' and * '*outCanonicalQuery' have unspecified values. */ - Status getRunner(Collection* collection, const std::string& ns, const BSONObj& unparsedQuery, - Runner** outRunner, CanonicalQuery** outCanonicalQuery, + Status getRunner(Collection* collection, + const std::string& ns, + const BSONObj& unparsedQuery, + Runner** outRunner, + CanonicalQuery** outCanonicalQuery, size_t plannerOptions = 0); /* diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp index d63e0c4d5ee..5b94d9bed9d 100644 --- a/src/mongo/db/query/query_planner.cpp +++ b/src/mongo/db/query/query_planner.cpp @@ -818,13 +818,19 @@ namespace mongo { } } - // TODO: Do we always want to offer a collscan solution? - // XXX: currently disabling the always-use-a-collscan in order to find more planner bugs. - if ( !QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) - && !QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT) - && hintIndex.isEmpty() - && ((params.options & QueryPlannerParams::INCLUDE_COLLSCAN) || (0 == out->size() && canTableScan))) - { + // geoNear and text queries *require* an index. + // Also, if a hint is specified it indicates that we MUST use it. + bool possibleToCollscan = !QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) + && !QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT) + && hintIndex.isEmpty(); + + // The caller can explicitly ask for a collscan. + bool collscanRequested = (params.options & QueryPlannerParams::INCLUDE_COLLSCAN); + + // No indexed plans? We must provide a collscan if possible or else we can't run the query. + bool collscanNeeded = (0 == out->size() && canTableScan); + + if (possibleToCollscan && (collscanRequested || collscanNeeded)) { QuerySolution* collscan = buildCollscanSoln(query, false, params); if (NULL != collscan) { SolutionCacheData* scd = new SolutionCacheData(); diff --git a/src/mongo/db/query/query_planner_params.h b/src/mongo/db/query/query_planner_params.h index a4cb4d34120..49557d8f34c 100644 --- a/src/mongo/db/query/query_planner_params.h +++ b/src/mongo/db/query/query_planner_params.h @@ -52,7 +52,8 @@ namespace mongo { // See http://docs.mongodb.org/manual/reference/parameters/ NO_TABLE_SCAN = 1, - // Set this if you want a collscan outputted even if there's an ixscan. + // Set this if you *always* want a collscan outputted, even if there's an ixscan. This + // makes ranking less accurate, especially in the presence of blocking stages. INCLUDE_COLLSCAN = 1 << 1, // Set this if you're running on a sharded cluster. We'll add a "drop all docs that diff --git a/src/mongo/dbtests/plan_ranking.cpp b/src/mongo/dbtests/plan_ranking.cpp index 211314ec5ce..2b44fcea157 100644 --- a/src/mongo/dbtests/plan_ranking.cpp +++ b/src/mongo/dbtests/plan_ranking.cpp @@ -37,6 +37,7 @@ #include "mongo/db/instance.h" #include "mongo/db/json.h" #include "mongo/db/query/multi_plan_runner.h" +#include "mongo/db/query/get_runner.h" #include "mongo/db/query/qlog.h" #include "mongo/db/query/query_planner.h" #include "mongo/db/query/query_planner_test_lib.h" @@ -82,7 +83,9 @@ namespace PlanRankingTests { Collection* collection = ctx.ctx().db()->getCollection(ns); QueryPlannerParams plannerParams; - plannerParams.options |= QueryPlannerParams::INDEX_INTERSECTION; + fillOutPlannerParams(collection, cq, &plannerParams); + // Turn this off otherwise it pops up in some plans. + plannerParams.options &= ~QueryPlannerParams::KEEP_MUTATIONS; // Fill out the available indices. IndexCatalog::IndexIterator ii = collection->getIndexCatalog()->getIndexIterator(false); @@ -101,8 +104,7 @@ namespace PlanRankingTests { Status status = QueryPlanner::plan(*cq, plannerParams, &solutions); ASSERT(status.isOK()); - // MPR requires >1 soln. - ASSERT_GREATER_THAN(solutions.size(), 1U); + ASSERT_GREATER_THAN_OR_EQUALS(solutions.size(), 1U); // Fill out the MPR. _mpr.reset(new MultiPlanRunner(collection, cq)); @@ -329,6 +331,72 @@ namespace PlanRankingTests { } }; + /** + * We have an index on _id and a query over _id with a sort. Ensure that we don't pick a + * collscan as the best plan even though the _id-scanning solution doesn't produce any results. + */ + class PlanRankingNoCollscan : public PlanRankingTestBase { + public: + void run() { + static const int N = 10000; + + for (int i = 0; i < N; ++i) { + insert(BSON("_id" << i)); + } + + addIndex(BSON("_id" << 1)); + + // Run a query with a sort. The blocking sort won't produce any data during the + // evaluation period. + CanonicalQuery* cq; + BSONObj queryObj = BSON("_id" << BSON("$gte" << 20 << "$lte" << 200)); + BSONObj sortObj = BSON("c" << 1); + BSONObj projObj = BSONObj(); + ASSERT(CanonicalQuery::canonicalize(ns, + queryObj, + sortObj, + projObj, + &cq).isOK()); + + // Takes ownership of cq. + QuerySolution* soln = pickBestPlan(cq); + + // The best must not be a collscan. + ASSERT(QueryPlannerTestLib::solutionMatches( + "{sort: {pattern: {c: 1}, limit: 0, node: {" + "fetch: {filter: null, node: " + "{ixscan: {filter: null, pattern: {_id: 1}}}}}}}}", + soln->root.get())); + } + }; + + /** + * No indices are available, output a collscan. + */ + class PlanRankingCollscan : public PlanRankingTestBase { + public: + void run() { + static const int N = 10000; + + // Insert data for which we have no index. + for (int i = 0; i < N; ++i) { + insert(BSON("foo" << i)); + } + + // Look for A Space Odyssey. + CanonicalQuery* cq; + verify(CanonicalQuery::canonicalize(ns, BSON("foo" << 2001), &cq).isOK()); + ASSERT(NULL != cq); + + // Takes ownership of cq. + QuerySolution* soln = pickBestPlan(cq); + + // The best must be a collscan. + ASSERT(QueryPlannerTestLib::solutionMatches( + "{cscan: {dir: 1, filter: {foo: 2001}}}", + soln->root.get())); + } + }; class All : public Suite { public: @@ -340,6 +408,8 @@ namespace PlanRankingTests { add<PlanRankingAvoidIntersectIfNoResults>(); add<PlanRankingPreferCoveredEvenIfNoResults>(); add<PlanRankingPreferImmediateEOF>(); + add<PlanRankingNoCollscan>(); + add<PlanRankingCollscan>(); } } planRankingAll; |