UUID v4 causes index fragmentation in SQL databases
In SQL databases that use B-tree indexes (PostgreSQL, MySQL, SQL Server), random UUID v4 primary keys cause severe index fragmentation over time. Because each new UUID is inserted at a random position in the index, the database constantly splits and rebalances index pages. For high-write tables, this degrades insertion performance significantly. Solutions: use ULID or UUID v7 (time-ordered), use a BIGSERIAL surrogate key with UUID as a secondary unique column, or use the database's UUID-to-binary optimisation (MySQL's UUID_TO_BIN with swap_flag=1).
Use ULID for time-series and event data
Any data that is naturally time-ordered — events, log entries, messages, transactions — benefits from a time-ordered identifier. ULID's sequential nature means new records are always inserted at the end of the index, which is dramatically more efficient than random UUID insertion. ULIDs also encode creation time, enabling time-range queries without a separate created_at column index.
Generate UUIDs in the database, not the application
Most databases have built-in UUID generation: gen_random_uuid() in PostgreSQL, UUID() in MySQL, NEWID() in SQL Server. Generating in the database rather than the application layer avoids the overhead of round-tripping the generated ID back to the application and ensures IDs are generated by a known-good source. The ToolHive generator is useful for seeding data, generating IDs for configuration, and generating IDs outside a database context.
NanoID for URL-safe identifiers
If your IDs appear in URLs (as path segments or query parameters), NanoID's URL-safe alphabet (no +, /, or = characters) means no percent-encoding is needed. A NanoID like "V1StGXR8_Z5jdHi6B-myT" is directly usable in a URL, whereas a standard UUID requires no encoding but is longer and less compact.
Keep a UUID v4 as a public ID even if you use sequential internal IDs
Exposing sequential integer IDs (1, 2, 3...) in public-facing APIs reveals your record count and enables enumeration attacks. A common pattern: use a BIGSERIAL (auto-increment integer) as the internal primary key for performance, and store a UUID v4 as a separate unique column exposed in your API. Internal joins use the fast integer key; external references use the non-guessable UUID.