Reputation: 3137
I have a document similar to this:
{
...
"LastAccess" : "2012-09-19T05:47:45.982Z", // Time of last document access
"Expires" : "2012-09-19T06:47:45.982Z", // Time this document expires
"MaxAge" : 3600, // Seconds to live
}
I believe the only safe way to accomplish what I want to is FindAndModify, so my question is, I want to update the LastAccess to the Current UTC Time (this is obviously easy), but then I want to update the Expires by the LastAccess + MaxAge where MaxAge can be different on a document to document basis.
In short, is there anyway to update a field in a document (in this case Expires) while it is dependent on another field on the document, in one FindAndModify call?
Upvotes: 2
Views: 1515
Reputation: 284
You cannot use existing fields in the document within the update query. Here are a few options.
You can use a time to live collection if you want to delete the document when it expires. In time to live collections, documents are automatically deleted after a specified duration, which means you can eliminate the "Expires" and "MaxAge" fields entirely.
Create the TTL index with a 30 second lifetime:
mongos> db.last.ensureIndex({ "LastAccess" : 1 }, { "expireAfterSeconds" : 30 })
Insert our documents:
mongos> db.last.insert({ "Value" : "No Expiration" })
mongos> db.last.insert({ "Value" : "Expiration", "LastAccess": new Date() })
mongos> db.last.find()
{ "_id" : ObjectId("505d15ac283b060dbc637ec0"), "Value" : "No Expiration" }
{ "_id" : ObjectId("505d15b1283b060dbc637ec1"), "Value" : "Expiration", "LastAccess" : ISODate("2012-09-22T01:34:41.102Z") }
Sleep for 15 seconds:
mongos> sleep(15000)
null
mongos> db.last.find()
{ "_id" : ObjectId("505d15ac283b060dbc637ec0"), "Value" : "No Expiration" }
{ "_id" : ObjectId("505d15b1283b060dbc637ec1"), "Value" : "Expiration", "LastAccess" : ISODate("2012-09-22T01:34:41.102Z") }
Update our access time:
mongos> db.last.update({ "Value" : "Expiration" }, { "$set" : { "LastAccess" : new Date() }})
mongos> sleep(15000)
null
Both documents are still around:
mongos> db.last.find()
{ "_id" : ObjectId("505d15ac283b060dbc637ec0"), "Value" : "No Expiration" }
{ "LastAccess" : ISODate("2012-09-22T01:35:07.629Z"), "Value" : "Expiration", "_id" : ObjectId("505d15b1283b060dbc637ec1") }
Sleep for 30 seconds:
mongos> sleep(30000)
null
Now only our document without an expiration is around:
mongos> db.last.find()
{ "_id" : ObjectId("505d15ac283b060dbc637ec0"), "Value" : "No Expiration" }
Note that the process that deletes the documents only runs once per minute, so the time something is deleted may be up to a minute after its actual expiration date.
Insert a document with the required values:
mongos> db.last.insert({"Value":"oldvalue","LastAccess":new Date(),"Expires":new Date((new Date()).valueOf() + 3200),"MaxAge":3200,InProgress:false})
mongos> db.last.find()
{ "_id" : ObjectId("505b89292271f63498810600"), "Value" : "oldvalue", "LastAccess" : ISODate("2012-09-20T21:22:49.637Z"), "Expires" : ISODate("2012-09-20T21:22:52.837Z"), "MaxAge" : 3200, "InProgress" : false }
Modify the InProgress field to warn others that we are looking at the MaxAge field (if you don't care if someone changes the MaxAge field while you are doing this, you don't need the InProgress field and you can just do a find on this step):
mongos> doc = db.last.findAndModify({ "query" : { "Value" : "oldvalue", "InProgress" : false }, "update" : { "$set" : { "InProgress" : true } } });
{
"_id" : ObjectId("505b89292271f63498810600"),
"Value" : "oldvalue",
"LastAccess" : ISODate("2012-09-20T21:22:49.637Z"),
"Expires" : ISODate("2012-09-20T21:22:52.837Z"),
"MaxAge" : 3200,
"InProgress" : false
}
mongos> db.last.find()
{ "_id" : ObjectId("505b89292271f63498810600"), "Value" : "oldvalue", "LastAccess" : ISODate("2012-09-20T21:22:49.637Z"), "Expires" : ISODate("2012-09-20T21:22:52.837Z"), "MaxAge" : 3200, "InProgress" : true }
Actually update the document with the new expiration date, and set InProgress to false to mark that we are no longer looking at MaxAge:
mongos> db.last.findAndModify({ "query" : { "Value" : "oldvalue", "InProgress" : true }, "update" : { "$set" : { "Value" : "newvalue", "LastAccess" : new Date(), "Expires" : new Date((new Date()).valueOf() + doc.MaxAge), "InProgress" : false } }, "new" : true });
{
"_id" : ObjectId("505b89292271f63498810600"),
"Value" : "newvalue",
"LastAccess" : ISODate("2012-09-20T21:23:29.058Z"),
"Expires" : ISODate("2012-09-20T21:23:32.258Z"),
"MaxAge" : 3200,
"InProgress" : false
}
db.eval() could also be used here, but should be avoided for performance reasons
Upvotes: 1