Definition
- UUIDv7 – A 128-bit identifier defined in RFC 9562 whose first 48 bits hold a Unix millisecond timestamp, followed by a 4-bit version field (value 7), a 2-bit variant field, and the remaining bits filled with randomness.
- Time ordering – Because the timestamp is in the most significant bits, values generated over time are approximately monotonically increasing.
- Index locality – New keys land near the right edge of the B-tree, keeping pages dense and page splits rare, just like auto-increment.
- No coordination – Unlike auto-increment (needs a central sequence) or Snowflake (needs worker-id assignment), UUIDv7 can be generated by any node independently.
- Approximate ordering – Clocks on different nodes can skew, so two ids from different machines in the same millisecond may not reflect true causal order.
A UUIDv7 is like a National Bank of India reference number that begins with a precise timestamp down to the millisecond and then ends with random digits. Because every new number starts with "now," newer numbers always sort after older ones, so they file neatly at the back of the cabinet like a running counter – but unlike a counter, no two branches ever need to phone head office to agree on the next value, because the random tail guarantees they never collide. It is a running-counter feel with no central clerk required.
Understanding the Concepts
What is the UUIDv7 binary structure?
The layout of a UUIDv7 is precise and defined in RFC 9562.
UUIDv7 (128 bits): ┌──────────────────────────────────────────────────────┐ │ Bits 0-47: Unix millisecond timestamp (48 bits) │ │ Bits 48-51: Version = 0111 (7) (4 bits) │ │ Bits 52-63: Random / sub-ms counter (12 bits) │ │ Bit 64-65: Variant = 10 (2 bits) │ │ Bits 66-127: Random (62 bits) │ └──────────────────────────────────────────────────────┘ Example (hex): 0192a3b4-c5d6-7890-abcd-ef1234567890 |--------| version=7 timestamp
The first 48 bits are the number of milliseconds since the Unix epoch (January 1, 1970). Then 4 bits encode the version (value 7). Then 2 bits encode the IETF variant. The remaining 74 bits are random, though some implementations use a sub-millisecond counter in part of that space for stricter monotonicity within the same millisecond.
How does the timestamp prefix give time ordering?
Because the most significant bits increase as real time passes, two ids generated a second apart differ in their high bits and sort in time order.
-- UUIDv7 values generated at different times:
-- 2026-10-25 14:30:00.000 -> '0192a3b4-0000-7xxx-xxxx-xxxxxxxxxxxx'
-- 2026-10-25 14:30:00.001 -> '0192a3b4-0001-7xxx-xxxx-xxxxxxxxxxxx'
-- 2026-10-25 14:30:01.000 -> '0192a3b7-e800-7xxx-xxxx-xxxxxxxxxxxx'
-- Sorting by the UUID column approximately equals sorting by time
SELECT payment_id, amount, created_at
FROM payments
ORDER BY payment_id; -- approximately time-ordered
How does the timestamp prefix give index locality?
Because the leading bits increase with time, every new insert targets a key larger than recent ones. In the B-tree, this means new entries land near the rightmost leaf page.
B-tree insert pattern with UUIDv7:
Time T1: insert '0192a3b4-0000-...' -> rightmost page
Time T2: insert '0192a3b4-0001-...' -> same rightmost page
Time T3: insert '0192a3b7-e800-...' -> same or next rightmost page
Pages fill densely and sequentially.
Page splits are rare (only at the right edge).
Fragmentation is minimal.
How does UUIDv7 compare to auto-increment?
Auto-increment gives compact, perfectly ordered ids but needs a central database sequence.
-- Auto-increment: needs a central sequence
CREATE TABLE txn_auto (
txn_id BIGSERIAL PRIMARY KEY -- 8 bytes, central sequence
);
-- UUIDv7: no central sequence needed
CREATE TABLE txn_v7 (
txn_id UUID PRIMARY KEY -- 16 bytes, any node can generate
);
| Feature | Auto-increment | UUIDv7 |
|---|---|---|
| Size | 4-8 bytes | 16 bytes |
| Ordering | Perfect | Approximate |
| Insert locality | Perfect | Near-perfect |
| Coordination | Central sequence | None |
| Distributed | Hard to scale | Easy |
| Guessable | Yes | Partially (timestamp) |
How does UUIDv7 compare to Snowflake IDs?
Snowflake IDs are 64-bit, time-ordered, and compact, but each generator needs a unique worker id that must be coordinated.
Snowflake ID (64 bits): ┌───────────────────────────────────────┐ │ 1 bit: sign (always 0) │ │ 41 bits: timestamp (ms since epoch) │ │ 10 bits: worker id (must be unique) │ │ 12 bits: sequence within ms │ └───────────────────────────────────────┘ Problem: who assigns the worker ids? - Needs a registry (ZooKeeper, database table) - If two workers get the same id, collisions occur - Worker id management adds operational complexity
| Feature | Snowflake | UUIDv7 |
|---|---|---|
| Size | 8 bytes | 16 bytes |
| Ordering | Strict within worker | Approximate globally |
| Coordination | Worker-id registry | None |
| Collision risk | If worker ids clash | Negligible (74 random bits) |
| Operational complexity | Medium (worker-id mgmt) | Low |
How does UUIDv7 compare to UUIDv4?
UUIDv4 is fully random with no ordering and no insert locality.
-- UUIDv4: random, no ordering
SELECT gen_random_uuid(); -- 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
SELECT gen_random_uuid(); -- '1a2b3c4d-5e6f-4789-abcd-ef0123456789'
-- No relationship between the two values
-- UUIDv7: timestamp prefix, approximately ordered
-- '0192a3b4-0000-7...' (generated at T1)
-- '0192a3b4-0001-7...' (generated at T2)
-- Second value is guaranteed to sort after the first
| Feature | UUIDv4 | UUIDv7 |
|---|---|---|
| Random bits | 122 | 74 |
| Time ordering | None | Approximate |
| Insert locality | Random (fragmented) | Near-sequential |
| Guessable | No | Partially (timestamp) |
| Uniqueness | Excellent | Excellent |
How do you generate UUIDv7 in Java?
// com.bank.util.UUIDv7Generator
package com.bank.util;
import java.security.SecureRandom;
import java.util.UUID;
public class UUIDv7Generator {
private static final SecureRandom RANDOM = new SecureRandom();
/**
* Generate a UUIDv7 with 48-bit timestamp prefix.
* In production, prefer a library like uuid-creator:
* com.github.f4b6a3:uuid-creator
* UuidCreator.getTimeOrderedEpoch()
*/
public static UUID generate() {
long now = System.currentTimeMillis();
// Most significant bits: timestamp + version
long msb = (now << 16) // 48-bit timestamp in high bits
& 0xFFFFFFFFFFFF0000L
| 0x0000000000007000L // version 7
| (RANDOM.nextLong() & 0x0FFF); // 12 random bits
// Least significant bits: variant + random
long lsb = 0x8000000000000000L // variant 10
| (RANDOM.nextLong() & 0x3FFFFFFFFFFFFFFFL);
return new UUID(msb, lsb);
}
public static void main(String[] args) {
// Generate 5 UUIDv7 values to show ordering
for (int i = 0; i < 5; i++) {
System.out.println(generate());
}
// Output: values sort in approximately chronological order
}
}
What are common mistakes?
-- Mistake 1: Storing UUIDv7 as a 36-character string
CREATE TABLE payments (
id CHAR(36) PRIMARY KEY -- 36 bytes, defeats the purpose
);
-- Correct:
CREATE TABLE payments (
id UUID PRIMARY KEY -- 16 bytes, native type
);
-- Mistake 2: Using UUIDv7 as an auth token
-- The timestamp prefix leaks creation time
-- A UUIDv7 is an identifier, NOT a secret
-- Mistake 3: Expecting strict global ordering
-- Clocks on different nodes can skew
-- Two UUIDv7 from different machines in the same ms
-- may not reflect true causal order
What should a senior engineer know?
- UUIDv7 is defined in RFC 9562, published in 2024.
- Some implementations add a sub-millisecond monotonic counter in the 12 random bits after the version to ensure strict monotonicity within the same millisecond on a single node.
- Keyset pagination with UUIDv7 works because
WHERE id > ?approximately equals "after this time," making it suitable for cursor-based APIs. - Store as
uuid(PostgreSQL),BINARY(16)(MySQL without native uuid type), oruniqueidentifier(SQL Server).
What are common interview follow-up questions?
- When should you choose auto-increment over UUIDv7?
- How does Snowflake's worker-id requirement compare to UUIDv7's coordination-free design?
- What is the collision probability of UUIDv7 with 74 random bits?
- How would you implement keyset pagination with UUIDv7 as the cursor?
Visual Overview
┌─────────────────────────────────────────────────┐ │ UUIDv7 Structure (128 bits) │ ├─────────────────────────────────────────────────┤ │ │ │ Bit layout: │ │ ┌────────────────────────────────────────┐ │ │ │ 48 bits: Unix ms timestamp │ │ │ ├────────────────────────────────────────┤ │ │ │ 4 bits: version = 0111 (7) │ │ │ ├────────────────────────────────────────┤ │ │ │ 12 bits: random / sub-ms counter │ │ │ ├────────────────────────────────────────┤ │ │ │ 2 bits: variant = 10 │ │ │ ├────────────────────────────────────────┤ │ │ │ 62 bits: random │ │ │ └────────────────────────────────────────┘ │ │ │ │ Comparison of ID strategies: │ │ ┌──────────────┬───────┬────────┬──────┐ │ │ │ │ Order │ Coord │ Size │ │ │ ├──────────────┼───────┼────────┼──────┤ │ │ │Auto-increment│ Yes │Central │ 8B │ │ │ │Snowflake │ Yes │Worker │ 8B │ │ │ │UUIDv4 │ No │ None │ 16B │ │ │ │UUIDv7 │ ~Yes │ None │ 16B │ │ │ └──────────────┴───────┴────────┴──────┘ │ │ UUIDv7 = unique + time-ordered + no coord │ └─────────────────────────────────────────────────┘
Key Takeaway
UUIDv7, from RFC 9562, is a 128-bit identifier laid out as a 48-bit Unix millisecond timestamp, then a four-bit version field holding the value seven, then a two-bit variant field, and then the remaining bits filled with randomness. Because the timestamp sits in the most significant bits, values generated over time are approximately monotonically increasing, and that single fact gives two valuable properties: time ordering, so sorting by the id roughly equals sorting by creation time, and index locality, so every new key lands near the right edge of the B-tree, keeping pages dense and page splits rare, just like auto-increment.
The reason UUIDv7 is attractive is that it combines three properties that the alternatives only offer partially. Auto-increment is compact and perfectly ordered but needs a central database sequence, which is a coordination point and a bottleneck in distributed or sharded systems. Snowflake ids are time-ordered and compact but require each generator to own a unique worker id, so you must coordinate and manage worker-id assignment. UUIDv4 needs no coordination and is globally unique but is fully random, so it has no ordering and fragments the index. UUIDv7 is the synthesis – unique like UUIDv4, time-ordered like Snowflake and auto-increment, and coordination-free like UUIDv4, with no sequence and no worker-id registry.
At the National Bank of India the team had many payment services across regions writing to a sharded transactions store, so a central sequence was out, and they did not want to manage Snowflake worker ids across hundreds of pods. UUIDv7 let every service mint unique, time-ordered ids locally, which kept inserts dense and even let Rajesh paginate reports directly on the primary key using keyset pagination, because ordering by the id approximated ordering by time.
The nuance Pankaj always states is that UUIDv7 gives approximate ordering, not a strict global sequence, because clocks on different nodes can skew, so ids from two machines in the same millisecond may not reflect true causal order. He stores it as a compact 16-byte native type, and treats it strictly as an identifier, never a secret, because the timestamp prefix openly reveals when the record was created.
Remember one sentence: UUIDv7 is a 48-bit timestamp prefix plus random fill, giving unique, time-ordered, coordination-free ids – the counter feel of auto-increment without a central sequence.
---