
Introduction
Choosing the right unique identifier strategy is critical for database performance and application scalability. While UUIDv4 has been the go-to choice for distributed systems, the new UUIDv7 standard introduces time-ordered identifiers that maintain randomness while providing natural sorting capabilities.
PostgreSQL 18โs introduction of native UUIDv7 support marks a turning point in this landscape. Our comprehensive benchmarking shows the native implementation lands within ~4% of UUIDv4 while adding time-ordered benefits that were previously considered a performance trade-off.
This analysis examines five different identifier implementations with real-world performance data, helping you make informed decisions for your PostgreSQL applications.
TL;DR: PostgreSQL 18โs native uuidv7() now ships and matches UUIDv4 within a few microseconds (74.3 vs 71.6 ฮผs per operation) with throughput parity around 21.7K ops/sec. The fastest UUIDv7 option is uuidv7_custom on PG18 at 71.8 ฮผs, landing within 0.6% of UUIDv4. PostgreSQL 17 remains highly competitive with similar numbers. Zero collisions detected across all implementations.
Understanding UUIDv7
Before diving into implementations, letโs understand what makes UUIDv7 special. Unlike its predecessor UUIDv4 (which is completely random), UUIDv7 incorporates a timestamp, making it naturally sortable by creation time.

The structure consists of:
- 48 bits: Unix timestamp in milliseconds
- 4 bits: Version field (0111 binary = 7)
- 12 bits: Random data or sub-millisecond precision
- 2 bits: Variant field
- 62 bits: Additional random data
This design provides both temporal ordering and sufficient randomness to prevent collisions.
UUIDv7 Implementation Approaches
PostgreSQL 18 Native Implementation
PostgreSQL 18 introduces native uuidv7() support with RFC 9562 compliance:
-- PostgreSQL 18+ native function
SELECT uuidv7();
-- Output: 01976408-e525-78fb-889c-818826fc412f
-- Optional time parameter for historical UUIDs
SELECT uuidv7('2024-01-01 00:00:00'::timestamp);
-- Extract timestamp from any UUIDv7
SELECT uuid_extract_timestamp('01976408-e525-78fb-889c-818826fc412f'::uuid);
-- Output: 2024-12-06 10:30:45.637+00Native Implementation Features:
- C-level performance: Direct PostgreSQL core implementation
- 12-bit sub-millisecond precision: Uses rand_a field for timestamp fraction
- Monotonicity guarantee: Ensures ordering within same database session
- Built-in extraction functions:
uuid_extract_timestamp(),uuid_extract_version() - RFC 9562 compliance: Follows latest UUID standard published May 2024
PostgreSQL 18 Native Analysis
PostgreSQL 18 Native Analysis
| Aspect | Assessment |
|---|---|
| Pros |
|
| Cons |
|
Custom UUIDv7 Implementations (PostgreSQL < 18)
Letโs examine each implementation in detail:
Implementation 1: PL/pgSQL Overlay Method (uuid_generate_v7)
CREATE OR REPLACE FUNCTION uuid_generate_v7()
RETURNS uuid
AS $$
BEGIN
-- use random v4 uuid as starting point (which has the same variant we need)
-- then overlay timestamp
-- then set version 7 by flipping the 2 and 1 bit in the version 4 string
RETURN encode(
set_bit(
set_bit(
overlay(uuid_send(gen_random_uuid())
placing substring(int8send(floor(extract(epoch from clock_timestamp()) * 1000)::bigint) from 3)
from 1 for 6
),
52, 1
),
53, 1
),
'hex')::uuid;
END
$$
LANGUAGE plpgsql
VOLATILE;This implementation:
- Generates a random UUIDv4 as the base
- Extracts the current timestamp in milliseconds
- Overlays the timestamp onto the first 48 bits
- Sets the version bits to make it a valid UUIDv7
Implementation 1 Analysis
Implementation 1 Analysis
| Aspect | Assessment |
|---|---|
| Pros |
|
| Cons |
|
Implementation 2: Pure SQL Method (uuidv7_custom)
CREATE FUNCTION uuidv7_custom() RETURNS uuid
AS $$
-- Replace the first 48 bits of a uuidv4 with the current
-- number of milliseconds since 1970-01-01 UTC
-- and set the "ver" field to 7 by setting additional bits
SELECT encode(
set_bit(
set_bit(
overlay(uuid_send(gen_random_uuid()) placing
substring(int8send((extract(epoch from clock_timestamp())*1000)::bigint) from 3)
from 1 for 6),
52, 1),
53, 1), 'hex')::uuid;
$$ LANGUAGE sql VOLATILE;This is essentially the same algorithm as Function 1, but implemented as a pure SQL function.
Implementation 2 Analysis
Implementation 2 Analysis
| Aspect | Assessment |
|---|---|
| Pros |
|
| Cons |
|
Implementation 3: Sub-millisecond Precision (uuidv7_sub_ms)
CREATE FUNCTION uuidv7_sub_ms() RETURNS uuid
AS $$
SELECT encode(
substring(int8send(floor(t_ms)::int8) from 3) ||
int2send((7<<12)::int2 | ((t_ms-floor(t_ms))*4096)::int2) ||
substring(uuid_send(gen_random_uuid()) from 9 for 8)
, 'hex')::uuid
FROM (SELECT extract(epoch from clock_timestamp())*1000 as t_ms) s
$$ LANGUAGE sql VOLATILE;This implementation builds the UUID from scratch:
- Extracts timestamp with fractional milliseconds
- Uses the fractional part for sub-millisecond precision
- Manually constructs the UUID by concatenating components
Implementation 3 Analysis
Implementation 3 Analysis
| Aspect | Assessment |
|---|---|
| Pros |
|
| Cons |
|
Implementation Flow Overview
Start UUID Generation
โ
Choose Implementation
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ โ
PostgreSQL 18+ Custom Implementations
โ โ
Native uuidv7 โโโโโโโโโผโโโโโโโโ
โ โ โ โ
C-level Processing Custom 1 Custom 2 Custom 3
โ โ โ โ
12-bit Sub-ms Precision Generate Generate Extract
โ UUIDv4 UUIDv4 Timestamp
Ensure Monotonicity Base Base โ
โ โ โ Split Integer
Return Native UUIDv7 Extract Extract & Fractional
โ Timestamp Timestamp โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโผโโโโโโโโโโ
โ โ
Overlay on Overlay on
48 bits 48 bits
โ โ
Set Version Set Version
Bits to 7 Bits to 7
โ โ
Return UUID Return UUID
โโโโโฌโโโโ
โ
Final UUIDv7Bit Manipulation Visualization
To better understand how Function 1 and 2 work, hereโs a visual representation of the bit manipulation process:

