The sequential_uuid extension provides two UUID generators that produce sequential values, solving the random I/O problem that random UUIDs cause on large indexes.
Prerequisites
The sequential_uuid extension is supported on PolarDB for PostgreSQL (Compatible with Oracle) clusters running the following engine versions:
PolarDB for PostgreSQL (Compatible with Oracle) 2.0 (revision version 2.0.14.1.0 or later)
PolarDB for PostgreSQL (Compatible with Oracle) 1.0 (revision version 1.1.28 or later)
To check the revision version of your cluster, run:
SHOW polar_version;How it works
The random UUID problem
A random UUID generator distributes values evenly across its entire range. Because each insert can land on any index page with equal probability, all index pages must stay in memory at once. For small indexes this is manageable, but as the index grows beyond the shared buffer size (or available RAM), the cache hit rate drops sharply and I/O spikes.
Sequential values — from sequences or timestamps — avoid this: new data almost always goes to the rightmost index page, which improves the cache hit rate significantly. The challenge with a naive prefix approach (prepend a sequence or timestamp, pad with random bytes to 16 bytes) is twofold:
Reduced randomness. Prefixing with a
bigintsequence cuts randomness from 16 bytes to 8 bytes, increasing collision probability and making UUIDs predictable.Index bloat. Ever-increasing prefixes leave gaps after old rows are deleted — a common problem in log tables.
Block-based design
To preserve randomness while keeping values sequential, sequential_uuid generates UUIDs in blocks that wrap around after a fixed number of UUIDs or a fixed time interval. Each UUID has the form:
(block ID; random data)The block ID size is fixed at two bytes for the default 64 KB block size. The block ID increments with each new block and wraps around when the limit is reached, so indexes never grow unboundedly.
Sequence-based blocks
block ID = (nextval('s') / 256) % 65536Block size is measured in UUID count (default: 256 UUIDs per block).
Wraps around after 16 M UUIDs (256 x 65,536).
Timestamp-based blocks
block ID = (timestamp / 60) % 65536Block size is a time interval (default: 60 seconds).
Wraps around every 45 days.
Generate sequential UUIDs
Install the extension once per database:
CREATE EXTENSION sequential_uuids;The extension provides two functions. Both accept optional parameters; the defaults work well for most workloads.
uuid_sequence_nextval
Generates a sequential UUID using a PostgreSQL sequence to determine the block ID.
Syntax
uuid_sequence_nextval(seq regclass, block_size integer DEFAULT 65536, block_count integer DEFAULT 65536)| Parameter | Type | Default | Description |
|---|---|---|---|
seq | regclass | — | The sequence used to derive the block ID |
block_size | integer | 65536 | Number of UUIDs per block |
block_count | integer | 65536 | Total number of blocks before wrapping |
Example: use as a primary key default
CREATE SEQUENCE events_uuid_seq;
CREATE TABLE events (
event_id UUID PRIMARY KEY DEFAULT uuid_sequence_nextval('events_uuid_seq'::regclass, 256, 65536),
event_type TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
INSERT INTO events (event_type) VALUES ('user_login'), ('page_view'), ('checkout');
SELECT event_id, event_type FROM events;Sample output:
uuid_sequence_nextval
--------------------------------------
00005547-8a67-452d-bdf7-b390f1edc49b
(1 row)The leading bytes of successive UUIDs in the same block are identical, which causes new rows to cluster on the same index page.
uuid_time_nextval
Generates a sequential UUID using the current timestamp to determine the block ID.
Syntax
uuid_time_nextval(interval_length integer DEFAULT 60, block_count integer DEFAULT 65536)| Parameter | Type | Default | Description |
|---|---|---|---|
interval_length | integer | 60 | Block duration in seconds |
block_count | integer | 65536 | Total number of blocks before wrapping |
Example
SELECT uuid_time_nextval(1, 256);Sample output:
uuid_time_nextval
--------------------------------------
08dac705-8776-4ce3-a45c-123fd65e11e8
(1 row)Tradeoffs
Before enabling sequential_uuid, consider the following tradeoffs:
Increased predictability. Because UUIDs follow a sequential pattern within each block, it becomes possible to infer when two UUIDs were generated or to estimate a row's insertion order. If your application relies on UUID unpredictability for security reasons, evaluate this risk before use.
Higher cross-machine collision probability. Sequential UUIDs generated independently on multiple machines are more likely to collide than purely random UUIDs. If your architecture generates UUIDs across distributed nodes, consider this when choosing block sizes.
For a detailed analysis of sequential UUID design and its tradeoffs, see Sequential UUID Generators.