65

The ObjectId used as the default key in mongodb documents has embedded timestamp (calling objectid.generation_time returns a datetime object). So it is possible to use this generation time instead of keeping a separate creation timestamp? How will you be able to sort by creation time or query for the last N items efficiently using this embedded timestamp?

Alex Bitek
  • 6,529
  • 5
  • 47
  • 77
kefeizhou
  • 6,234
  • 10
  • 42
  • 55

7 Answers7

103

I suppose since MongoDB ObjectId contain a timestamp, you can sort by 'created date' if you will sort by objectId:

items.find.sort( [['_id', -1]] ) // get all items desc by created date.

And if you want last 30 created items you can use following query:

items.find.sort( [['_id', -1]] ).limit(30) // get last 30 createad items 

I am actualy not sure,i just suppose that ordering by _id should work as described above. I'll create some tests later.

Update:

Yes it is so. If you order by _id you will automatically order by _id created date. I've done small test in c#, mb someone interest in it:

  public class Item
  {
    [BsonId]
    public ObjectId Id { get; set; }

    public DateTime CreatedDate { get; set; }

    public int Index { get; set; }
  }



 [TestMethod]
 public void IdSortingTest()
 {
   var server = MongoServer.Create("mongodb://localhost:27020");
   var database = server.GetDatabase("tesdb");

   var collection = database.GetCollection("idSortTest");
   collection.RemoveAll();

   for (int i = 0; i <= 500; i++)
   {
     collection.Insert(new Item() { 
             Id = ObjectId.GenerateNewId(), 
             CreatedDate = DateTime.Now, 
             Index = i });
   }

   var cursor = collection.FindAllAs<Item>();
   cursor.SetSortOrder(SortBy.Descending("_id"));
   var itemsOrderedById = cursor.ToList();

   var cursor2 = collection.FindAllAs<Item>();
   cursor2.SetSortOrder(SortBy.Descending("CreatedDate"));
   var itemsOrderedCreatedDate = cursor.ToList();

   for (int i = 0; i <= 500; i++)
   {
     Assert.AreEqual(itemsOrderedById[i].Index, itemsOrderedCreatedDate[i].Index);
   }
}
slezadav
  • 6,104
  • 7
  • 40
  • 61
