Генератор UUID
Создавайте UUID версии v4 или упорядоченные по времени v7 и разбирайте уже имеющиеся.
Случайные UUID
Разобрать UUID
Вставьте его, чтобы увидеть, какой это вариант и что в нём зашито, если вообще что-то есть.
Версии, и какие две из них важны
Их восемь, и для новой работы выбор на деле идёт между двумя. Остальные стоит уметь опознавать, встретив их в старой системе.
| Версия | Из чего построена | Когда брать |
|---|---|---|
| v4 | 122 случайных бита | Нужна неугадываемость. Выбор по умолчанию для токенов и идентификаторов. |
| v7 | Метка времени + случайность | Нужен ключ в базе данных. Сортируется по времени создания. |
| v1 | Метка времени + MAC-адрес | Наследие. Выдаёт машину и время. |
| v3 / v5 | Хеш пространства имён и имени | Нужно, чтобы одинаковый вход всегда давал одинаковый идентификатор. |
| v6 | Переупорядоченная v1 | Переносите данные с v1 и хотите, чтобы они сортировались. |
| v8 | Всё, что решите сами | Почти никогда. |
Проблема индекса — настоящая причина, по которой появилась v7
Это практический довод, и его стоит понять, а не принять на веру.
Первичный ключ в базе хранится в B-дереве, упорядоченно. Вставляйте последовательные целые — и каждая новая строка ложится к правому краю: одна страница остаётся горячей в памяти, дерево растёт аккуратно, а записи дёшевы.
Вставляйте UUID v4 — и каждая строка ложится в случайное место. Задеваются страницы по всему дереву, каждую надо сначала прочитать, чтобы записать, а заполняясь, страницы расщепляются. На таблице любого размера пропускная способность вставки заметно падает, а индекс фрагментируется; эффект хорошо задокументирован, и именно он создал ключам-UUID их репутацию.
v7 чинит это, ставя метку времени первой. Сгенерируйте выше несколько значений с выбранной v7 и посмотрите на общий префикс: созданные близко по времени идут рядом, поэтому вставки дописываются в конец индекса, как у целого числа, оставаясь при этом глобально уникальными. Вы сохраняете то свойство, ради которого UUID и брали — сгенерировать идентификатор где угодно, без всякой координации, — не расплачиваясь за него на каждой записи.
Строго говоря: порядок гарантирован между миллисекундами, а не внутри одной. Два UUID v7, созданные в одну и ту же миллисекунду, упорядочены своими случайными хвостами, что RFC 9562 разрешает, попутно позволяя реализациям ужесточить это счётчиком. На довод о локальности индекса это никак не влияет: он зависит только от ведущей метки времени.
Цена, остающаяся у обеих версий: 16 байт против 4 или 8, и они тиражируются в каждый индекс, который ссылается на ключ. На большой таблице с несколькими внешними ключами это вполне ощутимые гигабайты.
Что раскрывает v7
Метку времени прочтёт любой, у кого есть идентификатор. Вставьте UUID v7 в инспектор выше, и он назовёт миллисекунду, в которую тот был создан.
Обычно безобидно. Иногда нет: идентификатор v7 в публичном URL выдаёт, когда была создана запись, а этого может хватить, чтобы вывести темпы регистраций, объёмы заказов или то, что документ был датирован задним числом. Если это важно, простое разделение таково: v4 для всего, что видит пользователь, и v7 для внутренних ключей.
Где случайность обязана быть настоящей
Арифметика коллизий, которую все цитируют — 2,7 квинтиллиона до того, как вероятность одной коллизии сравняется с подбрасыванием монеты, — исходит из 122 по-настоящему случайных битов. Это утверждение о формате, а не о вашей реализации.
У UUID, построенного на Math.random(), нет ни той энтропии, ни той непредсказуемости, которые подразумевает это число, и коллизии в реальной жизни по сути всегда были следствием именно этого, а не невезения. Два процесса, запущенные в одну секунду с одинаковым зерном, дают одинаковую последовательность.
Эта страница использует crypto.randomUUID() там, где он есть, и crypto.getRandomValues в остальных случаях. Стоит проверить, чем пользуется ваш собственный код, особенно если UUID подменяют собой токены: предсказуемый идентификатор сброса пароля — это полный захват учётной записи.
Как хранить их экономно
Текстовая форма из 36 символов удобна и расточительна. Альтернативы — все те же 128 бит:
- Родной тип UUID — в Postgres он есть и занимает 16 байт. Пользуйтесь им.
- `BINARY(16)` — эквивалент в MySQL, примерно вдвое компактнее
CHAR(36). - Base64 — 22 символа, когда это обязано быть текстом и длина важна.
- Base32 — 26 символов, без учёта регистра; именно то, что нужно, если когда-нибудь человеку придётся прочитать такое вслух.
Смежное
Для секретов, которые человеку приходится набирать, а не машине хранить, генератор паролей берёт тот же источник случайности. Чтобы прочитать метку времени из UUID v7 в другом формате, дальше подхватит конвертер меток времени.
Вопросы про UUID
Могут ли два UUID совпасть?
В принципе да, на практике нет — при условии, что случайность настоящая. У UUID v4 122 случайных бита, и понадобилось бы сгенерировать около 2,7 квинтиллиона таких значений, чтобы вероятность одного совпадения достигла 50 % — примерно по миллиарду в секунду в течение 85 лет. Реальная поломка никогда не в математике, а в слабом источнике случайности. UUID, построенный на Math.random() или на плохо инициализированном генераторе, совпадать может и совпадает — поэтому эта страница берёт криптографический источник браузера.
Стоит ли использовать UUID как первичный ключ в базе?
Зависит от версии, и разница велика. UUID v4 случаен, поэтому последовательные вставки попадают в произвольные места B-дерева: страницы расщепляются, индекс фрагментируется, и на большой таблице пропускная способность записи заметно падает. UUID v7 начинается с метки времени в миллисекундах, поэтому вставки дописываются в конец, как у автоинкрементного целого, сохраняя при этом глобальную уникальность. Если хотите ключи-UUID — берите v7. Вторая цена одинакова для обоих: 16 байт против 4 или 8 у целого, и так в каждом индексе, который на него ссылается.
Что такое UUID v7 и можно ли его уже применять?
Это упорядоченный по времени вариант: 48 бит миллисекунд Unix, затем 74 случайных бита; стандартизован в RFC 9562 в мае 2024 года. Это опубликованный стандарт с поддержкой в актуальном Postgres, в большинстве языковых экосистем и в изрядном числе библиотек. Для новой работы это разумный выбор по умолчанию. Единственное свойство, о котором надо помнить: он несёт в себе время создания, поэтому идентификатор v7 в URL примерно выдаёт, когда запись была сделана.
Достаточно ли UUID безопасен, чтобы служить токеном?
У UUID v4 из криптографического источника 122 бита энтропии — этого с запасом хватает для ссылки сброса пароля или идентификатора сессии. Есть две оговорки. Первая: источник действительно должен быть криптографическим — crypto.randomUUID таков, большинство библиотечных реализаций тоже, а всё, что построено на Math.random(), нет. Вторая: другие версии не годятся — v1 несёт метку времени, а исторически и MAC-адрес; v3 и v5 суть детерминированные хеши своих входов; v7 раскрывает время создания. Если значение должно быть неугадываемым, берите v4.
Почему UUID занимает 36 символов, если это всего 16 байт?
Потому что он записан шестнадцатерично — по два символа на байт, то есть 32 — плюс четыре дефиса. Некоторые системы хранят вместо этого 16 сырых байтов; для этого и существует BINARY(16) в MySQL, и объём хранения примерно вдвое меньше. Другие используют более короткую текстовую запись: Base64 даёт 22 символа, Base32 — 26 и нечувствителен к регистру. Всё это одни и те же 128 бит в разной одежде.
Последняя проверка . Заметили, что что-то устарело? Напишите нам.
