Laravel: You can now use UUID and ULID
We’re a little too far ahead of standards, tho
The latest addition to Laravel is compatibility for Eloquent Models with UUID or ULID as primary keys. I was quickly to point my own thoughts about the implementation, but overall, what we’re getting is fine. Indeed, I will include them in Larawiz.com for your new projects.
It’s not that it was never considered, but rather, UUID were not that popular among services to obfuscate the number of records, or make them unpredictable for security reasons.. Well, now it has become standard for a lot of services, so it makes sense to opt-in.
You should be able to use both of them once Laravel 9.31 lands next week, but there are some caveats you should take into account before diving blindly into ULIDs.
Into the UUID and ULID rabbit holes
UUID has become standard by now by most systems. There are five standardized versions of UUID, most notably the v4 which offers pure randomness. There are many other similar solutions for creating unique identifiers, each one with their own advantages and disadvantages.
One common disadvantage of UUID is that is not lexicographically sortable. In other words, since the first bits are always random, you can’t sort them in any performant way. That’s where ULID and UUID v7 come into play.
The ULID spec appeared in 2017 as a way to fix the problem of length and sorting. It does by putting a millisecond-precise timestamp in 48 bits first, and 72 bits of random bytes afterwards. Additionally, it can be represented as a case-insensitive 26-character representation (5-bit characters plus padding).
This identifier type predates the new proposed RFC 4122 by three years, for standardizing UUID v6, v7 and v8. You can say that UUID v7 is essentially an ULID minus the 26-character representation. From there, there is no certainty that databases will offer auto-generating UUID v6-v8, and probably will never be compatible with ULID since it’s not an Internet Standard but more as patch-work over UUID than else.
Also, ULID don’t have a reserved version and variant bytes. These are also random, which makes them non-UUID standard but still compatible. In practice, they will work fine anywhere, but if strict validation is one of your goals, you may want to override the version and variant bytes yourself depending on how you’re generating them.
UUID v7 is the future, not the present
Sorting primary keys is a very performant way to paginate multiple records, or even to get the last record inserted in a table. That’s why UUID v7 may be the first choice for many applications.
Laravel will create an ULID as a string of 26 characters. While an ULID compatible with UUID, you will need some extra work if you plan to store it as a 128 bit UUID, and more if you plan to show it as an ULID outside the application.
If you go for the “ULID string” route for your database, be prepared. The index will be 62% larger and probably slower than a binary UUID; an ULID string uses 208 bits, which are 80 bits more than a binary UUID, and some databases are less efficient when using a string for primary keys. Also, consider that those 26 characters are case insensitive — you will have to parse and normalize them.
Now, I said that UUID v7 are the future because they are sortable, UUID-compatible and storable as binary, and can be represented as ULID. It won’t be the present soon because major database engines don’t support sorting UUID or binary columns. Just ask PostgreSQL users if the can use
Additionally, ULID are still large compared to other alternatives, like HashIDs. The latter has been my preferred solution because it is strictly presentational. It creates a uniquely short string from an integer through an encryption key. The caveat is that it breaks all URLs if the encryption key is regenerated, and if it leaks then the cat is out of the bag and everyone will know the amount of records.
An advantage of both ULID and UUID v7 is that is one table column less to think about. Since you can infer the creation of the record by stripping out the UNIX epoch milliseconds and transforming it into an UNIX timestamp through the application, the
created_at column becomes redundant in Eloquent Models with timestamps. You won’t need to query that column anymore.
What about Str::orderedUuid()?
As many of you may know,
Str::orderedUuid()is a thing. It creates the same UUID v7 with some minor differences, so going for UUID v7 or the “Ordered UUID” is practically the same.
So yeah, UUID v7 may be the future for applications thanks to the sortability, short string representation through ULID, and retrocompatibility with the current UUID/Binary primary keys. The only thing that we need is an approved standard — probably in 2023 if my hunch is right— and major database vendors adding sort capability for UUID and Binary columns.