Performance Benchmarks
To provide real-world performance data, I created a comprehensive benchmark suite testing:
- Single-threaded performance
- Concurrent generation under load
- Collision resistance
- Time ordering accuracy
Benchmark Environment Specifications:
- High-precision Testing: 100,000 iterations per run (10 runs) for statistical significance
- Warmup: 25,000 iterations per function to eliminate cold-start effects
- Runs: 10 complete benchmark cycles per function for consistency analysis
- Timing: Nanosecond precision using
time.perf_counter_ns() - Concurrency: 10 workers ร 10,000 iterations (1,000 warmup) for realistic load testing
- PostgreSQL Config: 512MB shared_buffers, 2GB effective_cache_size, SSD optimizations
- Resource limits: 2GB RAM, 2 CPU cores per container
The full benchmark code and methodology are available at github.com/spa5k/uuidv7-postgres-benchmark.
Comprehensive Benchmark Results
Based on professional-grade benchmarking with 10 runs ร 100,000 iterations per implementation (with warmups):
Single-Thread Performance
Key Finding: PostgreSQL 18โs native uuidv7() now delivers UUIDv4-parity performance (74.3 vs 71.6 ฮผs) with comparable throughput (~21.7K ops/sec). The fastest UUIDv7 option is the lightweight uuidv7_custom on PG18 at 71.8 ฮผs (within 0.6% of UUIDv4). This removes the historical performance penalty for time-ordered IDs.
Key Findings
- PostgreSQL 18 native UUIDv7: Within ~4% of UUIDv4 (74.3 vs 71.6 ฮผs) with matching throughput (~21.7K ops/sec)
- Fastest UUIDv7:
uuidv7_customon PG18 at 71.8 ฮผs (0.6% off UUIDv4 latency) - Throughput leader:
ulid_generateon PG17 at 23,160 ops/sec; top UUIDv7 throughput on PG18 isuuid_generate_v7at 21,877 ops/sec - Time-ordered at no penalty: Practical parity with UUIDv4 removes the historical trade-off
- Zero-downtime migration: Drop-in replacement for existing UUIDv4 columns
- Zero collisions detected across 50,000+ generations per implementation
Collision Probability Analysis
One concern with UUIDs is collision probability. Hereโs how our implementations compare:

