I have a collection that has a createdAt
field in it. When I get multiple documents from this collection, I use this field to sort them from the newest to the oldest. The thing is, that multiple documents can somehow be created at the same millisecond and therefore have the exact same date. Because of that, I need my sorting to have some tie-breaker for those situations (I don’t care what order they will be, but it has to be consistent). I tried using the _id for that, and sorting using {createdAt: -1, _id: -1}
, but it creates problems with the use of indexes with some queries, making some very common queries slower and inefficient.
Then, I thought that instead of saving createdAt
as a Date object, I can just save it as a string (ISO 8601) and then add a random string at the end. That way even if multiple documents have the same Date, thanks to the random string, they will still have a consistent order. It’s like a built-in tie-breaker that makes it very simple to work with sorting or getting all of the documents that were created before some other document (something that I use a lot for pagination). The only thing that it adds to my code is that when I want to get the actual date, I need to remove that random string and convert it to Date
, but this is very easily done with new Date(createdAt.split('Z')[0] + 'Z')
.
Does this approach has any downsides? Something that I need to consider?
2
Answers
Sounds like you might want to consider using Universally Unique Lexicographically Sortable Identifier (ULID).
It will create a random ID, based on the timestamp. It encodes the timestamp and appends a random part to avoid conflicts for the exact same date.
Note, if I generate a ulid for the same data again, the first 10 bytes of the string will remain the same, the rest will be changed only. This is because of the ULID format:
So in your case, instead of having a compound index for
{createdAt: -1, _id:1}
, you’ll have a simple index based on 1 field that stores a ULID.But wait, how do you search based on some date then? How do you decode the original date back?
Searching documents larger than a date:
Returning the original date back:
The index bounds are not optimal, as you see in the explain plan:
Rewrite the query and try this one:
When you create index as
db.questions.createIndex({ tags: 1, createdAt: -1 })
then you get this plan:
Field
_id
is always unique (and always has an index), so it makes sense for MongoDB to query on this field and run OR with the second condition.Thus, you could also write: