UUIDv7 vs UUIDv4: which should you use?
Short answer
Use UUIDv7 for database primary keys and anything you sort or index by ID: it starts with a millisecond timestamp, so new IDs always land at the end of the index instead of at random spots. In our test on a 5-million-row table, inserting with random UUIDv4 keys took 176.6 seconds against 11.0 seconds for v7, 16 times slower. Use v4 when the ID must reveal nothing, not even when the record was made.

Part of the article Pick a UUIDv7 library that keeps order: 10 tested, .NET fails 50%. This page answers one question from it in depth.
On this page · 10 sections
Both are the same 16 bytes and fit the same uuid column, so this is a choice about what the bits mean, not about storage. Below: the layouts, our benchmark, why the gap appears, and the catches with v7 that our test of 10 UUIDv7 libraries turned up.
Key facts
| What | Number | Source (checked Oct 23 2026) |
|---|---|---|
| Size of both | 128 bits (16 bytes) | RFC 9562 |
| UUIDv4 random bits | 122 | RFC 9562, section 5.4 |
| UUIDv7 layout | 48-bit Unix ms timestamp, 4 version bits, 12 + 62 bits of counter or random, 2 variant bits | RFC 9562, section 5.7 |
| RFC 9562 published | May 2024 | RFC Editor |
| Built into PostgreSQL | uuidv7() since PostgreSQL 18 | PostgreSQL 18 docs |
| Insert 5M rows, UUID as primary key | v4 176.6 s, v7 11.0 s (16x) | our test, Oct 23 2026 |
| Insert 5M rows, UUID in a secondary index | v4 80.6 s, v7 12.6 s (6.4x) | our test |
| Last 500,000 rows, per 50,000 batch | v4 2,482 ms, v7 81 ms | our test |
| File size after 5M rows (primary key) | v4 701.4 MB, v7 697.5 MB | our test |
| UUIDv7 libraries that kept order and a true clock | 2 of 10 tested | our library test |
Download the data: every batch time for all four runs.
The two layouts

A v4 ID is 122 random bits with 6 fixed bits marking the version and variant. Two v4 IDs made a millisecond apart are as far apart in sort order as two made a year apart.
A v7 ID puts the current Unix time in milliseconds in the first 48 bits, so sorting v7 IDs as bytes sorts them by when they were made. The remaining 74 bits are random, or partly a counter that keeps IDs from the same millisecond in order. The RFC leaves that choice to the library, which is why libraries differ more than people expect (more on that below).
What we measured
We inserted 5 million rows into SQLite with Python, in batches of 50,000 with one commit per batch, twice per ID type: once with the UUID as the table's primary key (a WITHOUT ROWID table, so the rows are stored in UUID order, the way InnoDB and SQL Server clustered keys work) and once with an integer key plus a unique index on the UUID (closer to a default Postgres table). The page cache was 64 MB. Everything else was identical.
| Run | Total time | Slowest part |
|---|---|---|
| v4, UUID primary key | 176.6 s | 2.5 s per batch near the end |
| v7, UUID primary key | 11.0 s | flat, about 80 ms per batch |
| v4, UUID secondary index | 80.6 s | 1.3 s per batch near the end |
| v7, UUID secondary index | 12.6 s | flat, about 85 to 117 ms per batch |
The v4 runs start just as fast as v7. The gap opens as the index grows past what the cache can hold, and it keeps widening: by the last batches each v4 batch took about 30 times longer than a v7 batch.
One thing did not change: size. The v4 table ended at 701.4 MB and the v7 table at 697.5 MB. SQLite rebalances neighboring pages when it splits, so random inserts did not leave it much emptier. Other databases split pages differently, and random keys are known to leave more half-empty pages in some of them; we did not measure that here.
Why random keys get slow

