12k
All articles

UUID v4 или v7: что выбрать?

UUID v4 и v7 для первичных ключей БД: почему v7 ускоряет вставки, когда v4 скрывает время создания и как генерировать оба варианта.

OpenReplay Team
OpenReplay Team
UUID v4 или v7: что выбрать?

Для нового первичного ключа базы данных в 2026 году по умолчанию выбирайте UUID v7 и обращайтесь к v4 только в тех случаях, когда идентификатор публичен, а время его создания должно оставаться конфиденциальным.

Если вы когда-нибудь наблюдали, как на нагруженной таблице постепенно растёт задержка вставки, и выяснили, что причина — первичный ключ, который каждый раз попадает в другой участок индекса, этот вопрос покажется вам знакомым. Решение оказывается куда проще, чем миграция схемы. Оба формата — это 128-битные UUID, стандартизированные в одном и том же RFC, так что выбор не связан ни с совместимостью, ни с безопасностью по коллизиям. Всё сводится к одному свойству: сортируются ли ваши идентификаторы в порядке создания. Временная упорядоченность v7 устраняет фрагментацию индекса, которую вызывают случайные ключи v4 в таблицах с интенсивной записью, — ценой встраивания читаемой временной метки в каждое значение. В этом руководстве разбираются структурные различия, механизм работы индексов БД, обеспечивающий производительность записи у v7, компромисс в области приватности, текущая поддержка в библиотеках и однозначный вариант по умолчанию.

Ключевые выводы

  • UUID v4 и v7 — это 128-битные, 16-байтные идентификаторы, стандартизированные в RFC 9562 (май 2024); в v7 первые 48 бит заменены Unix-меткой времени в миллисекундах, благодаря чему значения сортируются в порядке создания, тогда как v4 полностью случаен.
  • Случайные ключи v4 разбросаны по B-дереву индекса и вызывают расщепление страниц, вымывание кеша и фрагментацию; временно упорядоченный префикс v7 приводит к тому, что вставки происходят ближе к концу индекса, ведя себя гораздо больше похоже на последовательный ключ.
  • Идентификатор v7 раскрывает время своего создания с точностью до миллисекунды; идентификатор v4 — нет. Используйте v4 для публичных идентификаторов, пригласительных ссылок и везде, где темпы вашего роста должны оставаться непубличными.
  • Поддержка v7 — на высоком уровне: PostgreSQL 18 поставляется с нативной функцией uuidv7(), в Python 3.14 добавлен uuid.uuid7(), а JavaScript-пакет uuid (v14.x) экспортирует v7().
  • Ни один UUID не является секретом: используйте отдельный 256-битный случайный токен для аутентификации, а UUID оставьте для идентификации.

В чём разница между UUID v4 и v7?

UUID v4 и v7 — это 128-битные, 16-байтные идентификаторы, стандартизированные в RFC 9562, опубликованном в мае 2024 года в статусе Standards Track и отменяющем более ранний RFC 4122. Единственное структурное различие — в том, откуда берутся биты. UUID v4 — это 122 бита случайности плюс 6 бит, зарезервированных под маркеры версии и варианта, что делает его полностью случайным и неупорядоченным. UUID v7 заменяет первые 48 бит Unix-меткой времени в миллисекундах и заполняет оставшиеся ~74 бита случайными данными (плюс маркеры версии и варианта), поэтому значения v7 сортируются в порядке создания как лексикографически, так и побайтово, а значения v4 — нет.

UUID v4:  [ 122 random bits ...................... ] + version/variant
UUID v7:  [ 48-bit ms timestamp ][ ~74 random bits ] + version/variant

Это единственное изменение и определяет весь выбор. У обоих сохраняется одна и та же математика коллизий за счёт случайной части, оба помещаются в один и тот же столбец, и оба описаны одним стандартом.

Почему UUID v7 выигрывает в роли ключа базы данных?

