deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

PostgreSQL 18 benchmark: UUIDv7 inserts run nearly 10x faster than UUIDv4

A dev.to benchmark finds UUIDv7 primary keys in PostgreSQL 18 insert close to 10x faster than UUIDv4 at scale, with B-tree indexes roughly a quarter smaller at every tested size.

PostgreSQL 18 benchmark: UUIDv7 inserts run nearly 10x faster than UUIDv4

The benchmark

A hands-on comparison published on dev.to set out to quantify what PostgreSQL 18's native UUIDv7 support is actually worth. The author measured two things that matter for schema design: how long bulk inserts take, and how much disk the resulting primary-key index consumes.

The test loaded 1, 10 and 100 million rows into tables whose only column was a UUID marked PRIMARY KEY, which means PostgreSQL maintains a unique B-tree index on it by default. The two contenders were UUIDv4, the long-standing default for synthetic keys, and UUIDv7, which PostgreSQL 18 can now generate natively via uuidv7().

The outcome, according to the post: at small volumes the two variants are nearly indistinguishable, but as row counts climb the gap widens until UUIDv7 inserts complete almost ten times faster than UUIDv4. Index size grows linearly for both, but the v7 index stays about a quarter smaller at every scale tested.

The author also wanted to chart a billion rows but abandoned the idea after the UUIDv4 run alone looked set to take more than an hour on his machine.

Why random keys slow inserts down

The mechanics come from how the two formats are built. Under RFC 9562, as cited in the post, UUIDv7 devotes its leading 48 bits to a millisecond-resolution Unix timestamp, so identifiers generated later sort after earlier ones. UUIDv4, by contrast, is fully random.

That difference maps directly onto B-tree behavior. A new UUIDv7 always sorts after everything already in the index, so PostgreSQL only has to find the rightmost leaf — a page that is almost certainly hot in cache. A new UUIDv4 lands somewhere arbitrary in the key space, forcing the engine to pull entirely unrelated pages into RAM just to locate the correct leaf. That overhead multiplies as the table grows, which is why the insert-time curves diverge at scale. The post attributes the slowdown to exactly this pattern; it does not separately explain the index-size gap, though scattered random keys generally leave B-tree pages less densely packed than sequential appends do.

How the test was run

The setup is deliberately simple and reproducible. The author ran the postgres:18.6 image in Docker with an init script containing two bare tables, test_v4 and test_v7, each with a single UUID primary-key column. Rows were generated server-side with statements like insert into test_v7(id) select uuidv7() from generate_series(1,1_000_000), with psql's \timing on capturing elapsed time. Index sizes were read through pg_size_pretty(pg_relation_size(...)), and tables were truncated and vacuumed between runs. The full benchmark code is on the author's GitHub.

Caveats worth noting

The author is upfront about the limits of the numbers. Exact timings will differ on other hardware, and the benchmark inserted each dataset in one large statement — so it excludes application throughput, network round-trips and the extra WAL traffic that many small transactions would produce. Real-world workloads should expect different absolute figures, even if the relative trend holds.

The post also flags one further v7 advantage beyond raw speed: identifiers generated around the same moment end up stored on the same index page, which helps queries that fetch batches of related rows. One tradeoff it does not discuss is that a v7 value embeds its creation timestamp, so exposed identifiers reveal roughly when a row was written — worth weighing before using them in public-facing URLs or APIs.

Why it matters

Primary-key format is a decision made early, lived with for years, and painful to reverse — you cannot casually rewrite every key in a production system. For new schemas on PostgreSQL 18 and later, this benchmark makes a concrete case for defaulting to UUIDv7 on high-volume tables: ingest runs faster, indexes stay smaller, buffer-cache churn drops, and you get rough time-ordering of rows for free. Teams already committed to UUIDv4 should not read this as a migration mandate, since the benchmark's conditions differ from typical OLTP traffic, but it is hard to ignore a tenfold insert gap and a 25% index saving when the fix is a one-word change in a CREATE TABLE statement.

  • #postgresql
  • #databases
  • #performance
  • #uuid
  • #benchmark

Related posts