UUID против последовательных ID: когда что использовать
Выбор между UUID и последовательным ID кажется мелочью на старте проекта, но эта деталь определяет архитектуру системы на годы вперед. От стратегии идентификаторов зависит, как быстро растут индексы, как приложение масштабируется на несколько серверов и насколько просто угадать чужие записи по URL. В этом руководстве разберем реальные компромиссы, чтобы вы могли выбрать подходящий инструмент под свои задачи.
Что такое UUID?
UUID (Universally Unique Identifier) — это 128-битная метка, записанная в виде 36-символьной строки. Классический UUID v4 выглядит так: f47ac10b-58cc-4372-a567-0e02b2c3d479. Внутри 32 шестнадцатеричных цифры, сгруппированных в пять блоков и разделенных дефисами.
UUID v4 — самая распространенная версия в веб-разработке. Он генерирует 122 бита случайности, что дает примерно 5,3 ундециллиона возможных значений. Это число настолько велико, что если генерировать миллиард UUID в секунду на протяжении столетия, пространство значений практически не исчерпается.
Есть и другие версии. UUID v1 встраивает временную метку и MAC-адрес машины, которая создала идентификатор. Это раскрывает информацию об оборудовании и создает предсказуемые паттерны. UUID v7, самый свежий стандарт, комбинирует Unix-время со случайными байтами. Получается сортируемый по времени идентификатор с высокой энтропией. Большинство разработчиков выбирают v4 или v7, если нет специфических причин делать иначе.
Если нужно быстро сгенерировать валидные UUID для тестирования или прототипа, Генератор UUID на Randify создает идентификаторы версии 4 мгновенно.
Хранение и производительность
Главное практическое отличие UUID от последовательных ID — размер. Обычный автоинкрементный integer занимает 4 байта (или 8 байтов в случае BIGINT). UUID-строка требует 36 байтов при текстовом хранении, или 16 байтов в бинарном виде BINARY(16). Эта разница критична, когда в таблице сотни миллионов строк.
Индексы страдают сильнее, чем сами данные. PostgreSQL и MySQL строят B-tree индексы, которые работают лучше всего, когда новые значения приходят в отсортированном порядке. Последовательные ID аккуратно добавляются в конец индекса. UUID, будучи случайными, разбрасывают вставки по всему дереву. Это вызывает разделение страниц, фрагментацию и потерю дискового пространства.
В PostgreSQL индекс первичного ключа на UUID-колонке может вырасти на 30–50% больше, чем аналогичный индекс на BIGINT. Пропускная способность записи тоже падает, потому что база вынуждена читать и модифицировать разрозненные страницы индекса вместо добавления в конец. Движок InnoDB в MySQL, который кластеризует данные по первичному ключу, испытывает еще более ощутимые штрафы. Случайные первичные ключи превращают последовательную запись на диск в случайный ввод-вывод. Это самый медленный паттерн для классических жестких дисков и заметен даже на SSD.
Как уменьшить влияние на индексы
Необязательно мириться с падением производительности. Один из вариантов — хранить UUID в бинарном 16-байтовом формате вместо 36-символьной строки. Это сокращает занимаемое место вдвое. Другой путь — использовать упорядоченные идентификаторы вроде ULID или UUID v7, где префикс зависит от времени, а суффикс остается случайным. База по-прежнему видит случайность, но она локализована в пределах небольшого временного окна, поэтому вставки группируются ближе к концу индекса.
В MySQL некоторые команды используют составной первичный ключ вида (created_at, uuid), чтобы восстановить последовательные паттерны доступа. Плата — чуть более сложные запросы и необходимость обрабатывать коллизии временных меток.
Вероятность коллизии
Люди часто беспокоятся о коллизиях UUID. На практике это непроблема для UUID v4. Парадокс дней рождения говорит, что риск коллизии становится значимым, когда количество сгенерированных значений приближается к квадратному корню из общего пространства. Для 122 бит случайности этот корень составляет около 2,7 квинтиллиона. При генерации миллиона UUID в секунду потребуется примерно 85 лет, чтобы вероятность коллизии достигла 50%.
Реальные коллизии происходят из-за плохих генераторов случайных чисел, а не потому что ломается математика. Если сервер исчерпывает энтропию или использует слабый псевдослучайный источник, дубликаты становятся возможными. В продакшене всегда используйте криптографически стойкий генератор случайных чисел.
Чтобы посмотреть, как случайность ведет себя в больших объемах, Генератор хешей на Randify выдает дайджесты фиксированной длины с теми же свойствами устойчивости к коллизиям, что и хорошая генерация UUID.
Распределённые системы
Последовательные ID рассыпаются, как только появляется больше одной базы данных. Если два приложенных сервера вставляют записи в свои шарды, оба будут генерировать пересекающиеся автоинкрементные значения, если не организовать жесткую координацию. А координация вносит единые точки отказа и задержки.
UUID решают эту задачу по конструкции. Поскольку они не зависят от центрального счетчика, любой узел может создать идентификатор, не спрашивая никого. Это делает их идеальными для микросервисов, мультирегиональных развертываний и offline-first клиентов. Мобильное приложение может создавать записи локально, назначать им UUID и синхронизировать позже, не беспокоясь о конфликтах.
Альтернативы, которые стоит знать
UUID — не единственный вариант для распределенных систем.
Twitter Snowflake — 64-битный формат ID, который упаковывает временную метку, идентификатор дата-центра, идентификатор машины и порядковый номер в одно целое число. Результат сортируется по времени, помещается в BIGINT и не требует координации между машинами. Discord и Instagram используют вариации этого подхода.
MongoDB ObjectId — 12-байтовое бинарное значение, которое объединяет 4-байтовую временную метку, 5-байтовый идентификатор машины и процесса, и 3-байтовый счетчик. Оно меньше UUID, сортируется примерно по времени и встраивает метаданные о месте создания документа.
ULID (Universally Unique Lexicographically Sortable Identifier) — 26-символьная строка, выглядящая как 01ARZ3NDEKTSV4RRFFQ69G5FAV. Сортируется по времени, безопасна для URL и нечувствительна к регистру. Если нужен строковый формат, но хочется лучшего поведения индексов, чем у UUID v4, ULID — сильный кандидат.
Каждый из этих форматов жертвует частью случайности ради структуры. Snowflake ID предсказуемы, если знаешь их layout, что может быть проблемой приватности. UUID v4 остается самым непрозрачным вариантом.
Когда что использовать
Универсального победителя не существует. Лучшая стратегия идентификаторов базы данных зависит от ограничений конкретной системы.
Выбирайте последовательные ID, когда:
- Работаете с одним инстансом базы данных.
- Размер хранилища и индексов критичен.
- Нужны простые, человекочитаемые URL вроде
/users/1234. - Идентификаторы не показываются конечным пользователям, так что возможность угадать их неважна.
Выбирайте UUID, когда:
- Запущены несколько баз данных, шардов или микросервисов.
- Генерируете ID на клиенте или в edge-функциях.
- Нужны непрозрачные идентификаторы, которые нельзя угадать или перебрать.
- Планируете объединять данные из разных источников в будущем.
Рассмотрите гибриды, когда:
- Нужна сортируемость по времени при распределенной генерации. ULID, UUID v7 или Snowflake дают лучшее от обоих миров.
- Нужен минимально возможный размер ID. 64-битный Snowflake в два раза меньше UUID.
- Хотите встраивать метаданные. MongoDB ObjectId включает время создания и машину-источник по умолчанию.
Быстрый чек-лист для выбора
Пройдитесь по этому списку перед тем, как зафиксировать стратегию идентификаторов:
- Ваша система распределенная? Если да, избегайте чистых последовательных ID.
- Пользователи видят ID в URL или API? Если да, отдавайте предпочтение непрозрачным форматам, чтобы предотвратить перебор.
- Размер индекса — жесткое ограничение? Если да, рассмотрите BIGINT Snowflake ID или бинарное хранение UUID.
- Нужны ID, сортируемые по времени? Если да, смотрите на ULID, UUID v7 или Snowflake.
- Вы делаете прототип? Если да, последовательные ID вполне подойдут. Миграция болезненна, но не невозможна, если спланировать заранее.
Дебаты UUID vs последовательный ID — не про то, какой формат лучше в абсолютном смысле. Они про то, какой подходит вашей системе. Выбирайте формат, который решает реальные проблемы сегодня, и помните, что миграция всегда возможна, если архитектура изменится. Если хотите поэкспериментировать с внешним видом UUID, Генератор UUID на Randify — быстрый способ сгенерировать пачки для тестирования.