juliolinarez
ExCuid2 generates secure, collision-resistant unique identifiers designed for efficiency and horizontal scaling. They are an excellent choice for primary keys in distributed databases. Includes a type to work with Ecto
Features:
- Collision-Resistant: Uses multiple entropy sources to minimize the probability of collisions, even in high-concurrency systems.
- Secure: Starts with a random letter to prevent enumeration attacks and uses
:crypto.strong_rand_bytesfor cryptographically secure entropy. - Scalable: Includes a process fingerprint to ensure uniqueness across different nodes and application restarts.
- Efficient: Implemented with a stateful
Agentto manage an atomic counter quickly and safely. - Customizable: Allows generating IDs with a length between 24 and 32 characters.
- Supervisable: Can be added directly to your application’s supervision tree.
Thanks
Trending in Announcing
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries.
offset-based pagination with...
New
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
Hi there! We created Gust: A task orchestrator inspired by Airflow.
For those who have never heard about Aiflow, it’s a Python-based wor...
New
Hi all!
I want to present a small library which provides a mix task for generating an Entity-Relationship Diagram for Ecto schemas.
You...
New
Hello
Published a new library - ProcessHub!
ProcessHub is a library designed to manage process distribution within the Elixir cluster. ...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Other Trending Topics
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
This showed up on my feed.. anyone heard of it? Just hype?
Ox Alpha is a reasoning model designed for coding, sustained ag...
New
It’s not that it’s vocabulary is too advanced. It’s something worse.
I get lost trying to follow even a paragraph written by Claude. It’...
New
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself.
My main conc...
New
Today we’re releasing Oban for Python. Not an Oban client in Python. Not a pythonx wrapper embedded in Elixir. Nope, it’s a fully operati...
New
@hugobarauna, Dr. Dimitrios Koutmos (my brother) and I (Alex Koutmos) have been hard at work on writing a book on how you can use Elixir ...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
garrison
This is not a comment on your implementation, but I had never heard of these before and had a look at the original repo and I have to say it gives off some weird vibes. It seems like their main argument is that UUIDv4 is bad if you use a weak source of entropy (yeah, obviously) and that CUID is better because it combines “multiple sources of entropy”.
There are a lot of weird claims about security and other things. The section on k-sortability is particularly bizarre (“cloud databases” store all their data in memory so it doesn’t matter? seriously?).
Anyway,
:crypto.strong_rand_bytes()should have sufficient entropy to generate ids, or at least I hope it does! Is there any other functionality that CUID2 offers over the (well-standardized) UUIDv4/7?Of course, if someone is already using these it’s still good to have a library for them, so definitely don’t take my comments as directed at you
juliolinarez
Hi, apologies for the delay in getting back to you. I wanted to take some time to properly check the sources and read up more on the nuances of CUID2 before responding. Thank you so much for leaving such a thoughtful and well-researched comment. I really appreciate the feedback, and you’ve raised some excellent points.
You are absolutely right to point out the “weird vibes” from the original CUID2 spec. I’ll be the first to admit that I don’t agree with the author’s justification for dropping K-sortability. The argument that “modern cloud databases” keep everything in memory is a bizarre generalization that doesn’t reflect how systems like RDS, Cloud SQL, or even most NoSQL databases actually work at their core.
My perspective is that this is a great opportunity to leverage one of Ecto’s best features. Since Ecto automatically provides
inserted_atandupdated_attimestamps, we can and should rely on theinserted_atfield for any chronological sorting. This is an excellent way to take advantage of what the framework already gives us, freeing the ID from needing to be sortable and allowing it to be fully unpredictable—a clear win for security.About performance, using a random string as a primary key could index bloat with a standard B-Tree index in PostgreSQL. For anyone using this library in production, I would strongly recommend indexing the CUID2 column with a Hash index:
This approach is perfect for the primary use case of an ID—fast equality lookups (
WHERE id = ...)—while avoiding the write performance degradation associated with random keys in a B-Tree.So, to answer your question about what CUID2 offers over UUIDv4/7, the main practical benefits are:
Ultimately, my goal with
ex-cuid2was not to advocate for it as a superior standard to something like UUIDv7. It was to provide a robust, well-tested, and faithful Elixir implementation for teams that are already using CUID2 or are migrating from ecosystems like Node.js where it is more common. To that end, I made sure to port the original library’s collision-checking test suite to ensure this implementation is as reliable and close to the reference Node.js version as possible.Thanks again for the fantastic comment and for sparking this great discussion
garrison
I really don’t mean to be rude here, but I would appreciate it if you could reply in your own words rather than with an LLM.
This entire reply is AI slop. Almost every part of it is completely wrong, and there’s no way for me to know whether it’s because you’re mistaken or because GlazeGPT is hallucinating nonsense.
I had a look at your code because I was curious and it seems to me to be AI generated as well. The implementation is not good.
:crypto.strong_rand_bytes/1is cryptographically secure and provides sufficient entropy.You do not need to start an agent with a counter (serializing every single id generation via message passing in the process and incurring massive overhead) and then sha256 that with the actual random bytes. That does not add entropy. It’s completely absurd.
You are choosing the first letter with
Enum.random(). That function is not crypto-safe, all you’re doing is decreasing the entropy of your ids.Under no circumstances should anyone ever use this ridiculous id format for a new project, but if you have a legacy project you need to support then you can just generate random bytes with
:crypto.strong_rand_bytes/1and then base36-encode them directly. You do not need to implement any of this additional “entropy” nonsense.I recommend you update your library to do exactly that.
juliolinarez
My idea was to make this as close as possible to the original
cuid2. I don’t have enough experience to criticize cuid2. I tried to follow the original code, even the counter. cuid2 is only valid if it starts with a letter, so I usedEnum.randomfor that. I didn’t know another way.I didn’t trust in
:crypto.strong_rand_bytes/1for all ID generation. I’m not sure how it works under the hood. I’ll check it.Sorry for my b2 english.
garrison
The cuid2 project claims that the purpose of adding in these “other sources” of entropy is to provide robustness against weak entropy sources on the client in the browser. I don’t think this is a problem anymore, but it seems like at one time it was hard to get good random numbers in JS and it was causing UUID collisions. Fair enough.
Your library, however, is written in Elixir and runs on the server. We do not have this weak entropy problem;
:crypto.strong_rand_bytes/1generates crypto-safe random bytes. You can use it to generate unique ids, tokens, etc without worry. For example,phx.gen.authgenerates session tokens this way. It is very important that those session tokens cannot be guessed!For your library, you do not need to follow their generation method because it is totally unnecessary when we already have a good entropy source. The algorithm you’ve implemented, with counters and such, is not adding any entropy to the ids. A counter is a terrible source of entropy.
And please don’t worry about your English, it’s fine. The problem with using something like ChatGPT is that it seems to generate lies much faster than I, a human, can correct them
garrison
Instead of using the original algorithm, you can just generate bytes with
:crypto.strong_rand_bytes(n)and then base36-encode them (you already have code for this I think). That would produce a valid id with sufficient entropy.I don’t know what the purpose of the letter thing is, but I guess it’s part of the spec so fair enough. The problem is that you’re generating the first letter with a weak source (
Enum.random()with the defaults), so it’s not crypto-safe like the rest of the id. Maybe this matters in practice, maybe not, but it’s not ideal. For the record the original implementation also just usesMath.random()to do this (you know, the function they themselves are claiming is unsafe), which I find quite incredible, but whatever.I believe you can seed using
:crypto.rand_seed()to make it crypto-safe, but that will of course clobber the default random state for that process:You can also create a state with
:crypto.rand_seed_s/0and then pass it to:rand.uniform_s/2to do the same without clobbering the default PRNG for other purposes. I’m not sure of the tradeoffs between that and using:crypto.strong_rand_bytes/1, but perhaps somebody else could comment on that.jswanner
My guess would be starting with a letter allows the result to used as a DOM ID as is (which doesn’t like IDs that start with numbers)
mpraski
Thank you for working on this library!
I’ve noticed you’re serializing access to the integer counter with an
Agent- wouldn’t that become a performance issue in a high concurrency setting? Admittedly I didn’t read the CUID2 spec yet, but it doesn’t seem this ID was designed for distributed systems like the mentioned alternatives.As for URL-friendliness I agree. I’ve come to use a “proxy” Ecto type that encodes the UUID using base62 and adds a prefix a’la Stripe identifiers. This results in a contiguous string (Dan Schultzer: Prefixed base62 UUIDv7 Object IDs with Ecto).
garrison
It’s fine to use them as DOM ids, but apparently it means you have to escape CSS selectors because they can’t start with numbers. I can see how that would be annoying, but in practice you rarely scope CSS to ids. Maybe for a weird case where you are using
querySelector()with an id selector. I’ve never run into this before, personally.It certainly would, but more importantly it’s completely pointless and, if anything, weakens the entropy of the ids. The purpose of the counter in the original implementation was to prevent sub-ms collisions from the clock source because they only had access to a millisecond timestamp in JS. This does not apply here, first of all because we have microsecond timestamps, but also because again
:crypto.strong_rand_bytes()already has sufficient entropy.Yes, it’s important to understand that the underlying format and the format you present can be entirely decoupled. A UUIDv4 is literally just 16 random bytes save for 6 bits which are set in a particular place to tell you “this is a UUIDv4”. For the other
UUIDv*formats, a couple of those bits are different. You can encode the UUID however you want!I have personally grown somewhat fond of base16 with the hyphens stripped. Base32/64 are really not meaningfully shorter from a UI perspective IMO. Subjectively, I mean.
mpraski
Ah, got it. Yeah, seemed redundant.
That’s a good way to reason about it. Reaping the benefits of UUIDs (native postgres support, cursor pagination via primary key column, minuscule collision probability) while remaining “human readable” where warranted.