Database indexes are B-trees: keys are kept in sorted order on fixed-size pages. Each insert has to find the page where its key belongs, change it and eventually write it back.
With v4, that page is anywhere. While the index fits in memory this barely matters. Once it does not, almost every insert needs a page that is not cached, so it waits for a read and pushes another page out. With v7, the right page is nearly always the last one, which is already in memory. Writes become appends.
This is the same reason auto-increment integers have always been fast keys. UUIDv7 gets most of that speed while still letting any server, phone or browser create IDs without asking the database for the next number.
When v4 is still the right call
- The ID must not leak time. Anyone holding a v7 ID can read when it was made: paste the first 12 hex digits into our Unix timestamp converter as hex milliseconds, or call
uuid_extract_timestamp()in Postgres. For signup IDs, invoice numbers or anything where creation time is private, that is a leak. - The ID doubles as a secret. v7 has fewer random bits than v4's 122, and some libraries spend more of them on a counter: in our library test the fresh random bits per ID ran from 74 down to 32. For password-reset tokens or invite links, use a dedicated random token, not any UUID.
- Small tables. If the table will never outgrow memory, the insert gap in our chart never opens.
Common misconceptions
"UUIDv7 IDs are always in order."
The recordOnly if the library keeps a counter inside each millisecond. Microsoft's Guid.CreateVersion7() does not, and 50% of its IDs sorted out of order in a tight loop in our library test.
"The timestamp inside a UUIDv7 is the creation time."
The recordThe spec lets libraries alter it. One popular Python package ran 16.6 minutes ahead of the real clock under load, and another writes a pre-standard layout that decodes to the year 2202. Treat it as approximate.
"UUIDv4 wastes more space than UUIDv7."
The recordBoth are 16 bytes. In our test the two tables ended within 1% of each other in size; the difference was time, not space.
"Switching to v7 means a new column type."
The recordNo. v7 values go into the same uuid or binary(16) column as v4. Watch byte order in .NET, where Guid.ToByteArray() writes the first bytes little-endian unless you pass bigEndian: true.
Related
- Pick a UUIDv7 library that keeps order: 10 tested: the parent article, with every library's results.
- Build a database in Rust: AI passed 866K tests, then failed ROLLBACK: where ordering bugs like these bite.
- Why are crates.io download counts inflated?: the download numbers we used to rank those libraries, and what they really measure.
- How to publish a Rust crate: if you want to ship a better generator yourself.
FAQ
What is the difference between UUIDv4 and UUIDv7?
Both are 128-bit IDs in the same format. UUIDv4 is 122 random bits. UUIDv7, standardized in RFC 9562 in May 2024, starts with a 48-bit Unix timestamp in milliseconds followed by 74 bits of random data or a counter, so v7 IDs sort by creation time.
Is UUIDv7 faster than UUIDv4 for database inserts?
Usually, once the table is large. In our test inserting 5 million rows into a SQLite table keyed by the UUID, v4 took 176.6 seconds and v7 took 11.0 seconds. Random v4 keys land all over the index, so inserts slow down once the index no longer fits in memory.
Is UUIDv7 secure enough to expose in URLs?
It is unguessable enough for most uses, but less random than v4 and it reveals when the record was created. If anyone who changes an ID in a URL can read someone else's data, the problem is missing access control, not the ID format.
Does PostgreSQL support UUIDv7?
Yes. PostgreSQL 18 added a built-in uuidv7() function and uuid_extract_timestamp() reads the time back out. Older versions need an extension or IDs generated in the application.
Should I switch existing UUIDv4 keys to UUIDv7?
Only if insert speed or index size is a measured problem. Both fit the same uuid column, so new rows can use v7 while old rows keep v4, but the old part of the index stays random.
Sources
All checked October 23, 2026.
Specs and docs
- K. Davis, B. Peabody and P. Leach, RFC 9562: Universally Unique IDentifiers (UUIDs), May 2024: section 5.4 (UUIDv4), section 5.7 (UUIDv7), section 6.1 ("Implementations MAY alter the actual timestamp") and section 6.2 (counters).
- PostgreSQL, UUID functions (version 18): uuidv7(), uuidv4(), uuid_extract_timestamp(); PostgreSQL 18 release announcement.
- SQLite, WITHOUT ROWID tables (rows stored in primary key order).
- Microsoft, Guid.CreateVersion7 and Guid.ToByteArray(bool bigEndian).
Our data
- The benchmark behind every number in the "What we measured" section: Python 3.12.10 with SQLite 3.49.1 on an Intel i7-13700KF under Windows 11, run October 23, 2026. 5,000,000 rows per run, 100 batches of 50,000 rows with a 100-byte text payload, one commit per batch, WAL journal, synchronous=NORMAL, 64 MB page cache. v4 IDs from Python's uuid.uuid4(); v7 IDs built to the RFC 9562 layout (48-bit millisecond time, 12-bit counter, 62 random bits). All batch times are in data.csv.
- Library behavior (out-of-order rates, timestamp drift, random bits per library): our article Pick a UUIDv7 library that keeps order: 10 tested, October 23, 2026.
Credit
- Robert Alexander, UUIDv7 doesn't have the properties you think it does and UUIDs and IDOR, the posts that started our library test.
Images
- Benchmark chart, layout diagram and B-tree diagram: made by Tensorust with matplotlib.
Next question
Every number on this page links to its source and was checked on Oct 23, 2026. Spot an error or a newer figure? Email matt@tensorust.com and it gets fixed. Related: Unix timestamp converter.
Read next
- Article · Oct 23, 2026Pick a UUIDv7 library that keeps order: 10 tested, .NET fails 50%
- Article · Oct 18, 2026Build a database in Rust: AI passed 866K tests, then failed ROLLBACK
- Guide · Oct 23, 2026How to create an App Store Connect API key and use it in CI
Get the next one by email.
What my AI agents tried this week, with the real numbers: what worked, what flopped, what made money. One email when there is something worth reading.
No spam, never sold. Every email has a one-click unsubscribe.