UUID v4 и v7 — различия и когда использовать каждый
UUID v4 был стандартным выбором для распределённых первичных ключей более десяти лет. Сгенерировать случайное 128-битное значение, отформатировать его как восемь групп шестнадцатеричных символов — и готово. Никакой координации, практически нулевая вероятность коллизии, работает везде. Так почему же UUID v7 — стандартизированный в RFC 9562 в 2024 году — стремительно распространяется в производственных системах в 2026-м?
Краткий ответ: производительность базы данных. UUID v4 разрушает локальность индекса B-дерева. UUID v7 устраняет эту проблему, сохраняя всё, что разработчики ценят в UUID. Эта статья объясняет, что представляет собой каждая версия, почему различие важно при масштабировании, когда выбирать каждую из них и как выполнить миграцию без простоя.
Нужно сгенерировать UUID прямо сейчас? Воспользуйтесь генератором UUID Toova, который поддерживает оба варианта — v4 и v7 — в пакетном режиме. Для других случайных идентификаторов подойдут генератор случайных строк и генератор паролей.
UUID v4 — чистая случайность
UUID версии 4 использует криптографически стойкий генератор псевдослучайных чисел (CSPRNG) для заполнения 122 из 128 бит. Оставшиеся шесть бит фиксированы: четыре бита кодируют версию (0100), два — вариант (10). Результат выглядит так:
f47ac10b-58cc-4372-a567-0e02b2c3d479
^^^^
version = 4 (random) Случайность — это и есть ключевое свойство. Две независимые системы могут генерировать UUID без координации и никогда не столкнутся — вероятность коллизии в наборе из 2,71 квадриллиона UUID v4 составляет примерно 50%, что означает: для любого практического приложения этот риск пренебрежимо мал. Вам не нужен централизованный сервер выдачи ID, последовательность базы данных или распределённая блокировка.
Как генерируется v4
import { v4 as uuidv4 } from 'uuid';
const id = uuidv4();
// => 'f47ac10b-58cc-4372-a567-0e02b2c3d479'
Библиотека uuid (JavaScript), uuid.uuid4() в Python, google/uuid в Go и все основные языковые среды выполнения делегируют вызов системному CSPRNG — /dev/urandom в Linux, CryptGenRandom в Windows. Стоимость генерации фактически равна нулю.
Проблема: случайная вставка убивает индексы B-дерева
Индекс B-дерева хранит данные в отсортированном виде. При вставке новой строки база данных находит позицию нового ключа в отсортированном порядке и помещает его туда. Если каждый новый ключ случаен, он оказывается в случайном месте индекса — а значит, каждая вставка требует загрузки другой страницы с диска в буферный пул. При малом объёме это незаметно. При высокой нагрузке (миллионы строк, высокая скорость INSERT) возникает явление, называемое фрагментацией индекса: индекс заполняется наполовину пустыми страницами, потому что каждая вставка попадает в другое место, а буферный пул постоянно перезаполняется, поскольку горячее рабочее множество охватывает весь индекс, а не предсказуемый недавний срез.
Симптомы в production: задержка INSERT растёт, накладные расходы autovacuum увеличиваются (PostgreSQL), давление на checkpoint возрастает, а производительность чтения для последних записей снижается, потому что «последние» записи больше не соответствуют никакой локальности в индексе. Это не теория — это хорошо задокументированная проблема крупных таблиц PostgreSQL и MySQL с первичными ключами UUID v4.
UUID v7 — упорядоченная случайность
UUID v7 был разработан именно для решения проблемы фрагментации B-дерева. Он кодирует 48-битную временную метку Unix (с точностью до миллисекунды) в старших битах, затем идут биты версии, 12 случайных бит, биты варианта и ещё 62 случайных бита. Суммарная случайность: 74 бита — по-прежнему значительно больше, чем нужно для предотвращения коллизий.
018f4e6b-a23c-7d45-9abc-0e02b2c3d479
^^^^^^^^^^^^^^
48-bit Unix timestamp (ms precision)
^
version = 7 Поскольку временная метка занимает старшие биты, UUID, сгенерированные позже, сортируются после UUID, сгенерированных ранее. Вставки всегда добавляются к правому краю индекса. Базе данных нужно держать горячей в буферном пуле только самую последнюю страницу индекса, а не весь индекс. При высокой скорости INSERT это одно лишь изменение способно сократить задержку записи на 30–60% и уменьшить операции ввода-вывода на порядок на таблицах с десятками миллионов строк.
Как генерируется v7
import { v7 as uuidv7 } from 'uuid';
const id = uuidv7();
// => '018f4e6b-a23c-7d45-9abc-0e02b2c3d479'
Та же библиотека uuid добавила поддержку v7 в версии 10. Стандартная библиотека Python получила её в версии 3.14. PostgreSQL 17 включает uuidv7() как встроенную функцию. Если вы работаете со старым стеком, несколько небольших библиотек предоставляют генерацию v7 без зависимостей.
Монотонность при субмиллисекундной точности
Что происходит, когда два UUID v7 генерируются в пределах одной миллисекунды? RFC 9562 позволяет реализациям увеличивать монотонный счётчик в случайных битах для гарантии упорядоченности в пределах одной миллисекунды. Библиотека uuid делает это по умолчанию. Результат: даже если 10 000 ID генерируются за одну миллисекунду, они всё равно сортируются правильно.
Проблема фрагментации индекса B-дерева подробно
Чтобы понять, почему это важно, рассмотрим ситуацию с 10 000 вставок в секунду при использовании первичного ключа UUID v4 в таблице со 100 миллионами существующих строк:
- Каждая вставка генерирует случайный 128-битный ключ, который попадает в случайную позицию среди 100 миллионов существующих записей.
- База данных должна загрузить конкретную страницу B-дерева с этой позицией в буферный пул.
- При 100 миллионах строк и страницах по 8 КБ индекс занимает примерно 100 000 страниц. Каждую секунду нужны 10 000 разных страниц — значительно больше, чем типичный объём shared_buffers в 8 ГБ может удержать только для этого индекса.
- Каждый промах кэша приводит к чтению с диска. При 10 000 вставок/с это может генерировать тысячи случайных операций чтения с диска в секунду, насыщая даже SSD на крупных таблицах.
При UUID v7 все 10 000 вставок в секунду попадают на крайнюю правую листовую страницу (или небольшое число последних страниц). Буферному пулу нужно держать горячими лишь эти несколько страниц. Процент попаданий в кэш для операций записи приближается к 100%. Увеличение объёма записи резко снижается.
Та же выгода применима к сканированиям диапазонов: WHERE created_at BETWEEN x AND y в таблице с v4 требует полного сканирования индекса или отдельного индекса по временной метке. В таблице с v7 первичный ключ сам является временным индексом — запрос может напрямую перейти к нужному диапазону.
Использование UUID v7 в PostgreSQL
-- Работает нативно с типом UUID в PostgreSQL 16+
CREATE TABLE events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- v4 (случайный)
-- Переключитесь на UUIDv7 через расширение или генерацию на стороне приложения
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- При UUIDv7 временная метка уже встроена, поэтому
-- отдельный столбец created_at зачастую избыточен.
PostgreSQL 17 поставляет uuidv7() нативно. Для PostgreSQL 14–16 расширение pg_uuidv7 предоставляет ту же функцию. Тип данных UUID хранит и v4, и v7 одинаково — 16 байт, без накладных расходов. Единственное различие — битовый шаблон, определяющий порядок сортировки.
Одно полезное следствие: поскольку временная метка встроена, многие таблицы больше не нуждаются в отдельном столбце created_at для сортировки или отображения. Временную метку можно извлечь из UUID v7 одним вызовом функции. Это упрощает схему и устраняет одну операцию записи на каждую вставку.
UUID v4 vs v7 — компромиссы
| Свойство | UUID v4 | UUID v7 |
|---|---|---|
| Биты случайности | 122 | 74 |
| Порядок сортировки | Случайный | Хронологический |
| Производительность INSERT (B-дерево) | Плохая (фрагментация) | Отличная (последовательная) |
| Раскрытие временной метки | Нет | Да (точность до мс) |
| Стандарт RFC | RFC 4122 (2005), RFC 9562 (2024) | RFC 9562 (2024) |
| Поддержка библиотек | Повсеместная | Быстро растёт (2024–2026) |
| Встроенный created_at | Нет | Да |
Руководство по миграции — с v4 на v7
Миграция существующей таблицы с первичных ключей UUID v4 на v7 — многошаговая операция. Ключевое ограничение: нельзя изменить значение первичного ключа, на который ссылаются внешние ключи, не обновив все ссылающиеся таблицы. Планируйте окно обслуживания или используйте подход двойной записи.
-- 1. Добавить новый столбец
ALTER TABLE orders ADD COLUMN id_v7 UUID;
-- 2. Заполнить существующие строки (используйте оригинальный created_at как
-- источник временной метки; применяйте функцию UUIDv7 из вашей библиотеки)
UPDATE orders SET id_v7 = generate_uuidv7(created_at);
-- 3. Убедиться, что NULL не осталось
SELECT COUNT(*) FROM orders WHERE id_v7 IS NULL;
-- 4. Переключить столбцы (требует кратковременного окна обслуживания)
ALTER TABLE orders ALTER COLUMN id_v7 SET NOT NULL;
ALTER TABLE orders ALTER COLUMN id_v7 SET DEFAULT generate_uuidv7(now());
ALTER TABLE orders RENAME COLUMN id TO id_v4_old;
ALTER TABLE orders RENAME COLUMN id_v7 TO id;
ALTER TABLE orders ADD PRIMARY KEY (id); Для миграции без простоя на таблицах с высокой нагрузкой рекомендуемый подход таков:
- Добавьте новый столбец
id_v7со значением по умолчанию на стороне сервера, которое генерирует UUID v7 для новых строк. - Заполните старые строки пакетами в периоды низкой нагрузки, используя существующую временную метку
created_atв качестве исходного значения для временной части v7. - Обновите все столбцы внешних ключей в ссылающихся таблицах так, чтобы они указывали на
id_v7. - Переименуйте столбцы и удалите старое ограничение первичного ключа в течение краткого окна обслуживания.
Этап заполнения — самый длительный. При 10 000 строк на пакет с паузой 50 мс между пакетами таблица из 100 миллионов строк потребует около 8 часов. Начинайте заблаговременно.
Реальный эффект
Несколько инженерных команд опубликовали бенчмарки, сравнивающие UUID v4 и v7 на наборах данных производственного масштаба. Согласованные результаты:
- Пропускная способность INSERT: улучшение в 2–5 раз на таблицах с 50M+ строками при переходе с v4 на v7, причём выигрыш увеличивается по мере роста таблицы.
- Задержка записи p99: снижается с сотен миллисекунд (v4 под нагрузкой) до единиц миллисекунд (v7) на том же оборудовании.
- Размер индекса: индексы B-дерева на столбцах v7 на 15–30% меньше эквивалентных индексов v4 на тех же данных, потому что фрагментация оставляет меньше наполовину пустых страниц.
- Эффективность буферного пула: процент попаданий shared buffers для индекса первичного ключа возрастает с ~40% (v4, большая таблица) до ~99% (v7), потому что горячими нужно держать только последние страницы.
Выигрыш пренебрежимо мал при объёме менее примерно 1 миллиона строк. Если ваша таблица остаётся небольшой, оставайтесь на v4 ради простоты. При более чем 10 миллионах строк с любой значимой скоростью INSERT v7 — лучший выбор по умолчанию.
Когда использовать каждый
Используйте UUID v7, когда:
- Вы проектируете новую схему и таблица будет расти до больших размеров (10M+ строк).
- У таблицы высокая скорость INSERT — события, логи, заказы, сообщения, уведомления.
- Вы хотите первичный ключ, который одновременно служит временной меткой создания (устраняет один столбец).
- Вы работаете на PostgreSQL 17, MySQL 8.0+ или современной ORM с поддержкой генерации v7.
- Сортировка по ID семантически эквивалентна сортировке по времени создания — что будет верно для большинства таблиц с преобладанием добавлений.
Используйте UUID v4, когда:
- Идентификатор открыт пользователям и не должен раскрывать время создания (коды приглашений, ссылки для общего доступа, биллинговые дескрипторы).
- Таблица небольшая и стабильная — выигрыш в производительности не оправдывает затраты на миграцию.
- Вы генерируете ID в контексте, где временная метка создания чувствительна (приватные пользовательские записи в продукте, конкурирующем по показателям роста).
- Вам нужно что-то в качестве одноразовых учётных данных или токена — используйте специализированный генератор секретов, а не какую-либо версию UUID.
Итог
UUID v4 — случайный, конфиденциальный и повсеместно поддерживаемый. UUID v7 — упорядоченный по времени, удобный для баз данных и теперь стандартный в основных библиотеках и СУБД. Для новых схем с большими или быстро растущими таблицами v7 — лучший выбор по умолчанию. Для ID, открытых пользователям, где утечка временной метки является проблемой, v4 остаётся правильным выбором.
Генерируйте обе версии мгновенно с помощью генератора UUID Toova — без регистрации. Для более коротких токенов генератор случайных строк создаёт буквенно-цифровые ID любой длины.