8 мин

UUIDv7 как первичный ключ: поддержка в PostgreSQL 18

Разбираем, чем UUIDv7 лучше UUIDv4 и SERIAL для PK, как он влияет на B-tree индексы и как использовать UUIDv7 в PostgreSQL 18 стандартными средствами.

UUIDv7 как первичный ключ: поддержка в PostgreSQL 18

Почему выбор PK важнее, чем кажется

Первичный ключ (PK) — это не просто «уникальный номер» строки. От формата ключа зависит поведение B-tree индексов, скорость вставок и обновлений, размер таблиц и индексов, а значит — и стоимость хранения, бэкапов и кэша. PK почти всегда участвует в связях (FK), попадает в планы запросов и нередко становится тем самым полем, по которому система «живет» годами.

Почему команды так часто выбирают UUID

UUID выбирают не из любви к длинным строкам, а из-за свойств, которые сложно получить с SERIAL и IDENTITY:

  • Распределённые системы: несколько сервисов и баз могут генерировать ключи независимо, без центрального счётчика.
  • Офлайн-генерация: клиент или очередь может создать идентификатор до записи в PostgreSQL.
  • Безопасность ссылок: последовательный BIGINT легко угадывается, а UUID (как правило) нет — это влияет на UX и безопасность публичных URL.

Проблема в том, что классический UUIDv4 практически случайный. Для B-tree это означает менее предсказуемые вставки и больше «движения» страниц индекса при высокой нагрузке.

В чём идея UUIDv7

UUIDv7 сохраняет формат UUID, но делает идентификаторы примерно упорядоченными по времени (внутри остаётся место для случайности). Из-за этого вставки чаще идут «в конец» индекса, улучшается локальность вставок, а чтение свежих данных по диапазону становится проще.

Что разберём дальше

Дальше по шагам посмотрим, как UUIDv7 влияет на индексы и производительность в PostgreSQL 18, чем он отличается от UUIDv4 и BIGINT (SERIAL/IDENTITY), как генерировать его «из коробки», какие есть крайние случаи (время и конкуренция) и как безопасно провести миграцию ключей.

UUIDv7 простыми словами: чем отличается от UUIDv4

UUID часто выбирают как первичный ключ (PK), когда нужен уникальный идентификатор без централизованного «выдающего номера». Но разные версии UUID ведут себя по‑разному — и это напрямую влияет на то, как будет работать база.

UUIDv4: почти чистая случайность

UUIDv4 — это 128 бит, где основная часть значения случайна. Отсюда два практических следствия:

  • Нет временной упорядоченности: новые значения не «растут» по времени. В таблице и индексе записи распределяются хаотично.
  • Слабая читаемость: по самому UUID нельзя понять, что создано раньше, что позже.

С точки зрения уникальности UUIDv4 обычно более чем достаточен, но его случайность — это и плюс (сложно угадать), и минус (нет естественного порядка).

UUIDv7: UUID с «временем» и предсказуемым порядком

UUIDv7 тоже 128‑битный, но в нём заложена временная компонента (timestamp), а остальная часть добирается случайностью, чтобы сохранять уникальность.

Что это даёт на практике:

  • Временная упорядоченность: новые UUIDv7, как правило, сортируются «примерно по времени создания». Это удобно для логов, событий, заказов, сообщений.
  • Более естественная работа со списками: когда вы сортируете по PK, результат часто совпадает с хронологией.

Важно: UUIDv7 — не «магическое ускорение», но он делает идентификатор ближе к реальным сценариям работы с данными: появляется порядок, а уникальность сохраняется.

А что с UUIDv1/UUIDv6/ULID?

  • UUIDv1 исторически использует время и параметры узла (например, MAC), из‑за чего его иногда избегают из соображений приватности и предсказуемости.
  • UUIDv6 — попытка переупорядочить UUIDv1 так, чтобы сортировка лучше соответствовала времени. Его можно встретить, но он менее стандартизирован в прикладном мире.
  • ULID — популярный «похожий на UUID» формат: тоже time-ordered и часто более удобен для чтения, но это не UUID-стандарт.

UUIDv7 стал компромиссом: стандартный UUID, при этом time-ordered, без наследия UUIDv1.

Какие свойства важны для PK

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

Индексы и локальность вставок: главный практический плюс UUIDv7

Причина, почему UUIDv7 часто ощущается «быстрее» UUIDv4 в реальных базах, связана не с «форматом как магией», а с тем, как B-tree индекс живёт под нагрузкой.

