UUID v7 and Time-Ordered Keys: Benefits and Pitfalls as a Primary Key

UUID v7 places a millisecond Unix timestamp in its leading 48 bits. Because of that, the values themselves sort roughly in creation order, which is why v7 shows up wherever an identifier is expected to increase over time, such as a database primary key. This article explains why random v4 keys are a poor fit for primary keys, what v7 fixes, and the pitfalls to know before adopting it.

Short answer: use v7 when ordering of primary keys or logs is an actual problem, and v4 for general-purpose unique identifiers. Choose v7 only if you can accept two things: the creation time is readable, and ordering within the same millisecond is not guaranteed.

1. The v7 bit layout: what creates the ordering

A UUID is a 128-bit value. In v7, the first 48 bits hold the number of milliseconds since the Unix epoch, followed by a 4-bit version (7), a 2-bit variant, and random bits.

FieldBitsContents
unix_ts_ms48Milliseconds since the Unix epoch
ver4Version (7 for v7)
rand_a / rand_b74Random bits (part of which may be used for monotonicity)
var2Variant

The textual form is the same 36-character 8-4-4-4-12 hex layout as v4. Because the timestamp comes first, sorting the strings gives almost the same order as sorting by time, which is the property that matters in practice. Comparing the raw bytes yields the same order.

UUIDs were specified by RFC 4122 for a long time. RFC 9562 replaced it in 2024 and added v6, v7 and v8. v7 is defined by that newer specification.

2. Why random v4 keys hurt as primary keys

v4 has 122 random bits, so the values have no ordering. That interacts badly with the B-Tree index used by most relational databases.

With a time-ordered key, inserts always land near the end of the index. The pages you touch stay localized, the cache works, and splits happen only at the tail. That is the single problem v7 solves.

Note that this only becomes a measurable difference at scale. Moving a table with tens of thousands of rows from v4 to v7 buys almost nothing, and a migration touches both schema and existing data. Measure in your own environment before deciding.

3. Pitfall 1: the creation time is readable

The leading 48 bits of v7 are a plain millisecond timestamp. In other words, anyone holding the ID can tell when the record was created simply by converting the first 12 hex digits to decimal.

Unlike sequential integer IDs, this does not leak how many records exist. Still, the timestamp itself is sometimes sensitive: when an application was submitted, when an inquiry arrived, when an internal job ran. The straightforward fix is to separate the public identifier from the internal primary key.

4. Pitfall 2: ordering within a millisecond is not guaranteed

Within the same millisecond, the random bits decide the order, so values created in the same millisecond are ordered randomly relative to each other. In high-throughput code paths this surfaces as records that do not appear in creation order.

RFC 9562 describes optional monotonicity methods that use part of the random area as a counter. If you need strict ordering, pick an implementation that supports one of them, or keep a dedicated ordering column such as a sequence or an insertion timestamp. The point is not to treat "it is v7, therefore it is in creation order" as a guarantee.

5. Pitfall 3: it depends on the clock

A v7 value reflects the clock of the machine that generated it. If a host with a skewed clock joins in, the ordering skews with it. NTP synchronization is a prerequisite, and behavior when the clock moves backwards is implementation-specific (staying within the same millisecond, absorbing it with a counter, and so on).

When several hosts mint IDs, clock skew between them becomes ordering error. If millisecond-accurate ordering is a business requirement, a centrally issued sequence is a more dependable choice than a UUID.

6. Choosing between v4, v7 and ULID

UUID v4UUID v7ULID
OrderingRandomRoughly by timeRoughly by time
Notation36 hex characters36 hex characters26 characters (Crockford Base32)
Timestamp exposedNoYes (milliseconds)Yes (milliseconds)
StandardRFC 4122 / 9562RFC 9562Published spec, not an RFC

Whichever you choose, the random part must come from a cryptographically secure random source (CSPRNG): crypto.getRandomValues() in the browser, the crypto module in Node.js. Never use Math.random(), which is predictable.

TOOL UUID Generator (v4 / v7) Switch between v4 and v7, generate in bulk, and copy in one click. Everything runs in your browser; values are never sent anywhere.

Frequently Asked Questions

Does UUID v7 always sort in time order?

Not within the same millisecond. The ordering comes from the leading 48-bit millisecond timestamp; the remaining bits are random. Values generated in the same millisecond therefore appear in random order relative to each other. If you need finer ordering, implement one of the monotonicity methods described in RFC 9562, or keep a separate sequence column.

Are random v4 primary keys really a performance problem?

It depends on table size and insert rate. A B-Tree index stores nearby keys together, so random insert keys scatter writes across the whole index, keep it out of cache, and cause more page splits. On small tables you will not notice. The larger the table, the wider the gap. Measure in your own environment first and migrate only after you can observe the problem.

Is it safe to expose UUID v7 in a public API?

Only if you can accept that the creation time is readable. The leading 48 bits of v7 are the Unix millisecond timestamp, so anyone holding the ID can recover when the record was created. Unlike sequential integers it does not reveal record counts, but if the timestamp itself is sensitive (for example the exact time an application was received), expose a separate identifier instead.

← Back to the Tech Blog list