ULID: Universally Unique Lexicographically Sortable Identifier
github.com
github.com
https://firebase.googleblog.com/2015/02/the-2120-ways-to-ens...
Lexicographically sortable identifiers are critical for any distributed data store if you want anything close to consistency. I've run into the issue of not having them and having to settle for some kind of <autoincrement_id><UUID> key and it's a huge PITA. How this wasn't considered database 101 decades ago just blows my mind.
I'd like to see a spec included in this for synchronizing clocks or using RAFT/Paxos for generating ULIDs with strong guarantees on sort order.
Also a minor gripe - I wish that the ULID spec checked for microsecond collisions instead of millisecond, because that would be more useful for realtime networked gaming and simulations.
wat?
This doesn't make any sense to me. Requiring that events be partially ordered if not totally ordered is a consistency requirement, sure, but I'm failing to understand how a database's consistency depends on the data you're putting in the database.
Maybe CRDTs would be better.
Of course the guarantee is still merely eventual consistency, but data locality does reduce the chance this becomes visible.
If you can manage the difficult task of getting your clocks synced that precisely, it's probably easy to shift two characters over from random to timestamp.
It's only a matter of time (in both senses I guess). See e.g. https://blogs.technet.microsoft.com/networking/2018/07/18/to...
Decades ago they didn't have distributed data stores.
Most computers don't have wall clocks with even more millisecond precision, much less microsecond. The wall clocks are generally OS tick precision (~5-10ms ballpark) with poorer still accuracy.
Seems like the use case is one that is niche. Maybe an extension?
https://softwareengineering.stackexchange.com/questions/3714...
https://stackoverflow.com/questions/15360245/when-using-uuid...
https://softwareengineering.stackexchange.com/questions/1394...
https://softwareengineering.stackexchange.com/questions/3469...
To find events between two specific points.
It can also be used in Dynamodb which is what we use in adtech.
First, the main benefit of ULID is that you can generate the IDs within your own software rather than rely on the database. We can queue them or even reference them before they land in the database. The traditional roundtrip has been eliminated.
Secondly, being able to sort ULIDs is a nice plus, although not that big of a deal. It makes it relatively easy to shard or partition databases, and it provides a convenient sort if you're not looking for extreme accuracy.
ULIDs are also shorter and slightly more user friendly than UUIDs.
In some circumstances we found the actual implementations to be slightly lacking. For example, the JS library for ULID once returned a 25 character string rather than the standard 26 characters, causing a big ruckus that we had to manually resolve.
No you cannot, unless you're running a single threaded server process on a single machine. What you can do is _gamble_ that you probably won't have a collision, which is the same thing you could do with regular UUIDs and you'd be (nearly) guaranteed to never hit a conflict with UUID4 (or probably UUID1/2 if you trusted your mac address uniqueness).
You may find this gamble acceptable and many people do, but you should be aware that pre-generation of UUIDs on independent systems without coordination is not a solvable problem - all attempts to do so rely on the extreme unlikelihood of a collusion to feel good about it or use some coordinated information (like a guaranteed unique mac address).
(Again, if it's good enough for you, right on - but it isn't theoretically safe)
This seems like a pointless distinction.
If I did the math right, you can generate 1,000,000 ULIDs per second (1000 per millisecond) for around 50 million years before you can expect to hit your first collision.
I don't know about you, but I'm pretty sure any system I build won't be running 50 million years from now. Not to mention that the timestamp portion of the ULID will overflow in a mere 9000 years.
Here is the Python code I used: https://gist.github.com/ngrilly/565bd27f4ad63244f72578844bca...
But I'm curious to know how you computed this?
On the other hand, your consistency assumptions may be broken if you rely on the sort order for causality relationship.
You can have ULID1 < ULID2 and yet the event associated with ULID1 may be caused by the ULID2 event.
If there is no fatal flaw with the inaccurate/non-deterministic sorting that wall-clock prefix provides and one is utilizing optimistic concurrency (strict create on PK, abort/retry on conflict), I don't see how it is unsafe
UUID4 is no more safer than ULID since they both rely on randomness.
> Monotonic sort order (correctly detects and handles the same millisecond)
EDIT: None of these concerns are directly related to the data format, but would be something I'd explain before users make false assumptions.
Since it starts at a random point and can't overflow, even if you generate a small number of IDs per millisecond you have a constant 1/2^79 chance of failing. The chance is small but reachable for a large network. (bitcoin does 2^88 hashes a day)
It could have just wrapped with no problem, because there's no possible node that could generate 2^80 IDs by itself. And if you have multiple nodes it doesn't help there either.
Does it? At 50M TH/s (https://www.blockchain.com/en/charts/hash-rate) that's 10^8 TH/s, or 10^8.10^12 = 10^20 hashes per second. There's less than 10^5 seconds so surely that's only 10^25 per day. If I've messed up each of these numbers by an order of magnitude, that would leave it still well under 10^30.
2^88 is absolutely enormous.
10^25 is close to right for one day, and 10^25 is equal to 2^83
2^88 is both absolutely enormous and a real number that bitcoin hits in less than a year.
So the point stands that a large deployment could rarely hit a 2^79 random failure case.
But that's sort of an implementation detail. You could write an implementation that did that and still be compatible with all other implementations.
I suppose, if you're particularly worried about the one-node situation.
> It's a little weird that they didn't just reserve or add some bits for sorting within milliseconds before or instead of having to increment the randomness.
There's no particular reason to make the fields separate. If you mask out the top bit of the random number then they can share and also make overflow effectively impossible.
It's also worth noting that there are two bits that go completely unused. They could eat overflow if the layout was slightly rearranged.
This algorithm doesn't work if you have multiple nodes, so I don't think any other situation is relevant here.
> There's no particular reason to make the fields separate. If you mask out the top bit of the random number then they can share and also make overflow effectively impossible
Yes, this is what I meant by reserving some bits.
> It's also worth noting that there are two bits that go completely unused. They could eat overflow if the layout was slightly rearranged.
Yeah, it's super weird they didn't do that. I guess they just really like having a power of two number of bits.
Then what's the random part for?
> Yes, this is what I meant by reserving some bits.
What I'm saying is, if you separate it out into an increment-field and a random-field, then you need a lot of bits and you need to fundamentally change how it works.
If you merely make sure your random number starts below some threshold, you only need 1 bit, or a small fraction of a bit. You would still increment the random number, but you wouldn't have to worry about hitting the max value if you always start between 0 and 2^79.9, for example.
It's for multiple nodes, which is why this algorithm doesn't make any sense for their use case.
What percentage of milliseconds in the day would that be?
It’s turtles all the way down from there, but it’s still valuable to scope “best case scenario” with probability math regardless of potential bugs in code.
Ugh. Crockford's base32 character set doesn't actually solve any of the problems it sets out to solve. Using it suggests to me some uncritical thinking.
It[0] says things like L is excluded, because "[uppercase] L Can be confused with 1". Ignoring the part where that is wildly inaccurate for any font that I've ever seen, why not then also remove G, 6, B, 8, Z, 2, S, or 5?
Reducing 1/I/i/L/l to just 1 does little to resolve visual ambiguity for users, because a user could just as easily read l or I instead of 1 or O instead of 0, because users don't know your made-up rules, which causes real problems because you often don't control both sides of the channel.
From the page you quote:
> ”When decoding, upper and lower case letters are accepted, and i and l will be treated as 1 and o will be treated as 0. When encoding, only upper case letters are used.”
False. ULID specification clearly says that these identifiers are case-insensitive. There's no "uppercase" requirement anywhere.
I declare that 5 can be misread as S and yet there they are. (And that L looks like 1 or I far less frequently than 5 looks like S)
it's a1so very different in monospaced fonts where these
identifiers are 1ike1y to be read. 'l' 1ooks much more
1ike '1' in these fonts.Aren't your complaints solved by simply having the parser resolve inputs 1/I/i/L/l to the symbol 1, and inputs 0/O to the symbol 0?
The default id in MongoDB does about the same. I always thought the MongoDB identifiers worked well for a lot of use cases.
Its also worth mentioning that integer incrementing ids can scale just fine if you reserve them in large blocks and they are no longer guaranteed to match insertion order, e.g: https://github.com/pingcap/docs/blob/master/sql/mysql-compat...
Just a public service reminder that "never trust the client" still applies, in case you were imagining user agents generating their own ULIDs to relive servers of the duty or something. Nothing prevents them from sending duplicate or out-of-order IDs.
Another issue is that there are cases when you want to represent the ID as barcode of reasonable size and readability, which invariably leads to decimal-only Code128 with at most ~30 digits.
What would be more interesting to me is a benchmark of how would using those as priary keys on Postgres would affect performance.
We've been using sortable epoch-based UUIDs as primary key for two different software products, storing them in Postgresql with the builtin UUID type, utilising binary mode. Performance is good.
1. Why not go for 16-character strings (instead of 26 or 36), with each character representing 8 bits?
Sure, you'd need 256 possible characters, but it's almost 2019 and Unicode has been with us for decades now. Surely we could be more cosmopolitan than Americentric ASCII and curate 256 characters for an 8-bit encoding?
With a 16-byte string, we could compare and process strings much faster, particularly with SIMD instructions like Intel/AMD's SSE 4.2 string comparison instructions. They're optimized for 16-byte strings and were introduced many years ago in the Nehalem architecture. That's a couple of generations before Sandy Bridge, so any server today is going to support it.
2. What does it mean to be "user-friendly" when it comes to these sorts of IDs? What are some scenarios where users interact with them or communicate or share them with someone or some authority? Crockford wanted his 32 character set to be easy to convey on a telephone, which seems like an expiring use case today. It seems like we should be able to use all sorts of non-ASCII characters now, without resorting to the Unicode Klingon or Tengwar blocks. Do we really need to be able to pronounce them all like Crockford anticipated?
NOTE: Unicode characters beyond the Basic Latin block take two or more bytes each, so we wouldn't be able to use them encoded as Unicode. What I'm advocating is a 256 character set with each character encoded in one byte, strictly for the purposes of generating these sorts of unique IDs represented by compact 16-character strings. Call it Duarte's Base256. All these other BaseN systems seem orthogonal to character encodings, or they just assume ASCII. I guess my idea would require both a character set and an encoding scheme. The latter would be similar to ISO/IEC 8859-15 and Windows 1252, but more complete with 256 printable characters. A lot of them could probably be emoji.
How good or terrible is this idea?
Given that, inventing a new character set seems pointless, since you'd compare the data using 128-bit binary operations already anyways (as opposed to lexicographical string comparisons). Which leads to the question - how is ULID different from UUID in practice?
Granted, it's pretty rare to work on a big enough system to have solid data on this.
If your system isn't resilient to collisions (and distributed ID schemes are usually the chosen to enable a system which isn't), you are gambling. Perhaps with very good odds, but gambling.
That just means they also generate 128 bits.
> Cryptographically secure source of randomness, if possible
I don't think this should be a goal. If this is for IDs, you usually want to optimize for speed of generating IDs and evenness of distribution (aside from merely reducing collisions.) The top answer at below link has a good top list of hash algorithms. None of them are cryptographic.
https://softwareengineering.stackexchange.com/questions/4955...
It's certainly a compromise, but I like their choice
At the same time, optimizing the speed of generation that much is not necessary in many use cases, when objects are created much less often than accessed - performance impact won't be noticeable. It will likely be faster than another common choice for the source of IDs - a sequence in remote database.
I believe the comment you replied to, may be making an analogous argument here about distributed systems.
We also wrote a decentralized clock sync algorithm that can be used where NTP fails, check out https://github.com/amark/gun/blob/master/nts.js !
I find it a little odd they didn't use a separator symbol so that way it doesn't have to overflow after a certain year. Also, then you could have microseconds precision or beyond where it is supported.
Overall good progress getting people onboard with this! Solves a lot of problems before they even start.
This seems a surprising choice. Even the PowerPC now supports little endian. I would guess that 95%+ of all software is running in a little endian system and that any software that would use ULID is going to run in a little endian system. Other than for historical compatibility, I don't think there is any reason to use big endian today, and definitely not for greenfield protocols.