Почему случайные ключи ломают локальность вставок

B-tree (в том числе индекс по первичному ключу) устроен как отсортированное дерево страниц. Когда вы вставляете новую строку, PostgreSQL должен найти место в индексе по значению ключа.

С UUIDv4 ключи практически равномерно случайны. Это означает, что вставки «прыгают» по всему дереву: сегодня в одну страницу, через миллисекунду — в далёкую, потом обратно. В итоге чаще происходят:

  • частые page split (страницу «разрезает» на две, чтобы освободить место);
  • рост фрагментации индекса;
  • больше случайных записей в WAL и больше грязных страниц в буферах.

Что даёт приблизительная монотонность UUIDv7

UUIDv7 содержит временную компоненту, поэтому новые значения в среднем растут. Это не идеальная последовательность (возможны перестановки при конкуренции), но поток вставок чаще попадает в «хвост» индекса — в одну и ту же область листовых страниц.

Практический эффект: меньше хаотичных page split и лучше кэшируемость — одни и те же страницы индекса чаще остаются в shared buffers.

Write amplification, VACUUM и скорость вставок

Когда индекс меньше «рвёт» страницами, снижается write amplification: системе нужно меньше переписывать структуры индекса, меньше WAL на обслуживание разрезов и меньше фоновой работы на удержание порядка. Косвенно это помогает и VACUUM: фрагментация индексов обычно приводит к большему объёму данных, которые нужно прогонять и хранить.

Когда выигрыша может не быть

Если таблица маленькая, вставки редкие, всё помещается в память или узкое место вообще в другом (например, медленные внешние вызовы, сложные джойны, нехватка CPU), разницы между UUIDv4 и UUIDv7 можно почти не увидеть. UUIDv7 раскрывается именно на больших потоках записей и крупных индексах, где стоимость случайных вставок становится заметной.

UUIDv7 vs BIGINT (SERIAL/IDENTITY) vs UUIDv4

Выбор первичного ключа обычно сводится к трём вариантам: автоинкрементный BIGINT (SERIAL/IDENTITY), случайный UUIDv4 и «почти упорядоченный» UUIDv7. У каждого — своя цена в производительности, удобстве и безопасности.

BIGINT (SERIAL/IDENTITY): быстро, компактно, но «централизованно»

Плюсы BIGINT просты: 8 байт на значение, отличная работа B-tree индекса, предсказуемый рост — вставки почти всегда идут в конец индекса, а значит меньше дробления страниц и больше шансов на стабильные задержки.

Минусы тоже практические. Генерация номера обычно привязана к одной базе (или к одному узлу), что усложняет распределённые вставки и офлайн-сценарии. И ещё: последовательность видна наружу — по публичному ID легко угадывать объёмы данных и перебирать соседние записи, если контроль доступа сделан неидеально.

UUIDv4: распределённость ценой индекса и размера

UUIDv4 хорош там, где идентификатор должен генерироваться на клиентах/сервисах без координации. Но это «чистая случайность»: в B-tree индекс записи попадают в разные места, из‑за чего растёт фрагментация, чаще происходят split страниц и увеличивается нагрузка на кэш.

Также UUID занимает 16 байт (в 2 раза больше BIGINT), поэтому и ключи, и вторичные индексы обычно получаются тяжелее.

UUIDv7: ближе к BIGINT по вставкам, но остаётся распределённым

UUIDv7 сохраняет распределённую генерацию, как у UUIDv4, но добавляет временную составляющую. На практике это означает более «локальные» вставки: новые значения чаще оказываются рядом в индексе, что уменьшает хаос по сравнению с UUIDv4 и приближает поведение к автоинкременту.

Как выбрать

Если у вас монолит/одна БД, важна максимальная компактность и публичный ID не нужен — BIGINT чаще всего самый простой и быстрый.

Если ID генерируется в нескольких сервисах, нужен публичный идентификатор без угадываемой последовательности и вы хотите снизить проблемы UUIDv4 с индексом — UUIDv7 обычно выглядит как компромисс «скорость вставок + распределённость».

UUIDv4 стоит оставлять там, где временная упорядоченность нежелательна или уже есть жёсткие требования совместимости, а просадка по индексам приемлема.

Как использовать UUIDv7 в PostgreSQL 18 «из коробки»

Схема с UUIDv7 быстро
Опишите таблицы и связи, а TakProsto соберет схему PostgreSQL с UUIDv7 по умолчанию.

1) Генерация UUIDv7 без расширений: как проверить, что доступно

