Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

SERVER-6700 allow updates to TTL expiration time using collMod

  • Loading branch information...
commit a3a5fd617cc9bc7df0083c60094ec6d804ddba3a 1 parent 009df3c
@matulef matulef authored
View
71 jstests/collmod.js
@@ -0,0 +1,71 @@
+// Basic js tests for the collMod command.
+// Test setting the usePowerOf2Sizes flag, and modifying TTL indexes.
+
+var coll = "collModTest";
+db.createCollection( coll );
+var t = db.getCollection( coll );
+
+// Verify the new collection has userFlags set to 0
+assert.eq( t.stats().userFlags , 0 , "fresh collection doesn't have userFlags = 0 ");
+
+// Modify the collection with the usePowerOf2Sizes flag. Verify userFlags now = 1.
+var res = db.runCommand( { "collMod" : coll, "usePowerOf2Sizes" : true } );
+printjson( res );
+assert.eq( res.ok , 1 , "collMod failed" );
+assert.eq( t.stats().userFlags , 1 , "modified collection should have userFlags = 1 ");
+
+// Try to modify it with some unrecognized value
+var res = db.runCommand( { "collMod" : coll, "unrecognized" : true } );
+printjson( res );
+assert.eq( res.ok , 0 , "collMod shouldn't return ok with unrecognized value" );
+
+// add a TTL index
+t.ensureIndex( {a : 1}, { "expireAfterSeconds": 50 } )
+assert.eq( 1, db.system.indexes.count( { key : {a:1}, expireAfterSeconds : 50 } ),
+ "TTL index not added" );
+
+// try to modify it with a bad key pattern
+var res = db.runCommand( { "collMod" : coll,
+ "index" : { "keyPattern" : "bad" , "expireAfterSeconds" : 100 } } );
+printjson( res );
+assert.eq( 0 , res.ok , "mod shouldn't work with bad keypattern");
+
+// try to modify it without expireAfterSeconds field
+var res = db.runCommand( { "collMod" : coll,
+ "index" : { "keyPattern" : {a : 1} } } );
+printjson( res );
+assert.eq( 0 , res.ok , "TTL mod shouldn't work without expireAfterSeconds");
+
+// try to modify it with a non-numeric expireAfterSeconds field
+var res = db.runCommand( { "collMod" : coll,
+ "index" : { "keyPattern" : {a : 1}, "expireAfterSeconds" : "100" } } );
+printjson( res );
+assert.eq( 0 , res.ok , "TTL mod shouldn't work with non-numeric expireAfterSeconds");
+
+// this time modifying should finally work
+var res = db.runCommand( { "collMod" : coll,
+ "index" : { "keyPattern" : {a : 1}, "expireAfterSeconds" : 100 } } );
+printjson( res );
+assert.eq( 1, db.system.indexes.count( { key : {a:1}, expireAfterSeconds : 100 } ),
+ "TTL index not modified" );
+
+// try to modify a faulty TTL index with a non-numeric expireAfterSeconds field
+t.dropIndex( {a : 1 } );
+t.ensureIndex( {a : 1} , { "expireAfterSeconds": "50" } )
+var res = db.runCommand( { "collMod" : coll,
+ "index" : { "keyPattern" : {a : 1} , "expireAfterSeconds" : 100 } } );
+printjson( res );
+assert.eq( 0, res.ok, "shouldn't be able to modify faulty index spec" );
+
+// try with new index, this time set both expireAfterSeconds and the usePowerOf2Sizes flag
+t.dropIndex( {a : 1 } );
+t.ensureIndex( {a : 1} , { "expireAfterSeconds": 50 } )
+var res = db.runCommand( { "collMod" : coll ,
+ "usePowerOf2Sizes" : false,
+ "index" : { "keyPattern" : {a : 1} , "expireAfterSeconds" : 100 } } );
+printjson( res );
+assert.eq( 1, res.ok, "should be able to modify both userFlags and expireAfterSeconds" );
+assert.eq( t.stats().userFlags , 0 , "userflags should be 0 now");
+assert.eq( 1, db.system.indexes.count( { key : {a:1}, expireAfterSeconds : 100 } ),
+ "TTL index should be 100 now" );
+
View
16 jstests/slowNightly/ttl_repl.js
@@ -3,6 +3,7 @@
* and secondary get userFlags=1 (indicating that usePowerOf2Sizes is on),
* and check that the correct # of docs age out.
* Part 2: Add a new member to the set. Check it also gets userFlags=1 and correct # of docs.
+ * Part 3: Change the TTL expireAfterSeconds field and check successful propogation to secondary.
*/
var rt = new ReplSetTest( { name : "ttl_repl" , nodes: 2 } );
@@ -18,8 +19,9 @@ var slave1 = rt.liveNodes.slaves[0];
// shortcuts
var masterdb = master.getDB( 'd' );
+var slave1db = slave1.getDB( 'd' );
var mastercol = masterdb[ 'c' ];
-var slave1col = slave1.getDB( 'd' )[ 'c' ];
+var slave1col = slave1db[ 'c' ];
// create new collection. insert 24 docs, aged at one-hour intervalss
mastercol.drop();
@@ -77,5 +79,17 @@ printjson( slave2col.stats() );
assert.eq( 1 , slave2col.stats().userFlags , "userFlags not 1 on new secondary");
assert.eq( 6 , slave2col.count() , "wrong number of docs on new secondary");
+
+/******* Part 3 *****************/
+//Check that the collMod command successfully updates the expireAfterSeconds field
+masterdb.runCommand( { collMod : "c",
+ index : { keyPattern : {x : 1}, expireAfterSeconds : 10000} } );
+
+var newTTLindex = { "key": { "x" : 1 } , "ns": "d.c" , "expireAfterSeconds" : 10000 };
+assert.eq( 1, masterdb.system.indexes.find( newTTLindex ).count(),
+ "primary index didn't get updated");
+assert.eq( 1, slave1db.system.indexes.find( newTTLindex ).count(),
+ "secondary index didn't get updated");
+
// finish up
rt.stopSet();
View
24 jstests/slowNightly/ttl_sharded.js
@@ -2,6 +2,8 @@
* - Creates a new collection with a TTL index
* - Shards it, and moves one chunk containing half the docs to another shard.
* - Checks that both shards have TTL index, and docs get deleted on both shards.
+ * - Run the collMod command to update the expireAfterSeconds field. Check that more docs get
+ * deleted.
*/
// start up a new sharded cluster
@@ -54,7 +56,7 @@ print("Shard 1 coll stats:")
printjson( shard1.getCollection( coll ).stats() );
// Check that TTL index (with expireAfterSeconds field) appears on both shards
-var ttlIndexPattern = { key: { "x" : 1 } , ns: dbname + "." + coll , expireAfterSeconds : 20000 };
+var ttlIndexPattern = { "key": { "x" : 1 } , "ns": ns , "expireAfterSeconds" : 20000 };
assert.eq( 1 ,
shard0.system.indexes.find( ttlIndexPattern ).count() ,
"shard0 does not have TTL index");
@@ -62,5 +64,25 @@ assert.eq( 1 ,
shard1.system.indexes.find( ttlIndexPattern ).count() ,
"shard1 does not have TTL index");
+// Check that the collMod command successfully updates the expireAfterSeconds field
+s.getDB( dbname ).runCommand( { collMod : coll,
+ index : { keyPattern : {x : 1}, expireAfterSeconds : 10000} } );
+
+var newTTLindex = { "key": { "x" : 1 } , "ns": ns , "expireAfterSeconds" : 10000 };
+assert.eq( 1 ,
+ shard0.system.indexes.find( newTTLindex ).count(),
+ "shard0 index didn't get updated");
+assert.eq( 1 ,
+ shard1.system.indexes.find( newTTLindex ).count(),
+ "shard1 index didn't get updated");
+
+assert.soon(
+ function() {
+ return t.count() < 6;
+ }, "new expireAfterSeconds value not taking effect" , 70 * 1000
+);
+assert.eq( 0 , t.find( { x : { $lt : new Date( now - 10000000 ) } } ).count() );
+assert.eq( 3 , t.count() );
+
s.stop();
View
84 src/mongo/db/dbcommands.cpp
@@ -1263,13 +1263,14 @@ namespace mongo {
class CollectionModCommand : public Command {
public:
CollectionModCommand() : Command( "collMod" ){}
- virtual bool slaveOk() const { return true; }
+ virtual bool slaveOk() const { return false; }
virtual LockType locktype() const { return WRITE; }
virtual bool logTheOp() { return true; }
virtual void help( stringstream &help ) const {
help <<
"Sets collection options.\n"
- "Example: { collMod: 'foo', usePowerOf2Sizes:true }";
+ "Example: { collMod: 'foo', usePowerOf2Sizes:true }\n"
+ "Example: { collMod: 'foo', index: {keyPattern: {a: 1}, expireAfterSeconds: 600} }";
}
virtual void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
@@ -1286,23 +1287,76 @@ namespace mongo {
errmsg = "ns does not exist";
return false;
}
-
+
bool ok = true;
- int oldFlags = nsd->userFlags();
-
- BSONObjIterator i( jsobj );
- while ( i.more() ) {
- const BSONElement& e = i.next();
+
+ BSONForEach( e, jsobj ) {
if ( str::equals( "collMod", e.fieldName() ) ) {
// no-op
}
else if ( str::equals( "usePowerOf2Sizes", e.fieldName() ) ) {
- result.appendBool( "usePowerOf2Sizes_old" , nsd->isUserFlagSet( NamespaceDetails::Flag_UsePowerOf2Sizes ) );
- if ( e.trueValue() ) {
- nsd->setUserFlag( NamespaceDetails::Flag_UsePowerOf2Sizes );
+ bool oldPowerOf2 = nsd->isUserFlagSet(NamespaceDetails::Flag_UsePowerOf2Sizes);
+ bool newPowerOf2 = e.trueValue();
+
+ if ( oldPowerOf2 != newPowerOf2 ) {
+ // change userFlags
+ result.appendBool( "usePowerOf2Sizes_old", oldPowerOf2 );
+
+ newPowerOf2 ? nsd->setUserFlag( NamespaceDetails::Flag_UsePowerOf2Sizes ) :
+ nsd->clearUserFlag( NamespaceDetails::Flag_UsePowerOf2Sizes );
+ nsd->syncUserFlags( ns ); // must keep system.namespaces up-to-date
+
+ result.appendBool( "usePowerOf2Sizes_new", newPowerOf2 );
}
- else {
- nsd->clearUserFlag( NamespaceDetails::Flag_UsePowerOf2Sizes );
+ }
+ else if ( str::equals( "index", e.fieldName() ) ) {
+ BSONObj indexObj = e.Obj();
+ BSONObj keyPattern = indexObj.getObjectField( "keyPattern" );
+
+ if ( keyPattern.isEmpty() ){
+ errmsg = "no keyPattern specified";
+ ok = false;
+ continue;
+ }
+
+ BSONElement newExpireSecs = indexObj["expireAfterSeconds"];
+ if ( newExpireSecs.eoo() ) {
+ errmsg = "no expireAfterSeconds field";
+ ok = false;
+ continue;
+ }
+ if ( ! newExpireSecs.isNumber() ) {
+ errmsg = "expireAfterSeconds field must be a number";
+ ok = false;
+ continue;
+ }
+
+ int idxNo = nsd->findIndexByKeyPattern( keyPattern );
+ if( idxNo < 0 ){
+ errmsg = str::stream() << "cannot find index " << keyPattern
+ << " for ns " << ns;
+ ok = false;
+ continue;
+ }
+
+ IndexDetails idx = nsd->idx( idxNo );
+ BSONElement oldExpireSecs = idx.info.obj().getField("expireAfterSeconds");
+ if( oldExpireSecs.eoo() ){
+ errmsg = "no expireAfterSeconds field to update";
+ ok = false;
+ continue;
+ }
+ if( ! oldExpireSecs.isNumber() ) {
+ errmsg = "existing expireAfterSeconds field is not a number";
+ ok = false;
+ continue;
+ }
+
+ if ( oldExpireSecs != newExpireSecs ) {
+ // change expireAfterSeconds
+ result.appendAs( oldExpireSecs, "expireAfterSeconds_old" );
+ nsd->updateTTLIndex( idxNo , newExpireSecs );
+ result.appendAs( newExpireSecs , "expireAfterSeconds_new" );
}
}
else {
@@ -1311,10 +1365,6 @@ namespace mongo {
}
}
- if ( oldFlags != nsd->userFlags() ) {
- nsd->syncUserFlags( ns );
- }
-
return ok;
}
} collectionModCommand;
View
26 src/mongo/db/namespace_details.cpp
@@ -738,6 +738,32 @@ namespace mongo {
_keysComputed = true;
}
+ void NamespaceDetails::updateTTLIndex( int idxNo , const BSONElement& newExpireSecs ) {
+ // Need to get the actual DiskLoc of the index to update. This is embedded in the 'info'
+ // object inside the IndexDetails.
+ IndexDetails idetails = idx( idxNo );
+ BSONElement oldExpireSecs = idetails.info.obj().getField("expireAfterSeconds");
+
+ // Important that we set the new value in-place. We are writing directly to the
+ // object here so must be careful not to overwrite with a longer numeric type.
+ massert( 16630, "new 'expireAfterSeconds' must be a number", newExpireSecs.isNumber() );
+ BSONElementManipulator manip( oldExpireSecs );
+ switch( oldExpireSecs.type() ) {
+ case EOO:
+ massert( 16631, "index does not have an 'expireAfterSeconds' field", false );
+ break;
+ case NumberInt:
+ case NumberDouble:
+ manip.SetNumber( newExpireSecs.numberDouble() );
+ break;
+ case NumberLong:
+ manip.SetLong( newExpireSecs.numberLong() );
+ break;
+ default:
+ massert( 16632, "current 'expireAfterSeconds' is not a number", false );
+ }
+ }
+
void NamespaceDetails::setSystemFlag( int flag ) {
getDur().writingInt(_systemFlags) |= flag;
}
View
6 src/mongo/db/namespace_details.h
@@ -308,6 +308,12 @@ namespace mongo {
const IndexDetails* findIndexByPrefix( const BSONObj &keyPattern ,
bool requireSingleKey );
+ /* Updates the expireAfterSeconds field of the given index to the value in newExpireSecs.
+ * The specified index must already contain an expireAfterSeconds field, and the value in
+ * that field and newExpireSecs must both be numeric.
+ */
+ void updateTTLIndex( int idxNo , const BSONElement& newExpireSecs );
+
const int systemFlags() const { return _systemFlags; }
bool isSystemFlagSet( int flag ) const { return _systemFlags & flag; }
Please sign in to comment.
Something went wrong with that request. Please try again.