Toolbit
Guides

UUID v4 vs. v7: Which to Use and Why It Matters for Your Database

How UUID v4 and v7 are built, why v7 keeps database indexes compact, what each version reveals, and how they compare to ULIDs, Snowflake IDs, and auto-increment keys.

By Javier VallejoPublished 6 min read

The problem UUIDs solve

The simplest way to identify records is a number that counts up by one: the first user is 1, the second is 2. That works fine as long as a single database hands out the numbers. Once identifiers are created in several places at once — multiple servers, mobile apps creating records offline, services talking over message queues — you need coordination to avoid duplicates, and that coordination is slow, fragile, or outright impossible.

UUIDs remove the need for coordination: any process can generate a 128-bit identifier on its own, with a collision probability so low you can ignore it. For years the default choice was UUID v4, which is fully random. Since RFC 9562 was published in 2024, UUID v7 has become the recommended alternative for many use cases. This guide covers how each one is built, why the difference matters in a database, and what each format gives away. To generate or inspect UUIDs, the UUID generator handles both versions.

How they're built

Both take up 128 bits and share two fixed fields: 4 bits of version and 2 bits of variant. The difference is in what fills the rest.

UUID v4: the remaining 122 bits are random. It carries no information at all — not when it was created, not where.

xxxxxxxx-xxxx-4xxx-Vxxx-xxxxxxxxxxxx
               │    └─ variant (8, 9, a, or b)
               └────── version 4
x = 122 random bits

UUID v7: the first 48 bits are a Unix timestamp in milliseconds, and the rest — apart from version and variant — are 74 random bits (or partly a counter, when several are generated in the same millisecond).

tttttttt-tttt-7rrr-Vrrr-rrrrrrrrrrrr
└─────────┘    │
 48-bit timestamp (ms since 1970)
r = 74 random or counter bits

One example straight from RFC 9562 is 017f22e2-79b0-7cc3-98c4-dc0c0c07398f. Its first 12 hex digits, 017f22e279b0, are the number 1645557742000: the milliseconds elapsed from 1970 to February 22, 2022 at 19:22:22 UTC. Anyone holding the UUID can read that, as the timestamp converter shows.

Why v7 is better for indexes

Most relational databases store indexes — the primary key included — as B-trees: sorted structures split into fixed-size pages. Inserting a value means finding its place in the sort order and writing it to the right page.

With an auto-increment integer or a UUID v7, every new value is larger than the last, so it always lands on the index's last page. That page is almost always in memory, earlier pages stay full, and writes are sequential.

With a UUID v4, every new value lands at a random position. That has three effects that grow with table size:

  • More disk reads: the target page could be any page, and with large tables many of them won't be in memory.
  • Page splits: when a page in the middle fills up, the engine splits it in two, leaving half-empty pages behind. The index takes more space for the same data.
  • More write-ahead log traffic: each page modified for the first time after a checkpoint may be written in full to the WAL (in PostgreSQL, for instance), and random inserts touch far more distinct pages.

On small tables you won't notice. On tables with tens of millions of rows and heavy inserts, switching from v4 to v7 typically shrinks indexes and write load noticeably. As a bonus, v7 lets you sort by primary key to get records in creation order — something that's meaningless with v4.

What each version gives away

A readable timestamp is not a minor detail. A UUID v7 reveals when the record was created, to the millisecond. If a user's ID appears in a public URL, anyone can tell when they signed up; if order IDs are public, comparing two of them gives away your sales rate. When that's sensitive, there are two ways out: use v4 for identifiers you expose, or use v7 as the internal key and a separate identifier externally.

The extreme historical case is UUID v1, which embedded the MAC address of the network card that generated it. In 1999, investigators tracking down the author of the Melissa virus used, among other clues, an identifier of that kind that Microsoft Word had embedded in documents. That's why v1 is considered a legacy format today.

A UUID v4 gives away nothing, as long as it's generated from a cryptographic source. Even so, RFC 9562 is clear that no UUID should be treated as a secret: session tokens, reset links, and API keys call for a dedicated random token.

Collisions: v4 vs. v7

With 122 random bits, you'd need about 2.7 × 10¹⁸ UUID v4s for a 50% chance that any two match — a billion per second for 86 years. In a UUID v7 the random part is 74 bits, but it only competes with UUIDs generated in the same millisecond. Even with a million UUIDs generated within one millisecond, the collision probability is under 1 in 10¹⁰. And if the generator uses the counter RFC 9562 allows, UUIDs from the same process can't repeat within a millisecond at all.

How it compares to the alternatives

IdentifierSizeSortableDecentralized generationReveals
Auto-increment4 or 8 bytesYesNoRecord count and order
UUID v416 bytesNoYesNothing
UUID v716 bytesYesYesCreation time
ULID16 bytes (26 characters as text)YesYesCreation time
Snowflake (Twitter/X)8 bytesYesYes, with an assigned machine IDCreation time and machine

ULID was the community's answer to the same problem before v7 existed: 48 bits of timestamp and 80 random bits, written in Crockford's Base32. Today v7 offers the same thing in a standard format that fits the databases' uuid type. Snowflake-style IDs are half the size (64 bits) but require assigning a number to every generating machine, which brings some coordination back.

Generating each version in practice

Environmentv4v7
Browser and Node.jscrypto.randomUUID()A library (e.g., the uuid package)
Pythonuuid.uuid4()uuid.uuid7() from Python 3.14
PostgreSQLgen_random_uuid()uuidv7() from PostgreSQL 18
JavaUUID.randomUUID()A library
Gouuid.New() (google/uuid package)uuid.NewV7() (same package)

Which one to choose

  • Primary key on a fast-growing table: v7. It keeps indexes compact and lets you sort by creation.
  • Identifier shown in public URLs, where creation time is sensitive: v4.
  • Identifier derived from a name (the same email should always map to the same ID): v5.
  • Access token, reset link, or API key: neither — use a random token of at least 128 bits generated specifically for that purpose.
  • Existing system on v4: no need to migrate. You can start generating v7 for new rows; both versions coexist happily in the same column.

Summary

A UUID v4 is 122 bits of randomness and reveals nothing; a UUID v7 starts with a millisecond timestamp and therefore sorts by creation time. That ordering makes v7 far better for database indexes, at the cost of exposing when each record was created. For internal keys, v7; for public identifiers where timing matters, v4; and for secrets, neither.

Tools used in this guide

Related guides