Если вы рассчитываете на «из коробки», начните с проверки: есть ли в вашей сборке PostgreSQL 18 встроенная функция генерации UUIDv7 (имя может отличаться).

-- Посмотреть, какие функции с v7/uuid есть в системе
SELECT n.nspname, p.proname, pg_get_function_identity_arguments(p.oid) AS args
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE p.proname ILIKE '%uuid%v7%'
   OR p.proname ILIKE '%uuidv7%'
ORDER BY 1,2;

Если в списке есть функция вроде uuidv7() или uuid_generate_v7(), её можно использовать в DEFAULT. Если нет — самый практичный «без расширений» путь остаётся генерация на стороне приложения.

2) Рекомендуемый DDL-шаблон

Базовый шаблон один и тот же: тип uuid, DEFAULT на генератор, PRIMARY KEY.

CREATE TABLE events (
  id uuid PRIMARY KEY DEFAULT uuidv7(),  -- или uuid_generate_v7()
  created_at timestamptz NOT NULL DEFAULT now(),
  payload jsonb NOT NULL
);

Если вы хотите, чтобы приложение иногда присылало свой id, а иногда полагалось на БД — оставляйте DEFAULT: при вставке с явным id значение по умолчанию не используется.

3) Когда имеет смысл генерировать UUIDv7 в приложении

Генерация в приложении оправдана, когда:

  • нужно создавать ID до записи в БД (очереди, outbox, события, офлайн-режим);
  • несколько сервисов пишут в разные БД, но идентификаторы должны быть уникальны глобально;
  • вы хотите уменьшить «точки ответственности» БД и держать формат ID единым в коде.

В этом случае договоритесь об одном каноническом представлении UUID в коде (обычно строка в нижнем регистре с дефисами) и храните в БД именно uuid, а не text.

4) Проверка корректности: тип, ограничения и единый формат

Минимум — используйте тип uuid (он сам отсекает мусор). Если важно гарантировать именно версию 7, добавьте CHECK по битам версии/варианта:

ALTER TABLE events
ADD CONSTRAINT events_id_is_uuidv7
CHECK (
  (get_byte(uuid_send(id), 6) >> 4) = 7
  AND (get_byte(uuid_send(id), 8) & 192) = 128
);

Так вы защититесь от случайного UUIDv4/«самодельных» значений и сохраните единый стандарт между приложением и базой.

Хранение и размер: что будет с таблицами и кэшем

UUIDv7 часто выбирают из‑за индексов и порядка вставок, но «цена» в байтах никуда не девается. На больших объёмах именно размер ключа начинает влиять на кэш, скорость сканов по индексу и общий I/O.

UUID или BYTEA: что хранить и почему обычно достаточно UUID

Почти всегда стоит хранить идентификатор именно в типе uuid, а не в bytea.

uuid в PostgreSQL — нативный фиксированный тип на 16 байт с готовыми операторами сравнения, сортировкой, поддержкой B-tree и понятным текстовым представлением. Он проще для логов и дампов, легче валидируется на входе и совместим с большинством драйверов/ORM.

bytea имеет смысл лишь в редких случаях: когда вы сознательно работаете с «сырыми» байтами, храните не-UUID значение или используете собственный формат. Для обычных UUID это чаще лишняя сложность.

Стоимость по размеру: 16 байт и эффект на индексы и кэш

Само значение uuid занимает 16 байт (для сравнения, bigint — 8). Но в реальности важнее то, что ключ хранится:

  • в строке таблицы (heap tuple);
  • в каждой записи B-tree индекса первичного ключа;
  • во вторичных индексах, где этот ключ участвует (включая индексы на FK).

Из-за более крупного ключа на странице индекса помещается меньше записей. Это означает больше страниц на диске, меньше «полезных» попаданий в shared buffers/OS cache и более заметную разницу на нагрузках, где много точечных обращений по PK и по FK.

Внешние ключи: размер дублируется в дочерних таблицах

Если PK — UUIDv7, то каждая FK-колонка, ссылающаяся на него, тоже будет 16-байтной. В системах с множеством связей и таблиц‑событий это часто главная часть «переплаты»: ключ повторяется миллионы раз. Это не аргумент против UUIDv7, но повод заранее оценить объёмы и проверить, не раздуваются ли индексы на FK-таблицах.

Практический совет: не плодите вторичные индексы на UUID

