Skip to content

Commit

Permalink
SERVER-5301 Record optimal query plans in the query plan cache.
Browse files Browse the repository at this point in the history
  • Loading branch information
astaple committed Apr 20, 2012
1 parent 870e5cf commit 72e76dd
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 12 deletions.
31 changes: 31 additions & 0 deletions 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 ] } );
5 changes: 4 additions & 1 deletion src/mongo/db/queryoptimizer.cpp
Expand Up @@ -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 ) {
Expand All @@ -687,7 +690,7 @@ namespace mongo {
}

bool QueryPlanSet::hasPossiblyExcludedPlans() const {
return _usingCachedPlan;
return _usingCachedPlan && ( nPlans() == 1 ) && !firstPlan()->optimal();
}

QueryPlanSet::QueryPlanPtr QueryPlanSet::getBestGuess() const {
Expand Down
66 changes: 56 additions & 10 deletions src/mongo/dbtests/queryoptimizercursortests.cpp
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -3315,7 +3364,7 @@ namespace QueryOptimizerCursorTests {
ASSERT_EQUALS( string( "0" ), details.elemMatchKey() );
}
};

namespace GetCursor {

class Base : public QueryOptimizerCursorTests::Base {
Expand Down Expand Up @@ -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() );
Expand Down Expand Up @@ -4419,6 +4464,7 @@ namespace QueryOptimizerCursorTests {
add<AddOtherPlans>();
add<AddOtherPlansDelete>();
add<AddOtherPlansContinuousDelete>();
add<AddOtherPlansWhenOptimalBecomesNonOptimal>();
add<OrRangeElimination>();
add<OrDedup>();
add<EarlyDups>();
Expand Down
30 changes: 29 additions & 1 deletion src/mongo/dbtests/queryoptimizertests.cpp
Expand Up @@ -1061,6 +1061,7 @@ namespace QueryOptimizerTests {
ASSERT( qps->haveInOrderPlan() );
ASSERT( !qps->possibleOutOfOrderPlan() );
ASSERT( !qps->hasPossiblyExcludedPlans() );
ASSERT( !qps->usingCachedPlan() );
}

{
Expand All @@ -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 ) );
Expand All @@ -1085,6 +1099,7 @@ namespace QueryOptimizerTests {
ASSERT( !qps->haveInOrderPlan() );
ASSERT( qps->possibleOutOfOrderPlan() );
ASSERT( qps->hasPossiblyExcludedPlans() );
ASSERT( qps->usingCachedPlan() );
}

nsdt.registerCachedQueryPlanForPattern
Expand All @@ -1098,6 +1113,7 @@ namespace QueryOptimizerTests {
ASSERT( qps->haveInOrderPlan() );
ASSERT( qps->possibleOutOfOrderPlan() );
ASSERT( qps->hasPossiblyExcludedPlans() );
ASSERT( qps->usingCachedPlan() );
}

{
Expand All @@ -1107,6 +1123,7 @@ namespace QueryOptimizerTests {
ASSERT( !qps->haveInOrderPlan() );
ASSERT( qps->possibleOutOfOrderPlan() );
ASSERT( !qps->hasPossiblyExcludedPlans() );
ASSERT( !qps->usingCachedPlan() );
}
}
};
Expand Down Expand Up @@ -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 ) );
Expand Down

0 comments on commit 72e76dd

Please sign in to comment.