UUIDv4 vs UUIDv7: Why Modern Databases Are Moving to Timestamp-Ordered Identifiers
Stop B-tree index fragmentation in PostgreSQL and MySQL. Discover why timestamp-ordered UUIDv7 outpaces random UUIDv4 for high-throughput database tables.

For over twenty years, UUID version 4 has been the default choice for generating unique primary keys in distributed systems. Generated via 122 bits of pseudo-random entropy, UUIDv4 guarantees near-zero probability of collision across independent microservices without requiring centralized database sequences.
However, when relational databases like PostgreSQL, MySQL, and SQLite scale beyond millions of rows, UUIDv4 creates a severe, hidden bottleneck: B-tree index fragmentation. Because UUIDv4 values are completely random, every new insert lands in an arbitrary leaf page of the database's primary key index, causing non-stop cache eviction and random disk I/O.
In 2024, the IETF ratified RFC 9562, introducing UUIDv7. By embedding a millisecond-precision Unix timestamp into the leading bits of the identifier while preserving cryptographic randomness in the tail, UUIDv7 solves index fragmentation while retaining distributed generation safety. You can generate secure identifiers and cryptographic tokens client-side using Synctoolo's Random Generator and inspect their byte layout with our Base64 Decoder.
The Hidden Cost of Pure Random UUIDs (UUIDv4)
Relational databases store primary keys in a balanced tree (B-Tree) structure. B-Trees keep index entries sorted sequentially so search operations run in logarithmic time O(log N).
When you insert autoincrementing integers (like BIGSERIAL), new rows are always appended to the rightmost leaf of the tree. The database keeps that active page in high-speed RAM (buffer pool), resulting in fast, sequential writes.
In contrast, inserting UUIDv4 keys causes random insertion points across the entire index tree:
- Page Splits: When an internal B-Tree page runs out of space, the database must split the page in half, write both pages to disk, and rebalance the tree.
- Buffer Pool Cache Eviction: As your table exceeds available RAM, the database is forced to read random index pages from slow NVMe or SSD storage for every single write.
- Bloated Index Sizes: Fragmented B-Trees frequently operate at only 50% to 70% storage efficiency, consuming double the required disk space.
How UUIDv7 Solves the Problem: The RFC 9562 Anatomy
UUIDv7 maintains the exact same 128-bit length and hyphenated 36-character string format as UUIDv4 (018f471e-92b5-7c3a-8c3a-d68a514d7a01), making it a 100% drop-in replacement for existing database columns. However, its internal layout is meticulously structured:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms | ver | rand_a |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The bit allocation consists of:
- 48 Bits of Unix Timestamp: Represents milliseconds since the Unix epoch (valid until approximately 10,889 AD). Because this integer increases monotonically, new records always append to the edge of database B-Trees.
- 4 Bits of Version: Encoded as
0111(binary for 7). - 12 Bits of Additional Entropy (rand_a): Optional sub-millisecond counter or cryptographic random bits.
- 2 Bits of Variant: Standard RFC 4122 variant identifier (
10). - 62 Bits of Cryptographic Entropy (rand_b): Guarantees uniqueness even if millions of UUIDs are generated within the same millisecond across hundreds of distributed machines.
Comprehensive Identifier Comparison Matrix
| Identifier Standard | Length | Time-Ordered | Database B-Tree Friendly | Collision Risk |
|---|---|---|---|---|
| UUIDv4 | 128-bit (36 chars) | No (Pure random) | Poor (High fragmentation) | Extremely Low |
| UUIDv7 | 128-bit (36 chars) | Yes (Millisecond) | Excellent (Sequential append) | Extremely Low |
| ULID | 128-bit (26 chars Base32) | Yes (Millisecond) | Excellent | Extremely Low |
| BIGSERIAL | 64-bit (Integer) | Yes (Strict) | Maximum | None (Requires coordination) |
Generating UUIDv7 in Modern JavaScript & TypeScript
Because UUIDv7 relies on standard crypto.getRandomValues() and Date.now(), you can generate it in Node.js, Deno, Bun, or modern browsers without heavy dependencies:
export function generateUUIDv7(): string {
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
const timestamp = Date.now();
// 48-bit timestamp
bytes[0] = (timestamp / 0x10000000000) & 0xff;
bytes[1] = (timestamp / 0x100000000) & 0xff;
bytes[2] = (timestamp / 0x1000000) & 0xff;
bytes[3] = (timestamp / 0x10000) & 0xff;
bytes[4] = (timestamp / 0x100) & 0xff;
bytes[5] = timestamp & 0xff;
// Version 7 (0b0111)
bytes[6] = (bytes[6] & 0x0f) | 0x70;
// Variant 10 (RFC 4122)
bytes[8] = (bytes[8] & 0x3f) | 0x80;
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}
Migration Recommendations
If you have an existing production database using UUIDv4 columns, you do not need to convert historical records. Because UUIDv7 shares the exact same 128-bit binary storage schema (e.g. UUID type in PostgreSQL), you can start generating UUIDv7 for all newly inserted rows immediately. Over time, your newly written pages will benefit from contiguous, sequential caching.
Tools mentioned in this article
FAQ
Does UUIDv7 leak sensitive creation timestamps?+
Yes. The leading 48 bits encode the exact millisecond the UUID was generated. If your system requires privacy regarding when an entity was created (such as user IDs or invoice numbers), avoid exposing raw UUIDv7 values in public URLs, or use HMAC-based token obfuscation.
Can UUIDv7 replace the need for a created_at column?+
While you can technically extract the generation millisecond timestamp from the first 6 bytes of a UUIDv7, retaining an explicit created_at column is still recommended for indexing clarity, standard SQL date filtering, and schema documentation.
Is ULID better than UUIDv7?+
ULID and UUIDv7 solve the same B-tree indexing problem with comparable time-ordered structures. However, UUIDv7 is officially standardized by the IETF (RFC 9562) and uses the standard 36-character hyphenated UUID format natively supported by PostgreSQL and MySQL UUID columns, whereas ULID uses a 26-character Crockford Base32 string.
How does PostgreSQL support UUIDv7?+
PostgreSQL 17 and later include native support for generating UUIDv7 via the built-in uuidv7() function. For earlier PostgreSQL versions, you can generate UUIDv7 in application code (Node.js, Go, Python) or install the pg_uuidv7 extension.
We build and review free, privacy-first tools at Synctoolo.
Keep reading

Stop Regular Expression Denial of Service (ReDoS). Discover why nested quantifiers cause exponential execution times and how to test patterns safely.

Solve CORS blocking in web applications. Learn how Access-Control-Allow-Origin works, how to handle OPTIONS preflight requests, and how to debug headers locally.