Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

SERVER-5301 Store CandidatePlanCharacter in the query plan cache, and…

… use its value to avoid pre populating query plans.
  • Loading branch information...
commit 131dd557b9ea7d7088198c6558bb91d835294693 1 parent 40329ed
@astaple astaple authored
View
53 jstests/sortl.js
@@ -0,0 +1,53 @@
+// When one plan among candidate plans of mixed ordering types is cached, and then replayed, and the
+// remaining plans are then attempted, those remaining plans are iterated properly for their
+// ordering type. SERVER-5301
+
+t = db.jstests_sortl;
+t.drop();
+
+t.ensureIndex( { a:1 } );
+t.ensureIndex( { b:1 } );
+
+function recordIndex( index, query, sort ) {
+ // Run a query that records the desired index.
+ t.find( query ).sort( sort ).explain();
+ // Check that the desired index is recorded.
+ assert.eq( 'BtreeCursor ' + index,
+ t.find( query ).sort( sort ).explain( true ).oldPlan.cursor );
+}
+
+function checkBOrdering( result ) {
+ for( i = 1; i < result.length; ++i ) {
+ assert.lt( result[ i - 1 ].b, result[ i ].b );
+ }
+}
+
+// An out of order plan is recorded, then an in order plan takes over.
+t.save( { a:1 } );
+big = new Array( 1000000 ).toString();
+for( i = 0; i < 40; ++i ) {
+ t.save( { a:2, b:i, c:big } );
+}
+
+recordIndex( 'a_1', { a:1 }, { b:1 } );
+result = t.find( { a:2 }, { a:1, b:1 } ).sort( { b:1 } ).toArray();
+assert.eq( 40, result.length );
+checkBOrdering( result );
+
+// An optimal in order plan is recorded and reused.
+recordIndex( 'b_1', { b:{ $gte:0 } }, { b:1 } );
+result = t.find( { b:{ $gte:0 } }, { b:1 } ).sort( { b:1 } ).toArray();
+assert.eq( 40, result.length );
+checkBOrdering( result );
+
+t.remove();
+
+// An in order plan is recorded, then an out of order plan is added.
+for( i = 0; i < 20; ++i ) {
+ t.save( { a:1, b:19-i } );
+}
+
+recordIndex( 'b_1', { a:1, b:{ $gte:19 } }, { b:1 } );
+result = t.find( { a:1, b:{ $gte:0 } } ).sort( { b:1 } ).toArray();
+assert.eq( 20, result.length );
+checkBOrdering( result );
View
65 src/mongo/db/queryoptimizer.cpp
@@ -262,7 +262,8 @@ namespace mongo {
return _index->keyPattern();
}
- void QueryPlan::registerSelf( long long nScanned ) const {
+ void QueryPlan::registerSelf( long long nScanned,
+ CandidatePlanCharacter candidatePlans ) const {
// Impossible query constraints can be detected before scanning, and we
// don't have a reserved pattern enum value for impossible constraints.
if ( _impossible ) {
@@ -271,9 +272,9 @@ namespace mongo {
SimpleMutex::scoped_lock lk(NamespaceDetailsTransient::_qcMutex);
QueryPattern queryPattern = _frs.pattern( _order );
- CachedQueryPlan cachedQueryPlan( indexKey(), nScanned );
+ CachedQueryPlan queryPlanToCache( indexKey(), nScanned, candidatePlans );
NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get_inlock( ns() );
- nsdt.registerCachedQueryPlanForPattern( queryPattern, cachedQueryPlan );
+ nsdt.registerCachedQueryPlanForPattern( queryPattern, queryPlanToCache );
}
void QueryPlan::checkTableScanAllowed() const {
@@ -489,7 +490,6 @@ namespace mongo {
void QueryPlanSet::init() {
DEBUGQO( "QueryPlanSet::init " << ns << "\t" << _originalQuery );
_plans.clear();
- _fallbackPlans.clear();
_usingCachedPlan = false;
const char *ns = _frsp->ns();
@@ -572,6 +572,7 @@ namespace mongo {
CachedQueryPlan best = QueryUtilIndexed::bestIndexForPatterns( *_frsp, _order );
BSONObj bestIndex = best.indexKey();
long long oldNScanned = best.nScanned();
+ CandidatePlanCharacter bestPlanCharacter = best.planCharacter();
if ( !bestIndex.isEmpty() ) {
QueryPlanPtr p;
_oldNScanned = oldNScanned;
@@ -595,31 +596,29 @@ namespace mongo {
if ( !p->unhelpful() &&
!( _recordedPlanPolicy == UseIfInOrder && p->scanAndOrderRequired() ) ) {
_usingCachedPlan = true;
+ _cachedPlanCharacter = bestPlanCharacter;
_plans.push_back( p );
- addOtherPlans( _fallbackPlans );
return;
}
}
}
- addOtherPlans( _plans );
+ addOtherPlans( false );
}
- void QueryPlanSet::addPlan( QueryPlanPtr plan, PlanSet &planSet ) {
- planSet.push_back( plan );
+ void QueryPlanSet::addPlan( QueryPlanPtr plan, bool checkFirst ) {
+ if ( checkFirst && plan->indexKey() == firstPlan()->indexKey() ) {
+ return;
+ }
+ _plans.push_back( plan );
}
void QueryPlanSet::addFallbackPlans() {
- for( PlanSet::const_iterator i = _fallbackPlans.begin(); i != _fallbackPlans.end(); ++i ) {
- if ( (*i)->indexKey() != _plans[ 0 ]->indexKey() ) {
- _plans.push_back( *i );
- }
- }
- _fallbackPlans.clear();
+ addOtherPlans( true );
_mayRecordPlan = true;
}
- void QueryPlanSet::addOtherPlans( PlanSet &planSet ) {
+ void QueryPlanSet::addOtherPlans( bool checkFirst ) {
const char *ns = _frsp->ns();
NamespaceDetails *d = nsdetails( ns );
if ( !d )
@@ -632,7 +631,7 @@ namespace mongo {
QueryPlanPtr plan
( new QueryPlan( d, -1, *_frsp, _originalFrsp.get(), _originalQuery,
_order, _parsedQuery ) );
- addPlan( plan, planSet );
+ addPlan( plan, checkFirst );
return;
}
@@ -648,7 +647,7 @@ namespace mongo {
QueryPlanPtr p( new QueryPlan( d, i, *_frsp, _originalFrsp.get(), _originalQuery,
_order, _parsedQuery ) );
if ( p->impossible() ) {
- addPlan( p, planSet );
+ addPlan( p, checkFirst );
return;
}
if ( p->optimal() ) {
@@ -666,25 +665,27 @@ namespace mongo {
}
}
if ( optimalPlan.get() ) {
- addPlan( optimalPlan, planSet );
+ addPlan( optimalPlan, checkFirst );
// Record an optimal plan in the query cache immediately, with a small nscanned value
// that will be ignored.
- optimalPlan->registerSelf( 0 );
+ optimalPlan->registerSelf
+ ( 0, CandidatePlanCharacter( !optimalPlan->scanAndOrderRequired(),
+ optimalPlan->scanAndOrderRequired() ) );
return;
}
for( PlanSet::const_iterator i = plans.begin(); i != plans.end(); ++i ) {
- addPlan( *i, planSet );
+ addPlan( *i, checkFirst );
}
// Only add a special plan if no standard btree plans have been added. SERVER-4531
if ( plans.empty() && specialPlan ) {
- addPlan( specialPlan, planSet );
+ addPlan( specialPlan, checkFirst );
return;
}
// Table scan plan
addPlan( QueryPlanPtr( new QueryPlan( d, -1, *_frsp, _originalFrsp.get(), _originalQuery,
- _order, _parsedQuery ) ), planSet );
+ _order, _parsedQuery ) ), checkFirst );
_mayRecordPlan = true;
}
@@ -728,12 +729,7 @@ namespace mongo {
if ( haveInOrderPlan() ) {
return true;
}
- for( PlanSet::const_iterator i = _fallbackPlans.begin(); i != _fallbackPlans.end(); ++i ) {
- if ( !(*i)->scanAndOrderRequired() ) {
- return true;
- }
- }
- return false;
+ return _cachedPlanCharacter.mayRunInOrderPlan();
}
bool QueryPlanSet::possibleOutOfOrderPlan() const {
@@ -742,14 +738,13 @@ namespace mongo {
return true;
}
}
- for( PlanSet::const_iterator i = _fallbackPlans.begin(); i != _fallbackPlans.end(); ++i ) {
- if ( (*i)->scanAndOrderRequired() ) {
- return true;
- }
- }
- return false;
+ return _cachedPlanCharacter.mayRunOutOfOrderPlan();
}
+ CandidatePlanCharacter QueryPlanSet::characterizeCandidatePlans() const {
+ return CandidatePlanCharacter( possibleInOrderPlan(), possibleOutOfOrderPlan() );
+ }
+
bool QueryPlanSet::prepareToRetryQuery() {
if ( !hasPossiblyExcludedPlans() || _plans.size() > 1 ) {
return false;
@@ -891,7 +886,7 @@ namespace mongo {
nextOp( op );
if ( op.complete() ) {
if ( _plans._mayRecordPlan && op.mayRecordPlan() ) {
- op.qp().registerSelf( op.nscanned() );
+ op.qp().registerSelf( op.nscanned(), _plans.characterizeCandidatePlans() );
}
_done = true;
return holder._op;
View
51 src/mongo/db/queryoptimizer.h
@@ -28,9 +28,28 @@ namespace mongo {
class IndexDetails;
class IndexType;
-
class QueryPlanSummary;
+ /** Summarizes the candidate plans that may run for a query. */
+ class CandidatePlanCharacter {
+ public:
+ CandidatePlanCharacter( bool mayRunInOrderPlan, bool mayRunOutOfOrderPlan ) :
+ _mayRunInOrderPlan( mayRunInOrderPlan ),
+ _mayRunOutOfOrderPlan( mayRunOutOfOrderPlan ) {
+ }
+ CandidatePlanCharacter() :
+ _mayRunInOrderPlan(),
+ _mayRunOutOfOrderPlan() {
+ }
+ bool mayRunInOrderPlan() const { return _mayRunInOrderPlan; }
+ bool mayRunOutOfOrderPlan() const { return _mayRunOutOfOrderPlan; }
+ bool valid() const { return mayRunInOrderPlan() || mayRunOutOfOrderPlan(); }
+ bool hybridPlanSet() const { return mayRunInOrderPlan() && mayRunOutOfOrderPlan(); }
+ private:
+ bool _mayRunInOrderPlan;
+ bool _mayRunOutOfOrderPlan;
+ };
+
/** A plan for executing a query using the given index spec and FieldRangeSet. */
class QueryPlan : boost::noncopyable {
public:
@@ -76,7 +95,7 @@ namespace mongo {
/** @return a new reverse cursor if this is an unindexed plan. */
shared_ptr<Cursor> newReverseCursor() const;
/** Register this plan as a winner for its QueryPattern, with specified 'nscanned'. */
- void registerSelf( long long nScanned ) const;
+ void registerSelf( long long nScanned, CandidatePlanCharacter candidatePlans ) const;
int direction() const { return _direction; }
BSONObj indexKey() const;
@@ -282,26 +301,6 @@ namespace mongo {
}
};
- /** Summarizes the candidate plans that may run for a query. */
- class CandidatePlanCharacter {
- public:
- CandidatePlanCharacter( bool mayRunInOrderPlan, bool mayRunOutOfOrderPlan ) :
- _mayRunInOrderPlan( mayRunInOrderPlan ),
- _mayRunOutOfOrderPlan( mayRunOutOfOrderPlan ) {
- }
- CandidatePlanCharacter() :
- _mayRunInOrderPlan(),
- _mayRunOutOfOrderPlan() {
- }
- bool mayRunInOrderPlan() const { return _mayRunInOrderPlan; }
- bool mayRunOutOfOrderPlan() const { return _mayRunOutOfOrderPlan; }
- bool valid() const { return mayRunInOrderPlan() || mayRunOutOfOrderPlan(); }
- bool hybridPlanSet() const { return mayRunInOrderPlan() && mayRunOutOfOrderPlan(); }
- private:
- bool _mayRunInOrderPlan;
- bool _mayRunOutOfOrderPlan;
- };
-
/**
* A set of candidate query plans for a query. This class can return a best guess plan or run a
* QueryOp on all the plans.
@@ -355,6 +354,8 @@ namespace mongo {
/** @return true if an active or fallback plan is out of order. */
bool possibleOutOfOrderPlan() const;
+ CandidatePlanCharacter characterizeCandidatePlans() const;
+
bool prepareToRetryQuery();
string toString() const;
@@ -414,9 +415,9 @@ namespace mongo {
};
private:
- void addOtherPlans( PlanSet &planSet );
+ void addOtherPlans( bool checkFirst );
void addFallbackPlans();
- void addPlan( QueryPlanPtr plan, PlanSet &planSet );
+ void addPlan( QueryPlanPtr plan, bool checkFirst );
void init();
void addHint( IndexDetails &id );
@@ -425,9 +426,9 @@ namespace mongo {
auto_ptr<FieldRangeSetPair> _frsp;
auto_ptr<FieldRangeSetPair> _originalFrsp;
PlanSet _plans;
- PlanSet _fallbackPlans;
bool _mayRecordPlan;
bool _usingCachedPlan;
+ CandidatePlanCharacter _cachedPlanCharacter;
BSONObj _hint;
BSONObj _order;
shared_ptr<const ParsedQuery> _parsedQuery;
View
1  src/mongo/db/queryoptimizercursor.h
@@ -25,6 +25,7 @@
namespace mongo {
class QueryPlan;
+ class CandidatePlanCharacter;
/**
* An interface for policies overriding the query optimizer's default query plan selection
View
6 src/mongo/db/querypattern.cpp
@@ -106,9 +106,11 @@ namespace mongo {
return b.obj();
}
- CachedQueryPlan::CachedQueryPlan( const BSONObj &indexKey, long long nScanned ) :
+ CachedQueryPlan::CachedQueryPlan( const BSONObj &indexKey, long long nScanned,
+ CandidatePlanCharacter planCharacter ) :
_indexKey( indexKey ),
- _nScanned( nScanned ) {
+ _nScanned( nScanned ),
+ _planCharacter( planCharacter ) {
}
View
6 src/mongo/db/querypattern.h
@@ -18,6 +18,7 @@
#pragma once
#include "jsobj.h"
+#include "mongo/db/queryoptimizer.h"
namespace mongo {
@@ -62,12 +63,15 @@ namespace mongo {
CachedQueryPlan() :
_nScanned() {
}
- CachedQueryPlan( const BSONObj &indexKey, long long nScanned );
+ CachedQueryPlan( const BSONObj &indexKey, long long nScanned,
+ CandidatePlanCharacter planCharacter );
BSONObj indexKey() const { return _indexKey; }
long long nScanned() const { return _nScanned; }
+ CandidatePlanCharacter planCharacter() const { return _planCharacter; }
private:
BSONObj _indexKey;
long long _nScanned;
+ CandidatePlanCharacter _planCharacter;
};
inline bool QueryPattern::operator<( const QueryPattern &other ) const {
View
2  src/mongo/db/queryutil.cpp
@@ -1197,7 +1197,7 @@ namespace mongo {
_singleKey -= scanned;
_multiKey -= scanned;
return *this;
- }
+ }
string FieldRangeSetPair::toString() const {
return BSON(
View
5 src/mongo/dbtests/namespacetests.cpp
@@ -1234,8 +1234,9 @@ namespace NamespaceTests {
nsdt().cachedQueryPlanForPattern( _pattern ).indexKey() );
}
void registerIndexKey( const BSONObj &indexKey ) {
- nsdt().registerCachedQueryPlanForPattern( _pattern,
- CachedQueryPlan( indexKey, 1 ) );
+ nsdt().registerCachedQueryPlanForPattern
+ ( _pattern,
+ CachedQueryPlan( indexKey, 1, CandidatePlanCharacter( true, false ) ) );
}
FieldRangeSet _fieldRangeSet;
QueryPattern _pattern;
View
39 src/mongo/dbtests/queryoptimizertests.cpp
@@ -707,11 +707,23 @@ namespace QueryOptimizerTests {
void run() {
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "a_1" );
Helpers::ensureIndex( ns(), BSON( "a" << 1 ), false, "b_2" );
- auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), BSON( "a" << 4 ) ) );
+ BSONObj query = BSON( "a" << 4 );
+ auto_ptr< FieldRangeSetPair > frsp( new FieldRangeSetPair( ns(), query ) );
auto_ptr< FieldRangeSetPair > frspOrig( new FieldRangeSetPair( *frsp ) );
- QueryPlanSet s( ns(), frsp, frspOrig, BSON( "a" << 4 ),
- BSONObj() );
+ QueryPlanSet s( ns(), frsp, frspOrig, query, BSONObj() );
+
+ // Only one optimal plan is added to the plan set.
ASSERT_EQUALS( 1, s.nPlans() );
+
+ // The optimal plan is recorded in the plan cache.
+ FieldRangeSet frs( ns(), query, true );
+ CachedQueryPlan cachedPlan =
+ NamespaceDetailsTransient::get( ns() ).cachedQueryPlanForPattern
+ ( QueryPattern( frs, BSONObj() ) );
+ ASSERT_EQUALS( BSON( "a" << 1 ), cachedPlan.indexKey() );
+ CandidatePlanCharacter planCharacter = cachedPlan.planCharacter();
+ ASSERT( planCharacter.mayRunInOrderPlan() );
+ ASSERT( !planCharacter.mayRunOutOfOrderPlan() );
}
};
@@ -1077,7 +1089,8 @@ namespace QueryOptimizerTests {
NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() );
nsdt.registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSONObj() ),
- CachedQueryPlan( BSON( "a" << 1 ), 1 ) );
+ CachedQueryPlan( BSON( "a" << 1 ), 1,
+ CandidatePlanCharacter( true, false ) ) );
{
shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSONObj() );
ASSERT_EQUALS( 1, qps->nPlans() );
@@ -1090,7 +1103,8 @@ namespace QueryOptimizerTests {
nsdt.registerCachedQueryPlanForPattern
( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ),
- CachedQueryPlan( BSON( "a" << 1 ), 1 ) );
+ CachedQueryPlan( BSON( "a" << 1 ), 1,
+ CandidatePlanCharacter( true, true ) ) );
{
shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ) );
@@ -1104,7 +1118,8 @@ namespace QueryOptimizerTests {
nsdt.registerCachedQueryPlanForPattern
( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ),
- CachedQueryPlan( BSON( "b" << 1 ), 1 ) );
+ CachedQueryPlan( BSON( "b" << 1 ), 1,
+ CandidatePlanCharacter( true, true ) ) );
{
shared_ptr<QueryPlanSet> qps = makeQps( BSON( "a" << 1 ), BSON( "b" << 1 ) );
@@ -1198,7 +1213,8 @@ namespace QueryOptimizerTests {
NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() );
nsdt.registerCachedQueryPlanForPattern( makePattern( BSON( "a" << 1 ), BSONObj() ),
- CachedQueryPlan( BSON( "a" << 1 ), 1 ) );
+ CachedQueryPlan( BSON( "a" << 1 ), 1,
+ CandidatePlanCharacter( true, false ) ) );
{
shared_ptr<MultiPlanScanner> mps = makeMps( BSON( "a" << 1 ), BSONObj() );
ASSERT_EQUALS( 1, mps->currentNPlans() );
@@ -1210,7 +1226,8 @@ namespace QueryOptimizerTests {
nsdt.registerCachedQueryPlanForPattern
( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ),
- CachedQueryPlan( BSON( "a" << 1 ), 1 ) );
+ CachedQueryPlan( BSON( "a" << 1 ), 1,
+ CandidatePlanCharacter( true, true ) ) );
{
shared_ptr<MultiPlanScanner> mps =
@@ -1224,7 +1241,8 @@ namespace QueryOptimizerTests {
nsdt.registerCachedQueryPlanForPattern
( makePattern( BSON( "a" << 1 ), BSON( "b" << 1 ) ),
- CachedQueryPlan( BSON( "b" << 1 ), 1 ) );
+ CachedQueryPlan( BSON( "b" << 1 ), 1,
+ CandidatePlanCharacter( true, true ) ) );
{
shared_ptr<MultiPlanScanner> mps =
@@ -1301,7 +1319,8 @@ namespace QueryOptimizerTests {
SimpleMutex::scoped_lock lk(NamespaceDetailsTransient::_qcMutex);
NamespaceDetailsTransient::get_inlock( ns() ).
registerCachedQueryPlanForPattern( frs.pattern( BSON( "b" << 1 ) ),
- CachedQueryPlan( BSON( "a" << 1 ), 0 ) );
+ CachedQueryPlan( BSON( "a" << 1 ), 0,
+ CandidatePlanCharacter( true, true ) ) );
}
c = NamespaceDetailsTransient::bestGuessCursor( ns(), fromjson( "{a:1,$or:[{y:1}]}" ),
View
12 src/mongo/dbtests/queryutiltests.cpp
@@ -1333,10 +1333,12 @@ namespace QueryUtilTests {
NamespaceDetailsTransient &nsdt = NamespaceDetailsTransient::get( ns() );
QueryPattern singleKey = FieldRangeSet( ns(), query, true ).pattern( sort );
nsdt.registerCachedQueryPlanForPattern( singleKey,
- CachedQueryPlan( BSON( "a" << 1 ), 1 ) );
+ CachedQueryPlan( BSON( "a" << 1 ), 1,
+ CandidatePlanCharacter( true, true ) ) );
QueryPattern multiKey = FieldRangeSet( ns(), query, false ).pattern( sort );
nsdt.registerCachedQueryPlanForPattern( multiKey,
- CachedQueryPlan( BSON( "a" << 1 ), 5 ) );
+ CachedQueryPlan( BSON( "a" << 1 ), 5,
+ CandidatePlanCharacter( true, true ) ) );
// The single and multi key fields for this query must differ for the test to be
// valid.
@@ -1370,14 +1372,16 @@ namespace QueryUtilTests {
// A multikey index query plan is returned if recorded.
QueryPattern multiKey = FieldRangeSet( ns(), query, false ).pattern( sort );
nsdt.registerCachedQueryPlanForPattern( multiKey,
- CachedQueryPlan( BSON( "a" << 1 ), 5 ) );
+ CachedQueryPlan( BSON( "a" << 1 ), 5,
+ CandidatePlanCharacter( true, true ) ) );
ASSERT_EQUALS( BSON( "a" << 1 ),
QueryUtilIndexed::bestIndexForPatterns( frsp, sort ).indexKey() );
// A non multikey index query plan is preferentially returned if recorded.
QueryPattern singleKey = FieldRangeSet( ns(), query, true ).pattern( sort );
nsdt.registerCachedQueryPlanForPattern( singleKey,
- CachedQueryPlan( BSON( "b" << 1 ), 5 ) );
+ CachedQueryPlan( BSON( "b" << 1 ), 5,
+ CandidatePlanCharacter( true, true ) ) );
ASSERT_EQUALS( BSON( "b" << 1 ),
QueryUtilIndexed::bestIndexForPatterns( frsp, sort ).indexKey() );
Please sign in to comment.
Something went wrong with that request. Please try again.