Skip to content
Назад к блогу
Руководства

UUID v4 vs v7 vs ULID vs Snowflake: руководство по ID (2026)

Сравнение UUID v4, v7, ULID, Snowflake ID и NanoID по производительности БД, сортируемости, размеру хранения и поддержке экосистем с примерами кода.

15 мин чтения

Только знакомитесь с 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 ID64-битное целое, монотонно в пределах воркера, нативное хранение в BIGINT
Короткий URL-безопасный токен или клиентский IDNanoID21 символ, URL-безопасный алфавит, настраиваемая длина
Legacy-система уже на ULIDULIDОставьте — функционально эквивалентен 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 v4UUID v7Улучшение
Пропускная способность вставки (строк/с)12 40028 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 — стоит ли переключаться?

АспектULIDUUID v7
СортируемДаДа
Длина строки26 символов36 символов
Хранение16 байт16 байт
СтандартCommunity specIETF 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 v4UUID v7ULIDSnowflakeNanoID
Длина36 символов36 символов26 символов15-20 цифр21 символ (по умолчанию)
Хранение16 байт16 байт16 байт8 байт~21 байт
СортируемНетДа (по времени)Да (по времени)Да (по времени)Нет
ТаймстампНет48 бит мс48 бит мс41 бит мсНет
Случайность122 бита74 бита80 бит12 бит послед.~126 бит
СтандартRFC 9562RFC 9562CommunityProprietaryCommunity
Нативный тип в БДuuiduuidНет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. Миграция схемы не нужна.

Стратегия миграции

  1. Новые записи используют v7, старые остаются на v4. Оба сосуществуют в одном столбце. Запросы и join работают одинаково.
  2. Обновите код генерации ID — замените uuidv4() на uuidv7() в слое приложения.
  3. НЕ перезаписывайте существующие v4-ID. Это сломает внешние ключи, внешние ссылки и закешированные URL.
  4. Мониторьте производительность индекса. По мере смещения соотношения 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% в браузере.

Похожие статьи

Все статьи