Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

SERVER-5301 Record optimal query plans in the query plan cache.

  • Loading branch information...
commit 72e76dd5d8dc2a85ac1973e2bd00c5c2496c833c 1 parent 870e5cf
@astaple astaple authored
View
31 jstests/queryoptimizer10.js
@@ -0,0 +1,31 @@
+// Optimal indexes are saved in the query plan cache, but not geo indexes. SERVER-5301
+
+t = db.jstests_queryoptimizer10;
+t.drop();
+
+function clearQueryPlanCache() {
+ t.ensureIndex( { zzz:1 } );
+ t.dropIndex( { zzz:1 } );
+}
+
+function assertIndexRecordedForQuery( expectedCursor, query, explainQuery ) {
+ clearQueryPlanCache();
+ explainQuery = explainQuery || query;
+ t.find( query ).itcount();
+ if ( !expectedCursor ) {
+ assert( !t.find( explainQuery ).explain( true ).oldPlan );
+ }
+ else {
+ assert.eq( expectedCursor, t.find( explainQuery ).explain( true ).oldPlan.cursor );
+ }
+}
+
+t.ensureIndex( { a:1 } );
+assertIndexRecordedForQuery( 'BtreeCursor a_1', { $or:[ { a:1 } ] }, { a:1 } );
+assertIndexRecordedForQuery( 'BtreeCursor a_1', { a:1 } );
+
+t.drop();
+
+t.ensureIndex( { a:'2d' } );
+assertIndexRecordedForQuery( null, { a:{ $near:[ 50, 50 ] } } );
+assertIndexRecordedForQuery( null, { a:[ 50, 50 ] } );
View
5 src/mongo/db/queryoptimizer.cpp
@@ -667,6 +667,9 @@ namespace mongo {
}
if ( optimalPlan.get() ) {
addPlan( optimalPlan, planSet );
+ // Record an optimal plan in the query cache immediately, with a small nscanned value
+ // that will be ignored.
+ optimalPlan->registerSelf( 0 );
return;
}
for( PlanSet::const_iterator i = plans.begin(); i != plans.end(); ++i ) {
@@ -687,7 +690,7 @@ namespace mongo {
}
bool QueryPlanSet::hasPossiblyExcludedPlans() const {
- return _usingCachedPlan;
+ return _usingCachedPlan && ( nPlans() == 1 ) && !firstPlan()->optimal();
}
QueryPlanSet::QueryPlanPtr QueryPlanSet::getBestGuess() const {
View
66 src/mongo/dbtests/queryoptimizercursortests.cpp
@@ -260,8 +260,8 @@ namespace QueryOptimizerCursorTests {
ClientCursor::find( ns(), nsCursors );
return nsCursors.size();
}
- BSONObj cachedIndexForQuery( const BSONObj &query ) {
- QueryPattern queryPattern = FieldRangeSet( ns(), query, true ).pattern();
+ BSONObj cachedIndexForQuery( const BSONObj &query, const BSONObj &order = BSONObj() ) {
+ QueryPattern queryPattern = FieldRangeSet( ns(), query, true ).pattern( order );
NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() );
return nsdt.cachedQueryPlanForPattern( queryPattern ).indexKey();
}
@@ -951,6 +951,55 @@ namespace QueryOptimizerCursorTests {
ASSERT_EQUALS( 2U, _cli.count( ns(), BSONObj() ) );
}
};
+
+ /**
+ * When an index becomes multikey and ceases to be optimal for a query, attempt other plans
+ * quickly.
+ */
+ class AddOtherPlansWhenOptimalBecomesNonOptimal : public Base {
+ public:
+ void run() {
+ _cli.ensureIndex( ns(), BSON( "a" << 1 << "b" << 1 ) );
+
+ {
+ // Create a btree cursor on an optimal a:1,b:1 plan.
+ Client::ReadContext ctx( ns() );
+ shared_ptr<Cursor> cursor = getCursor();
+ ASSERT_EQUALS( "BtreeCursor a_1_b_1", cursor->toString() );
+
+ // The optimal a:1,b:1 plan is recorded.
+ ASSERT_EQUALS( BSON( "a" << 1 << "b" << 1 ),
+ cachedIndexForQuery( BSON( "a" << 1 ), BSON( "b" << 1 ) ) );
+ }
+
+ // Make the a:1,b:1 index multikey.
+ _cli.insert( ns(), BSON( "a" << 1 << "b" << BSON_ARRAY( 1 << 2 ) ) );
+
+ // Create a QueryOptimizerCursor, without an optimal plan.
+ Client::ReadContext ctx( ns() );
+ shared_ptr<Cursor> cursor = getCursor();
+ ASSERT_EQUALS( "QueryOptimizerCursor", cursor->toString() );
+ ASSERT_EQUALS( BSON( "a" << 1 << "b" << 1 ), cursor->indexKeyPattern() );
+ ASSERT( cursor->advance() );
+ // An alternative plan is quickly attempted.
+ ASSERT_EQUALS( BSONObj(), cursor->indexKeyPattern() );
+ }
+ private:
+ static shared_ptr<Cursor> getCursor() {
+ // The a:1,b:1 index will be optimal for this query and sort if single key, but if
+ // the index is multi key only one of the upper or lower constraints will be applied and
+ // the index will not be optimal.
+ BSONObj query = BSON( "a" << GTE << 1 << LTE << 1 );
+ BSONObj order = BSON( "b" << 1 );
+ shared_ptr<ParsedQuery> parsedQuery
+ ( new ParsedQuery( ns(), 0, 0, 0,
+ BSON( "$query" << query << "$orderby" << order ),
+ BSONObj() ) );
+ return NamespaceDetailsTransient::getCursor( ns(), query, order,
+ QueryPlanSelectionPolicy::any(), 0,
+ parsedQuery );
+ }
+ };
/** Check $or clause range elimination. */
class OrRangeElimination : public Base {
@@ -3315,7 +3364,7 @@ namespace QueryOptimizerCursorTests {
ASSERT_EQUALS( string( "0" ), details.elemMatchKey() );
}
};
-
+
namespace GetCursor {
class Base : public QueryOptimizerCursorTests::Base {
@@ -3510,22 +3559,18 @@ namespace QueryOptimizerCursorTests {
};
/**
- * If an optimal plan is a candidate, return a cursor for it rather than a QueryOptimizerCursor. Avoid
- * caching optimal plans since simple cursors will not save a plan anyway (so in the most common case optimal
- * plans won't be cached) and because this simplifies the implementation for selecting a simple cursor.
+ * If an optimal plan is cached, return a Cursor for it rather than a QueryOptimizerCursor.
*/
class BestSavedOptimal : public QueryOptimizerCursorTests::Base {
public:
void run() {
_cli.insert( ns(), BSON( "_id" << 1 ) );
_cli.ensureIndex( ns(), BSON( "_id" << 1 << "q" << 1 ) );
- // {_id:1} index not recorded for these queries since it is an optimal index.
ASSERT( _cli.query( ns(), QUERY( "_id" << GT << 0 ) )->more() );
- ASSERT( _cli.query( ns(), QUERY( "$or" << BSON_ARRAY( BSON( "_id" << GT << 0 ) ) ) )->more() );
Lock::GlobalWrite lk;
Client::Context ctx( ns() );
- // Check that no plan was recorded for this query.
- ASSERT_EQUALS( BSONObj(), cachedIndexForQuery( BSON( "_id" << GT << 0 ) ) );
+ // Check the plan that was recorded for this query.
+ ASSERT_EQUALS( BSON( "_id" << 1 ), cachedIndexForQuery( BSON( "_id" << GT << 0 ) ) );
shared_ptr<Cursor> c = NamespaceDetailsTransient::getCursor( ns(), BSON( "_id" << GT << 0 ) );
// No need for query optimizer cursor since the plan is optimal.
ASSERT_EQUALS( "BtreeCursor _id_", c->toString() );
@@ -4419,6 +4464,7 @@ namespace QueryOptimizerCursorTests {
add<AddOtherPlans>();
add<AddOtherPlansDelete>();
add<AddOtherPlansContinuousDelete>();
+ add<AddOtherPlansWhenOptimalBecomesNonOptimal>();
add<OrRangeElimination>();
add<OrDedup>();
add<EarlyDups>();
View
30 src/mongo/dbtests/queryoptimizertests.cpp
@@ -1061,6 +1061,7 @@ namespace QueryOptimizerTests {
ASSERT( qps->haveInOrderPlan() );
ASSERT( !qps->possibleOutOfOrderPlan() );
ASSERT( !qps->hasPossiblyExcludedPlans() );
+ ASSERT( !qps->usingCachedPlan() );
}
{
@@ -1070,10 +1071,23 @@ namespace QueryOptimizerTests {
ASSERT( qps->haveInOrderPlan() );
ASSERT( qps->possibleOutOfOrderPlan() );
ASSERT( !qps->hasPossiblyExcludedPlans() );
+ ASSERT( !qps->usingCachedPlan() );
}
NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() );
+ nsdt.registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSONObj() ),
+ CachedQueryPlan( BSON( "a" << 1 ), 1 ) );
+ {
+ shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSONObj() );
+ ASSERT_EQUALS( 1, qps->nPlans() );
+ ASSERT( qps->possibleInOrderPlan() );
+ ASSERT( qps->haveInOrderPlan() );
+ ASSERT( !qps->possibleOutOfOrderPlan() );
+ ASSERT( !qps->hasPossiblyExcludedPlans() );
+ ASSERT( qps->usingCachedPlan() );
+ }
+
nsdt.registerCachedQueryPlanForPattern
( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ),
CachedQueryPlan( BSON( "a" << 1 ), 1 ) );
@@ -1085,6 +1099,7 @@ namespace QueryOptimizerTests {
ASSERT( !qps->haveInOrderPlan() );
ASSERT( qps->possibleOutOfOrderPlan() );
ASSERT( qps->hasPossiblyExcludedPlans() );
+ ASSERT( qps->usingCachedPlan() );
}
nsdt.registerCachedQueryPlanForPattern
@@ -1098,6 +1113,7 @@ namespace QueryOptimizerTests {
ASSERT( qps->haveInOrderPlan() );
ASSERT( qps->possibleOutOfOrderPlan() );
ASSERT( qps->hasPossiblyExcludedPlans() );
+ ASSERT( qps->usingCachedPlan() );
}
{
@@ -1107,6 +1123,7 @@ namespace QueryOptimizerTests {
ASSERT( !qps->haveInOrderPlan() );
ASSERT( qps->possibleOutOfOrderPlan() );
ASSERT( !qps->hasPossiblyExcludedPlans() );
+ ASSERT( !qps->usingCachedPlan() );
}
}
};
@@ -1179,7 +1196,18 @@ namespace QueryOptimizerTests {
}
NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() );
-
+
+ nsdt.registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSONObj() ),
+ CachedQueryPlan( BSON( "a" << 1 ), 1 ) );
+ {
+ shared_ptr<MultiPlanScanner> mps = makeMps( BSON( "a" << 1 ), BSONObj() );
+ ASSERT_EQUALS( 1, mps->currentNPlans() );
+ ASSERT( mps->possibleInOrderPlan() );
+ ASSERT( mps->haveInOrderPlan() );
+ ASSERT( !mps->possibleOutOfOrderPlan() );
+ ASSERT( !mps->hasPossiblyExcludedPlans() );
+ }
+
nsdt.registerCachedQueryPlanForPattern
( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ),
CachedQueryPlan( BSON( "a" << 1 ), 1 ) );
Please sign in to comment.
Something went wrong with that request. Please try again.