UUID-колонки легко превращаются в «индексируем всё подряд», а это прямой множитель размера. Если вы уже фильтруете по PK/FK, отдельные индексы на ту же UUID-колонку часто не дают выигрыша.

Держите правило: каждый индекс должен оправдываться конкретным запросом. Если нужен индекс под частый фильтр — делайте составной индекс по реально селективным полям, а UUID добавляйте только при необходимости (например, как часть ключа или через INCLUDE, когда важна выдача без чтения таблицы).

Запросы и сортировки: что меняется с UUIDv7

UUIDv7 устроен так, что в начале значения зашит «кусочек времени». Поэтому при сортировке по UUIDv7 вы часто получаете порядок, близкий к времени создания записей. Это влияет на типовые запросы: выборку по PK, сортировку «новые сверху» и пагинацию.

Частые запросы: PK, «по времени», пагинация

Выборка по PK не меняется: WHERE id = ... остаётся таким же точным и быстрым. Изменение заметнее там, где раньше вы полагались на created_at.

Если вам нужно «показать последние записи», то ORDER BY id DESC с UUIDv7 зачастую даёт разумный результат без дополнительной сортировки по времени. Это удобно для простых лент, логов и списков.

Может ли UUIDv7 заменить created_at?

Частично — как приближение.

  • значения растут в среднем по времени, и B-tree может быстро идти по хвосту;
  • для UI это часто выглядит как корректная хронология.

Но полностью created_at он не заменяет: точные временные метки нужны для фильтров «за период», аналитики, временных зон, импорта задним числом и ситуаций, когда порядок критичен юридически или бизнес-логикой.

Пагинация: keyset по UUIDv7

С UUIDv7 удобно делать keyset pagination без OFFSET: вы запоминаете последний id и продолжаете выборку WHERE id < :last_id ORDER BY id DESC LIMIT 50. Это обычно стабильнее и быстрее, чем большие OFFSET.

Ограничение подхода: UUIDv7 даёт порядок «почти по времени», но при высокой конкуренции или генерации на разных узлах возможны «перестановки» близких по времени записей. Для строго детерминированного порядка лучше использовать created_at, id.

Составные индексы: когда добавлять created_at, tenant_id и т.п.

Если данные многопользовательские, чаще всего нужен индекс под ваш реальный фильтр, например (tenant_id, id) для списков внутри арендатора. Если сортировка должна быть строго по времени — добавляйте (tenant_id, created_at, id) и сортируйте по ним. UUIDv7 хорош как первичный ключ и удобный курсор, но индексы всё равно стоит строить под конкретные запросы.

Крайние случаи: время, конкуренция и порядок

Снизьте стоимость разработки
Заработайте кредиты за контент о TakProsto или приглашайте коллег по реферальной программе.

UUIDv7 хорош тем, что «в среднем» растёт по времени и поэтому вставки в B-tree индекс чаще попадают ближе к правому краю. Но в реальных системах есть ситуации, где ожидания «будет строго по порядку» ломаются — и это нормально, если понимать причины.

Распределённые вставки и гонки времени

При одновременной генерации UUIDv7 на нескольких приложениях/сервисах порядок зависит не только от времени, но и от того, какие именно часы использует каждый узел и с какой точностью/коррекцией.

Если два сервиса сгенерировали UUID «в одном и том же миллисекундном окне», их относительный порядок в индексе может перемешаться. Для БД это не проблема: коллизии у UUIDv7 практически невероятны, а перемешивание затрагивает лишь идеальную «локальность вставок», но не корректность данных.

Почему «временной» UUID не равен строгому порядку событий

UUIDv7 кодирует время, но это не журнал причинно‑следственных связей.

  • Событие могло произойти раньше, но попасть в БД позже (очереди, ретраи, сетевые задержки).
  • Время на узлах может отличаться на миллисекунды/секунды.
  • Даже при одинаковом времени генерации порядок между параллельными процессами не обязан отражать реальную последовательность бизнес‑событий.

Если вам нужен строгий порядок, используйте отдельное поле (например, created_at + бизнес‑логика) и сортируйте по нему, а UUIDv7 оставьте как удобный PK.

Часы, NTP, дрейф: риски и как снизить

Главный риск — скачки системного времени (NTP step), дрейф или неверная настройка времени на части узлов. Тогда можно увидеть «прыжки назад»: новые UUIDv7 окажутся меньше уже вставленных, и индекс начнёт получать вставки не только в конец.

