В PostgreSQL появилась встроенная функция генерации UUIDv7 для первичных ключей

UUIDv7 - это звезда первой величины среди типов идентификаторов и ключей
UUIDv7 - это звезда первой величины среди типов идентификаторов и ключей

В конце сентября 2025 года вышла СУБД PostgreSQL 18. Она получила долгожданную встроенную функцию uuidv7(). Функция uuidv7() генерирует согласно международному стандарту RFC 9562 идентификаторы типа UUID версии 7 (UUIDv7) с бинарным типом данных uuid, рекомендованные и используемые в качестве первичных ключей. При необходимости таймстемп с часовым поясом может быть извлечен из них с помощью функции uuid_extract_timestamp().

UUIDv7 сочетает в себе глобальную уникальность первичных ключей, пренебрежимо малую вероятность коллизий (недопустимых случайных совпадений) и упорядоченность по моменту времени генерации. При этом не используются централизованная координация вычислений и MAC-адреса. Риск коллизий не выше, чем у прежде самого популярного (случайного) типа UUID версии 4.

Благодаря упорядоченности по моменту времени генерации UUIDv7 обеспечивают гораздо большую производительность и меньшее потребление дискового пространства для индексов по сравнению с UUIDv4. Старшие биты идентификаторов UUIDv7 могут использоваться в качестве ключа секционирования (partition key).

UUIDv7 обеспечивают такую же производительность CRUD-операций БД, как при использовании автоинкремента (типа serial и его современного аналога GENERATED ... AS IDENTITY). Время генерации идентификатора UUIDv7 приблизительно в тысячу раз меньше времени вставки записи, поэтому темп генерации UUIDv7 не влияет на производительность БД.

Использование UUIDv7 позволяет избавиться от фундаментальных недостатков автоинкремента:

В отличие от сторонних расширений PostgreSQL для генерации UUIDv7 и от генерации идентификаторов UUIDv7 в приложениях, встроенная функция обеспечивает простоту использования и монотонность (возрастание) идентификаторов, генерируемых в интервалах короче миллисекунды. Эта монотонность в течение миллисекунды нужна для поиска причин ошибок, пагинации по ключу (keyset pagination), поиска в логах, использования в БД временных рядов и т.п.

Особенности реализации функции uuidv7() в PostgreSQL 18:

Использование таймстемпа в качестве счетчика в критических ситуациях гарантирует монотонность и уникальность при генерации в одном процессе даже при временной недоступности системных часов или их откате назад, а также при сколь угодно высоких темпах генерации. При параллельной генерации в нескольких процессах (микросервисах) как правило обеспечивается монотонность - за счет дополнительного 12-битного субмиллисекундного сегмента таймстемпа. Незначительные нарушения монотонности не влияют на производительность БД.

Смещение значения таймстемпа с помощью параметра позволяет замаскировать фактическую дату создания записи, предотвращает конфликты блокировок при параллельной генерации UUIDv7 в нескольких процессах, а также улучшает монотонность при генерации на удаленных клиентах. В случае смещении значения таймстемпа с помощью параметра при генерации UUIDv7 функция uuid_extract_timestamp() будет выдавать смещенное значение даты и времени.

Теоретически смещение значения таймстемпа позволяет также гарантировать уникальность при генерации в разных процессах (микросервисах). Но в этом нет реальной необходимости. Ведь удлиненный таймстемп вместе с длинным случайным сегментом обеспечивают пренебрежимо малую вероятность коллизии идентификаторов UUIDv7.

Пример использования:

SELECT uuidv7();

-- Create clients table with UUIDv7 as primary key with masked timestamp (5500 years + 12.5 hours forward)
CREATE TABLE clients (
    id uuid DEFAULT uuidv7(INTERVAL '5500 years 12 hours 30 minutes') PRIMARY KEY,
    name text NOT NULL,
    email text,
    created_at timestamptz DEFAULT CURRENT_TIMESTAMP
);

-- Insert clients. Let the DEFAULT value generate the UUID
INSERT INTO clients (name, email) VALUES
    ('John Smith', 'john.smith@example.com'),
    ('Emma Watson', 'emma.watson@example.com'),
    ('Michael Brown', 'michael.brown@example.com');

-- If you need a UUID for a past date, the interval should be negative
INSERT INTO clients (id, name, email) VALUES
    (uuidv7(INTERVAL '-11 years -5 hours -44 minutes'), 'James Anderson', 'james.anderson@example.com'),
    (uuidv7(), 'Olivia Parker', 'olivia.parker@example.com');

-- Verify the masked timestamp
SELECT 
    id,
    name,
    email,
    created_at as actual_creation_time,
    uuid_extract_timestamp(id) as masked_uuid_timestamp,
    uuid_extract_timestamp(id) - created_at as timestamp_shift
FROM clients
ORDER BY id;

Английский вариант этой новости (English version of this news)

@SergeyProkhorenko
25.09.2025 19:52 UTC
Первоисточник

Комментарии

@n0wheremany
25.09.2025 15:25 UTC
0

Нет информации что он всегда больше предыдущего или для PG это не актуально при фрагментации индексов?

@CatAssa
25.09.2025 16:29 UTC
+2

сочетает в себе ... и упорядоченность по моменту времени генерации

@SergeyProkhorenko
25.09.2025 20:10 UTC
+1

Нарушение строгой монотонности идентификаторов UUIDv7 возможно только при экстремально высокой параллельной нагрузке, когда несколько процессов БД генерируют идентификаторы при одном и том же значении встроенного таймстемпа (с разрешением 250 наносекунд). Это крайне маловероятно. Но даже в этом случае все такие идентификаторы с близкими значениями будут располагаться на одной странице индекса (в худшем случае - на соседних), не вызывая значительной фрагментации.

Незначительные нарушения монотонности не влияют на производительность БД.

@LeshaRB
25.09.2025 16:28 UTC
0

В конце сентября 2025 года вышла СУБД PostgreSQL 18

Почему не написать сегодня, 25 сентября вышла...?

@SergeyProkhorenko
25.09.2025 20:11 UTC
0

Спасибо за уточнение!

@t0rr
30.09.2025 16:32 UTC
0

Подскажите, пожалуйста, а в чём смысл генерации таких ключей на уровне бд?

Я жил с представлением о том, uuid7 используется как раз для многоинстансовых систем, где каждый состоятельно генерит идентификаторы, а потом уже они сливаются в бд без конфликтов и с возможностью обеспечения порядка

@SergeyProkhorenko
30.09.2025 18:03 UTC
+1

Генерация UUIDv7 в БД - это возможность избавиться от автоинкремента со всеми его недостатками (см. аж 7 недостатков в тексте этой новости). UUIDv7 - это просто находка для хранилищ данных (уменьшается хаос, легче проходит адаптация к изменениям, предотвращаются ошибки, интеграция систем стоновится "бесшовной" и т.д.) и для баз данных временных рядов.

Кроме того, генерация UUIDv7 на стороне базы данных нужна в для обеспечения строгой монотонности идентификаторов. "Эта монотонность в течение миллисекунды нужна для поиска причин ошибок, пагинации по ключу (keyset pagination), поиска в логах, использования в БД временных рядов и т.п."

Авторы и контрибьюторы стандарта RFC 9562, котрый ввел UUIDv7, в первую очередь думали о генерации на стороне базы данных, и лишь попутно - о распределенных системах. К сожалению, в СМИ и на форумах много мифов о UUIDv7, в частности, что UUIDv7 это в основном про генерацию на клиентах. Вот статья (на английском), которая развеивает самые популярные мифы.