A UUID is a 128-bit identifier you can generate anywhere without coordinating with anyone. The version number, encoded in the identifier itself, says how those bits were chosen.
The versions that matter
v1, timestamp plus the machine's MAC address. Sortable, and it leaks both when and where it was created. The MAC address disclosure is why it fell out of favour; it was used to trace the author of the Melissa virus in 1999.
v4, 122 random bits (6 are fixed for version and variant). The default nearly everywhere for two decades. No information leakage, no coordination needed.
v7, a 48-bit Unix millisecond timestamp followed by random bits. Standardised in RFC 9562, which replaced the old RFC 4122 in 2024. Time-ordered while still unpredictable.
Why v4 hurts as a primary key
Most relational databases store rows in a B-tree ordered by primary key. Insert sequential keys and every new row goes at the right-hand edge of the tree, the same few pages stay hot in memory and writes are cheap.
Insert random keys and each one lands in an arbitrary position. The database must read the target page from disk, split pages that are full, and keep effectively the entire index hot to avoid thrashing. Index fragmentation grows and write throughput degrades as the table gets large.
The effect is negligible on a table with 10,000 rows and very real on one with 100 million. This is why teams reach a scale where "we should not have used random UUIDs" becomes a migration project.
MySQL's InnoDB clusters the table data itself on the primary key, so the penalty is particularly sharp there.
What v7 fixes
Because the leading 48 bits are a millisecond timestamp, v7 values generated over time are broadly increasing. Inserts cluster at the end of the index, restoring the locality that makes sequential keys fast, while keeping the "generate anywhere without coordination" property that made UUIDs attractive.
You also get a free creation timestamp embedded in the identifier, and natural sort order for free.
The cost is that a v7 UUID reveals roughly when it was created. For a database primary key that is almost never a problem. For a password-reset token it might be.
Choosing
| Use case | Choose |
|---|---|
| Database primary key | v7 |
| Public-facing resource ID | v4 or v7 |
| Session token, reset token, API key | Neither, use a CSPRNG |
| Correlation / trace ID | v7 (sortable logs are useful) |
| Existing system already on v4 | Stay on v4 unless you have measured a problem |
Do not use UUIDs as secrets
A v4 UUID has 122 bits of randomness, which is plenty of entropy, if it came from a cryptographically secure generator. Many older libraries used Math.random() or a similar non-cryptographic PRNG, whose internal state can be recovered from a handful of outputs, making subsequent values predictable.
For anything security-sensitive, generate bytes from a CSPRNG directly (crypto.getRandomValues in the browser, crypto.randomBytes in Node) rather than relying on a UUID library's implementation choices.
Also: a v7 UUID's timestamp portion is not random at all. Never use one as a secret.
Collisions, briefly
For v4, the probability of a collision is negligible at any realistic scale. Generating a billion UUIDs a second for a century leaves the chance of a single collision far below the chance of a hardware fault silently corrupting the data instead.
Collisions are not the thing to worry about. Weak randomness sources are.
Storage
A UUID is 128 bits, 16 bytes. Stored as a hyphenated string it is 36 bytes, more than twice the size, and every index entry pays for it.
Use a native uuid type where the database has one (PostgreSQL does), or BINARY(16) where it does not. On a large table with several UUID columns and indexes, the difference is measured in gigabytes.
Generating them
Both versions are cheap to generate wherever the record is created, so there is no reason to ask a server for one. The UUID generator produces v4 values in bulk with the browser's own random generator; for v7, use your language's UUID library, which adds the timestamp for you, and the timestamp converter to read that embedded time back out when you are debugging insertion order.