Member-only story
Why We Stopped Using UUIDs as Primary Keys (Even Though Everyone Loves Them)
Index fragmentation, join performance, and a hybrid solution that gave us the best of both worlds.
There’s a certain comfort in reaching for UUIDs. You’ve got a globally unique identifier, no coordination needed between nodes, no risk of ID collision when you merge data across services, and your URLs look pleasantly opaque instead of exposing /users/1, /users/2 to the world.
We did the same thing. For about two years, every table in our system used uuid as the primary key. It felt like the responsible, production-ready choice.
Then we started noticing something. Write performance was slowly degrading. Bulk inserts that used to take 200ms were creeping toward 800ms. Index sizes were ballooning. A simple join between orders and order_items — tables with fewer than 10 million rows combined — was doing full index scans when it had no business doing so.
We went looking for the culprit, and it kept pointing back to the same thing: UUIDs.
The Real Problem With UUIDs as Primary Keys
Let me be specific. The issue isn’t UUIDs themselves — it’s using random UUIDs as a clustered primary key on a B-tree index, which is what most PostgreSQL and MySQL setups do by default.









