ГлавнаяБлогUUID v7: реализация на JavaScript и сравнение с v4
Алгоритмы

UUID v7: реализация на JavaScript и сравнение с v4

Узнайте, как реализовать UUID v7 в браузере: структура, код на JavaScript, ловушки и когда использовать v4, v7 или ULID. Попробуйте прямо сейчас!

Al
Редакция Algolitalgolit.ru
8 мин чтения23 июля 2026 г.

Зачем UUID v7, если есть v4?

Случайный UUID v4 — плохой первичный ключ: его значения разбросаны по всему индексу, что вызывает частые разделения страниц B-дерева. UUID v7 помещает метку времени в старшие биты (стандартизирован в RFC 9562), благодаря чему порядок генерации совпадает с порядком сортировки, и локальность восстанавливается. В этой статье я покажу, как вручную реализовать UUID v7 в браузере, разберу структуру, подводные камни и дам практические рекомендации.

Структура UUID v7

128 бит UUID v7 делятся так:

| 48 бит unix_ms | 4 бит ver(=7) | 12 бит rand_a | 2 бит variant | 62 бит rand_b |

Первые 48 бит — это Unix-время в миллисекундах, порядок big-endian. Затем идут биты версии (7) и варианта, а всё остальное — случайные данные. Случайная часть должна быть криптостойкой (crypto.getRandomValues), а не Math.random().

Реализация: упаковка бит

Задача — упаковать метку времени в первые шесть байт (старший байт первым), затем перезаписать полубайты версии и варианта.

function rnd(n) {
    const a = new Uint8Array(n);
    crypto.getRandomValues(a);
    return a;
}

function hex(b) {
    let s = '';
    for (const x of b) s += ('0' + x.toString(16)).slice(-2);
    return s;
}

function uuidV7() {
    const ts = Date.now(); // 48-битная метка времени
    const b = rnd(16);

    // 48-битная метка времени big-endian (первые шесть байт)
    b[0] = (ts / 0x10000000000) & 0xff; // ts >> 40
    b[1] = (ts / 0x100000000) & 0xff;   // ts >> 32
    b[2] = (ts / 0x1000000) & 0xff;     // ts >> 24
    b[3] = (ts / 0x10000) & 0xff;       // ts >> 16
    b[4] = (ts / 0x100) & 0xff;         // ts >> 8
    b[5] = ts & 0xff;

    b[6] = (b[6] & 0x0f) | 0x70;       // версия 7
    b[8] = (b[8] & 0x3f) | 0x80;       // вариант (10xx)

    const h = hex(b);
    return `${h.slice(0,8)}-${h.slice(8,12)}-${h.slice(12,16)}-${h.slice(16,20)}-${h.slice(20)}`;
}

Важный нюанс: сдвигать биты нужно делением, а не оператором >>. ts >> 40 не сработает, потому что побитовые операторы в JavaScript работают с 32-битными числами, и 48-битное значение будет испорчено. Деление с последующим & 0xff безопасно.

Реализация UUID v4: используйте стандартный API

Для v4, если платформа предоставляет стандартный API, не изобретайте велосипед — это быстрее и безопаснее.

function uuidV4() {
    if (crypto.randomUUID) return crypto.randomUUID(); // один вызов там, где поддерживается
    const b = rnd(16);
    b[6] = (b[6] & 0x0f) | 0x40;
    b[8] = (b[8] & 0x3f) | 0x80;
    const h = hex(b);
    return `${h.slice(0,8)}-${h.slice(8,12)}-${h.slice(12,16)}-${h.slice(16,20)}-${h.slice(20)}`;
}

Ловушка: порядок не гарантирован в пределах одной миллисекунды

Если два ID сгенерированы в одну миллисекунду, их первые 48 бит совпадают, а всё остальное случайно — относительный порядок произволен. Если вы предполагаете, что порядок генерации строго возрастает, то при всплеске ID это предположение нарушится.

Когда нужна строгая монотонность, используйте 12-битное поле rand_a как счётчик: увеличивайте его в пределах одной миллисекунды и сбрасывайте при смене миллисекунды.

let lastMs = 0, seq = 0;

function uuidV7Monotonic() {
    const ts = Date.now();
    if (ts === lastMs) {
        seq = (seq + 1) & 0x0fff; // та же мс: +1 (12 бит)
    } else {
        lastMs = ts;
        seq = rnd(2)[0] & 0x0fff; // новая мс: случайное начало
    }
    // ...сохраняем seq в младшие 12 бит b[6..7], перезаписывая версию = 7...
}

Если для вашего случая достаточно «примерно упорядоченных по времени», обычная случайность подойдёт. Главное решение — нужно ли вам строгая монотонность, а не то, какая реализация выглядит умнее.

Проверка вместо доверия спецификации

Я проверил следующее как в Node, так и в браузере, вместо того чтобы полагаться на спецификацию:

  • Установлены ли биты версии и варианта? (u[14] === '7', и u[19] — один из 8, 9, a, b).
  • Всегда ли два ID, сгенерированные через границу миллисекунд, сортируются по возрастанию?
  • Сгенерируйте 1000 ID в одной миллисекунде — нарушает ли обычная реализация возрастающий порядок, и сохраняет ли его версия со счётчиком?

Воспроизвести сбой самостоятельно — вот что делает границу ловушки видимой. «Спецификация говорит, что должно» — это не то же самое, что увидеть это своими глазами.

v4 vs v7 vs ULID

Сортируемость по времениЛокальностьСтандартВ одной строке
UUID v4НетНизкаяRFC 9562Полностью случайный. Хорошо распределяется, но плох для индексов
UUID v7Да (миллисекунды)ВысокаяRFC 9562Хороший первичный ключ. Подходит для существующего столбца UUID
ULIDДаВысокаяДе-факто26-символьный Base32. Стоит рассмотреть, если не нужна совместимость с UUID

Если вы используете UUID как первичные ключи, v7 стоит рассмотреть в первую очередь. На практике большое преимущество в том, что он подходит для существующего столбца uuid/UUID без миграции — нужно сменить только генератор.

Результат

Я опубликовал генератор как браузерный инструмент: он поддерживает v4 и v7, с опциями количества, дефисов и верхнего регистра. Генерация происходит на странице, ничего не отправляется на сервер: https://hashitosystem.com/tools/uuidgen/

Практический вывод

UUID v7 — правильный выбор, если нужны «примерно упорядоченные по времени ключи с хорошей локальностью». Реализация — просто разбиение метки времени на байты делением и перезапись полубайтов версии/варианта. Единственное предостережение: порядок в пределах одной миллисекунды не гарантирован, поэтому используйте счётчик, если нужна строгая монотонность. Учтите этот момент — и v7 можно использовать смело.

Что делать прямо сейчас: откройте консоль браузера и скопируйте функцию uuidV7 из статьи. Сгенерируйте 10 ID и проверьте, что они упорядочены по времени. Затем попробуйте сгенерировать 1000 ID в одном цикле и убедитесь, что порядок нарушается. Если ваш проект требует строгой монотонности, реализуйте версию со счётчиком. Это займёт 5 минут, но сэкономит часы отладки.

#uuid v7#uuid v4#генерация UUID#JavaScript#первичные ключи
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →