What v4 actually gives you
Version 4 UUIDs are 122 bits of randomness dressed up as a string. That is enough that accidental collisions are not your realistic failure mode; operational mistakes are. You get opaque identifiers that clients can generate without calling home, which matters for offline-first mobile apps and for APIs that create drafts before persistence.
The trade-off is readability. A bigint `user_id=1042` tells a story on a support call. `a3f2c1d8-…` does not. We still pick v4 when multiple services insert rows without coordination, or when exposing sequential IDs is a privacy leak.
Before you bake UUIDs into every table, skim your ORM docs for native UUID types versus storing strings. Postgres `uuid` is four bytes smaller than `char(36)` and indexes behave better when the column type matches what the planner expects.
Indexing and performance reality
Random primary keys fragment B-tree indexes compared to monotonic integers. Inserts land all over the index instead of at the right edge, which can mean more page splits and warmer caches on write-heavy tables. For most indie apps the difference is noise; for high-throughput fact tables it is worth measuring.
If you need distributed IDs but want friendlier index locality, consider time-ordered alternatives (UUID v7, ULID, Snowflake-style IDs). They are not magic — you still document the format — but they reduce the “random insert” penalty while keeping uniqueness across nodes.
Secondary indexes on UUID foreign keys are fine; just avoid converting back and forth between string and binary in application code. Pick one representation in the schema and stick to it in migrations.
Generating IDs in dev without ceremony
We have seen teams commit seed scripts that hard-code three UUIDs and wonder why staging looks empty. Faster path: generate a batch when you need fixtures, paste into SQL or JSON, move on.
The UUID Generator on DroidXP uses Web Crypto in the browser — hyphenated or compact, upper or lower case, as many as you need for a test run. Nothing uploads; it is the same “open a tab, copy, close” workflow we use for JWT samples and fake API keys.
Pair generated IDs with realistic fake payloads from your usual data tools so integration tests catch type mismatches early. UUIDs fail quietly when a column is still `bigint` in one environment and `uuid` in another.
When not to use v4
Skip random UUIDs as primary keys on small lookup tables where humans grep logs daily. Use integers or short slugs. Use v4 (or v7) on entities that leave your service boundary or merge from multiple writers.
Public URLs are a separate decision: opaque UUIDs in paths are fine for security through obscurity-ish resources; slugs are better for marketing pages. Do not confuse “database primary key” with “canonical URL.”
Document which tables use which strategy in your README so the next contributor does not “standardize” everything to v4 in one enthusiastic PR.