UUIDs

 Using Type 3 UUIDs (name-based) as primary keys or indexed columns in PostgreSQL (which defaults to B-tree indexes) is generally not a good idea for performance-critical tables with high insert rates.

Here's why, in the context of B-tree indexes and PostgreSQL:

  1. B-Tree Structure and Ordering: PostgreSQL's default indexes are B-trees. B-trees maintain their data in a sorted order. When you insert a new row, the database must find the correct sorted position for its primary key (or indexed column) and insert the new entry there.

  2. Non-Sequential Nature of Type 3 UUIDs: While Type 3 UUIDs are deterministic (the same input name always produces the same UUID), they are not sequentially ordered in time or alphabetically. As we've discussed, a slight change in the input "name" (e.g., 'a' vs. 'b' or 'Artist.class' vs. 'Area.class') results in a dramatically different, seemingly random UUID value due to the cryptographic hashing.

  3. Index Fragmentation (The Problem):

    • When you insert new Type 3 UUIDs, their random-like numerical values mean they will fall into random places within the existing B-tree index.

    • This forces the database to constantly split and reorganize index pages to accommodate the new entries, leading to:

      • Slower Inserts: Frequent page splits are computationally expensive and can lead to more disk I/O.

      • Larger Indexes: Index pages might not be fully utilized, increasing the overall size of the index.

      • Reduced Read Performance: Fragmented indexes can lead to less efficient cache utilization and more random disk reads, even for SELECT queries.

Analogy: Imagine trying to keep a phone book perfectly sorted alphabetically (a B-tree) but instead of adding new names in their correct sorted position, you randomly insert new pages in the middle. You'd constantly have to rip pages apart and move sections, leading to a messy and inefficient book.

Recommendation for PostgreSQL:

If you need to use UUIDs as primary keys in PostgreSQL and desire optimal insert and query performance, especially for tables with a high volume of inserts, consider using sequential UUID types:

  • UUIDv7: This is a new UUID standard (RFC 9562) that combines a time-ordered component with a random component. UUIDv7s are globally unique and lexicographically sortable, meaning later-generated UUIDs are numerically greater than earlier ones. This makes them ideal for B-tree indexes as new inserts will primarily append to the end of the index, minimizing fragmentation.

  • ULIDs (Universally Unique Lexicographically Sortable Identifiers): Similar to UUIDv7, ULIDs are 128-bit identifiers that are designed to be globally unique and are also sortable by time. Many database drivers and libraries offer support for generating and storing ULIDs.

Using UUIDv7 or ULIDs allows you to leverage the benefits of UUIDs (global uniqueness) while maintaining the performance characteristics of monotonically increasing IDs in B-tree indexes. If you are stuck with Type 3 or Type 4 UUIDs, make sure to analyze the performance impact on your specific workload, as for low-insert-rate tables, the impact might be negligible.


With Type 7 UUIDs or ULIDs (Universally Unique Lexicographically Sortable Identifiers), you can indeed achieve significant performance improvements, especially when they are used as primary keys or indexed columns in databases that rely on B-tree indexes (like PostgreSQL).

Here's why they offer better performance compared to Type 3 or Type 4 UUIDs:

  • Sequential Ordering: Unlike Type 3 (name-based) or Type 4 (random) UUIDs, Type 7 UUIDs and ULIDs are designed to be lexicographically sortable due to incorporating a timestamp component at their beginning. This means that a UUID generated later in time will, with extremely high probability, be numerically greater than one generated earlier.

  • B-Tree Optimization: B-tree indexes are optimized for sequentially increasing keys.

    • Reduced Fragmentation: When new IDs are always greater than previous ones, new data is mostly appended to the end of the index. This drastically reduces the need for expensive page splits and reorganizations within the B-tree.

    • Faster Inserts: Appending data is much faster than inserting it into random locations, as it minimizes random disk I/O.

    • Improved Cache Utilization: Sequential writes are more cache-friendly, leading to better performance from database buffer pools and CPU caches.

    • Smaller Indexes: Less fragmentation means indexes stay more compact and efficient.

In essence, Type 7 UUIDs and ULIDs allow you to retain the benefits of global uniqueness that UUIDs offer, while simultaneously gaining the performance advantages traditionally associated with auto-incrementing integer IDs in B-tree indexed tables.

Comments