1
//@file indexupdatetests.cpp : mongo/db/index_update.{h,cpp} tests
4
* Copyright (C) 2012 10gen Inc.
6
* This program is free software: you can redistribute it and/or modify
7
* it under the terms of the GNU Affero General Public License, version 3,
8
* as published by the Free Software Foundation.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU Affero General Public License for more details.
15
* You should have received a copy of the GNU Affero General Public License
16
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19
#include "mongo/db/index_update.h"
21
#include "mongo/db/btree.h"
22
#include "mongo/db/btreecursor.h"
23
#include "mongo/db/dbhelpers.h"
24
#include "mongo/db/kill_current_op.h"
25
#include "mongo/db/pdfile.h"
26
#include "mongo/db/sort_phase_one.h"
27
#include "mongo/platform/cstdint.h"
29
#include "mongo/dbtests/dbtests.h"
31
namespace IndexUpdateTests {
33
static const char* const _ns = "unittests.indexupdate";
34
DBDirectClient _client;
37
* Test fixture for a write locked test using collection _ns. Includes functionality to
38
* partially construct a new IndexDetails in a manner that supports proper cleanup in
41
class IndexBuildBase {
45
_client.createCollection( _ns );
48
_client.dropCollection( _ns );
49
killCurrentOp.reset();
52
/** @return IndexDetails for a new index on a:1, with the info field populated. */
53
IndexDetails& addIndexWithInfo() {
54
BSONObj indexInfo = BSON( "v" << 1 <<
55
"key" << BSON( "a" << 1 ) <<
58
int32_t lenWHdr = indexInfo.objsize() + Record::HeaderSize;
59
const char* systemIndexes = "unittests.system.indexes";
60
DiskLoc infoLoc = allocateSpaceForANewRecord( systemIndexes,
61
nsdetails( systemIndexes ),
64
Record* infoRecord = reinterpret_cast<Record*>( getDur().writingPtr( infoLoc.rec(),
66
memcpy( infoRecord->data(), indexInfo.objdata(), indexInfo.objsize() );
67
addRecordToRecListInExtent( infoRecord, infoLoc );
68
IndexDetails& id = nsdetails( _ns )->getNextIndexDetails( _ns );
69
nsdetails( _ns )->addIndex( _ns );
70
id.info.writing() = infoLoc;
74
Client::WriteContext _ctx;
77
/** addKeysToPhaseOne() adds keys from a collection's documents to an external sorter. */
78
class AddKeysToPhaseOne : public IndexBuildBase {
81
// Add some data to the collection.
83
for( int32_t i = 0; i < nDocs; ++i ) {
84
_client.insert( _ns, BSON( "a" << i ) );
86
IndexDetails& id = addIndexWithInfo();
87
// Create a SortPhaseOne.
88
SortPhaseOne phaseOne;
89
ProgressMeterHolder pm (cc().curop()->setMessage("AddKeysToPhaseOne",
90
"AddKeysToPhaseOne Progress",
93
// Add keys to phaseOne.
94
addKeysToPhaseOne( _ns, id, BSON( "a" << 1 ), &phaseOne, nDocs, pm.get(), true );
95
// Keys for all documents were added to phaseOne.
96
ASSERT_EQUALS( static_cast<uint64_t>( nDocs ), phaseOne.n );
100
/** addKeysToPhaseOne() aborts if the current operation is killed. */
101
class InterruptAddKeysToPhaseOne : public IndexBuildBase {
103
InterruptAddKeysToPhaseOne( bool mayInterrupt ) :
104
_mayInterrupt( mayInterrupt ) {
107
// It's necessary to index sufficient keys that a RARELY condition will be triggered.
109
// Add some data to the collection.
110
for( int32_t i = 0; i < nDocs; ++i ) {
111
_client.insert( _ns, BSON( "a" << i ) );
113
IndexDetails& id = addIndexWithInfo();
114
// Create a SortPhaseOne.
115
SortPhaseOne phaseOne;
116
ProgressMeterHolder pm (cc().curop()->setMessage("InterruptAddKeysToPhaseOne",
117
"InterruptAddKeysToPhaseOne Progress",
120
// Register a request to kill the current operation.
121
cc().curop()->kill();
122
if ( _mayInterrupt ) {
123
// Add keys to phaseOne.
124
ASSERT_THROWS( addKeysToPhaseOne( _ns,
132
// Not all keys were added to phaseOne due to the interrupt.
133
ASSERT( static_cast<uint64_t>( nDocs ) > phaseOne.n );
136
// Add keys to phaseOne.
137
addKeysToPhaseOne( _ns,
144
// All keys were added to phaseOne despite to the kill request, because
145
// mayInterrupt == false.
146
ASSERT_EQUALS( static_cast<uint64_t>( nDocs ), phaseOne.n );
153
/** buildBottomUpPhases2And3() builds a btree from the keys in an external sorter. */
154
class BuildBottomUp : public IndexBuildBase {
157
IndexDetails& id = addIndexWithInfo();
158
// Create a SortPhaseOne.
159
SortPhaseOne phaseOne;
160
phaseOne.sorter.reset( new BSONObjExternalSorter( id.idxInterface(),
161
BSON( "a" << 1 ) ) );
162
// Add index keys to the phaseOne.
164
for( int32_t i = 0; i < nKeys; ++i ) {
165
phaseOne.sorter->add( BSON( "a" << i ), /* dummy disk loc */ DiskLoc(), false );
167
phaseOne.nkeys = phaseOne.n = nKeys;
168
phaseOne.sorter->sort( false );
169
// Set up remaining arguments.
171
CurOp* op = cc().curop();
172
ProgressMeterHolder pm (op->setMessage("BuildBottomUp",
173
"BuildBottomUp Progress",
178
// The index's root has not yet been set.
179
ASSERT( id.head.isNull() );
180
// Finish building the index.
181
buildBottomUpPhases2And3<V1>( true,
191
// The index's root is set after the build is complete.
192
ASSERT( !id.head.isNull() );
193
// Create a cursor over the index.
194
scoped_ptr<BtreeCursor> cursor(
195
BtreeCursor::make( nsdetails( _ns ),
197
BSON( "" << -1 ), // startKey below minimum key.
198
BSON( "" << nKeys ), // endKey above maximum key.
199
true, // endKeyInclusive true.
200
1 // direction forward.
202
// Check that the keys in the index are the expected ones.
203
int32_t expectedKey = 0;
204
for( ; cursor->ok(); cursor->advance(), ++expectedKey ) {
205
ASSERT_EQUALS( expectedKey, cursor->currKey().firstElement().number() );
207
ASSERT_EQUALS( nKeys, expectedKey );
211
/** buildBottomUpPhases2And3() aborts if the current operation is interrupted. */
212
class InterruptBuildBottomUp : public IndexBuildBase {
214
InterruptBuildBottomUp( bool mayInterrupt ) :
215
_mayInterrupt( mayInterrupt ) {
218
IndexDetails& id = addIndexWithInfo();
219
// Create a SortPhaseOne.
220
SortPhaseOne phaseOne;
221
phaseOne.sorter.reset( new BSONObjExternalSorter( id.idxInterface(),
222
BSON( "a" << 1 ) ) );
223
// It's necessary to index sufficient keys that a RARELY condition will be triggered,
224
// but few enough keys that the btree builder will not create an internal node and check
225
// for an interrupt internally (which would cause this test to pass spuriously).
227
// Add index keys to the phaseOne.
228
for( int32_t i = 0; i < nKeys; ++i ) {
229
phaseOne.sorter->add( BSON( "a" << i ), /* dummy disk loc */ DiskLoc(), false );
231
phaseOne.nkeys = phaseOne.n = nKeys;
232
phaseOne.sorter->sort( false );
233
// Set up remaining arguments.
235
CurOp* op = cc().curop();
236
ProgressMeterHolder pm (op->setMessage("InterruptBuildBottomUp",
237
"InterruptBuildBottomUp Progress",
242
// The index's root has not yet been set.
243
ASSERT( id.head.isNull() );
244
// Register a request to kill the current operation.
245
cc().curop()->kill();
246
if ( _mayInterrupt ) {
247
// The build is aborted due to the kill request.
249
( buildBottomUpPhases2And3<V1>( true,
260
// The root of the index is not set because the build did not complete.
261
ASSERT( id.head.isNull() );
264
// The build is aborted despite the kill request because mayInterrupt == false.
265
buildBottomUpPhases2And3<V1>( true,
275
// The index's root is set after the build is complete.
276
ASSERT( !id.head.isNull() );
283
/** doDropDups() deletes the duplicate documents in the provided set. */
284
class DoDropDups : public IndexBuildBase {
287
// Insert some documents.
288
int32_t nDocs = 1000;
289
for( int32_t i = 0; i < nDocs; ++i ) {
290
_client.insert( _ns, BSON( "a" << ( i / 4 ) ) );
292
// Find the documents that are dups.
295
for( boost::shared_ptr<Cursor> cursor = theDataFileMgr.findAll( _ns );
297
cursor->advance() ) {
298
int32_t currA = cursor->current()[ "a" ].Int();
299
if ( currA == last ) {
300
dups.insert( cursor->currLoc() );
304
// Check the expected number of dups.
305
ASSERT_EQUALS( static_cast<uint32_t>( nDocs / 4 * 3 ), dups.size() );
307
doDropDups( _ns, nsdetails( _ns ), dups, true );
308
// Check that the expected number of documents remain.
309
ASSERT_EQUALS( static_cast<uint32_t>( nDocs / 4 ), _client.count( _ns ) );
313
/** doDropDups() aborts if the current operation is interrupted. */
314
class InterruptDoDropDups : public IndexBuildBase {
316
InterruptDoDropDups( bool mayInterrupt ) :
317
_mayInterrupt( mayInterrupt ) {
320
// Insert some documents.
321
int32_t nDocs = 1000;
322
for( int32_t i = 0; i < nDocs; ++i ) {
323
_client.insert( _ns, BSON( "a" << ( i / 4 ) ) );
325
// Find the documents that are dups.
328
for( boost::shared_ptr<Cursor> cursor = theDataFileMgr.findAll( _ns );
330
cursor->advance() ) {
331
int32_t currA = cursor->current()[ "a" ].Int();
332
if ( currA == last ) {
333
dups.insert( cursor->currLoc() );
337
// Check the expected number of dups. There must be enough to trigger a RARELY
338
// condition when deleting them.
339
ASSERT_EQUALS( static_cast<uint32_t>( nDocs / 4 * 3 ), dups.size() );
340
// Kill the current operation.
341
cc().curop()->kill();
342
if ( _mayInterrupt ) {
343
// doDropDups() aborts.
344
ASSERT_THROWS( doDropDups( _ns, nsdetails( _ns ), dups, _mayInterrupt ),
346
// Not all dups are dropped.
347
ASSERT( static_cast<uint32_t>( nDocs / 4 ) < _client.count( _ns ) );
350
// doDropDups() succeeds.
351
doDropDups( _ns, nsdetails( _ns ), dups, _mayInterrupt );
352
// The expected number of documents were dropped.
353
ASSERT_EQUALS( static_cast<uint32_t>( nDocs / 4 ), _client.count( _ns ) );
360
/** DataFileMgr::insertWithObjMod is killed if mayInterrupt is true. */
361
class InsertBuildIndexInterrupt : public IndexBuildBase {
364
// Insert some documents.
365
int32_t nDocs = 1000;
366
for( int32_t i = 0; i < nDocs; ++i ) {
367
_client.insert( _ns, BSON( "a" << i ) );
370
cc().curop()->reset();
371
// Request an interrupt.
372
killCurrentOp.killAll();
373
BSONObj indexInfo = BSON( "key" << BSON( "a" << 1 ) << "ns" << _ns << "name" << "a_1" );
374
// The call is interrupted because mayInterrupt == true.
375
ASSERT_THROWS( theDataFileMgr.insertWithObjMod( "unittests.system.indexes",
379
// The new index is not listed in system.indexes because the index build failed.
381
_client.count( "unittests.system.indexes",
382
BSON( "ns" << _ns << "name" << "a_1" ) ) );
386
/** DataFileMgr::insertWithObjMod is not killed if mayInterrupt is false. */
387
class InsertBuildIndexInterruptDisallowed : public IndexBuildBase {
390
// Insert some documents.
391
int32_t nDocs = 1000;
392
for( int32_t i = 0; i < nDocs; ++i ) {
393
_client.insert( _ns, BSON( "a" << i ) );
396
cc().curop()->reset();
397
// Request an interrupt.
398
killCurrentOp.killAll();
399
BSONObj indexInfo = BSON( "key" << BSON( "a" << 1 ) << "ns" << _ns << "name" << "a_1" );
400
// The call is not interrupted because mayInterrupt == false.
401
theDataFileMgr.insertWithObjMod( "unittests.system.indexes", indexInfo, false );
402
// The new index is listed in system.indexes because the index build completed.
404
_client.count( "unittests.system.indexes",
405
BSON( "ns" << _ns << "name" << "a_1" ) ) );
409
/** DataFileMgr::insertWithObjMod is killed when building the _id index. */
410
class InsertBuildIdIndexInterrupt : public IndexBuildBase {
413
// Recreate the collection as capped, without an _id index.
414
_client.dropCollection( _ns );
416
ASSERT( _client.runCommand( "unittests",
417
BSON( "create" << "indexupdate" <<
419
"size" << ( 10 * 1024 ) <<
420
"autoIndexId" << false ),
422
// Insert some documents.
423
int32_t nDocs = 1000;
424
for( int32_t i = 0; i < nDocs; ++i ) {
425
_client.insert( _ns, BSON( "_id" << i ) );
428
cc().curop()->reset();
429
// Request an interrupt.
430
killCurrentOp.killAll();
431
BSONObj indexInfo = BSON( "key" << BSON( "_id" << 1 ) <<
434
// The call is interrupted because mayInterrupt == true.
435
ASSERT_THROWS( theDataFileMgr.insertWithObjMod( "unittests.system.indexes",
439
// The new index is not listed in system.indexes because the index build failed.
440
ASSERT_EQUALS( 0U, _client.count( "unittests.system.indexes", BSON( "ns" << _ns ) ) );
445
* DataFileMgr::insertWithObjMod is not killed when building the _id index if mayInterrupt is
448
class InsertBuildIdIndexInterruptDisallowed : public IndexBuildBase {
451
// Recreate the collection as capped, without an _id index.
452
_client.dropCollection( _ns );
454
ASSERT( _client.runCommand( "unittests",
455
BSON( "create" << "indexupdate" <<
457
"size" << ( 10 * 1024 ) <<
458
"autoIndexId" << false ),
460
// Insert some documents.
461
int32_t nDocs = 1000;
462
for( int32_t i = 0; i < nDocs; ++i ) {
463
_client.insert( _ns, BSON( "_id" << i ) );
466
cc().curop()->reset();
467
// Request an interrupt.
468
killCurrentOp.killAll();
469
BSONObj indexInfo = BSON( "key" << BSON( "_id" << 1 ) <<
472
// The call is not interrupted because mayInterrupt == false.
473
theDataFileMgr.insertWithObjMod( "unittests.system.indexes", indexInfo, false );
474
// The new index is listed in system.indexes because the index build succeeded.
475
ASSERT_EQUALS( 1U, _client.count( "unittests.system.indexes", BSON( "ns" << _ns ) ) );
479
/** DBDirectClient::ensureIndex() is not interrupted. */
480
class DirectClientEnsureIndexInterruptDisallowed : public IndexBuildBase {
483
// Insert some documents.
484
int32_t nDocs = 1000;
485
for( int32_t i = 0; i < nDocs; ++i ) {
486
_client.insert( _ns, BSON( "a" << i ) );
489
cc().curop()->reset();
490
// Request an interrupt. killAll() rather than kill() is required because the direct
491
// client will build the index using a new opid.
492
killCurrentOp.killAll();
493
// The call is not interrupted.
494
_client.ensureIndex( _ns, BSON( "a" << 1 ) );
495
// The new index is listed in system.indexes because the index build completed.
497
_client.count( "unittests.system.indexes",
498
BSON( "ns" << _ns << "name" << "a_1" ) ) );
502
/** Helpers::ensureIndex() is not interrupted. */
503
class HelpersEnsureIndexInterruptDisallowed : public IndexBuildBase {
506
// Insert some documents.
507
int32_t nDocs = 1000;
508
for( int32_t i = 0; i < nDocs; ++i ) {
509
_client.insert( _ns, BSON( "a" << i ) );
512
cc().curop()->reset();
513
// Request an interrupt.
514
killCurrentOp.killAll();
515
// The call is not interrupted.
516
Helpers::ensureIndex( _ns, BSON( "a" << 1 ), false, "a_1" );
517
// The new index is listed in system.indexes because the index build completed.
519
_client.count( "unittests.system.indexes",
520
BSON( "ns" << _ns << "name" << "a_1" ) ) );
524
class IndexBuildInProgressTest : public IndexBuildBase {
527
// _id_ is at 0, so nIndexes == 1
532
int offset = IndexBuildsInProgress::get(_ns, "b_1");
533
ASSERT_EQUALS(2, offset);
535
IndexBuildsInProgress::remove(_ns, offset);
536
nsdetails(_ns)->indexBuildsInProgress--;
538
ASSERT_EQUALS(2, IndexBuildsInProgress::get(_ns, "c_1"));
539
ASSERT_EQUALS(3, IndexBuildsInProgress::get(_ns, "d_1"));
541
offset = IndexBuildsInProgress::get(_ns, "d_1");
542
IndexBuildsInProgress::remove(_ns, offset);
543
nsdetails(_ns)->indexBuildsInProgress--;
545
ASSERT_EQUALS(2, IndexBuildsInProgress::get(_ns, "c_1"));
546
ASSERT_EQUALS(-1, IndexBuildsInProgress::get(_ns, "d_1"));
548
offset = IndexBuildsInProgress::get(_ns, "a_1");
549
IndexBuildsInProgress::remove(_ns, offset);
550
nsdetails(_ns)->indexBuildsInProgress--;
552
ASSERT_EQUALS(1, IndexBuildsInProgress::get(_ns, "c_1"));
556
IndexDetails& halfAddIndex(const std::string& key) {
557
BSONObj indexInfo = BSON( "v" << 1 <<
558
"key" << BSON( key << 1 ) <<
560
"name" << (key+"_1"));
561
int32_t lenWHdr = indexInfo.objsize() + Record::HeaderSize;
562
const char* systemIndexes = "unittests.system.indexes";
563
DiskLoc infoLoc = allocateSpaceForANewRecord( systemIndexes,
564
nsdetails( systemIndexes ),
567
Record* infoRecord = reinterpret_cast<Record*>( getDur().writingPtr( infoLoc.rec(),
569
memcpy( infoRecord->data(), indexInfo.objdata(), indexInfo.objsize() );
570
addRecordToRecListInExtent( infoRecord, infoLoc );
571
IndexDetails& id = nsdetails( _ns )->getNextIndexDetails( _ns );
573
nsdetails(_ns)->indexBuildsInProgress++;
579
class IndexUpdateTests : public Suite {
582
Suite( "indexupdate" ) {
586
add<AddKeysToPhaseOne>();
587
add<InterruptAddKeysToPhaseOne>( false );
588
add<InterruptAddKeysToPhaseOne>( true );
589
add<BuildBottomUp>();
590
add<InterruptBuildBottomUp>( false );
591
add<InterruptBuildBottomUp>( true );
593
add<InterruptDoDropDups>( false );
594
add<InterruptDoDropDups>( true );
595
add<InsertBuildIndexInterrupt>();
596
add<InsertBuildIndexInterruptDisallowed>();
597
add<InsertBuildIdIndexInterrupt>();
598
add<InsertBuildIdIndexInterruptDisallowed>();
599
add<DirectClientEnsureIndexInterruptDisallowed>();
600
add<HelpersEnsureIndexInterruptDisallowed>();
601
add<IndexBuildInProgressTest>();
605
} // namespace IndexUpdateTests