Benchmarks
How long it takes to generate one ID, in each library and database. For what IDs cost once stored (index size, insert speed, compression), see Data engineering and storage. Those effects are usually much larger than generation time: at 70 ns per ID, a million IDs take 70 ms to generate.
Measured on an Apple M-series Mac (arm64, 14 cores), single-threaded unless
marked ×8. The databases ran in Docker (PostgreSQL) or natively (MySQL, Redis).
Rerun everything with make bench; the full report is in
bench/results/.
Libraries
Nanoseconds per ID; lower is better.
| ULID | ULID monotonic | ULID parse | UUIDv4 | UUIDv7 | UUIDv7 monotonic | Relative ID | Snowflake | Nano ID (21) | |
|---|---|---|---|---|---|---|---|---|---|
| C core | 81 | 25 | 21 | 91 | 73 | 23 | 75 | 244 | 132 |
| Rust | 65 | 33 | 8 | 74 | 70 | 33 | 117 | 244 | 131 |
| Go | 125 | 45 | 15 | 257 | 122 | 54 | 141 | 244 | 297 |
| Java 24 | 239 | 28 | 34 | 139 | 250 | 28 | 255 | 244 | 226 |
| Python 3.12 | 1,346 | 439 | 434 | 1,868 | 2,003 | 973 | 1,577 | 415 | 1,198 |
Source: bench/results/2026-09-25-darwin-arm64.txt, generated by bench/doc_tables.py.
- Snowflake is capped at 4,096 IDs per millisecond per generator by its 12-bit sequence, which works out to at least 244 ns per ID. Every native implementation reaches that ceiling. To go faster, add generators with different machine ids.
- Random IDs cost one call to the OS random source. That call dominates ULID, UUID and Nano ID timings. The monotonic generators are faster because, within a millisecond, they only increment the previous value.
- On macOS the call gets about 6 times slower above 10 bytes (≈ 40 ns up
to 10 bytes, ≈ 230 ns above). The C core and Rust ask for at most 10 bytes at
a time, so UUIDv4’s 16 bytes cost about the same as UUIDv7’s 10. Go’s
crypto/randdoesn’t split requests, which is why Go’s UUIDv4 and Nano ID cost about twice its UUIDv7. - Java’s UUIDv4 is the JDK’s
UUID.randomUUID(), which uses the fasterNativePRNG. UUIDv7 and ULID use a per-threadSecureRandomDRBG instead, to avoid contending on one lock when many threads generate IDs. - Parallel generation on macOS contends on the system random source.
Generating ULIDs on all 14 cores in Go reaches 2.9 million per second in
total, fewer than the 8 million a single thread manages. Linux’s
getrandomscales better here.
Databases
| ULID | ULID monotonic | ULID as uuid |
UUIDv7 | UUIDv7 monotonic | Relative ID | Snowflake | Nano ID | Built-in reference | |
|---|---|---|---|---|---|---|---|---|---|
| PostgreSQL 17 (ns per row) | 81 | 43 | 71 | 61 | 28 | 84 | 196 | 99 | gen_random_uuid() 612 |
| MySQL 9.2 (ns per call) | 103 | 65 | — | 111 | 65 | 133 | 282 | 203 | UUID() 70 |
| Redis 8.4 (requests/s) | 528k | 462k | — | 504k | 481k | 515k | 432k | 490k | PING 551k |
Source: bench/results/2026-09-25-darwin-arm64.txt, generated by bench/doc_tables.py.
- PostgreSQL figures are
SELECT count(expr) FROM generate_series(1, 1e6)minus the cost ofcount(n)alone. Snowflake’s net figure can read below the 244 ns cap because some of that overhead overlaps with waiting for the next millisecond; its wall time (287 ms per million) respects the cap. The UUIDv7 columns are idgenkit’suuidv7_generate(), which works on 14+; the built-ingen_random_uuid()(UUIDv4) took about 8 times as long in this run. See the insert timings on the storage page for what each format costs once stored. - MySQL figures use
BENCHMARK(1000000, expr)on one connection. MySQL’sUUID()returns version 1 UUIDs (time based), which are cheap to generate. - Redis throughput is bound by networking and command dispatch (50 clients,
pipeline depth 16): every command runs at
PINGspeed.