3

There is this question which asks essentially the same question but is three questions in one and the 1/3 I'm after isn't really answered very well so I'm asking it again:

I'm starting an new MongoDB project (in NodeJS). For the required document _id field I'm considering using the string serialization of an ObjectId instead of MongoDB's default ObjectId (i.e. setting { _id: String(mongodb.ObjectId()), ...} instead of inbuilt default). Tell me why shouldn't I do this?

In general the argument for, is it makes comparisons easier:

  • If I grab an object identifier off the wire or out of some JSON document, I don't need to hydrate it into an ObjectId before doing comparison queries in a find or aggregation pipeline. And I don't need to know a priori a given field is an ObjectId and special case it.
  • It saves confusing about comparison in code if object identifiers are just always a strings. For example in NodeJS <ObjectId> === <String> is always false you have to use ObjectId.equals or ==.

I gather there was a very good reason MongoDB decided to use ObjectId over just strings. I mean sure, I can see some potential advantages but I never understood or found any clear documentation outlining why they did this.

3
  • Consider this as a general statement: Use always the proper data type, i.e. do not store ObjectId as string. Another rather common misuse is to store Date values as string - it's a design flaw and sooner or later it will create trouble. Commented Nov 22, 2022 at 9:11
  • The situation is less clear than with Date. ObjectId is only the default for _id. There are many examples even in the official MongoDB docs that use an integer or something else. General advice appreciated but I prefer not to follow general rules dogmatically w/o understanding reasoning behind them.
    – spinkus
    Commented Nov 24, 2022 at 3:01
  • I guess there are space and performance considerations. An ObjectId has always 12 bytes, string "63808b1c4d9cd4c7c184f40f" has 24 bytes and in general it can have any length and any character (not just hex symbols). The CPU can handle raw bit values more efficient than text strings. Strings in MongoDB are stored as UTF-8, which decoding is complex, because UTF-8 is a multi-byte characters set. Apart from that, nobody forces you to use ObjectId. You just need a unique _id value, in general the format/datatype is arbitrary. Commented Nov 25, 2022 at 10:41

1 Answer 1

2

Why shouldn't I do this? Having reflected on this I decided to not use a stringification of ObjectId over ObjectId. Reasons:

First, ObjectId is 12 bytes, not 24 bytes as @Wernfried pointed out.

Second, I'm using Typescript. Using Typescript and typing DB schema can remove much of the ambiguity around what your comparing. Yes _id as raw string is convenient sometimes, but convenient in the similar way scripting language is convenient over typed one - you'll pay for it when project becomes large.

Third, the mongodb docs state there is two benefits to using ObjectId:

  1. In mongosh, you can access the creation time of the ObjectId, using the ObjectId.getTimestamp() method.
  2. sorting on an _id field that stores ObjectId values is roughly equivalent to sorting by creation time

Point 1 isn't very useful and you can create an ObjectId from a string value trivially anyway. However, the aggregation date expression operators actually work with ObjectIds too.

Fourth, _id field is immutable so if you ever want to change it back you'll have to recreate each document with new _id value.

Fifth, a vague reason is along the lines of forward compatibility. Like for example, you can't currently use _id as a TTL index even though it contains a timestamp and work with other date operators. Maybe MongoDB will release that in future.

Finally, I've heard it can be indexed better but I'm not sure that's actually true (maybe in the Enterprise version or something ..) and haven't found any doc on it. A quick very basic test of random lookup in v5.0.9 show identical performance - for lookup.

Probably there are other arguments against I've missed. But in summary, although there may be legit use cases for using a unique strings for _id, if your just going to set it to the string value of an ObjectId it's probably not worth it on balance.

5
  • "... although docs don't state it AFAIK, the aggregation date expression operators actually seem to work over ObjectId." I think for all operators the documentation states like: "... must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID.", e.g. $dateAdd Commented Nov 27, 2022 at 13:37
  • Regarding your "quick very basic test". It is not very smart to run 10k insertOne commands, actually it's the worst you can do. The entire collection will be cached in your RAM, so this test does not mean anything. Commented Nov 27, 2022 at 13:47
  • 'Regarding your "quick very basic test". It is not very smart to run 10k insertOne commands, actually it's the worst you can do.' Yeah yeah I know that, but this isn't prod... The 10k number used to be 1e6 and I got OOM error on NodeJS interpreter so solved it by doing insertOne. I just chagend to 10k so people can run in short time. The index is still used if in memory or not and time in test is exactly the same so I think the test means soemthing but it's obviously not very good. If you can link me a better one objectively showing ObjectId out performing String on lookup I'd be grateful.
    – spinkus
    Commented Nov 27, 2022 at 17:25
  • "I think for all operators the documentation states like ..." Oh yeah, your correct. I'll update answer. Thanks!
    – spinkus
    Commented Nov 27, 2022 at 17:27
  • See Optimize Query Performance: "Index keys that are of the BinData type are more efficiently stored in the index if the binary subtype value is in the range of 0-7 or 128-135, and the length of the byte array is: 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, or 32." So, an index on ObjectId seems to be more efficient. Commented Nov 27, 2022 at 19:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.