Практичные меры:

  • Генерировать UUIDv7 на стороне БД, чтобы все записи опирались на один источник времени.
  • Следить за NTP/chrony: избегать резких корректировок (предпочитать плавную коррекцию), мониторить offset.
  • Для критичных систем — держать created_at от clock_timestamp()/now() в БД и считать его источником правды для времени.

Тесты и наблюдаемость

Чтобы вовремя заметить проблемы с монотонностью:

  • Нагрузочный тест: параллельные вставки с нескольких клиентов и проверка доли «вставок не в конец» по косвенным метрикам (рост page splits, изменение поведения autovacuum).
  • Аудит выборкой: сравнивать порядок id и created_at на небольших интервалах; резкие «откаты» по времени — сигнал проблем с часами.
  • Мониторинг времени хостов/контейнеров (offset, leap, step) и алерты на нетипичные корректировки.

UUIDv7 снимает многие практические боли UUIDv4, но дисциплина вокруг времени и наблюдаемости всё ещё важна — особенно в распределённых системах.

Миграция на UUIDv7: безопасные шаги

Переезд первичного ключа — это не «одна миграция», а серия небольших, обратимых изменений. Хорошая новость: на UUIDv7 обычно можно перейти без простоя, если не пытаться «перевернуть всё» за один релиз.

Выбор стратегии: добавить surrogate key или заменить PK

Есть два рабочих подхода:

  1. Оставить старый PK (например, BIGINT) и добавить UUIDv7 как surrogate key. Это самый безопасный вариант: старые внешние ключи и интеграции продолжают жить, а новые сервисы/эндпоинты постепенно переходят на UUIDv7.

  2. Полная замена PK на UUIDv7. Даёт более единообразную схему, но дороже: придётся обновить все FK, индексы, API-контракты и, возможно, порядок сортировок/пагинации.

На практике часто начинают с (1), а на (2) переходят только если есть сильная причина.

План миграции без простоя (пошагово)

Ниже типовой план, который можно растянуть на 2–4 релиза:

-- 1) Добавляем колонку
ALTER TABLE orders ADD COLUMN id_v7 uuid;

-- 2) Включаем генерацию для новых строк (в PG18 — встроенная генерация UUIDv7)
ALTER TABLE orders ALTER COLUMN id_v7 SET DEFAULT (/* uuidv7 generator */);

-- 3) Заполняем старые строки батчами
-- UPDATE orders SET id_v7 = (/* uuidv7 generator */) WHERE id_v7 IS NULL ...;

-- 4) Строим уникальный индекс без блокировок записи
CREATE UNIQUE INDEX CONCURRENTLY orders_id_v7_uidx ON orders (id_v7);

Дальше — переключение внешних ключей: добавляете в дочерние таблицы колонку order_id_v7, заполняете по join-у, создаёте FK на orders(id_v7), переводите приложение на новые связи, и только потом удаляете старые ограничения.

Что учесть в приложении и API

На время миграции почти неизбежен «двойной ключ» в моделях/DTO: старый id и новый id_v7. В API полезно:

  • поддерживать оба поля (или версионировать контракт),
  • не ломать интеграции: вебхуки, выгрузки, BI-отчёты,
  • обновить трассировку/логи: чтобы искать сущность можно было по любому идентификатору.

Где помогает TakProsto.AI

Если вы быстро собираете сервис (React на фронте, Go + PostgreSQL на бэкенде) и параллельно «примеряете» разные стратегии PK, удобно иметь среду, где можно прогнать варианты схемы и миграций без долгой ручной сборки окружения. В TakProsto.AI это обычно делается в формате чата: описываете требования (UUIDv7 как PK, сценарий миграции, нужные индексы/пагинация) — и получаете каркас приложения и DDL/миграции, которые можно доработать, развернуть и при необходимости экспортировать исходники.

Для рискованных изменений (вроде миграции ключей) особенно полезны snapshots и rollback, а «planning mode» помогает заранее разложить работу на шаги и учесть влияние на API и внешние ключи. Платформа работает на серверах в России и использует локализованные/opensource LLM-модели, что важно для проектов с требованиями к размещению данных.

Репликация, бэкапы и прогон на стенде

Перед продом прогоните миграции на стенде с копией объёма данных: заполнение UUID может занять время и создать нагрузку. Убедитесь, что батчи не раздувают таблицу, а репликация и бэкапы укладываются в окна. Самое важное — держать миграции идемпотентными и иметь план отката (например, пока не удалены старые FK/индексы).

Безопасность, UX и совместимость в экосистеме

Код у вас в руках
Получите исходники и продолжайте развивать проект локально или в своей инфраструктуре.