Key insights:
- Native uuidv7(): 62 bits of randomness + 12-bit sub-millisecond precision
- Custom UUIDv7: 74 bits of randomness
- ULID: 80 bits of randomness (with time-based ordering)
- TypeID: Based on UUIDv7 with type prefix for additional validation
- Even at 1 billion UUIDs per millisecond, collision probability remains negligible
- Native implementationโs monotonicity guarantee provides additional collision protection
- Zero collisions observed in 50,000 generations across all implementations
Choosing the Right Implementation
Hereโs a decision matrix to help you choose:
Use PostgreSQL 18 Native uuidv7() when:
- Youโre using PostgreSQL 18+ (released 2025)
- You want the best performance AND time ordering
- You need guaranteed monotonicity within sessions
- You prefer official, maintained implementations
- You want built-in timestamp extraction functions
Use Custom Implementation 1 (uuid_generate_v7) when:
- Youโre on PostgreSQL < 18
- You prefer readable, maintainable code
- Youโre already using PL/pgSQL functions
- Performance is good enough (>18K UUIDs/sec concurrent)
- You want a well-documented approach
Use Custom Implementation 2 (uuidv7_custom) when:
- Youโre on PostgreSQL < 18
- You prefer pure SQL functions
- You want balanced performance
- You donโt need sub-millisecond precision
Use Custom Implementation 3 (uuidv7_sub_ms) when:
- Youโre on PostgreSQL < 18
- You need sub-millisecond time precision
- Youโre generating many UUIDs within the same millisecond
- Time ordering accuracy is paramount
- You can accept slightly lower performance
Implementation Recommendations
1. Indexing Strategy
-- Create a B-tree index for time-based queries
CREATE INDEX idx_uuid_time ON your_table (id);
-- For composite indexes, put UUID first if it's the primary filter
CREATE INDEX idx_uuid_status ON your_table (id, status);2. Migration from UUIDv4
-- Add new column
ALTER TABLE your_table ADD COLUMN new_id uuid DEFAULT uuidv7_custom();
-- Migrate existing data (optional)
UPDATE your_table SET new_id = uuidv7_custom() WHERE new_id IS NULL;
-- Switch primary key
ALTER TABLE your_table DROP CONSTRAINT your_table_pkey;
ALTER TABLE your_table ADD PRIMARY KEY (new_id);3. Monitoring Performance
-- Track UUID generation performance
CREATE OR REPLACE FUNCTION benchmark_uuid_generation(
func_name TEXT,
iterations INT DEFAULT 1000
) RETURNS TABLE (
avg_microseconds NUMERIC,
total_seconds NUMERIC
) AS $$
DECLARE
start_time TIMESTAMP;
end_time TIMESTAMP;
BEGIN
start_time := clock_timestamp();
EXECUTE format('SELECT %I() FROM generate_series(1, %s)', func_name, iterations);
end_time := clock_timestamp();
RETURN QUERY SELECT
EXTRACT(EPOCH FROM (end_time - start_time)) * 1000000 / iterations,
EXTRACT(EPOCH FROM (end_time - start_time));
END;
$$ LANGUAGE plpgsql;Production Considerations
High Availability
All three functions are deterministic based on system time, making them safe for:
- Read replicas
- Logical replication
- Multi-master setups (with proper clock synchronization)
Clock Synchronization
Important: UUIDv7 relies on accurate system time. Ensure your servers use NTP synchronization to prevent time drift, which could affect ordering.
Storage Optimization
UUIDs are 128-bit values, stored as 16 bytes in PostgreSQL. For large tables:
- Consider using BRIN indexes for time-range queries
- Partition by time ranges that align with your UUID timestamps
- Use
CLUSTERperiodically to maintain physical ordering
Performance Comparison Charts
Updated Performance Results (Professional Benchmark Data)
Our professional-grade benchmarks with 10 runs ร 100,000 iterations and statistical analysis reveal clear performance leaders:
Performance Rankings
| Rank | Implementation | Avg Time (ฮผs) | Throughput (ops/sec) | Performance vs UUIDv4 |
|---|---|---|---|---|
| 1 | UUIDv4 (gen_random_uuid, PG17) | 71.4 | 21,425 | Baseline fastest |
| 2 | UUIDv7 (uuidv7_custom, PG18) | 71.8 | 21,688 | ~0.6% slower latency, throughput parity |
| 3 | UUIDv7 (uuid_generate_v7, PG18) | 73.8 | 21,877 | ~3% slower latency, top UUIDv7 throughput |
| 4 | UUIDv7 (native uuidv7, PG18) | 74.3 | 21,674 | ~4% slower latency, throughput parity |
| 5 | ULID (ulid_generate, PG17) | 80.3 | 23,160 | ~12% slower latency, throughput leader |
Key Insights from Professional Benchmark Analysis
- Native UUIDv7 near parity: PostgreSQL 18 native uuidv7() measures 74.3 ฮผs vs UUIDv4 at 71.6 ฮผs with ~21.7K ops/sec
- Fastest UUIDv7:
uuidv7_custom(PG18) at 71.8 ฮผs; top UUIDv7 throughput isuuid_generate_v7(PG18) at 21,877 ops/sec - Throughput leader overall:
ulid_generate(PG17) reaches 23,160 ops/sec with human-readable IDs - Statistical Significance: Results based on 10 runs ร 100,000 iterations with multiple runs for reliability
- Zero Collision Rate: All implementations maintain perfect uniqueness guarantees (50k collision sample)

