Генератор UUID

Генерируйте UUID — случайные v4 или упорядоченные по времени v7 — поштучно или пачками.

Версия

122 случайных бита. Ничего не сообщает о времени создания.

Сколько

Создаётся…

Что такое UUID и зачем он нужен

UUID — Universally Unique Identifier, в документации Microsoft называемый GUID — это 128-битное значение, записанное как 32 шестнадцатеричные цифры в привычной группировке 8-4-4-4-12. Весь его смысл в том, чтобы разные системы могли создавать идентификаторы независимо, без всякой согласованности между собой, и всё же быть уверенными, что результаты не столкнутся. Именно это отличает его от автоинкрементного столбца: два сервера, два мобильных клиента без сети в самолёте и фоновая задача могут создавать записи в один и тот же момент, ни у кого не спрашивая разрешения.

Уникальность здесь вероятностная, а не гарантированная. В версии 4 на случайность отведено 122 бита — пространство настолько велико, что даже после миллиардов сгенерированных значений вероятность повтора остаётся далеко ниже вероятности того, что диск молча испортит данные. На практике его можно считать уникальным: математика тут не слабое звено.

Версия 4 против версии 7 — выбор, который важен

Версия 4 случайна от начала до конца. Она не несёт никакой информации: ни когда создана, ни кем, ни в каком порядке. Это либо её главное достоинство, либо её основной изъян — смотря куда её поставить.

Версия 7, стандартизованная в RFC 9562 в 2024 году, заменяет первые 48 бит меткой времени Unix в миллисекундах, а остальное заполняет случайностью. Поскольку время идёт первым, а значение читается слева направо, сортировка v7-идентификаторов как обычного текста одновременно упорядочивает их хронологически.

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

  • Выбирайте v7 для первичных ключей, идентификаторов событий и журналов и всего, что вы будете сортировать или запрашивать по диапазону времени создания.
  • Выбирайте v4, когда идентификатор попадает в недоверенную среду и не должен выдавать ничего — включая то, что одна запись создана чуть раньше другой.
  • Обе годятся для correlation ID, ключа идемпотентности или имени файла, где порядок не важен.

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

Порядок внутри одной миллисекунды

Тонкость, на которой спотыкаются многие реализации v7: современное железо создаёт куда больше одного идентификатора в миллисекунду. Если у двух значений одна и та же метка времени, их порядок определяют следующие за ней случайные биты — то есть случай. Сгенерируйте пятьдесят в плотном цикле, и получите пятьдесят значений, упорядоченных лишь приблизительно, потеряв именно то свойство, ради которого выбирали v7.

RFC 9562 решает это монотонным счётчиком, и этот инструмент его реализует: 12 бит сразу после метки времени считают вверх в пределах миллисекунды, так что пачка строго возрастает, а не приблизительно. Если счётчик переполняется — более 4096 значений в одной миллисекунде — генератор занимает следующую миллисекунду, вместо того чтобы обнулиться и выдать значение, которое встанет раньше предыдущего. Ход часов назад, случающийся при коррекциях NTP, обрабатывается так же.

Читая значение v7 слева направо, на примере 0190a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b:

  • 0190a1b2-c3d4 — 48-битная метка времени Unix в миллисекундах. Она идёт первой, поэтому текстовый порядок совпадает с временным.
  • 7 — цифра версии, из-за которой это v7, а не v4.
  • e5f — 12-битный монотонный счётчик, растущий внутри одной миллисекунды.
  • 8 — биты варианта, закреплённые RFC 9562 для любого современного UUID (всегда 8, 9, a или b).
  • a9b-0c1d2e3f4a5b — оставшиеся 62 бита, чисто случайные.

Как хранить и использовать UUID

Каноническая форма — строчные буквы с дефисами, и RFC 9562 предписывает генераторам выдавать именно её; поэтому этот инструмент не предлагает настроек формата. Другие написания вам всё равно встретятся: прописными и в фигурных скобках в инструментах Microsoft, и без дефисов там, где кому-то понадобился столбец покороче. Это одни и те же 128 бит, а сравнение должно игнорировать регистр.

По-настоящему это важно при хранении. UUID занимает 16 байт, но его текстовая форма — 36 символов, поэтому хранение строкой более чем удваивает место в строке таблицы и, что важнее, в каждом индексе, куда она входит. Используйте нативный тип там, где он есть:

uuid                      -- PostgreSQL: нативный 16-байтовый тип
BINARY(16)                -- MySQL: компактно; CHAR(36) тратит 20 байт на строку
uniqueidentifier          -- SQL Server
crypto.randomUUID()       // JavaScript: только v4, нужен защищённый контекст
uuid.uuid4() / uuid7()    # Python: v4 в стандартной библиотеке; v7 через библиотеку

И последнее предостережение: UUID идентифицирует, но не даёт прав. Поскольку его нельзя угадать, соблазнительно считать частной неопубликованную ссылку, содержащую его. Но идентификаторы утекают — через журналы, историю браузера, заголовки referrer и скриншоты, — поэтому всё, что действительно нуждается в защите, по-прежнему требует настоящей проверки прав за собой.

Частые вопросы

Что выбрать — v4 или v7?
Берите v7 для первичных ключей и всего, что будете сортировать по времени создания: ведущая метка времени держит вставки собранными в конце индекса, а не разбросанными по нему. Берите v4, когда идентификатор не должен раскрывать ничего, в том числе время создания.
Могут ли два UUID совпасть?
Это возможно, но исчезающе маловероятно. В версии 4 — 122 случайных бита, так что даже после миллиардов сгенерированных значений вероятность коллизии остаётся намного ниже вероятности того, что хранилище молча их испортит.
Что стало с версиями 1, 3 и 5?
Версия 1 кодирует метку времени и MAC-адрес машины, тем самым раскрывая идентичность оборудования, и в браузере её вообще не получить. Версии 3 и 5 детерминированно выводят UUID из пространства имён и имени через MD5 или SHA-1 — полезно, когда один и тот же вход обязан всегда давать один и тот же идентификатор, но это другая задача, чем создать новый.
Достаточно ли UUID безопасен в роли секретного токена?
UUID версии 4 нельзя угадать, но версия 7 открыто кодирует время создания, и ни та ни другая не задумывались как учётные данные. Для сброса пароля, сессионных токенов и ссылок общего доступа создавайте отдельный случайный секрет и проверяйте права на сервере, а не полагайтесь на то, что идентификатор трудно угадать.
Почему моя пачка v7 не идеально отсортирована в других инструментах?
Потому что многие реализации пропускают монотонный счётчик. Когда несколько значений попадают в одну миллисекунду, их порядок отдан следующим за меткой случайным битам. Этот инструмент реализует счётчик из RFC 9562, поэтому сгенерированная здесь пачка строго возрастает.
Как лучше хранить UUID в базе данных?
В нативном 16-байтовом типе там, где он есть, — uuid в PostgreSQL, uniqueidentifier в SQL Server, BINARY(16) в MySQL. Хранение вместо этого текстовой формы из 36 символов более чем удваивает место в строке и в каждом индексе, куда входит столбец.
Они создаются на ваших серверах?
Нет. Они создаются в вашем браузере через crypto.getRandomValues — криптографический источник случайности платформы, тот же, что использует crypto.randomUUID. Ни одно значение никуда не отправляется, а перезагрузка страницы даёт полностью новый набор.