diff options
Diffstat (limited to 'src/mongo/dbtests/oplogstarttests.cpp')
-rw-r--r-- | src/mongo/dbtests/oplogstarttests.cpp | 672 |
1 files changed, 359 insertions, 313 deletions
diff --git a/src/mongo/dbtests/oplogstarttests.cpp b/src/mongo/dbtests/oplogstarttests.cpp index 9c3efe42afe..1c73c533e6b 100644 --- a/src/mongo/dbtests/oplogstarttests.cpp +++ b/src/mongo/dbtests/oplogstarttests.cpp @@ -36,347 +36,393 @@ namespace OplogStartTests { - using std::unique_ptr; - using std::string; - - class Base { - public: - Base() : _txn(), - _scopedXact(&_txn, MODE_X), - _lk(_txn.lockState()), - _context(&_txn, ns()), - _client(&_txn) { - - Collection* c = _context.db()->getCollection(ns()); - if (!c) { - WriteUnitOfWork wuow(&_txn); - c = _context.db()->createCollection(&_txn, ns()); - wuow.commit(); - } - ASSERT(c->getIndexCatalog()->haveIdIndex(&_txn)); +using std::unique_ptr; +using std::string; + +class Base { +public: + Base() + : _txn(), + _scopedXact(&_txn, MODE_X), + _lk(_txn.lockState()), + _context(&_txn, ns()), + _client(&_txn) { + Collection* c = _context.db()->getCollection(ns()); + if (!c) { + WriteUnitOfWork wuow(&_txn); + c = _context.db()->createCollection(&_txn, ns()); + wuow.commit(); } + ASSERT(c->getIndexCatalog()->haveIdIndex(&_txn)); + } + + ~Base() { + client()->dropCollection(ns()); + + // The OplogStart stage is not allowed to outlive it's RecoveryUnit. + _stage.reset(); + } + +protected: + static const char* ns() { + return "unittests.oplogstarttests"; + } + static const char* dbname() { + return "unittests"; + } + static const char* collname() { + return "oplogstarttests"; + } + + Collection* collection() { + return _context.db()->getCollection(ns()); + } + + DBDirectClient* client() { + return &_client; + } + + void setupFromQuery(const BSONObj& query) { + CanonicalQuery* cq; + Status s = CanonicalQuery::canonicalize(ns(), query, &cq); + ASSERT(s.isOK()); + _cq.reset(cq); + _oplogws.reset(new WorkingSet()); + _stage.reset(new OplogStart(&_txn, collection(), _cq->root(), _oplogws.get())); + } + + void assertWorkingSetMemberHasId(WorkingSetID id, int expectedId) { + WorkingSetMember* member = _oplogws->get(id); + BSONElement idEl = member->obj.value()["_id"]; + ASSERT(!idEl.eoo()); + ASSERT(idEl.isNumber()); + ASSERT_EQUALS(idEl.numberInt(), expectedId); + } + + unique_ptr<CanonicalQuery> _cq; + unique_ptr<WorkingSet> _oplogws; + unique_ptr<OplogStart> _stage; + +private: + // The order of these is important in order to ensure order of destruction + OperationContextImpl _txn; + ScopedTransaction _scopedXact; + Lock::GlobalWrite _lk; + OldClientContext _context; + + DBDirectClient _client; +}; - ~Base() { - client()->dropCollection(ns()); - // The OplogStart stage is not allowed to outlive it's RecoveryUnit. - _stage.reset(); +/** + * When the ts is newer than the oldest document, the OplogStart + * stage should find the oldest document using a backwards collection + * scan. + */ +class OplogStartIsOldest : public Base { +public: + void run() { + for (int i = 0; i < 10; ++i) { + client()->insert(ns(), BSON("_id" << i << "ts" << i)); } - protected: - static const char *ns() { - return "unittests.oplogstarttests"; - } - static const char *dbname() { - return "unittests"; - } - static const char *collname() { - return "oplogstarttests"; - } + setupFromQuery(BSON("ts" << BSON("$gte" << 10))); - Collection* collection() { - return _context.db()->getCollection( ns() ); - } + WorkingSetID id = WorkingSet::INVALID_ID; + // collection scan needs to be initialized + ASSERT_EQUALS(_stage->work(&id), PlanStage::NEED_TIME); + // finds starting record + ASSERT_EQUALS(_stage->work(&id), PlanStage::ADVANCED); + ASSERT(_stage->isBackwardsScanning()); - DBDirectClient* client() { return &_client; } + assertWorkingSetMemberHasId(id, 9); + } +}; - void setupFromQuery(const BSONObj& query) { - CanonicalQuery* cq; - Status s = CanonicalQuery::canonicalize(ns(), query, &cq); - ASSERT(s.isOK()); - _cq.reset(cq); - _oplogws.reset(new WorkingSet()); - _stage.reset(new OplogStart(&_txn, collection(), _cq->root(), _oplogws.get())); +/** + * Find the starting oplog record by scanning backwards + * all the way to the beginning. + */ +class OplogStartIsNewest : public Base { +public: + void run() { + for (int i = 0; i < 10; ++i) { + client()->insert(ns(), BSON("_id" << i << "ts" << i)); } - void assertWorkingSetMemberHasId(WorkingSetID id, int expectedId) { - WorkingSetMember* member = _oplogws->get(id); - BSONElement idEl = member->obj.value()["_id"]; - ASSERT(!idEl.eoo()); - ASSERT(idEl.isNumber()); - ASSERT_EQUALS(idEl.numberInt(), expectedId); - } + setupFromQuery(BSON("ts" << BSON("$gte" << 1))); - unique_ptr<CanonicalQuery> _cq; - unique_ptr<WorkingSet> _oplogws; - unique_ptr<OplogStart> _stage; - - private: - // The order of these is important in order to ensure order of destruction - OperationContextImpl _txn; - ScopedTransaction _scopedXact; - Lock::GlobalWrite _lk; - OldClientContext _context; - - DBDirectClient _client; - }; - - - /** - * When the ts is newer than the oldest document, the OplogStart - * stage should find the oldest document using a backwards collection - * scan. - */ - class OplogStartIsOldest : public Base { - public: - void run() { - for(int i = 0; i < 10; ++i) { - client()->insert(ns(), BSON( "_id" << i << "ts" << i )); - } - - setupFromQuery(BSON( "ts" << BSON( "$gte" << 10 ))); - - WorkingSetID id = WorkingSet::INVALID_ID; - // collection scan needs to be initialized + WorkingSetID id = WorkingSet::INVALID_ID; + // collection scan needs to be initialized + ASSERT_EQUALS(_stage->work(&id), PlanStage::NEED_TIME); + // full collection scan back to the first oplog record + for (int i = 0; i < 9; ++i) { ASSERT_EQUALS(_stage->work(&id), PlanStage::NEED_TIME); - // finds starting record - ASSERT_EQUALS(_stage->work(&id), PlanStage::ADVANCED); ASSERT(_stage->isBackwardsScanning()); + } + ASSERT_EQUALS(_stage->work(&id), PlanStage::ADVANCED); + + assertWorkingSetMemberHasId(id, 0); + } +}; - assertWorkingSetMemberHasId(id, 9); +/** + * Find the starting oplog record by hopping to the + * beginning of the extent. + */ +class OplogStartIsNewestExtentHop : public Base { +public: + void run() { + for (int i = 0; i < 10; ++i) { + client()->insert(ns(), BSON("_id" << i << "ts" << i)); } - }; - - /** - * Find the starting oplog record by scanning backwards - * all the way to the beginning. - */ - class OplogStartIsNewest : public Base { - public: - void run() { - for(int i = 0; i < 10; ++i) { - client()->insert(ns(), BSON( "_id" << i << "ts" << i )); - } - - setupFromQuery(BSON( "ts" << BSON( "$gte" << 1 ))); - - WorkingSetID id = WorkingSet::INVALID_ID; - // collection scan needs to be initialized + + setupFromQuery(BSON("ts" << BSON("$gte" << 1))); + + WorkingSetID id = WorkingSet::INVALID_ID; + // ensure that we go into extent hopping mode immediately + _stage->setBackwardsScanTime(0); + + // We immediately switch to extent hopping mode, and + // should find the beginning of the extent + ASSERT_EQUALS(_stage->work(&id), PlanStage::ADVANCED); + ASSERT(_stage->isExtentHopping()); + + assertWorkingSetMemberHasId(id, 0); + } +}; + +class SizedExtentHopBase : public Base { +public: + SizedExtentHopBase() { + client()->dropCollection(ns()); + } + virtual ~SizedExtentHopBase() { + client()->dropCollection(ns()); + } + + void run() { + buildCollection(); + + WorkingSetID id = WorkingSet::INVALID_ID; + setupFromQuery(BSON("ts" << BSON("$gte" << tsGte()))); + + // ensure that we go into extent hopping mode immediately + _stage->setBackwardsScanTime(0); + + // hop back extent by extent + for (int i = 0; i < numHops(); i++) { ASSERT_EQUALS(_stage->work(&id), PlanStage::NEED_TIME); - // full collection scan back to the first oplog record - for (int i = 0; i < 9; ++i) { - ASSERT_EQUALS(_stage->work(&id), PlanStage::NEED_TIME); - ASSERT(_stage->isBackwardsScanning()); - } - ASSERT_EQUALS(_stage->work(&id), PlanStage::ADVANCED); - - assertWorkingSetMemberHasId(id, 0); - } - }; - - /** - * Find the starting oplog record by hopping to the - * beginning of the extent. - */ - class OplogStartIsNewestExtentHop : public Base { - public: - void run() { - for(int i = 0; i < 10; ++i) { - client()->insert(ns(), BSON( "_id" << i << "ts" << i)); - } - - setupFromQuery(BSON( "ts" << BSON( "$gte" << 1 ))); - - WorkingSetID id = WorkingSet::INVALID_ID; - // ensure that we go into extent hopping mode immediately - _stage->setBackwardsScanTime(0); - - // We immediately switch to extent hopping mode, and - // should find the beginning of the extent - ASSERT_EQUALS(_stage->work(&id), PlanStage::ADVANCED); ASSERT(_stage->isExtentHopping()); - - assertWorkingSetMemberHasId(id, 0); } - }; + // find the right loc without hopping again + ASSERT_EQUALS(_stage->work(&id), finalState()); - class SizedExtentHopBase : public Base { - public: - SizedExtentHopBase() { - client()->dropCollection(ns()); + int startDocId = tsGte() - 1; + if (startDocId >= 0) { + assertWorkingSetMemberHasId(id, startDocId); } - virtual ~SizedExtentHopBase() { - client()->dropCollection(ns()); + } + +protected: + void buildCollection() { + BSONObj info; + // Create a collection with specified extent sizes + BSONObj command = BSON("create" << collname() << "capped" << true << "$nExtents" + << extentSizes() << "autoIndexId" << false); + ASSERT(client()->runCommand(dbname(), command, info)); + + // Populate documents. + for (int i = 0; i < numDocs(); ++i) { + client()->insert(ns(), BSON("_id" << i << "ts" << i << "payload" << payload8k())); } + } + + static string payload8k() { + return string(8 * 1024, 'a'); + } + /** An extent of this size is too small to contain one document containing payload8k(). */ + static int tooSmall() { + return 1 * 1024; + } + /** An extent of this size fits one document. */ + static int fitsOne() { + return 10 * 1024; + } + /** An extent of this size fits many documents. */ + static int fitsMany() { + return 50 * 1024; + } + + // to be defined by subclasses + virtual BSONArray extentSizes() const = 0; + virtual int numDocs() const = 0; + virtual int numHops() const = 0; + virtual PlanStage::StageState finalState() const { + return PlanStage::ADVANCED; + } + virtual int tsGte() const { + return 1; + } +}; - void run() { - buildCollection(); +/** + * Test hopping over a single empty extent. + * + * Collection structure: + * + * [--- extent 0 --] [ ext 1 ] [--- extent 2 ---] + * [ {_id: 0} ] [<empty>] [ {_id: 1} ] + */ +class OplogStartOneEmptyExtent : public SizedExtentHopBase { + virtual int numDocs() const { + return 2; + } + virtual int numHops() const { + return 1; + } + virtual BSONArray extentSizes() const { + return BSON_ARRAY(fitsOne() << tooSmall() << fitsOne()); + } +}; - WorkingSetID id = WorkingSet::INVALID_ID; - setupFromQuery(BSON( "ts" << BSON( "$gte" << tsGte() ))); +/** + * Test hopping over two consecutive empty extents. + * + * Collection structure: + * + * [--- extent 0 --] [ ext 1 ] [ ext 2 ] [--- extent 3 ---] + * [ {_id: 0} ] [<empty>] [<empty>] [ {_id: 1} ] + */ +class OplogStartTwoEmptyExtents : public SizedExtentHopBase { + virtual int numDocs() const { + return 2; + } + virtual int numHops() const { + return 1; + } + virtual BSONArray extentSizes() const { + return BSON_ARRAY(fitsOne() << tooSmall() << tooSmall() << fitsOne()); + } +}; - // ensure that we go into extent hopping mode immediately - _stage->setBackwardsScanTime(0); +/** + * Two extents, each filled with several documents. This + * should require us to make just a single extent hop. + */ +class OplogStartTwoFullExtents : public SizedExtentHopBase { + virtual int numDocs() const { + return 10; + } + virtual int numHops() const { + return 1; + } + virtual BSONArray extentSizes() const { + return BSON_ARRAY(fitsMany() << fitsMany()); + } +}; - // hop back extent by extent - for (int i = 0; i < numHops(); i++) { - ASSERT_EQUALS(_stage->work(&id), PlanStage::NEED_TIME); - ASSERT(_stage->isExtentHopping()); - } - // find the right loc without hopping again - ASSERT_EQUALS(_stage->work(&id), finalState()); +/** + * Four extents in total. Three are populated with multiple + * documents, but one of the middle extents is empty. This + * should require two extent hops. + */ +class OplogStartThreeFullOneEmpty : public SizedExtentHopBase { + virtual int numDocs() const { + return 14; + } + virtual int numHops() const { + return 2; + } + virtual BSONArray extentSizes() const { + return BSON_ARRAY(fitsMany() << fitsMany() << tooSmall() << fitsMany()); + } +}; - int startDocId = tsGte() - 1; - if (startDocId >= 0) { - assertWorkingSetMemberHasId(id, startDocId); - } - } +/** + * Test that extent hopping mode works properly in the + * special case of one extent. + */ +class OplogStartOneFullExtent : public SizedExtentHopBase { + virtual int numDocs() const { + return 4; + } + virtual int numHops() const { + return 0; + } + virtual BSONArray extentSizes() const { + return BSON_ARRAY(fitsMany()); + } +}; - protected: - void buildCollection() { - BSONObj info; - // Create a collection with specified extent sizes - BSONObj command = BSON( "create" << collname() << "capped" << true << - "$nExtents" << extentSizes() << "autoIndexId" << false ); - ASSERT(client()->runCommand(dbname(), command, info)); - - // Populate documents. - for(int i = 0; i < numDocs(); ++i) { - client()->insert(ns(), BSON( "_id" << i << "ts" << i << "payload" << payload8k() )); - } - } +/** + * Collection structure: + * + * [ ext 0 ] [--- extent 1 --] [--- extent 2 ---] + * [<empty>] [ {_id: 0} ] [ {_id: 1} ] + */ +class OplogStartFirstExtentEmpty : public SizedExtentHopBase { + virtual int numDocs() const { + return 2; + } + virtual int numHops() const { + return 1; + } + virtual BSONArray extentSizes() const { + return BSON_ARRAY(tooSmall() << fitsOne() << fitsOne()); + } +}; - static string payload8k() { return string(8*1024, 'a'); } - /** An extent of this size is too small to contain one document containing payload8k(). */ - static int tooSmall() { return 1*1024; } - /** An extent of this size fits one document. */ - static int fitsOne() { return 10*1024; } - /** An extent of this size fits many documents. */ - static int fitsMany() { return 50*1024; } - - // to be defined by subclasses - virtual BSONArray extentSizes() const = 0; - virtual int numDocs() const = 0; - virtual int numHops() const = 0; - virtual PlanStage::StageState finalState() const { return PlanStage::ADVANCED; } - virtual int tsGte() const { return 1; } - }; - - /** - * Test hopping over a single empty extent. - * - * Collection structure: - * - * [--- extent 0 --] [ ext 1 ] [--- extent 2 ---] - * [ {_id: 0} ] [<empty>] [ {_id: 1} ] - */ - class OplogStartOneEmptyExtent : public SizedExtentHopBase { - virtual int numDocs() const { return 2; } - virtual int numHops() const { return 1; } - virtual BSONArray extentSizes() const { - return BSON_ARRAY( fitsOne() << tooSmall() << fitsOne() ); - } - }; - - /** - * Test hopping over two consecutive empty extents. - * - * Collection structure: - * - * [--- extent 0 --] [ ext 1 ] [ ext 2 ] [--- extent 3 ---] - * [ {_id: 0} ] [<empty>] [<empty>] [ {_id: 1} ] - */ - class OplogStartTwoEmptyExtents : public SizedExtentHopBase { - virtual int numDocs() const { return 2; } - virtual int numHops() const { return 1; } - virtual BSONArray extentSizes() const { - return BSON_ARRAY( fitsOne() << tooSmall() << tooSmall() << fitsOne() ); - } - }; - - /** - * Two extents, each filled with several documents. This - * should require us to make just a single extent hop. - */ - class OplogStartTwoFullExtents : public SizedExtentHopBase { - virtual int numDocs() const { return 10; } - virtual int numHops() const { return 1; } - virtual BSONArray extentSizes() const { - return BSON_ARRAY( fitsMany() << fitsMany() ); - } - }; - - /** - * Four extents in total. Three are populated with multiple - * documents, but one of the middle extents is empty. This - * should require two extent hops. - */ - class OplogStartThreeFullOneEmpty : public SizedExtentHopBase { - virtual int numDocs() const { return 14; } - virtual int numHops() const { return 2; } - virtual BSONArray extentSizes() const { - return BSON_ARRAY( fitsMany() << fitsMany() << tooSmall() << fitsMany() ); - } - }; - - /** - * Test that extent hopping mode works properly in the - * special case of one extent. - */ - class OplogStartOneFullExtent : public SizedExtentHopBase { - virtual int numDocs() const { return 4; } - virtual int numHops() const { return 0; } - virtual BSONArray extentSizes() const { - return BSON_ARRAY( fitsMany() ); - } - }; - - /** - * Collection structure: - * - * [ ext 0 ] [--- extent 1 --] [--- extent 2 ---] - * [<empty>] [ {_id: 0} ] [ {_id: 1} ] - */ - class OplogStartFirstExtentEmpty : public SizedExtentHopBase { - virtual int numDocs() const { return 2; } - virtual int numHops() const { return 1; } - virtual BSONArray extentSizes() const { - return BSON_ARRAY( tooSmall() << fitsOne() << fitsOne() ); - } - }; - - /** - * Find that we need to start from the very beginning of - * the collection (the EOF case), after extent hopping - * to the beginning. - * - * This requires two hops: one between the two extents, - * and one to hop back to the "null extent" which precedes - * the first extent. - */ - class OplogStartEOF : public SizedExtentHopBase { - virtual int numDocs() const { return 2; } - virtual int numHops() const { return 2; } - virtual BSONArray extentSizes() const { - return BSON_ARRAY( fitsOne() << fitsOne() ); - } - virtual PlanStage::StageState finalState() const { return PlanStage::IS_EOF; } - virtual int tsGte() const { return 0; } - }; - - class All : public Suite { - public: - All() : Suite("oplogstart") { } - - void setupTests() { - add< OplogStartIsOldest >(); - add< OplogStartIsNewest >(); - - // These tests rely on extent allocation details specific to mmapv1. - // TODO figure out a way to generically test this. - if (getGlobalServiceContext()->getGlobalStorageEngine()->isMmapV1()) { - add< OplogStartIsNewestExtentHop >(); - add< OplogStartOneEmptyExtent >(); - add< OplogStartTwoEmptyExtents >(); - add< OplogStartTwoFullExtents >(); - add< OplogStartThreeFullOneEmpty >(); - add< OplogStartOneFullExtent >(); - add< OplogStartFirstExtentEmpty >(); - add< OplogStartEOF >(); - } +/** + * Find that we need to start from the very beginning of + * the collection (the EOF case), after extent hopping + * to the beginning. + * + * This requires two hops: one between the two extents, + * and one to hop back to the "null extent" which precedes + * the first extent. + */ +class OplogStartEOF : public SizedExtentHopBase { + virtual int numDocs() const { + return 2; + } + virtual int numHops() const { + return 2; + } + virtual BSONArray extentSizes() const { + return BSON_ARRAY(fitsOne() << fitsOne()); + } + virtual PlanStage::StageState finalState() const { + return PlanStage::IS_EOF; + } + virtual int tsGte() const { + return 0; + } +}; + +class All : public Suite { +public: + All() : Suite("oplogstart") {} + + void setupTests() { + add<OplogStartIsOldest>(); + add<OplogStartIsNewest>(); + + // These tests rely on extent allocation details specific to mmapv1. + // TODO figure out a way to generically test this. + if (getGlobalServiceContext()->getGlobalStorageEngine()->isMmapV1()) { + add<OplogStartIsNewestExtentHop>(); + add<OplogStartOneEmptyExtent>(); + add<OplogStartTwoEmptyExtents>(); + add<OplogStartTwoFullExtents>(); + add<OplogStartThreeFullOneEmpty>(); + add<OplogStartOneFullExtent>(); + add<OplogStartFirstExtentEmpty>(); + add<OplogStartEOF>(); } - }; + } +}; - SuiteInstance<All> oplogStart; +SuiteInstance<All> oplogStart; -} // namespace OplogStartTests +} // namespace OplogStartTests |