03 / ГИДЫ
UUID против ULID: выбираем идентификаторы для приложения
UUID v4 против v7, структура и сортируемость ULID, что случайные идентификаторы делают с индексом-B-деревом, вероятность коллизий, раскрытие в URL и локальная генерация идентификаторов.
Что обязан делать идентификатор приложения
У генерируемого идентификатора одна работа — быть уникальным — плюс несколько, которые он подбирает по пути: генерироваться где угодно без координации, компактно храниться, предсказуемо сортироваться и не сливать ничего компрометирующего. UUID и ULID оба проходят планку уникальности; расходятся они во всём остальном.
- Без координацииДецентрализованная генерация: любой сервис, воркер или вкладка браузера может отчеканить идентификатор, не спрашивая центральный счётчик, — именно поэтому оба формата подходят распределённым системам.
- Те же 128 бит128 бит в обоих случаях: UUID — 128-битное значение, записанное 36 шестнадцатеричными символами с дефисами; ULID несёт те же 128 бит в 26 символах base32.
- Раскладка — вот разницаНастоящий вопрос — раскладка: какие биты случайны, а какие кодируют время, решает, как идентификатор ведёт себя в индексе и что он выдаёт.
Версии UUID: v4 случайна, v7 упорядочена по времени
RFC 9562 определяет несколько версий UUID, но в прикладной работе доминируют две. Версия 4 заполняет случайностью 122 из 128 бит и ничего больше не встраивает; версия 7 начинается с 48-битной метки времени Unix в миллисекундах, а оставшиеся 74 бита заполняет случайностью.
- v4: чистая случайностьv4 — выбор максимальной приватности: значение ничего не говорит о том, когда и где его сделали, а 122 случайных бита — больше уникальности, чем любое приложение когда-либо потратит.
- v7: время впередиv7 сортируется по времени создания с точностью до миллисекунды, так что сгенерированные позже идентификаторы идут после ранних — то свойство, которое базы данных хотят от первичного ключа.
- Пропустите v1Старые версии — наследие: v1 встраивала MAC-адрес и значение часов, сливая оба; v3 и v5 — именные хеши для выведения стабильных ID, а не свежая случайность.
ULID: сортируемый и компактный как текст
ULID — это 26 символов Crockford base32: первые 10 символов кодируют 48-битную миллисекундную метку времени, остальные 16 — 80 случайных бит. Алфавит намеренно исключает I, L, O и U, так что идентификатор переживает прочтение вслух и набор со скриншота.
- Сортировка строк = сортировка по времениЛексикографический порядок — это порядок по времени: сортировка ULID как обычных строк сортирует их по времени создания без всякого парсинга — удобно в строках логов, именах файлов и key-value-хранилищах.
- 26 безопасных символовКомпактно и URL-безопасно: 26 регистронезависимых символов без дефисов и знаков, короче 36 у UUID и безопасно в любом сегменте пути или параметре запроса.
- Монотонность в миллисекундеВнутри одной миллисекунды порядок даёт случайная часть; генераторы, монотонно её инкрементирующие, — как пакетный генератор Toolars — держат в порядке генерации даже идентификаторы одной миллисекунды.
Случайные ID фрагментируют ваши индексы
Индекс-B-дерево упорядочен, и вставка случайного UUID v4 каждый раз пишет в случайный лист: страницы расщепляются, буферный кеш мечется, индекс раздувается мёртвым пространством. Упорядоченные по времени идентификаторы дописываются у правого края индекса, превращая вставки в лучший случай B-дерева.
Вот почему выбор делается на этапе проектирования схемы, а не после первого медленного квартала: миграция первичного ключа означает перезапись каждой строки и каждого внешнего ключа, который на неё ссылается. Если таблица навсегда останется маленькой, беспорядок v4 никогда не проявится; если она вырастет до сотен миллионов строк под постоянной записью, упорядоченные форматы окупаются с первого дня.
- Случайность разбрасываетС v4 в роли первичного ключа таблицы с интенсивными вставками показывают больше расщеплений страниц, меньшую заполненность и измеримо худшую пропускную способность записи, чем с упорядоченным ключом того же размера.
- Порядок дописываетv7 и ULID возвращают локальность, сохраняя децентрализованную генерацию — ни последовательности, ни координации и по-прежнему никакой предсказуемости ID чужих строк.
- Измерьте платуПлата реальна, но умеренна: 128 бит — вдвое больше хранения bigint, а упорядоченные по времени ключи концентрируют сегодняшние записи у правого края индекса, что важно только при очень высоких темпах вставки.
Коллизии, URL и что выдаёт ID
Сначала математика коллизий: со 122 случайными битами в v4 генерация миллиарда идентификаторов оставляет вероятность коллизии около одной на 10^18 — фактически никогда. 80 случайных бит ULID охраняют другой бюджет: идентификаторы, созданные в одну и ту же миллисекунду.
Вставьте любое подозрительное значение в валидатор: он распознаёт каноническую 36-символьную форму UUID с вариантом RFC 9562 и нибблом версии, 26-символьный алфавит ULID с правилом переполнения и сообщает встроенную метку времени для упорядоченных по времени форматов — так что значение из лога скажет вам, что оно такое, прежде чем вы на нём построите.
- Вероятность ничтожнаНи один формат не является границей безопасности: идентификатор в URL годится для адресации, но любой, кто его видит, может его процитировать — авторизация должна исходить из проверок доступа, а не из непрозрачности ID.
- ID — не секретv7 и ULID по дизайну раскрывают время создания; валидатор извлекает его открыто. Обычно это безобидные метаданные, но для публичных объектов решайте это осознанно.
- Время видноПоследовательные ID базы данных сливают объём и темп роста; случайные и время-плюс-случайные идентификаторы ничего не сообщают о количестве записей.
Генерация и проверка без сервера
Генерации идентификаторов нужен ровно один дефицитный ресурс — хорошая случайность — и у браузера она уже есть. Рабочая область берёт из crypto.getRandomValues, генерирует пакеты целиком во вкладке и никогда ничего не передаёт.
- 1–100 за пакетВыберите UUID v4, UUID v7 или ULID и генерируйте от 1 до 100 за пакет; пакеты v7 и ULID монотонно инкрементируют случайное поле, так что весь пакет остаётся в порядке генерации.
- Регистр и экспортРегистр вывода — вопрос отображения: канонический, верхний или нижний, с копированием всех разом плюс скачиванием текста с переводами строк и CSV, где записано встроенное время каждого идентификатора.
- Мгновенная проверкаПроверка мгновенно отвечает на структурные вопросы: длина, алфавит, версия и вариант UUID, специальные значения nil и max, переполнение ULID и встроенная метка времени в UTC.
ULID — это base32; ваши токены, скорее всего, Base64.
Кодировки байт-в-текст лежат под каждым идентификатором и токеном — узнайте, во что они обходятся и где окупаются.