UUID и ULID — механизмы выдачи «уникальных идентификаторов» в распределённых системах и базах данных. Формат у них похож, но различия проявляются в наличии стандарта, эффективности хранения, удобочитаемости и рисках раскрытия информации. Ниже я систематизирую признаки и критерии выбора трёх ключевых вариантов — UUID v4, UUID v7, ULID, а затем кратко напоминаю об особенностях UUID v1 / v6.

Вот набор инструментов, которые генерируют идентификаторы и извлекают время в браузере — без передачи данных наружу:


UUID v4: полностью случайный формат

У UUID v4 из 128 бит 122 бита занимают случайные данные, поэтому вероятность коллизий ничтожна. Обратная сторона — отсутствие естественной сортировки по времени. В B-Tree-индексах страницы будут часто расщепляться, и локальность вставок ухудшается. Такой формат уместен для сессионных идентификаторов или одноразовых ключей, где «чистая случайность» допустима.


UUID v7: упорядоченный по времени стандарт

UUID v7 закреплён в RFC 9562 (2024). Первые 48 бит содержат миллисекундное время Unix-эпохи, оставшиеся биты заполняет случайность. Лексикографический порядок строк совпадает с порядком генерации, поэтому даже в роли первичного ключа индекс сохраняет хорошую локальность. Идентификатор можно напрямую хранить в типах uuid или BINARY(16), что упрощает внедрение.

  • Длина случайной части: за вычетом битов version/variant остаётся примерно 74 бита случайности. Реализации могут применять монотонную генерацию (чтобы избежать коллизий в одной миллисекунде) и слегка переназначать случайные биты, но статистическое качество при этом сохраняется.
  • Риск раскрытия информации: из идентификатора можно восстановить момент генерации (до миллисекунд). При больших объёмах выдачи и слабой реализации генератора случайностей предсказать соседние идентификаторы становится проще. Внутренние системы обычно терпимы к этому, но публичные эндпоинты, где нежелательны «полусерийные» ID, требуют дополнительного контроля.

ULID: человекоориентированный идентификатор

ULID предложен в 2016 году и стал де-факто стандартом. Формат — 26 символов Crockford Base32, из набора исключены легко путаемые символы (O/I/L/1), что облегчает вставку в URL и копирование. Как и UUID v7, ULID содержит миллисекундное время в первых 48 битах, так что лексикографический порядок строк равен порядку по времени.

  • Гарантия сортировки: строковое сравнение ULID в Base32 всегда соответствует хронологии. Монотонная генерация для устранения коллизий в одной миллисекунде получила широкое распространение.
  • Форма хранения: в TEXT(26) идентификатор читается напрямую. Для бинарного хранения в БД потребуется конвертация Base32↔16 байт. Ширина остаётся 128 бит, но тип uuid в СУБД обычно несовместим с ULID.
  • Риск раскрытия информации: аналогичен UUID v7 — миллисекундное время видно. Если ресурсный ID в публичном API не должен раскрывать порядок выдачи, ULID стоит применять осторожно.

Сводная таблица трёх форматов (практический ракурс)

Параметр UUID v4 UUID v7 ULID
Стандартизация RFC 4122 RFC 9562 (2024) Не стандартизирован (де-факто стандарт)
Битовая структура 122 бита случайности из 128 48bit=UNIX ms + оставшиеся случайные биты (опционально монотонно) 48bit=UNIX ms + 80bit случайности
Энтропия ≈122 бита ≈74 бита (оценка с учётом version/variant) 80 бит
Строковое представление 36 символов (hex + дефисы) 36 символов (hex + дефисы) 26 символов (Crockford Base32)
Естественная сортировка (строки) × (случайно) ○ (лексикографический порядок = хронология) ○ (лексикографический порядок = хронология)
Естественная сортировка (байты) × ○ (memcmp по BINARY(16) даёт возрастающий порядок) ○ (если первые 6 байт времени хранятся в big-endian, memcmp даёт возрастающий порядок)
Характер хранения в БД Лучше всего uuid / BINARY(16) Лучше всего uuid / BINARY(16) TEXT(26) удобно читать, BINARY(16) требует конверсии / обычно несовместим с типом UUID
Локальность индекса Низкая (случайные вставки дробят страницы) Высокая (аппенды в конец / следить за hot-spot) Высокая (аппенды в конец / следить за hot-spot)
Стоимость равенства Низкая (сравнение 16 байт) Низкая (сравнение 16 байт) Для TEXT чуть выше (зависит от collation) / для BINARY низкая
Затраты на кодирование Hex↔16 байт (минимальные) Hex↔16 байт (минимальные) Base32↔16 байт (чуть тяжелее)
Удобочитаемость / пригодность для URL Низкая Низкая Высокая
Раскрытие времени Нет Есть (миллисекунды) Есть (миллисекунды)
Типичные сценарии Сессионные ID, одноразовые токены PK в БД, идентификаторы событий, журналы по времени URL, публичные ID, визуализация логов

