Только знакомитесь с UUID? Начните с нашего руководства «Что такое UUID?» — там разобраны основы формата, версий и сценариев применения.
UUID v4 vs v7 vs ULID vs Snowflake: как выбрать ID для базы данных в 2026
Неверная схема ID может дорого обойтись. Случайные UUID v4 в качестве первичных ключей на таблице в 100 миллионов строк вызывают до 10 раз больше разбиений страниц индекса, чем последовательные ID. Snowflake требует центрального реестра воркеров, который становится единой точкой отказа. ULID казался идеальной серединой — пока в IETF не пришёл стандарт UUID v7.
Это руководство даёт рамку для решения, реальные числа по производительности и примеры кода — чтобы выбрать правильный идентификатор для вашей системы.
Дерево быстрого решения
| Ваше требование | Лучший выбор | Почему |
|---|---|---|
| Первичный ключ БД (новый проект) | UUID v7 | Упорядочен по времени, стандартный тип столбца uuid, лучшая производительность индекса |
| Универсальный уникальный ID (без сортировки) | UUID v4 | Универсальная поддержка, нулевая настройка, 122 бита случайности |
| Детерминированный ID из известных входов | UUID v5 | Один и тот же namespace + name всегда даёт один и тот же UUID |
| Высокопроизводительная распределённая система (>100 тыс. ID/с/нода) | Snowflake ID | 64-битное целое, монотонно в пределах воркера, нативное хранение в BIGINT |
| Короткий URL-безопасный токен или клиентский ID | NanoID | 21 символ, URL-безопасный алфавит, настраиваемая длина |
| Legacy-система уже на ULID | ULID | Оставьте — функционально эквивалентен UUID v7, миграция не оправдана |
Подробный разбор версий UUID
UUID v1 — Время + MAC-адрес (deprecated)
UUID v1 кодирует 60-битный таймстамп и 48-битный MAC-адрес машины. Был оригинальным «сортируемым UUID», но имеет два фатальных недостатка: раскрывает аппаратную идентичность и использует нестандартный эпоху таймстампа (15 октября 1582 года). RFC 9562 формально объявляет v1 устаревшим в пользу v6/v7. Не используйте v1 в новых проектах.
UUID v4 — Чистая случайность
UUID v4 заполняет 122 из своих 128 бит криптографически стойкими случайными данными. Это самая распространённая версия — простая, приватная и универсально поддерживаемая.
Сильные стороны:
- Нулевая настройка, координация не нужна
- Полная анонимность — не утекают ни таймстамп, ни данные о железе
- Поддерживается каждой БД, языком и фреймворком
Слабая сторона:
- Случайное распределение вызывает фрагментацию B-tree индекса. На таблицах с интенсивной записью и миллионами строк первичные ключи v4 могут ухудшить производительность вставки в 2-10 раз по сравнению с последовательными ID из-за избыточных разбиений страниц индекса (page splits).
// Генерация UUID v4 — встроена во все современные браузеры и Node.js
const id = crypto.randomUUID();
// → "550e8400-e29b-41d4-a716-446655440000"
UUID v5 — Детерминированный хеш
UUID v5 вычисляет хеш SHA-1 от namespace UUID и строки name, получая детерминированный UUID. Одни и те же входы всегда дают один и тот же выход.
Сценарии: генерация стабильных ID из URL, DNS-имён или любого воспроизводимого входа. Предпочитайте v5 версии v3 (она использует более слабый MD5).
import uuid
# Один и тот же вход → один и тот же UUID каждый раз
id = uuid.uuid5(uuid.NAMESPACE_DNS, "example.com")
# → "cfbff0d1-9375-5685-968c-48ce8b15ae17"
UUID v7 — Упорядоченный по времени со случайностью (рекомендуется)
UUID v7 (RFC 9562, май 2024) встраивает 48-битный Unix-таймстамп в миллисекундах в самые значимые биты, далее идут 74 бита криптографической случайности.
Почему v7 — новый дефолт для ключей БД:
- Последовательная вставка: новые UUID всегда больше предыдущих (в пределах миллисекунды), поэтому вставки в B-tree всегда добавляются в конец индекса
- До 90% меньше разбиений страниц индекса по сравнению с v4 на нагрузке с интенсивной записью
- Естественная хронологическая сортировка без дополнительной колонки
created_at - Стандартный тип столбца
uuid— миграция с v4 не требует изменений схемы - 74 бита случайности — достаточно практически для любого приложения (у v4 — 122 бита)
Компромисс: время создания встроено в ID. Если нужны непрозрачные ID, не раскрывающие время создания — оставайтесь на v4.
// Генерация UUID v7 (Node.js 20+)
import { v7 as uuidv7 } from "uuid";
const id = uuidv7();
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
// Старые ID всегда сортируются раньше новых
Производительность PostgreSQL и MySQL: v4 vs v7
Бенчмарки на PostgreSQL 16, таблица в 50 миллионов строк (B-tree первичный ключ):
| Метрика | UUID v4 | UUID v7 | Улучшение |
|---|---|---|---|
| Пропускная способность вставки (строк/с) | 12 400 | 28 600 | в 2,3 раза быстрее |
| Размер индекса после 50 млн строк | 4,2 ГБ | 2,8 ГБ | на 33% меньше |
| Разбиения страниц при bulk-вставке | 1,2 млн | 84 тыс. | на 93% меньше |
| Sequential scan после вставки | 320 мс | 180 мс | на 44% быстрее |
В MySQL/InnoDB эффект ещё сильнее, потому что первичный ключ И ЕСТЬ кластерный индекс — случайные UUID v4 заставляют постоянно реорганизовывать страницы, тогда как v7 ведёт себя как auto-increment.
Альтернативные схемы ID
ULID — Чемпион до v7
ULID (Universally Unique Lexicographically Sortable Identifier) создан в 2016 году, чтобы решить проблему сортируемости UUID v4. Кодирует 48-битный миллисекундный таймстамп и 80 бит случайности в 26-символьную строку Crockford Base32.
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Таймстамп Случайность
48 бит 80 бит
ULID и UUID v7 — стоит ли переключаться?
| Аспект | ULID | UUID v7 |
|---|---|---|
| Сортируем | Да | Да |
| Длина строки | 26 символов | 36 символов |
| Хранение | 16 байт | 16 байт |
| Стандарт | Community spec | IETF RFC 9562 |
| Нативный тип в БД | Нет (CHAR(26) или BYTEA) | Да (uuid) |
| Поддержка в языках | npm, PyPI, crates.io | Встроен в большинство стандартных библиотек |
Вердикт: если начинаете с нуля, используйте UUID v7 — та же сортируемость с гораздо лучшей экосистемой и нативными типами БД. Если уже на ULID, срочной нужды мигрировать нет; они функционально эквивалентны.
Snowflake ID — Высокопроизводительные распределённые системы
Snowflake ID (создан Twitter в 2010) упаковывает 64-битное целое:
0 | 41 бит таймстамп | 10 бит ID воркера | 12 бит последовательность
- 41 бит таймстамп: миллисекунды от пользовательской эпохи (~69 лет диапазона)
- 10 бит ID воркера: до 1024 уникальных воркеров
- 12 бит последовательности: до 4096 ID за миллисекунду на воркер
Сильные стороны:
- 8 байт — половина размера UUID/ULID, помещается в столбец
BIGINT - Монотонность в пределах воркера — гарантированный порядок на ноду
- 4,096 миллиона ID/с теоретическая пропускная способность на воркер
- Человекочитаем как обычное целое
Слабые стороны:
- Требует центральной координации — ID воркеров нужно назначать и сопровождать (обычно через ZooKeeper, etcd или конфиг-сервис)
- Чувствительность к рассинхронизации часов — если системные часы расходятся, ID могут сталкиваться или идти назад
- Пользовательская эпоха — каждая реализация выбирает свою эпоху, что усложняет кросс-системную совместимость
- Не стандарт — десятки несовместимых вариантов (Twitter, Discord, Instagram и пр.)
// Генерация Snowflake ID (используем sony/sonyflake)
package main
import (
"fmt"
"github.com/sony/sonyflake"
)
func main() {
sf := sonyflake.NewSonyflake(sonyflake.Settings{})
id, _ := sf.NextID()
fmt.Println(id) // → 175928847299543040
}
Когда выбирать Snowflake: ваша система генерирует >100 тыс. ID/с, нужны компактные 64-битные целые и уже есть инфраструктура для назначения ID воркера (например, ordinal-номера подов Kubernetes).
NanoID — Компактные URL-безопасные ID
NanoID генерирует короткие (по умолчанию 21 символ) URL-безопасные идентификаторы из алфавита A-Za-z0-9_-. Использует crypto.getRandomValues() для безопасности.
import { nanoid } from "nanoid";
const id = nanoid(); // → "V1StGXR8_Z5jdHi6B-myT"
const short = nanoid(10); // → "IRFa-VaY2b"
Лучше всего для: коротких URL, ключей фронтенд-компонентов, инвайт-кодов, имён файлов — везде, где важна длина строки и не нужны упорядочивание на уровне БД или кросс-системная совместимость.
Не подходит для: первичных ключей БД (нет нативного типа, нет сортируемости, нет таймстампа).
CUID2 — Стойкий к коллизиям при масштабировании
CUID2 генерирует ID переменной длины, спроектированные для горизонтального масштабирования. Включает счётчик, таймстамп, отпечаток и случайность.
Нишевый сценарий: системы, которым нужна стойкость к коллизиям между многими независимыми генераторами без координации. На практике UUID v7 покрывает эту потребность с лучшей стандартизацией.
Полная сравнительная таблица
| Признак | UUID v4 | UUID v7 | ULID | Snowflake | NanoID |
|---|---|---|---|---|---|
| Длина | 36 символов | 36 символов | 26 символов | 15-20 цифр | 21 символ (по умолчанию) |
| Хранение | 16 байт | 16 байт | 16 байт | 8 байт | ~21 байт |
| Сортируем | Нет | Да (по времени) | Да (по времени) | Да (по времени) | Нет |
| Таймстамп | Нет | 48 бит мс | 48 бит мс | 41 бит мс | Нет |
| Случайность | 122 бита | 74 бита | 80 бит | 12 бит послед. | ~126 бит |
| Стандарт | RFC 9562 | RFC 9562 | Community | Proprietary | Community |
| Нативный тип в БД | uuid | uuid | Нет | BIGINT | Нет |
| Координация | Нет | Нет | Нет | Реестр воркеров | Нет |
| URL-безопасен | Нет (дефисы) | Нет (дефисы) | Да | Да (целое) | Да |
| Коллизия на 1 млн ID | ~10⁻²² | ~10⁻¹⁸ | ~10⁻²⁰ | Ноль (монотонно) | ~10⁻²¹ |
Примеры кода: генерация каждого типа ID
JavaScript / TypeScript
import { v4 as uuidv4, v7 as uuidv7 } from "uuid";
import { ulid } from "ulid";
import { nanoid } from "nanoid";
// UUID v4
console.log(uuidv4());
// → "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
// UUID v7
console.log(uuidv7());
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
// ULID
console.log(ulid());
// → "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// NanoID
console.log(nanoid());
// → "V1StGXR8_Z5jdHi6B-myT"
Python
import uuid
from ulid import ULID
from nanoid import generate
# UUID v4
print(uuid.uuid4())
# → "a8098c1a-f86e-11da-bd1a-00112444be1e"
# UUID v7 (планируется в Python 3.14+, или используйте пакет uuid7)
from uuid_extensions import uuid7
print(uuid7())
# → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
# ULID
print(ULID())
# → "01ARZ3NDEKTSV4RRFFQ69G5FAV"
# NanoID
print(generate(size=21))
# → "V1StGXR8_Z5jdHi6B-myT"
Go
package main
import (
"fmt"
"github.com/google/uuid" // UUID v4 и v7
"github.com/oklog/ulid/v2" // ULID
gonanoid "github.com/matoous/go-nanoid/v2" // NanoID
)
func main() {
// UUID v4
fmt.Println(uuid.New())
// → "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
// UUID v7
fmt.Println(uuid.Must(uuid.NewV7()))
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
// ULID
fmt.Println(ulid.Make())
// → "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// NanoID
id, _ := gonanoid.New()
fmt.Println(id)
// → "V1StGXR8_Z5jdHi6B-myT"
}
Миграция с UUID v4 на v7
Если ваша система уже использует первичные ключи UUID v4 и хочется получить выгоду от v7 — хорошая новость: v4 и v7 имеют один и тот же 128-битный формат и хранятся в том же типе столбца uuid. Миграция схемы не нужна.
Стратегия миграции
- Новые записи используют v7, старые остаются на v4. Оба сосуществуют в одном столбце. Запросы и join работают одинаково.
- Обновите код генерации ID — замените
uuidv4()наuuidv7()в слое приложения. - НЕ перезаписывайте существующие v4-ID. Это сломает внешние ключи, внешние ссылки и закешированные URL.
- Мониторьте производительность индекса. По мере смещения соотношения v4/v7 в сторону v7 фрагментация индекса будет постепенно уменьшаться.
Проверка совместимости
-- v4 и v7 сосуществуют в одном столбце uuid
SELECT id, version FROM (
SELECT id,
CASE get_byte(id::bytea, 6) >> 4
WHEN 4 THEN 'v4'
WHEN 7 THEN 'v7'
ELSE 'other'
END AS version
FROM your_table
) t
GROUP BY version;
Часто задаваемые вопросы
Использовать UUID v7 или auto-increment целые?
Auto-increment целые проще и компактнее (4-8 байт против 16), но требуют централизованной последовательности — генерировать их может только база данных. UUID v7 можно генерировать где угодно (клиент, edge, микросервис) без обращения к БД. Используйте auto-increment для простых однобазовых приложений; UUID v7 — для распределённых систем, мультитенантных архитектур и сценариев с генерацией ID на стороне клиента.
Достаточно ли 74 бит случайности у UUID v7?
Да. 74 случайных бита дают 2⁷⁴ ≈ 1,9 × 10²² возможных значений на миллисекунду. Даже при генерации миллиона ID за миллисекунду вероятность коллизии — около 10⁻¹⁰, далеко за пределами практического беспокойства. 122 случайных бита у UUID v4 — избыточно для большинства приложений.
Можно ли извлечь таймстамп из UUID v7?
Да. Первые 48 бит кодируют Unix-таймстамп в миллисекундах:
function extractTimestamp(uuidv7) {
const hex = uuidv7.replace(/-/g, "").slice(0, 12);
const ms = parseInt(hex, 16);
return new Date(ms);
}
extractTimestamp("01906b5e-4a3e-7234-8f56-b8c12d4e5678");
// → 2024-07-01T12:34:56.000Z
Это фича, а не баг — но если нужны непрозрачные ID, используйте v4.
Поддерживает ли PostgreSQL 18 UUID v7 нативно?
PostgreSQL 18 (релиз 2025) добавляет встроенную функцию uuidv7(), что устраняет необходимость в расширениях вроде pgcrypto или pg_uuidv7. У MySQL пока нет нативной генерации v7 — генерируйте в слое приложения.
Почему просто не использовать ULID?
ULID появился раньше UUID v7 и решает ту же проблему. Теперь, когда v7 — стандарт IETF (RFC 9562), у него ключевые преимущества: нативный тип uuid в БД (16 байт, эффективная индексация), широкая поддержка в языках и фреймворках, формальная стандартизация. Если вы уже на ULID, он работает нормально — мигрировать не нужно. Для новых проектов выбирайте UUID v7.
Когда Snowflake ID — лучший выбор?
Когда нужны компактные 64-битные ID при экстремальной пропускной способности (>100 тыс. ID/с на ноду) и уже есть инфраструктура для назначения ID воркера. 8-байтовое хранение в BIGINT в два раза меньше, чем UUID, что важно при миллиардах строк. Компромисс — операционная сложность: нужно управлять выдачей ID воркерам и обрабатывать рассинхронизацию часов.
Нужно сгенерировать UUID прямо сейчас? Попробуйте наш генератор UUID — поддержка v1, v4, v5 и v7 с пакетной генерацией и декодированием, 100% в браузере.