UUIDv7 часто выбирают не только ради индексов, но и из‑за «побочных эффектов» для безопасности и удобства интеграций. Важно понимать границы: UUID — это идентификатор, а не секрет.

Безопасность: UUID не скрывает данные, но снижает риск перебора

Последовательные ID (SERIAL/IDENTITY) легко угадываются: если у объекта был id=120, следующий, вероятно, 121. Это упрощает перебор URL и API-параметров, если доступ контролируется неидеально.

UUIDv7 делает угадывание существенно сложнее: значение длиннее и содержит случайную составляющую. При этом он не защищает сам по себе — права доступа, проверка ownership и rate limiting всё равно обязательны. UUIDv7 также частично «подсказывает» время создания (из-за временной компоненты), что может быть нежелательно в некоторых доменах; если это критично, подумайте об уровне доступа или альтернативных схемах идентификаторов.

UX для логов и трассировки: удобно, но нужна дисциплина

UUID хорош для сквозной трассировки: один и тот же идентификатор можно протащить через сервисы, очереди и логи. Практические требования:

  • храните UUID как тип UUID в БД и как строку в логах;
  • договоритесь о формате (обычно lowercase с дефисами) и не «обрезайте» значение;
  • валидируйте входные UUID на границе системы, чтобы не плодить мусорные ключи и ошибки 500.

Совместимость: драйверы, ORM, JSON

Большинство драйверов и ORM давно умеют UUID: PostgreSQL тип uuid мапится на строки/byte array в зависимости от языка. Проверьте, что:

  • сериализация в JSON стабильна (строка, а не массив байт);
  • схемы OpenAPI/JSON Schema имеют валидацию по паттерну UUID;
  • клиенты не предполагают «числовой» ID.

Где держать «человекочитаемый» номер

Если нужен короткий номер для поддержки или документов (например, «Заказ № 104392»), не пытайтесь сделать его PK. Храните отдельно:

  • id uuid как первичный ключ;
  • public_number bigint как уникальный, генерируемый последовательностью (или отдельной таблицей счётчиков по типам/филиалам).

Так вы получаете удобный внешний номер без компромиссов по архитектуре и безопасности.

Рекомендации: когда UUIDv7 станет «любимым PK», а когда нет

UUIDv7 — хороший компромисс между «глобальной уникальностью UUID» и «предсказуемым порядком вставок». Но он не универсален: выбор PK зависит от архитектуры, нагрузки и того, как ID живёт вне базы.

Если у вас монолит и одна БД

Чаще всего BIGINT (SERIAL/IDENTITY) всё ещё лучший выбор, если:

  • ID не должен генерироваться вне PostgreSQL;
  • важны минимальный размер индексов и максимальная плотность кэша;
  • вы не публикуете ID наружу (или можете прятать его за отдельным публичным идентификатором).

UUIDv7 в монолите имеет смысл, когда:

  • вы хотите генерировать ключи в приложении (офлайн/очереди/батчи) без похода в БД;
  • вы заранее думаете о возможном шардинге/слиянии данных;
  • вам важна сортировка «примерно по времени создания» без отдельного поля.

Если микросервисы и несколько источников данных

Здесь UUIDv7 обычно удобнее: разные сервисы и пайплайны могут безопасно создавать записи независимо, а при объединении данных не возникает коллизий ключей и «войны последовательностей». Это упрощает репликацию, синхронизацию и импорт из внешних систем, особенно когда нет единого центра выдачи идентификаторов.

Как принять решение: короткий чек-лист

Перед тем как фиксировать PK, ответьте себе:

  • Нагрузка: упираетесь ли вы в запись/индексы и размер буферного кэша? Если да — BIGINT может быть заметно экономичнее.
  • Репликация и консистентность: будут ли записи создаваться в нескольких местах и потом сливаться? Если да — склоняйтесь к UUIDv7.
  • Публичные API: будете ли вы отдавать ID клиентам? UUID удобнее скрывает объём данных и снижает риск «угадывания» соседних записей.
  • Сортировка: нужна ли сортировка по времени без отдельного поля created_at? UUIDv7 поможет, но всё равно проверяйте запросы.

Куда копать дальше

Лучший финальный шаг — тест на ваших данных: снимите типичные запросы и профили (вставка, выборки по PK, джойны, сортировки, размер индекса), сделайте сравнительный бенчмарк «до/после» на копии продакшн-нагрузки и сравните метрики, а не впечатления.

Похожие статьи