Дополнение про hot-spot у v7/ULID. Если все вставки в одном шарде или на единственном лидере приходятся на «хвост» B-Tree, листовые страницы перегреваются. Подумайте о лёгкой перестановке старших битов (prefix shuffle) или добавочном ключе шардинга.


Заметки по реализации в БД (PostgreSQL / MySQL / SQLite)

  • PostgreSQL
    • v4/v7: тип uuid оптимален. При записи v7 в uuid строкой мы автоматически получаем сортировку по времени.
    • ULID: используйте char(26) / text или конвертируйте в bytea(16).
  • MySQL/InnoDB
    • BINARY(16) обеспечивает высокую скорость и компактность для v4/v7. UUID v7 в big-endian-формате сравнивается по времени через memcmp. ULID можно хранить в CHAR(26) либо в BINARY(16) после конверсии.
  • SQLite
    • Специализированного типа UUID нет. Используйте BLOB(16) или TEXT. Индексированный BLOB сравнивается достаточно эффективно.

Дополнительные меры безопасности

  • Устойчивость к прогнозированию: у v7/ULID одинаковые временные биты. Если в одной миллисекунде выдаётся много идентификаторов и генератор случайностей слабый, растёт риск угадать соседние значения. Используйте только криптографический PRNG ОС и минимизируйте смещение монотонного увеличения.
  • Утечка метаданных: по ID можно оценить момент генерации и примерный объём выдачи. В публичных API разумно разделять внутренние и внешние идентификаторы.

Итоговые рекомендации по выбору

  • UUID v4: полностью случайный. Не сортируется. Теоретически коллизии возможны, пусть и с мизерной вероятностью. Применим к сессионным ID и токенам CSRF.
  • UUID v7: стандартизированный упорядоченный UUID. Лучший кандидат для внутренних систем и первичных ключей БД. Монотонная генерация делает коллизии практически невозможными.
  • ULID: более «человеческий» формат. Подходит, когда важна читаемость или наглядность логов, хотя в роли внутреннего PK чаще уступает v7. Монотонная генерация также позволяет избегать коллизий на практике.

Дополнение: кратко про UUID v1 / v6

  • UUID v1 (время + MAC) Содержит 60-битный таймстемп (точность 100 нс) и идентификатор узла (часто MAC-адрес). Обеспечивает хорошую хронологию, но раскрывает информацию о хосте и требует аккуратно обрабатывать откат системного времени. Из соображений конфиденциальности в новых системах формат почти не советуют.

  • UUID v6 (переупорядоченный v1) Переставляет таймстемп v1 в big-endian-порядке, чтобы лексикографическая сортировка была естественной. Когда-то его рассматривали как «v1, которым удобно сортировать», но стандартизация в итоге сошлась на v7. В новых проектах обычно выбирают v7 вместо v6.


Чек-лист для реализации

  • Генератор случайных чисел должен быть криптографическим PRNG из ОС.
  • В v7/ULID при монотонной генерации важно избегать коллизий в одной миллисекунде и снижать смещение случайных битов.
  • В БД предпочтительнее uuid / BINARY(16), чтобы не терять производительность из-за collation строковых колонок.
  • Заранее определите политику публичных идентификаторов (можно ли разглашать порядок выдачи). При необходимости разделяйте внутренний и внешний ID.

Вывод: сейчас UUID v7 — главный кандидат для внутренних первичных ключей. Если важна читаемость или есть технологические ограничения, ULID — достойная альтернатива. Требование полностью скрыть временную метку может вернуть нас к v4 или другим схемам.