Multi-dimensional Performance Analysis
Implementation Architecture Overview
Understanding the architectural differences helps explain the performance characteristics:
UUID Family
โโโ ๐ด UUIDv4 (Baseline)
โ โโโ Pure random
โ โโโ No time info
โ โโโ PostgreSQL native
โ
โโโ UUIDv7 Implementations
โ โโโ ๐ต UUIDv7 (PL/pgSQL)
โ โ โโโ Overlay method
โ โ โโโ Best single-thread
โ โ โโโ Readable code
โ โ
โ โโโ ๐ข UUIDv7 (Pure SQL)
โ โ โโโ Bit operations
โ โ โโโ No PL/pgSQL overhead
โ โ โโโ Balanced performance
โ โ
โ โโโ ๐ก UUIDv7 (Sub-ms)
โ โโโ Custom precision
โ โโโ Manual construction
โ โโโ Best time ordering
โ
โโโ Alternative Formats
โโโ ๐ ULID
โ โโโ Base32 encoded
โ โโโ Human readable
โ โโโ Lexicographic sort
โ โโโ Compact storage
โ
โโโ ๐ฃ TypeID
โโโ Prefixed identifiers
โโโ Type safety
โโโ Based on UUIDv7
โโโ Self-documenting
Performance Characteristics:
๐ Single-threaded: gen_random_uuid fastest โ 71.4 ฮผs (UUIDv4 baseline)
๐ Fastest UUIDv7: uuidv7_custom (PG18) โ 71.8 ฮผs (โ0.6% off UUIDv4)
โก Throughput: ulid_generate (PG17) โ 23,160 ops/sec
๐พ Storage: ULID most compact โ 26 bytes textThe architecture diagram reveals why certain implementations perform differently:
- UUIDv4: Direct PostgreSQL C implementation with no timestamp manipulation
- UUIDv7 variants: Add timestamp overlay operations with varying complexity
- ULID: Custom timestamp formatting with Base32 encoding overhead
- TypeID: Builds on UUIDv7 with additional prefix concatenation
Feature Comparison Matrix
| Feature | UUIDv4 | UUIDv7 (PL/pgSQL) | UUIDv7 (SQL) | UUIDv7 (Sub-ms) | ULID | TypeID |
|---|---|---|---|---|---|---|
| Time Ordered | โ | โ | โ | โ | โ | โ |
| Human Readable | โ | โ | โ | โ | โ | โ |
| Type Safe | โ | โ | โ | โ | โ | โ |
| Compact Binary | โ | โ | โ | โ | โ | โ |
| PostgreSQL Native | โ | โ | โ | โ | โ | โ |
| Lexicographic Sort | โ | โ | โ | โ | โ | โ |
Beyond UUIDv7: ULID and TypeID Alternatives
While UUIDv7 provides excellent time-ordering capabilities, there are other modern identifier formats worth considering for specific use cases. Letโs explore ULID and TypeID implementations in PostgreSQL.
ULID (Universally Unique Lexicographically Sortable Identifier)
ULID offers a human-readable alternative to UUIDs with natural lexicographic sorting:
CREATE OR REPLACE FUNCTION ulid_generate() RETURNS TEXT AS $$
DECLARE
timestamp_ms BIGINT;
chars TEXT := '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
result TEXT := '';
i INT;
idx INT;
BEGIN
-- Get current timestamp in milliseconds
timestamp_ms := (EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::BIGINT;
-- Create time-sortable prefix (10 chars) based on timestamp
result := lpad(to_hex(timestamp_ms), 10, '0');
-- Add 16 random base32 characters
FOR i IN 1..16 LOOP
idx := (random() * 31)::INT + 1;
result := result || substr(chars, idx, 1);
END LOOP;
RETURN upper(result);
END;
$$ LANGUAGE plpgsql VOLATILE;ULID Characteristics:
- Length: 26 characters
- Encoding: Crockford Base32 (case-insensitive)
- Example:
01ARZ3NDEKTSV4RRFFQ69G5FAV - Storage: 26 bytes as text
- Time precision: Millisecond
TypeID (Type-safe Prefixed Identifiers)
TypeID adds type safety by prefixing identifiers with their entity type:
-- Create composite type for binary TypeID
DROP TYPE IF EXISTS typeid CASCADE;
CREATE TYPE typeid AS (
prefix TEXT,
uuid UUID
);
-- Function returning composite type
CREATE OR REPLACE FUNCTION typeid_generate(prefix_param TEXT DEFAULT 'obj')
RETURNS typeid AS $$
BEGIN
RETURN ROW(prefix_param, uuidv7_custom())::typeid;
END;
$$ LANGUAGE plpgsql VOLATILE;
-- Function returning text representation
CREATE OR REPLACE FUNCTION typeid_generate_text(prefix_param TEXT DEFAULT 'obj')
RETURNS TEXT AS $$
DECLARE
uuid_val UUID;
chars TEXT := '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
result TEXT := '';
i INT;
idx INT;
BEGIN
uuid_val := uuidv7_custom();
-- Generate 26 characters base32-like representation
FOR i IN 1..26 LOOP
idx := (random() * 31)::INT + 1;
result := result || substr(chars, idx, 1);
END LOOP;
RETURN prefix_param || '_' || result;
END;
$$ LANGUAGE plpgsql VOLATILE;TypeID Characteristics:
- Format:
prefix_base32encodedid - Examples:
user_01h4qm3k5n2p7r8s9t0v1w2x3y,order_01h4qm3k5n2p7r8s9t0v1w2x3y - Storage: Variable length (prefix + 27 characters)
- Type safety: Entity type embedded in identifier
Extended Performance Comparison
Based on professional-grade benchmarking with 10 runs ร 100,000 iterations per test:
Storage Efficiency Analysis
Comprehensive Performance Summary
Collision Resistance
All implementations achieved zero collisions in 50,000 ID generation tests (collision sample size), demonstrating excellent entropy and uniqueness guarantees across all identifier types.
PostgreSQL 18 Native UUIDv7 Support
PostgreSQL 18 introduces native uuidv7() support with significant advantages:
-- PostgreSQL 18+ native function
SELECT uuidv7();
-- Output: 01976408-e525-78fb-889c-818826fc412f
-- Optional time parameter
SELECT uuidv7('2024-01-01 00:00:00'::timestamp);
-- Extract timestamp from UUIDv7
SELECT uuid_extract_timestamp('01976408-e525-78fb-889c-818826fc412f'::uuid);Native UUIDv7 Features:
- C-level implementation for maximum performance
- 12-bit sub-millisecond precision (vs 62-bit random in custom implementations)
- Monotonicity guarantee within the same database session
- Built-in extraction functions for timestamp and version
- Backward compatibility with existing UUID infrastructure
Choosing the Right Identifier
Use UUIDv7 when:
- You need maximum PostgreSQL compatibility
- Binary storage efficiency is critical (16 bytes)
- Youโre already using UUID infrastructure
- Database indexing performance is a priority
- You need PostgreSQL 18โs native implementation benefits
Use ULID when:
- Human readability is important for debugging
- You need case-insensitive identifiers
- Lexicographic sorting is required in application code
- You want a single string representation without dashes
- URL safety is important (no special characters)
Use TypeID when:
- Type safety is critical for preventing ID misuse
- You have multiple entity types to identify
- API clarity and self-documentation are important
- You want to prevent accidentally using wrong ID types
- Debugging requires knowing entity type from ID alone
Implementation Recommendations
Database Schema Design
-- UUIDv7 primary keys (PostgreSQL 18+ native function)
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuidv7(), -- PostgreSQL 18+ native
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- For PostgreSQL < 18, use custom function:
-- id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
-- ULID for human-readable IDs
CREATE TABLE orders (
id TEXT PRIMARY KEY DEFAULT ulid_generate(),
user_id UUID REFERENCES users(id),
amount DECIMAL(10,2)
);
-- TypeID for type-safe multi-entity systems
CREATE TABLE entities (
id TEXT PRIMARY KEY,
entity_type TEXT NOT NULL,
data JSONB
);
-- Insert with TypeID
INSERT INTO entities (id, entity_type, data)
VALUES (typeid_generate_text('product'), 'product', '{"name": "Widget"}');Migration Strategy
-- Gradual migration from UUIDv4 to UUIDv7
-- PostgreSQL 18+: Use native function
ALTER TABLE existing_table ADD COLUMN new_id UUID DEFAULT uuidv7();
-- PostgreSQL < 18: Use custom function
-- ALTER TABLE existing_table ADD COLUMN new_id UUID DEFAULT uuid_generate_v7();
-- Backfill existing records (optional)
UPDATE existing_table SET new_id = uuidv7() WHERE new_id IS NULL;
-- Switch primary key
ALTER TABLE existing_table DROP CONSTRAINT existing_table_pkey;
ALTER TABLE existing_table ADD PRIMARY KEY (new_id);
ALTER TABLE existing_table DROP COLUMN id;
ALTER TABLE existing_table RENAME COLUMN new_id TO id;Future Considerations
PostgreSQL 18 Improvements
PostgreSQL 18 provides significant advances:
- Native uuidv7(): C-level implementation with sub-millisecond precision
- Monotonicity: Guaranteed ordering within database sessions
- Built-in functions:
uuid_extract_timestamp(),uuid_extract_version() - Performance: Demonstrated parity with UUIDv4 (74.3 vs 71.6 ฮผs) and throughput around 21.7K ops/sec
- Backward compatibility: Seamless replacement for custom functions
Performance Recommendations
Based on our benchmarks:
- For PostgreSQL 18+ projects (when available): Use native
uuidv7() - For existing systems: Custom UUIDv7 implementations remain excellent
- For human-readable IDs: ULID provides best developer experience
- For type safety: TypeID prevents costly ID-related bugs
Conclusion
Modern applications have excellent choices for time-ordered identifiers in PostgreSQL. Based on our comprehensive benchmarking:
Performance Summary
Latest Benchmark Results (10 runs ร 100k iterations):
- UUIDv4 (
gen_random_uuid, PG17): 71.4 ฮผs avg, ~21.4K ops/sec (baseline fastest) - UUIDv7 (
uuidv7_custom, PG18): 71.8 ฮผs avg, 21,688 ops/sec (fastest UUIDv7, ~0.6% off UUIDv4) - UUIDv7 (
uuid_generate_v7, PG18): 73.8 ฮผs avg, 21,877 ops/sec (top UUIDv7 throughput on PG18) - UUIDv7 (
uuidv7_native, PG18): 74.3 ฮผs avg, 21,674 ops/sec (native support with throughput parity) - ULID (
ulid_generate, PG17): 80.3 ฮผs avg, 23,160 ops/sec (throughput leader when readability matters) - TypeID (
typeid_generate_text, PG18): 87.8 ฮผs avg, 22,857 ops/sec (type-safe, slightly slower)
Decision Matrix
| Priority | Recommendation | Why |
|---|---|---|
| Maximum Performance | `gen_random_uuid` (UUIDv4) | Fastest single-thread (โ71.4 ฮผs) and simplest baseline option |
| Time-ordered on PostgreSQL 18 | `uuidv7_custom` (or native `uuidv7()`) | 71.8โ74.3 ฮผs with throughput parity (~21.7K ops/sec); closest to UUIDv4 latency |
| PostgreSQL 17 Compatibility | Custom `uuidv7()` implementation | 73 ฮผs single-thread with time ordering and drop-in compatibility |
| Human Readability | ULID | Case-insensitive, 26-byte compact storage, lexicographic sorting; throughput leader (23,160 ops/sec on PG17) |
| Type Safety | TypeID | Prevents ID misuse, self-documenting, API clarity |
| Storage Efficiency | UUIDv4/UUIDv7 | 16 bytes binary, mature indexing, wide tool support |
| Proven Stability | UUIDv4 (`gen_random_uuid`) | Battle-tested, PostgreSQL native, zero compatibility issues; best raw latency |
Key Findings
- Native uuidv7() lands at UUIDv4 parity - 74.3 vs 71.6 ฮผs with ~21.7K ops/sec
- Fastest UUIDv7 -
uuidv7_custom(PG18) at 71.8 ฮผs, within 0.6% of UUIDv4 - Throughput leader -
ulid_generate(PG17) at 23,160 ops/sec; top UUIDv7 throughput isuuid_generate_v7(PG18) at 21,877 ops/sec - Updated methodology - 10 runs ร 100k iterations + concurrency (10ร10k) with warmups
- Zero collisions observed across 50,000+ generations per implementation
- Storage efficiency varies by format: UUIDs (16 bytes binary) vs ULID (26 bytes text) vs TypeID (31+ bytes text)
- Time-ordered identifiers are production-ready with negligible performance penalty
Decision Guide
Choose ID Generation Method
โ
Primary Requirement?
โ
โโโโโโผโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
โ โ โ โ
Maximum Time Human Type
Concurrent Ordering Readability Safety
Performance โ โ โ
โ โ โ โ
๐ด UUIDv4 Need Sub-ms ๐ ULID ๐ฃ TypeID
~21.6K Precision? Base32 Prefixed
ops/sec โ encoded identifiers
Battle- โ Lexicographic Self-documenting
tested โโโโดโโโ sort
Yes No
โ โ
PostgreSQL Performance
Version? Priority?
โ โ
โโโโดโโโ โโโโดโโโ
18+ <18 Single- Balanced
โ โ threaded Approach
๐ข PostgreSQL ๐ก UUIDv7 โ โ
18 native Sub-ms ๐ต UUIDv7 ๐ข UUIDv7
uuidv7 Custom PL/pgSQL Pure SQL
C-level precision 75.9 ฮผs No PL/pgSQL
performance Best time ~71.8โ73 ฮผs overhead
ordering UUIDv4 Good all-aroundPerformance Summary
๐ Performance Champions
- ๐ฅ Overall Latency:
gen_random_uuid(PG17) โ 71.4 ฮผs; fastest UUIDv7 isuuidv7_custom(PG18) โ 71.8 ฮผs - ๐ฅ Throughput:
ulid_generate(PG17) โ 23,160 ops/sec; top UUIDv7 throughput on PG18 isuuid_generate_v7โ 21,877 ops/sec - ๐ฅ Storage Efficient: ULID โ 26 bytes (Most compact text representation)
๐ฏ Specialized Features
- ๐๏ธ Human Readable: ULID โ Base32 encoding, no special characters
- ๐ก๏ธ Type Safe: TypeID โ Prefixed identifiers, self-documenting
- โฑ๏ธ Time Precision: UUIDv7 (Sub-ms) โ Sub-millisecond, best ordering
๐ฎ Future Ready
- ๐ PostgreSQL 18+: native uuidv7() โ C-level implementation, GA with near-baseline latency
The modern identifier landscape offers powerful options beyond traditional UUIDs. Choose based on your applicationโs specific requirements for readability, type safety, storage efficiency, and compatibility needs.
Resources
- Benchmark Repository - Full benchmark code and results
- IETF UUIDv7 Draft - Official specification
- PostgreSQL UUID Functions - PostgreSQL documentation
Last updated: December 2025 | PostgreSQL 17 & 18 GA with latest 10ร100k benchmark data
Related Articles
Recreating PlanetScale's pg_strict in Rust: A Build Log
A detailed build log of cloning PlanetScale's pg_strict in Rust. From the pitfalls of sqlparser and executor hooks to the zero-overhead solution using Postgres' native post_parse_analyze_hook.
dockerBest Dockerfile for Golang, Optimize Your Dockerfile
Create best Dockerfile for Golang, optimize your Dockerfile for Golang and make it blazingly fast! ๐ฅ