Compare a database sequence or auto-increment integer with a randomly generated UUID (version 4) as the primary key of a large, insert-heavy table. What are the real costs, and where does a time-ordered UUID (version 7) fit?
answer
- Random key = random leaf = whole index is the hot set
- Right-edge split keeps pages dense; mid-page split leaves them half full
- 16 bytes native, 36 as text - never text
- Clustered engines copy the PK into every secondary index
- v7 = time prefix + random: locality back, timestamp leaks
basics
~20 sSequences give small, ordered keys, so inserts land at the right edge of the index and stay cache-friendly. Random UUIDv4 keys scatter inserts across the whole index, causing random I/O, page splits and a much larger working set, and they are twice the width. UUIDv7 is time-ordered, restoring locality while staying client-generatable.
solid answer
~60 sA sequence produces increasing 8-byte values, so B-tree inserts concentrate on the rightmost pages: those pages stay in cache, splits are cheap right-edge splits, and the index stays dense. The downsides are that values must come from the database, so clients cannot mint ids offline, and gaps appear after rollbacks or cached allocations. Random UUIDv4 destroys that locality. Each insert targets a random leaf, so the hot set is the entire index rather than its tail. Once the index exceeds memory, every insert becomes a read plus a write, splits happen mid-page leaving pages half full, and write-ahead log volume grows because more distinct pages are dirtied. It is also 16 bytes rather than 8 - and if stored as 36-character text, far worse. In engines that cluster by the primary key, the key is embedded in every secondary index, so that width multiplies. UUIDv7 puts a millisecond timestamp in the high bits and randomness below, so inserts are append-mostly like a sequence while remaining globally unique and generatable by clients. The trade is that it leaks creation time and reintroduces right-edge contention.
code
sql · 6 lines-- fine
id uuid NOT NULL PRIMARY KEY -- 16 bytes, byte comparison
-- avoid on a hot table
id varchar(36) NOT NULL PRIMARY KEY -- 36+ bytes, collation-aware compare,
-- copied into every referencing columngo deeper
Know that auto-increment ids are small and ordered while random UUIDs are large and unordered, and that unordered inserts are harder on indexes.
Explain the B-tree consequence - random inserts touch random leaves, cause mid-page splits and a large hot set - plus the 16-versus-8-byte width and never storing UUIDs as text.
Add the memory cliff, write-ahead log and checkpoint effects, key copying into secondary indexes in clustered engines, sequence gaps and non-transactionality, and where UUIDv7 helps and what it costs.
Decide the identifier strategy against generation location, shard or region topology, migration cost and what may be exposed externally, and require measurement at production data volume before committing.
## Why key ordering dominates insert cost Primary keys are backed by a B-tree. Where a new entry lands is decided by the key's value, so the distribution of generated keys decides the physical write pattern. **Monotonic keys.** With an increasing sequence, every insert goes to the rightmost leaf page. That page is almost certainly already in the buffer cache, so no read is needed. When it fills, the split is a specialised right-edge split that leaves the old page full and starts a new one - so pages stay dense and the index stays compact. The set of pages touched by inserts is tiny regardless of how large the table is. **Random keys.** With UUIDv4 the target leaf is uniformly random across the whole index. While the index fits in memory this costs little. Once it does not, each insert must read a page from storage, modify it, and eventually write it back: one random read and one random write per insert. Splits occur in the middle of the key space, typically leaving both halves around half full, so the index inflates - commonly to well over the size an ordered build would produce - and needs more memory, which worsens the cache miss rate in a feedback loop. Checkpointing costs more too, because dirty pages are scattered rather than clustered, and in engines that log full pages after a checkpoint, log volume rises with the number of distinct pages touched. This is why teams report an insert-throughput cliff: performance is fine until the index no longer fits in RAM, then it falls off sharply. ## Width, and where the key gets copied A `bigint` is 8 bytes; a UUID is 16 as a native type, 36 if stored as text (and worse still with a collation applied to comparisons). The multiplier matters because the key is copied. In an index-organised or clustered-primary-key engine such as InnoDB, every secondary index entry stores the primary key as its row pointer, so an extra 8 bytes per row is paid in every secondary index. In heap-organised engines such as PostgreSQL, secondary indexes store a physical tuple pointer instead, so the multiplication is smaller - but every foreign key column referencing the table, and every index over those columns, still doubles in width. Wider keys mean fewer entries per page, deeper trees, and more pages to cache. **Never store UUIDs as text** if you use them. Native `uuid` or `binary(16)` halves the storage and compares as bytes. ## What random keys buy - **Generation without the database.** Clients, offline apps and separate services can mint an id before any write, which makes the id usable as an idempotency key and lets an aggregate be assembled entirely in memory before insertion. - **Merge safety.** Rows created independently in different shards, regions or environments can be combined without collisions. - **No shared allocator.** No sequence to be a bottleneck or a single point of coordination. - **Non-enumerable.** They do not reveal row counts or allow guessing neighbouring ids - though that is a defence-in-depth property, never a substitute for authorization. ## UUIDv7 and friends UUIDv7 (standardised alongside the other UUID versions in the current IETF specification) places a Unix millisecond timestamp in the most significant bits, then random bits. Sorting by the value approximately sorts by creation time, so inserts cluster at the right edge like a sequence while retaining decentralised generation. ULID and KSUID are earlier designs with the same idea. Practical consequences: - Insert locality is restored; index density and cache behaviour approach the sequence case. - Range scans and partition pruning by time become possible on the key itself. - **It leaks creation time** to anyone who sees the id - usually harmless, occasionally not. - **Right-edge contention returns.** Many concurrent inserters hammering the same rightmost page can contend on latches, which is precisely the problem some teams use random keys to avoid. Within a millisecond the random suffix spreads inserts slightly, which helps. - Ids from different nodes interleave only as well as their clocks agree; clock skew reorders but does not break uniqueness. ## Sequences: the sharp edges Sequences are not transactional. A rolled-back transaction consumes the value, and caching or preallocation blocks means gaps and, across restarts or failover, sometimes non-monotonic ordering. So **never treat a sequence value as a count, an audit ordering, or a gapless document number** - invoice numbering that must be gapless needs its own serialised allocation. Sequences also require a round trip or a RETURNING clause to learn the id, and they collide when independently generated datasets are merged unless you use per-node offsets or ranges. ## A defensible default For a single-database OLTP system: `bigint` identity, and expose something else publicly if enumeration matters. When ids must be generated outside the database, or rows are created across shards or regions, use UUIDv7 stored natively. Reserve UUIDv4 for cases where unpredictability of the value itself is a requirement, and accept the index cost knowingly. And measure at your real data size, because every one of these effects is invisible while the index still fits in memory.
- Why does insert throughput with random UUID keys often fall off a cliff rather than degrading gradually?While the index fits in the buffer cache, a random target page is already in memory, so the random distribution costs almost nothing. Once the index exceeds available memory, nearly every insert misses the cache and requires a physical read before the write, so per-insert cost jumps by orders of magnitude. Half-full pages from mid-page splits inflate the index further, which pushes it past memory sooner and accelerates the collapse.
- Your primary key column is defined as varchar(36) holding UUID text. What would you change and why?Store it as a native uuid or binary(16) column. Text form is more than twice the bytes, is copied into every foreign key column and index that references it, and compares under collation rules rather than as raw bytes, which is slower and can be locale-sensitive. Fewer entries per page also means a deeper tree and more cache pressure on every lookup, not just on inserts.
- Does switching to a time-ordered UUID remove all the downsides of random ids?No. It restores insert locality and index density, but it reintroduces right-edge page contention under heavy concurrent insert load, it still costs 16 bytes rather than 8, and it embeds a millisecond creation timestamp that anyone holding the id can read. It also relies on reasonably synchronised clocks across generators for ordering to be meaningful, though uniqueness does not depend on that.
A sequence is filing new documents at the end of a drawer you always have open. Random UUIDs mean each new document goes into a randomly chosen drawer somewhere in the building - fine when the building is one room, ruinous once you have to walk.
saying these in an interview costs you the question
- Claiming UUIDs are slow purely because they are 'bigger', without mentioning insert locality and page splits
- Storing UUIDs as 36-character text on a hot table
- Assuming sequence values are gapless and can be used as counts or audit ordering
- Believing unguessable keys provide access control rather than defence in depth
- Thinking a time-ordered UUID has no downsides - it leaks creation time and brings back right-edge contention