v7 превосходит v4 в качестве первичного ключа, потому что префикс с меткой времени обеспечивает вставкам локальность в индексе, которую случайные ключи разрушают. Случайные ключи v4 попадают в произвольные позиции B-дерева индекса, вызывая расщепление страниц, вымывание кеша и фрагментацию, поскольку каждая вставка адресуется в другую часть дерева. Именно эту проблему RFC 9562 указывает как причину определения v7: когда идентификаторы не несут временного порядка, каждая новая строка должна записываться туда, куда случайно попадает её значение, тогда как значения, сгенерированные одно за другим по временно упорядоченной схеме, оказываются соседями в индексе. Монотонный префикс v7 означает, что новые строки добавляются ближе к концу индекса, поэтому расщепление страниц становится редким, а пропускная способность вставки приближается к показателям последовательного целочисленного ключа — при сохранении защиты от коллизий и возможности распределённой генерации, свойственных UUID.

Сильнее всего штраф проявляется в движках с кластеризованным индексом. В MySQL InnoDB и SQL Server таблица физически упорядочена по первичному ключу, поэтому случайные вставки перезаписывают страницы по всей структуре. PostgreSQL хранит строки в куче с отдельными индексами и менее чувствителен к случайности ключа, но его индексы всё равно теряют локальность кеша при случайных ключах v4. Масштаб эффекта зависит от движка, нагрузки и оборудования, поэтому к конкретным процентам из блог-бенчмарков без ссылок на источники стоит относиться скептически и измерять на собственной таблице. Сам механизм при этом сомнению не подлежит.

Временная упорядоченность сохраняется и при пиковых вставках. Реализации добавляют субмиллисекундный счётчик, чтобы идентификаторы, сгенерированные в одну и ту же миллисекунду, всё равно сортировались правильно: в Python uuid.uuid7() отводит 42 бита под счётчик, благодаря чему значения, созданные в пределах одной миллисекунды, сохраняют свой порядок, а uuidv7() в PostgreSQL 18 формирует каждое значение из Unix-метки времени в миллисекундах, субмиллисекундной доли и случайных битов.

Когда UUID v4 по-прежнему правильный выбор

Выбирайте v4, когда идентификатор доступен извне, а время его создания чувствительно, поскольку идентификатор v7 встраивает время своего создания с точностью до миллисекунды. Любой, кто может прочитать такой идентификатор, узнает, когда была создана запись, что делает v7 плохим выбором для публичных идентификаторов. Команда Aiven приходит к тому же выводу: как только первичный ключ передаётся конечным пользователям через внешнее приложение или API, v7 перестаёт быть разумным выбором, потому что идентификатор выдаёт время создания записи. Используйте v4 для пригласительных токенов, ссылок для обмена и везде, где конкурент мог бы вывести темпы вашего роста из диапазонов идентификаторов.

Одна оговорка касается обеих версий: никакой UUID не является токеном безопасности. Неслучайные биты в v7 предсказуемы, а качество случайности в v4 зависит от реализации, поэтому ни один из них не должен использоваться для контроля аутентификации. Генерируйте отдельную криптографически стойкую случайную строку длиной не менее 256 бит для секретов, а UUID применяйте только для идентификации.

Как генерировать v7 уже сегодня и мигрировать постепенно

Поддержка v7 сегодня широко представлена в базах данных и языках, хотя конкретная версия имеет значение:

ПлатформаГенерация v7Версия
PostgreSQLuuidv7() (нативно)PostgreSQL 18
Pythonuuid.uuid7() (стандартная библиотека)Python 3.14
JavaScript / Nodev7() из uuiduuid v14.x

В PostgreSQL 18 появилась нативная функция uuidv7(), а также псевдоним uuidv4() для существующей gen_random_uuid(); в PostgreSQL 17 и более ранних версиях потребуется расширение или библиотека на стороне приложения. Python добавил uuid.uuid7() в стандартную библиотеку в версии 3.14, а не 3.12, где такой вызов приводит к AttributeError. В JavaScript пакет uuid предоставляет v7 через именованный ESM-импорт, и, согласно его changelog, в версии v12 пакет отказался от поддержки CommonJS:

// Node.js, uuid v14.x
import { v7 as uuidv7 } from "uuid";
const id = uuidv7();
-- PostgreSQL 18
SELECT uuidv7();

