UUIDv7 was standardised in RFC 9562 to solve a specific, well-documented problem: version 4 identifiers are uniformly random, and uniformly random primary keys are unkind to database indexes. Version 7 keeps the uniqueness guarantee while making identifiers sort in the order they were created.
It occupies the same 128 bits and the same textual format as every other UUID, so adopting it requires no schema change and no new column type. What changes is the internal layout, and that change has measurable consequences.
The layout of a version 7 identifier
The first forty-eight bits hold a Unix timestamp in milliseconds. Then come four version bits set to 7, twelve bits available for sub-millisecond ordering or additional randomness, two variant bits, and sixty-two random bits.
Because the timestamp occupies the most significant bits, comparing two identifiers as byte strings compares their creation times. Sorting a column of version 7 values chronologically requires no extra timestamp column and no secondary index.
Why random keys hurt database performance
A B-tree index stores entries in sorted order. Inserting a version 4 identifier lands at an unpredictable position, so each insert touches a different page. Under load, the pages a workload needs are scattered across the whole index, which is far larger than memory in any substantial table.
The result is a low buffer cache hit rate and heavy random write amplification. Version 7 inserts arrive in ascending order and therefore concentrate on the rightmost pages of the index, which stay resident in memory. The same workload performs dramatically less physical input and output.
Comparing v7 with v4 directly
Both versions are 128 bits with the same format, and both are effectively collision-free at any realistic scale. Version 4 offers 122 random bits, version 7 offers 74. That is a large reduction in absolute terms and irrelevant in practice, since 74 bits still makes a collision within a single millisecond astronomically unlikely.
The genuine trade-off is disclosure. A version 7 identifier reveals when a record was created to anyone who can read it, at millisecond precision. That enables an enumeration attack on business volume: an observer who collects two identifiers can estimate how many records were created between them. Where creation time is sensitive, version 4 remains correct.
UUID v7 to timestamp: reading the creation time
Converting a UUID v7 to timestamp form is straightforward because the value is not obfuscated. Remove the hyphens, take the first twelve hexadecimal digits, parse them as an integer, and treat the result as milliseconds since the Unix epoch.
Many libraries provide this directly, and a UUID decoder will display the parsed time alongside the version and variant. Treat the value as informational rather than authoritative: it comes from the clock of whichever machine generated the identifier, so it is subject to skew, and it can be forged by anyone who constructs an identifier by hand.
Clock behaviour and monotonicity
The millisecond timestamp means many identifiers can share the same prefix under load. The specification allows the twelve bits after the version field to be used as a counter, so a UUID v7 generator can guarantee that identifiers produced in the same millisecond still increase monotonically.
Clocks that move backwards, through NTP correction or a virtual machine snapshot, are the awkward case. A good implementation detects this and continues from the last issued value rather than emitting identifiers that sort before existing rows. This behaviour differs between libraries and is worth checking before relying on strict ordering.
UUIDv7 compared with ULID
ULID solved the same problem earlier, with a forty-eight-bit millisecond timestamp followed by eighty random bits. Functionally the two are close relatives. The differences are standardisation and encoding: UUIDv7 is a published IETF standard with a version field, while ULID is a community specification rendered in Crockford base32 as twenty-six characters.
Put plainly, the UUID v7 vs ULID decision is about ecosystem rather than capability. A UUID v7 example looks like any other UUID, hyphens and all, while a ULID is a single unbroken token. ULID's text form is shorter and avoids ambiguous characters, which is pleasant in a URL. UUIDv7's advantage is that it is a UUID, so it fits existing uuid column types, existing validation, and existing tooling without translation. For new systems on a database with native UUID support, version 7 is usually the lower-friction choice.
Adopting version 7 in an existing system
Because the format is unchanged, new rows can use version 7 while old rows keep version 4 in the same column. Nothing breaks, and the ordering benefit applies immediately to new inserts. There is no need to migrate historical data, and rewriting existing identifiers would invalidate every external reference to them.
Before switching, confirm two things. Check that your database stores UUIDs as a native 128-bit type rather than as a thirty-six character string, since text storage discards most of the benefit. Then confirm that no part of the system treats identifiers as unguessable, because version 7 makes the creation time public by design.
Where the benefit does not apply
Time ordering helps write-heavy tables with a growing index, which is the common case for primary keys. It does nothing for a small lookup table that fits in memory, and it does nothing for a hash index, which scatters entries deliberately. Measure before treating version 7 as a general performance improvement.
Distributed databases that partition by key deserve particular thought. Sequential identifiers concentrate every new write on one partition, turning an evenly spread workload into a hotspot. Systems built on range-partitioned storage often want randomness in the key prefix precisely to avoid this, which makes version 4 the better fit despite the index behaviour that motivated version 7.