Andrew Orsich
  • 52,935
  • 16
  • 139
  • 134
  • 21
    I'm pretty sure this is only correct when inserting using one process, bc. Mongo objectids are created using something akin to hilo sequences. More specific: `A BSON ObjectID is a 12-byte value consisting of a 4-byte timestamp (seconds since epoch), a 3-byte machine id, a 2-byte process id, and a 3-byte counter` – Geert-Jan Jan 03 '13 at 09:10
  • 10
    @Geert-Jan Because the time stamp is the [most significant part](http://en.wikipedia.org/wiki/Most_significant_bit) of an `ObjectId`, it will sort correctly across inserts from multiple processes (to one second precision, assuming the process clocks are well synchronized). To confirm this, see the [implementation of `getTimeStamp()` uses `.slice(0,8)`](https://github.com/mongodb/mongo/blob/v2.4.1/src/mongo/shell/types.js#L324). This `(0,8)` is selecting the most significant four bytes of the `ObjectId` as the time stamp. – davetapley May 01 '13 at 16:25
  • 7
    @dukedave: Sure that would work if 1 sec resolution is enough. – Geert-Jan May 01 '13 at 16:53
23

Yes, you can use the generation_time of BSON ObjectId for the purposes you want. So,

db.collection.find().sort({ _id : -1 }).limit(10)

will return the last 10 created items. However, since the embedded timestamps have a one second precision, multiple items within any second are stored in the order of their creation.

user105991
  • 531
  • 2
  • 6
3

From: http://www.mongodb.org/display/DOCS/Object+IDs#ObjectIDs-DocumentTimestamps

"sorting on an _id field that stores ObjectId values is roughly equivalent to sorting by creation time, although this relationship is not strict with ObjectId values generated on multiple systems within a single second."

wprl
  • 24,489
  • 11
  • 55
  • 70
3

The code to convert a DateTime to its corresponding timestamp with the c# driver is as follows:

    public static ObjectId ToObjectId(this DateTime dateTime)
    {
        var timestamp = (int)(dateTime - BsonConstants.UnixEpoch).TotalSeconds;
        return new ObjectId(timestamp, 0, 0, 0);
    }

More info here: http://www.danharman.net/2011/10/26/mongodb-ninjitsu-using-objectid-as-a-timestamp/

DanH
  • 3,772
  • 2
  • 27
  • 31
  • 3
    `ObjectId` constructor now has an overload that accepts DateTime as timestamp, so your conversion is no longer necessary. Simply do `new ObjectId(dateTime, 0, 0, 0);` – Zaid Masud Aug 23 '13 at 09:02
1

To query projects created within 7 days, I use below snippet:

db.getCollection('projects').find({
  $where: function() {
    // last 7 days
    return Date.now() - this._id.getTimestamp() < (7 * 24 * 60 * 60 * 1000)
  }
}).sort({
  '_id': -1
})

and if you want to get items with specified fields:

db.getCollection('projects').find({
  $where: function() {
    // last 7 days
    return Date.now() - this._id.getTimestamp() < (7 * 24 * 60 * 60 * 1000)
  }
}).sort({
  '_id': -1
}).toArray().map(function(item) {
  var res = {};
  res['Project Name'] = item.config.label;
  res['Author'] = item.author;
  res['Created At'] = item._id.getTimestamp().toLocaleDateString();
  res['Last Modified Date'] = item.config.lastModifDate.toLocaleString();
  return res;
});

it will return something like this:

[{
  "Project Name": "Newsletter",
  "Author": "larry.chen",
  "Created At": "Thursday, January 19, 2017",
  "Last Modified Date": "Thursday, January 19, 2017 17:05:40"
}...]

PS: the software I use to connect to MongoDB is Robo 3T

Hope this will help you.

Chen Dachao
  • 1,736
  • 2
  • 21
  • 36
  • 3
    not that I know much of that, but your suggested solution looks insanely inefficient to me. what do you do when you have 10kk entries? should the `$where` function applied to each and one of them? – phil294 Nov 30 '18 at 21:32
  • Sorry I didn't get you – Chen Dachao Dec 02 '18 at 13:42
1

See

http://www.mongodb.org/display/DOCS/Object+IDs#ObjectIDs-DocumentTimestamps

Likely doable however I would always prefer having a dedicated timestamp instead of relying on some such internals like timestamp somehow embedded in some object id.

  • Please explain why it's doable, how can you efficiently sort data without a btree index? – kefeizhou Feb 26 '11 at 07:28
  • 1
    When I say *likely* doable then I must not explain everything in detail. Please check the given link and you will detect yourself that the ObjectIds are increasing only. And as I said: go with a standard created field. I must not explain everything in depth which I would never do. –  Feb 26 '11 at 08:23
  • 5
    sorry but this does not answer the question at all – kefeizhou Feb 26 '11 at 16:22
1

For those wanting to truly use ObjectId for datetime, and not just rely on the fact that ObjectId's are always increasing over time and can therefore be used to order documents by creation time indirection, then here's how:

One can create their filter criteria to return documents whose IDs were made in some datetime range (in Python) by making a dummy ObjectID.from_datetime() like so:

# gets docs which were created in last 5 minutes
resp = await collection.update_one({'_id': {'$gte': ObjectId.from_datetime(datetime.utcnow() - timedelta(minutes=5))}},
hamx0r
  • 4,081
  • 1
  • 33
  • 46
  • At least for the Rust MongoDB driver, this is now considered bad practice (and has been removed in the newer versions): https://github.com/mongodb/bson-rust/pull/153 – Matthew Trent Feb 02 '23 at 10:34