Если вы хотите увидеть разницу до того, как выбрать тип столбца, сгенерируйте партию значений каждого вида и сравните их. Генератор UUID от OpenReplay создаёт значения v4 или v7 прямо в браузере — до 500 за раз, с переключателями для верхнего регистра, вывода без дефисов и в кавычках, так что их можно сразу вставить в SQL или в JSON-фикстуру. Отсортируйте столбец значений v7 — они окажутся в порядке создания; проделайте то же самое с v4 — они разбросаются. Значения формируются через Web Crypto API в вашей собственной вкладке, поэтому ни одно из них никуда не отправляется.

Миграция не требует переписывания. v4 и v7 используют один и тот же 16-байтный тип столбца uuid, поэтому вы можете сохранить существующие строки с v4 и генерировать новые строки как v7 в той же таблице. Версия закодирована в самом значении, и обратное заполнение не требуется. Новые вставки группируются в конце индекса, а фрагментация постепенно ослабевает по мере перезаписи страниц; это прогрессивный эффект, а не мгновенная дефрагментация.

Вердикт: что же выбрать?

По умолчанию используйте UUID v7 для новых первичных ключей базы данных, логов и потоков событий; выбирайте v4, когда важна непредсказуемость. v7 даёт производительность записи последовательного ключа вместе с защитой от коллизий и распределённой генерацией UUID, и сегодня у него есть нативная поддержка в платформах, которые уже используют большинство команд. Оставьте v4 для идентификаторов, которые публичны и время создания которых должно оставаться конфиденциальным.

Картину дополняют две альтернативы. ULID кодирует ту же идею «метка времени плюс случайность» в Crockford base32, давая более короткую строку из 26 символов, удобную для URL, но он не является стандартом IETF и не имеет нативного типа столбца uuid. Обычный автоинкрементный bigint остаётся самым компактным и быстрым вариантом для небольшой одноузловой системы, которой никогда не понадобится распределённая генерация идентификаторов.

Если вы создаёте новую таблицу сегодня и уже испытываете проблемы с индексом из-за ключей v4, переведите новые вставки на v7, оставьте старые строки на месте и дайте индексу устояться. Выигрыш доступен практически без затрат на миграцию.

Часто задаваемые вопросы

Можно ли извлечь временную метку создания из значения UUID v7?

Да. Поскольку UUID v7 хранит 48-битную Unix-метку времени в миллисекундах в старших битах, можно декодировать момент генерации значения. В PostgreSQL 18 для этого есть функция uuid_extract_timestamp(), которая была расширена для поддержки значений версии 7. Это удобно для отладки и запросов по временным диапазонам, но это же и причина того, что v7 раскрывает время создания и не должен использоваться для публичных идентификаторов, где такая информация чувствительна.

Одинаков ли риск коллизий у UUID v7 и v4?

Нет, но на практике разница пренебрежимо мала. UUID v4 содержит 122 случайных бита, тогда как в v7 остаётся примерно 74 случайных бита после выделения 48 бит под временную метку плюс маркеры версии и варианта. У v7 меньше случайных битов, однако коллизии возможны только между идентификаторами, сгенерированными в одну и ту же миллисекунду, а реализации добавляют монотонный счётчик внутри этого окна. Для реальных нагрузок оба варианта фактически свободны от коллизий при любой реалистичной скорости генерации.

Улучшает ли UUID v7 производительность запросов на чтение или только вставок?

v7 в первую очередь помогает производительности записи и диапазонным сканированиям, размещая строки рядом друг с другом в индексе, что снижает число расщеплений страниц и улучшает локальность кеша. Не приписывайте значительное ускорение чтения одному лишь v7. Официальное улучшение чтения «до 3x» в PostgreSQL 18 обеспечивается новой подсистемой асинхронного ввода-вывода — функциональностью, независимой от uuidv7(). Корректная область выигрыша v7 — это локальность индекса и пропускная способность вставки.

Нужно ли мигрировать существующие первичные ключи UUID v4 на v7?

Обычно полная миграция не требуется. v4 и v7 используют один и тот же 16-байтный тип столбца uuid, а версия закодирована в самом значении, поэтому можно оставить существующие строки с v4 нетронутыми и генерировать новые строки как v7 в той же таблице без обратного заполнения. Новые вставки группируются в конце индекса, а фрагментация постепенно ослабевает по мере перезаписи страниц. Переписывание старых строк оправдано, только если фрагментация уже вызывает измеримые проблемы.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.