{
  "id": 19364549,
  "title": "UUIDv4 Is Not Free: Why Random Primary Keys Are Silently Crushing Your Write Performance",
  "url": "https://urgent.news/2026/11/10/uuidv4-is-not-free-why-random-primary-keys-are-silently-crushing-your",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-11-10T16:30:00.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/johnnylemonny/uuidv4-is-not-free-why-random-primary-keys-are-silently-crushing-your-write-performance-2ea8"
  },
  "original_language": "en",
  "account": "When database write latency unexpectedly increased by 340% after surpassing 50 million rows, it became evident that the issue wasn't related to CPU usage or missing indexes. The culprit was the default primary key setup using UUIDv4 as the primary key. Many backend engineers typically avoid auto-incrementing integers in public-facing systems due to several reasons, such as sequential IDs revealing business metrics, enabling enumeration attacks, and complicating distributed ID generation across microservices. However, UUIDv4, despite its popularity, introduces its own set of performance issues that often go unnoticed.\n\nUUIDv4 utilizes 128 bits of randomness, requiring no coordination among nodes and guaranteeing global uniqueness. While this may seem like an appealing architectural choice, it turns into a storage engine nightmare. Relational databases like PostgreSQL, MySQL (InnoDB), and SQL Server manage primary key indexes using B-Tree structures. These trees keep keys sorted in fixed-size pages on disk, typically 8 KB in PostgreSQL and 16 KB in InnoDB. Sequential keys (1, 2, 3...) allow the database to simply append each new entry to the rightmost leaf page. However, UUIDv4, with its 122 bits of random bits, lands in a completely arbitrary position within the sorted B-Tree, leading to a phenomenon known as page thrashing.\n\nThe mechanics of page thrashing involve three expensive operations in the database engine: random disk reads (cache misses), frequent page splits, and index bloat & write amplification. Random disk reads occur when the target leaf page for a random UUID is not in memory, forcing the database to fetch a cold 8 KB or 16 KB page from disk every time a new key is added. Frequent page splits happen when the target page is usually already full, requiring the database to split the page in half, allocate a new page, move 50% of the keys over, and update parent pointers. This results in index bloat and write amplification, with constant 50/50 page splits leaving B-Tree pages half-empty and writing full-page images to the Write-Ahead Log (WAL), thereby multiplying disk write volumes.\n\nThe impact of using random UUIDv4 primary keys becomes even more pronounced as the table grows beyond 50-100 million rows and exceeds memory capacity. In such scenarios, the throughput difference can widen to 5x-10x, with every random insert waiting on physical disk I/O. To mitigate these performance issues, it is essential to generate UUIDv7 in the application layer instead. UUIDv7, a time-ordered UUID standard, places a 48-bit Unix millisecond timestamp at the very beginning of the identifier, followed by 74 bits of randomness. This approach allows new UUIDv7 keys to behave like sequential integers within a B-Tree while remaining globally unique and unguessable.\n\nTo verify the impact of using random UUIDv4 keys versus time-ordered UUIDv7 keys, a benchmark test was conducted on PostgreSQL using pgstattuple to inspect internal B-Tree page fragmentation. The test involved creating two tables, one with random UUIDv4 primary keys and another with time-ordered UUIDv7 primary keys, and inserting 1,000,000 rows into each table. The results revealed that, although the index size increased only slightly (39 MB for UUIDv4 and 21 MB for UUIDv7), the impact on write speed, buffer hits/reads, and WAL generation was significant. The UUIDv4 test resulted in a 3x slower write time, 46% larger index, 61% more WAL write amplification, and nearly double the primary key index size compared to the UUIDv7 test. Once the table surpassed 50-100 million rows and exceeded memory capacity, the throughput difference between the two key types widened significantly, with UUIDv4 causing a 5x-10x slowdown due to disk I/O waiting.\n\nTo migrate safely, it is not necessary to abandon the distributed benefits of UUIDs or change column types. Both UUIDv4 and UUIDv7 share the same 128-bit binary layout and fit into the native uuid column type. If the database engine lacks native support for uuidv7(), you can generate it in the application layer using code in languages like Node.js or TypeScript. By adopting this approach, you can avoid the performance pitfalls associated with random UUIDv4 primary keys and maintain the benefits of distributed ID generation while ensuring optimal write performance in your storage engine.",
  "summary": "When my database write latency spiked by 340% after crossing 50 million rows, my CPU wasn't maxed out. My queries weren't missing indexes. Instead, my storage IOPS was completely saturated by a single architectural default I set up on day one: using UUIDv4 as my primary key. Most backend engineers avoid auto-incrementing integers ( BIGINT / SERIAL ) in public-facing systems for good reasons.…",
  "key_points": [],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}