--- title: 'PostgreSQL 18 UUIDv7 Support' page_title: 'PostgreSQL 18 UUIDv7 Support - Generate Timestamp-Ordered UUIDs' page_description: 'In this tutorial, you will learn about PostgreSQL 18 UUIDv7 support, which allows you to generate timestamp-ordered UUIDs that improve B-tree index performance while maintaining global uniqueness.' ogImage: '' updatedOn: '2025-06-29T03:30:00+00:00' enableTableOfContents: true previousLink: title: 'PostgreSQL 18 Asynchronous I/O' slug: 'postgresql-18/asynchronous-io' nextLink: title: 'PostgreSQL 18 Enhanced Returning' slug: 'postgresql-18/enhanced-returning' --- **Summary**: In this tutorial, you will learn how to use PostgreSQL 18's new UUIDv7 support to generate timestamp-ordered UUIDs that improve B-tree index performance while maintaining global uniqueness. ## Introduction to UUIDv7 PostgreSQL 18 introduces native support for UUIDv7, a new UUID format specified in RFC 9562. UUIDv7 addresses performance limitations that have made developers hesitant to use UUIDs as primary keys in database applications. Traditional UUIDs (like UUIDv4) are completely random, which creates problems for database indexes. When you insert records with random UUIDs as primary keys, the database must insert new rows at random locations in B-tree indexes rather than at the end. This causes index page splits, reduces cache efficiency, and can significantly impact insert performance. UUIDv7 solves this problem by incorporating a timestamp as the most significant part of the UUID, making new UUIDs naturally sortable by creation time. This allows B-tree indexes to perform sequential inserts, similar to auto-incrementing integers, while maintaining the global uniqueness and distributed generation benefits of UUIDs. ## Understanding UUIDv7 Structure UUIDv7 uses a 128-bit structure that combines temporal and random components: - **48 bits**: Unix timestamp in milliseconds - **12 bits**: Sub-millisecond timestamp fraction for additional ordering - **62 bits**: Random data for uniqueness - **6 bits**: Version and variant identifiers This structure ensures that UUIDs generated later in time will have lexicographically larger values, making them naturally sortable and optimal for B-tree indexes. Some of the key benefits of UUIDv7 include chronological ordering, reduced index fragmentation, and improved cache locality. This makes UUIDv7 particularly suitable for applications that require high insert rates and efficient querying. ## New UUID Functions in PostgreSQL 18 PostgreSQL 18 introduces several new UUID-related functions to work with UUIDv7. These functions allow you to generate, manipulate, and extract information from UUIDs easily. Let's explore the key functions: ### `uuidv7()` Function The `uuidv7()` function generates new UUIDv7 values: ```sql -- Generate a UUIDv7 with current timestamp SELECT uuidv7(); -- Example output: 0197f96c-b278-7f64-a32f-dae3cabe1ff0 ``` You can also generate UUIDs for different timestamps by providing an interval: ```sql -- Generate a UUIDv7 for 1 hour ago SELECT uuidv7(INTERVAL '-1 hour'); -- Generate a UUIDv7 for 30 minutes in the future SELECT uuidv7(INTERVAL '30 minutes'); ``` ### `uuidv4()` Alias For consistency, PostgreSQL 18 adds `uuidv4()` as an alias for the existing `gen_random_uuid()` function: ```sql -- These are equivalent SELECT gen_random_uuid(); SELECT uuidv4(); ``` This allows you to generate random UUIDs (UUIDv4) alongside the new timestamp-ordered UUIDv7. ### Enhanced uuid_extract_timestamp() The existing `uuid_extract_timestamp()` function now supports UUIDv7 in addition to UUIDv1: ```sql -- Extract timestamp from a UUIDv7 SELECT uuid_extract_timestamp(uuidv7()); -- Example output: 2025-06-30 12:20:49.409+00 ``` This function returns the timestamp when the UUID was generated, allowing you to track when records were created. ### `uuid_extract_version()` Function You can determine which UUID version you're working with: ```sql -- Check UUID version SELECT uuid_extract_version(uuidv7()); -- Returns 7 SELECT uuid_extract_version(uuidv4()); -- Returns 4 ``` This is useful for validating UUIDs and ensuring compatibility with your application logic. ## Setting Up UUIDv7 in Your Database To start using UUIDv7 in PostgreSQL 18, you need to ensure your database is configured correctly. Let's go through the steps to enable UUIDv7 support and create tables that utilize it. ### Creating Tables with UUIDv7 Primary Keys Here's how to create a table using UUIDv7 as the primary key: ```sql CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT uuidv7(), username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100) NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); ``` The `id` column is defined as a UUID with a default value generated by the `uuidv7()` function. This ensures that every new user record will automatically receive a timestamp-ordered UUID. ### Inserting Data When you insert data, the UUIDv7 is automatically generated: ```sql INSERT INTO users (username, email) VALUES ('alice', 'alice@example.com'); INSERT INTO users (username, email) VALUES ('bob', 'bob@example.com'); -- View the results ordered by ID (chronological order) SELECT id, username, uuid_extract_timestamp(id) as uuid_timestamp FROM users ORDER BY id; ``` This will show the users in the order they were created, thanks to the timestamp-ordered nature of UUIDv7. ## Performance Benefits With UUIDv7, you can expect significant performance improvements over traditional UUIDs, especially in B-tree indexes. The timestamp-ordered structure allows for more efficient inserts and better cache utilization. ### B-tree Index Efficiency UUIDv7's timestamp-ordered structure provides significant benefits for B-tree indexes. Let's compare the performance of UUIDv4 and UUIDv7 in a PostgreSQL database. ```sql -- Create a test table to demonstrate performance CREATE TABLE performance_test ( id_v4 UUID DEFAULT uuidv4(), id_v7 UUID DEFAULT uuidv7(), data TEXT DEFAULT 'sample data' ); -- Create indexes CREATE INDEX idx_v4 ON performance_test (id_v4); CREATE INDEX idx_v7 ON performance_test (id_v7); -- Insert test data INSERT INTO performance_test (data) SELECT 'test record ' || i FROM generate_series(1, 100000) i; ``` The above creates a table with both UUIDv4 and UUIDv7 columns, allowing you to compare their performance. ### Comparing Insert Performance You can observe the difference in index behavior by examining page splits and index bloat: ```sql -- Check index statistics SELECT schemaname, relname AS table_name, indexrelname AS index_name, idx_blks_read, idx_blks_hit FROM pg_statio_user_indexes WHERE indexrelname IN ('idx_v4', 'idx_v7'); ``` This query will show you how many index blocks were read and hit for both UUIDv4 and UUIDv7 indexes. You should see that the UUIDv7 index has fewer page splits and better cache utilization due to its natural ordering. ### Sorting Performance UUIDv7's natural ordering eliminates the need for additional timestamp columns in many cases: ```sql -- This query benefits from the natural ordering of UUIDv7 SELECT id_v7, data FROM performance_test ORDER BY id_v7 LIMIT 10; -- Compare with UUIDv4 (no meaningful order) SELECT id_v4, data FROM performance_test ORDER BY id_v4 -- Random order, no chronological meaning LIMIT 10; ``` The first query will return results in the order they were created, while the second will return random results. This is helpful for applications that need to display records in the order they were created without additional timestamp columns. ## Practical Usage Patterns The timestamp-ordered nature of UUIDv7 makes it suitable for various application scenarios. Some common use cases include: ### Multi-tenant Applications UUIDv7 is particularly useful in multi-tenant applications where you need globally unique identifiers: ```sql CREATE TABLE tenant_records ( id UUID PRIMARY KEY DEFAULT uuidv7(), tenant_id UUID NOT NULL, record_type VARCHAR(50) NOT NULL, data JSONB, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -- Create a composite index that benefits from UUIDv7 ordering CREATE INDEX idx_tenant_records_tenant_created ON tenant_records (tenant_id, id); ``` With the above setup, you can efficiently query records by tenant while maintaining chronological order. ### Distributed Systems UUIDv7 works well in distributed systems where multiple services generate IDs: ```sql -- Each service can generate UUIDs independently -- while maintaining chronological ordering CREATE TABLE distributed_events ( event_id UUID PRIMARY KEY DEFAULT uuidv7(), service_name VARCHAR(50) NOT NULL, event_type VARCHAR(50) NOT NULL, payload JSONB, processed_at TIMESTAMP WITH TIME ZONE ); -- Natural ordering by creation time across all services SELECT event_id, service_name, event_type FROM distributed_events ORDER BY event_id LIMIT 100; ``` This allows you to maintain a global event log that is naturally ordered by creation time, making it easier to process events in the correct sequence. ## Best Practices With the introduction of UUIDv7, consider the following factors when designing your database schema: ### When to Use UUIDv7 Use UUIDv7 when you need: - Globally unique identifiers across distributed systems - Natural chronological ordering - Better B-tree index performance than UUIDv4 - Protection against sequential ID enumeration attacks ### When to Stick with Auto-incrementing IDs Consider staying with `SERIAL` or `BIGSERIAL` when: - Your application is not distributed - You need the smallest possible primary key size - You frequently perform range scans on the primary key - Legacy systems require integer keys ## Current Limitations and Considerations With PostgreSQL 18 beta releases, there are some limitations and considerations to keep in mind when using UUIDv7: ### Clock Synchronization UUIDv7 relies on system clocks, so ensure your servers have synchronized time: - Use NTP or similar time synchronization - Be aware of clock skew in distributed environments - Consider the impact of leap seconds or clock adjustments ### Timestamp Precision UUIDv7 uses millisecond precision for timestamps: ```sql -- The extracted timestamp may differ slightly from the actual generation time SELECT uuidv7() as uuid, CURRENT_TIMESTAMP as actual_time, uuid_extract_timestamp(uuidv7()) as extracted_time; ``` This means that if you generate multiple UUIDs within the same millisecond, they will still be unique but may not reflect the exact order of creation. This is generally acceptable for most applications, but be aware of this behavior in high-frequency scenarios. ### Migration Planning When migrating from UUIDv4 to UUIDv7: - Plan for application changes to handle the new format - Consider the impact on existing indexes and queries - Test performance thoroughly with your specific workload - Update any external systems that depend on UUID format ## Summary PostgreSQL 18's UUIDv7 support addresses long-standing performance concerns with using UUIDs as primary keys. Thanks to the global uniqueness benefits of UUIDs with the B-tree index efficiency of chronologically ordered values, UUIDv7 provides a practical solution for modern distributed applications. The timestamp-ordered nature of UUIDv7 makes it particularly suitable for applications that need both global uniqueness and efficient database operations. Combined with PostgreSQL 18's other performance improvements like skip scans and async I/O, UUIDv7 enables more efficient database designs for distributed systems.