UUID v4 vs v7: which one should be your primary key

2026-08-29·Developer reference·4 min read·by Sourabh Singh

UUID v4 vs v7: which one should be your primary key

Why random UUIDs wreck database index performance, how UUID v7 fixes it with a timestamp prefix, and when a plain integer is still the right answer.

UUID v4 vs v7: which one should be your primary key

Use UUID v7 for primary keys. It puts a 48-bit millisecond timestamp in the leading bits so inserts append to the index instead of scattering across it, avoiding the page splits and index bloat random v4 keys cause. Use v4 where unlinkability matters more than index locality.

Both are 128-bit identifiers in the same textual format. The difference is what fills the bits, and that difference decides whether your primary key index stays healthy at ten million rows.

What each one is

v4 is 122 random bits plus 6 bits of version and variant metadata. Every value is independent of every other. Two v4s generated a millisecond apart sort nowhere near each other.

v7 puts a 48-bit Unix millisecond timestamp in the leading bits, then fills the rest with randomness. Two v7s generated a millisecond apart sort adjacently, and any v7 sorts after every v7 generated before it.

v4  9f1c2d3e-8b4a-4c5d-9e6f-1a2b3c4d5e6f
v7  01929c4a-7f3b-7c5d-9e6f-1a2b3c4d5e6f
    └── timestamp ──┘└── random ──────┘
Free toolUUID GeneratorGenerate cryptographically random UUID v4s, or time-ordered UUID v7s that index far better as database primary keys.

Why v4 hurts as a primary key

A B-tree index stores keys in sorted order. When you insert a value, the database finds the right leaf page and writes into it.

With sequential keys, every insert lands on the same rightmost page. That page stays in memory, fills up, splits once, and the process repeats. One hot page, near-perfect page utilisation.

With random keys, every insert lands on a different page, scattered across the whole index. That means:

  • Random reads before every write. The target page probably is not in the buffer pool, so it must be read from disk first.
  • Constant page splits. Inserting into the middle of a full page splits it in two, each half-empty. Index size grows to roughly 1.5–2x what a sequential key needs.
  • Write amplification. In Postgres, every touched page is written to the WAL in full the first time it is modified after a checkpoint. Scattered writes touch far more pages.

At a hundred thousand rows none of this is measurable. At ten million with an index larger than RAM, insert throughput can drop by an order of magnitude.

v7 has none of these problems, because it appends. It behaves almost exactly like a sequential integer key while keeping the properties people choose UUIDs for.

What you keep with v7

Client-side generation. The whole point of a UUID: generate the ID before the round trip, so you can build the object graph offline, batch inserts, and avoid a RETURNING id round trip per row.

No collisions across systems. Merge two databases without renumbering anything.

No enumeration. A sequential integer in a URL tells everyone how many customers you have and lets them walk your data by incrementing. A UUID does not.

What you lose with v7

Timestamps are public. The creation time is right there in the first 48 bits of every ID you expose. For most applications this is irrelevant. If your IDs appear in URLs and creation time is sensitive - a private beta signup order, say - v7 leaks it.

That is the only real trade-off. It is a smaller problem than it sounds, and a much smaller problem than a bloated index.

Storage: do not store UUIDs as text

A UUID is 16 bytes. The canonical string form is 36 characters, and stored as varchar in a UTF-8 database that is 37 bytes with the length header - more than double.

  • Postgres: use the native uuid type. 16 bytes, indexes properly.
  • MySQL: BINARY(16), with UUID_TO_BIN/BIN_TO_UUID to convert. MySQL's UUID_TO_BIN(uuid, 1) also swaps the time fields of a v1 UUID to make it sortable - unnecessary for v7, which is already sortable.
  • SQLite: BLOB, or accept the text overhead if the table is small.

Storing as text also breaks range scans, because lexical string ordering only matches value ordering if the encoding is fixed-width and uppercase-consistent.

When to use what

Use v7 for primary keys on anything that will grow - the default choice for new tables in 2026.

Use v4 for anything where unlinkability matters more than index locality: public share tokens, idempotency keys, correlation IDs in logs, anything a user might compare against another.

Use a bigint when rows are only ever created by your own server, IDs never appear in a URL, and you will never merge databases. It is 8 bytes instead of 16, and it is faster than both. Do not adopt UUIDs by default if you do not need what they give you.

Never use v1. It embeds the MAC address of the generating machine, which is an information leak with no upside now that v7 exists.

Generating v7 without a library

function uuidV7() {
  const bytes = crypto.getRandomValues(new Uint8Array(16));
  const stamp = Date.now();
  for (let i = 0; i < 6; i++) {
    bytes[i] = Math.floor(stamp / 2 ** (8 * (5 - i))) & 0xff;
  }
  bytes[6] = (bytes[6] & 0x0f) | 0x70; // version 7
  bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant
  const hex = [...bytes].map((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)}`;
}

Two details people get wrong: the version nibble must be set to 7 in byte 6, and the variant bits in byte 8 must be 10. Skip either and the value is a random 128-bit number that no parser will recognise as a v7 UUID.

Postgres has no built-in v7 generator before version 18. On older versions, generate in the application or add a small PL/pgSQL function - do not fall back to gen_random_uuid() (which is v4) for a table you expect to grow.

Launch it where the numbers are checked

RankCert ranks products on domain control we verify ourselves. Listing is free and the link stays dofollow whether or not you display the badge.

Submit a product - free

Tools from this guide