Stateless Tools

The short answer first

Choose v4 when

The identifier appears in public URLs, behaves like a secret token, or belongs to data where ordering is meaningless. If unpredictability is itself the requirement, v4 is correct and there is no reason to change.

Choose v7 when

It is the primary key of a large, append-heavy table, an event log, or queue records — workloads where insert order and sort order being the same is what pays off.

Do not bother when

If the table holds a few hundred thousand rows, the primary key is not a UUID, or inserts are rare, switching from v4 to v7 changes little you can measure. The benefit comes from scale.

The reasoning follows. To compare actual values, switch between v4, v7 and ULID in the generation mode of the UUID converter.

The difference is clear at the bit level

Both are 128 bits and share the same string form. What differs is what fills those bits. Standardised in RFC 9562, v7 hands the leading bits over to time.

v4 is almost entirely random

Excluding four version bits and two variant bits, 122 bits are random. Collisions are not a practical concern, and the value reveals nothing about when it was created. The 4 at the 14th character of the string is the version marker.

v7 puts a millisecond timestamp in the first 48 bits

Unix epoch milliseconds come first, then four version bits (0111, so a 7 at the 14th character), two variant bits, and 74 random bits. Because time leads, sorting the strings lexicographically sorts them by creation order. That is the whole idea.

What that looks like

01a013ab-09ca-7958-88e4-7f9866134ad6 and 01a013ab-09d0-7ec6-ac2e-079d51b83123 are two v7 values generated moments apart. The first eight characters match and the next segment ticks up. Two v4 values differ from the very first character.

What about several in the same millisecond?

The timestamp is identical, so the remaining 74 random bits decide. Order within a single millisecond is not guaranteed. If you need strict monotonicity, choose an implementation using the counter method the RFC permits; millisecond-level ordering is enough for most applications.

What actually slows down in a database

"v4 is bad for indexes" is repeated often but rarely explained. Looking at how a B-tree works reduces it to a single cause.

Page splits

A B-tree stores values in sorted pages. Sequential keys always append to the rightmost page, so a full page simply gets a new sibling. Random keys land in the middle of already-full pages, which forces repeated page splits. Split pages stay half empty, inflating the index and increasing disk reads.

Buffer pool locality

With sequential keys the page you just wrote is still in memory, so the next insert reuses it. Random keys touch a different page every time, pulling pages in from disk. The gap widens sharply once the table outgrows memory.

MySQL feels it most

In InnoDB the primary key is the clustered index, so primary key order is the physical row order. A random primary key scatters the rows themselves. Every secondary index also stores the primary key, so a large key inflates all of them.

The important part is that all of this is a question of scale. While the table fits in memory, neither page splits nor cache misses are noticeable. Reports of a visible improvement from switching to v7 generally come from tables of tens of millions of rows and up.

What v7 gives up: creation time is visible

The sorting benefit has a price, and this is the part most v7 introductions omit.

The value reveals when it was created

Since the first 48 bits are a millisecond timestamp, anyone can recover the creation time from a v7 value. If a user ID is v7 and appears in a public profile URL, that person's sign-up time becomes public. For an order ID it is the order time; for a document ID, the authoring time.

Ordering is information too

Comparing two v7 values tells you which came first. A competitor can sign up, compare their own ID with another, and estimate how many records were created in between. The 74 random bits prevent an exact count, but the time range is fully exposed.

How to split it

A common compromise is to separate internal and external identifiers. Keep a v7 primary key for the index benefit and expose a separate v4 or short slug in public URLs. It costs one extra column and gives you both properties.

Is v4 therefore safe?

Safe in the sense that it leaks no timestamp. But v4 is not a secret either — it is merely hard to guess, and cannot replace access control. A design where "anyone with the URL can view it" is weak regardless of UUID version.

Storage form and ULID

36 characters versus 16 bytes

Storing a UUID as CHAR(36) uses 36 bytes including hyphens; BINARY(16) stores the same value in 16. On a primary key, that 20-byte difference multiplies across every secondary index. The cost is that humans can no longer read it or grep it out of a log. The hex tabs in the UUID converter convert between the two forms.

How ULID differs

ULID also puts a 48-bit timestamp first, followed by 80 random bits, so its ordering property matches v7. The difference is representation: Crockford base32 yields a 26-character string, shorter than UUID's 36, using characters chosen to avoid visual confusion. In exchange it does not drop into a native UUID column.

Which to pick

If your database has a UUID type and your code already handles UUIDs, v7 is the natural fit. If the value is read by people in URLs or logs and shorter is better, ULID wins. Mixing both is not advisable.

What actually bites during migration

Existing data cannot be migrated

There is no way to convert stored v4 values into v7 — the creation time was never in them. So a switch always means "only new values are v7", and the table keeps both versions side by side.

Mixed versions break the sorting assumption

If queries or pagination assume "ordering by primary key means chronological", rows from the v4 era sit at random positions and results look wrong. Record the cut-over point and sort older data by an explicit created_at column. If you need time ordering at all, an indexed timestamp column is more explicit than relying on a UUID version.

Check library and runtime support

v7 is a relatively recent standard, so support varies by language and library version. Verify that your database driver, ORM and any validating schema (for example JSON Schema's format: uuid) accept version 7. The browser's crypto.randomUUID() produces v4 only.

UUIDs live outside primary keys too

Request trace IDs, idempotency keys and filenames also use UUIDs. In those positions ordering is not a benefit and the exposed timestamp is pure downside. Decide per use site rather than standardising everything on v7.

In summary

  1. Both are 128 bits. v4 is 122 random bits; v7 is a 48-bit millisecond timestamp followed by 74 random bits.
  2. v7's benefit is fewer B-tree page splits and cache misses, and it only shows once the table outgrows memory.
  3. v7's cost is that creation time and ordering are visible in the value. Keep a separate v4 for publicly exposed identifiers.
  4. BINARY(16) storage beats CHAR(36), and on a primary key that difference multiplies across every index.
  5. Existing v4 values cannot become v7, so plan any switch around a permanently mixed table.

Generate and compare values in the UUID converter by switching the generation mode between v4, v7 and ULID, and use the hex tabs to see the 16-byte form. The Unix timestamp converter helps when you want to check what time a v7 prefix actually encodes.