UUID v1 Generator
Generate time-based UUID v1 with embedded timestamp
…
Форматировать
Что такое UUID v1?
UUID v1 — версия UUID, основанная на времени, определённая в <strong>RFC 4122</strong>. Она объединяет высокоточную временную метку, MAC-адрес и счётчик тактов для генерации уникальных идентификаторов.
UUID v1 разработаны для распределённых систем, где несколько машин должны независимо генерировать UUID без координации. MAC-адрес гарантировал уникальность между машинами.
Сегодня UUID v1 в значительной степени устарел. UUID v7 решает ту же проблему без утечки идентификатора хоста. RFC 9562 официально устаревает v1 в пользу v6 и v7.
Анатомия UUID v1
UUID v1 упаковывает несколько полей в 128 бит:
| Поле | Размер | Описание |
|---|---|---|
| time_low | 32 bits | 60-битная временная метка: 100-наносекундные интервалы с 15 октября 1582 (Gregorian-эпоха UUID). Поле разделено на три части (low, mid, hi). |
| time_mid | 16 bits | Номер версии: <code>0001</code> (v1). Занимает 4 бита в поле time_hi_and_version. |
| time_hi_and_version | 16 bits | Счётчик тактов: 14-битный счётчик, увеличивающийся при изменении временной метки назад. |
| clock_seq_hi_res | 8 bits | Маркер варианта: 2 бита, установленные в <code>10</code>, идентифицирующие UUID как RFC 4122. |
| clock_seq_low | 8 bits | Узел: 48-битный MAC-адрес. Если недоступен, используется случайное значение с установленным bitом multicast. |
| node | 48 bits | Комбинация временной метки и узла гарантирует уникальность без центральной координации. |
Поле последовательности часов (clock_seq_hi_res + clock_seq_low) — это 14-битный счётчик. Он увеличивается каждый раз, когда системные часы идут назад (например, корректировка NTP) или когда система перезапускается без сохранения последнего известного timestamp. Это предотвращает генерацию дублирующихся UUID, если часы не являются монотонно возрастающими.
Как кодируется временная метка
60-битная временная метка UUID v1 — не стандартная Unix-временная метка:
- Начальная точка — Gregorian-эпоха: 15 октября 1582 г. 00:00:00.00 UTC.
- Подсчитывается количество 100-наносекундных интервалов с этой даты.
- Это даёт 60-битное целое число.
- Целое разделяется: 32 младших бита → time_low, следующие 16 → time_mid, верхние 12 → time_hi.
- Четыре бита версии вставляются в time_hi_and_version.
- Для извлечения: объедините time_hi, time_mid и time_low в 60-битное целое; вычтите смещение Gregorian-эпохи (122192928000000000 × 100нс) для получения Unix-времени.
- Разрешение 100нс позволяет одному генератору производить до 10 миллионов UUID в секунду.
UUID v7 использует Unix-временные метки в миллисекундах — значительно проще для декодирования.
Соображения конфиденциальности
UUID v1 содержит MAC-адрес хоста в 48-битном поле узла, что создаёт проблемы конфиденциальности:
MAC-адрес постоянен и уникален. UUID v1 однозначно идентифицирует хост-машину. Два UUID v1 с одной машины имеют одинаковое поле узла.
Многие реализации заменяют реальный MAC случайным 48-битным значением с установленным bitом multicast для обозначения случайности.
Сценарии использования
UUID v1 против UUID v7
Сравнение UUID v1 с UUID v7 для нового проекта:
| Аспект | UUID v1 | UUID v7 |
|---|---|---|
| Эпоха временной метки | 15 октября 1582 (Gregorian) | 1 января 1970 (Unix) |
| Точность временной метки | 100 наносекунд | Миллисекунды |
| Лексикографическая сортировка | Нет (поля переставлены) | Да |
| Конфиденциальность | Раскрывает MAC-адрес | Полностью случайный узел |
| Стандарт | RFC 4122 (2005), устаревший | RFC 9562 (2024) |
| Производительность индексов | Плохая (несортируемые вставки) | Отличная (монотонные вставки) |
Для новых проектов используйте UUID v7 — строгое улучшение по отношению к v1.
Примеры кода
Node.js не включает генератор UUID v1 в стандартную библиотеку. Используйте пакет <code>uuid</code>:
// Generate a UUID v1 using the Web Crypto API
function generateUuidV1() {
const buf = new Uint8Array(16)
crypto.getRandomValues(buf)
const ms = BigInt(Date.now())
const gregorianOffset = 122192928000000000n
const t = ms * 10000n + gregorianOffset
const tLow = Number(t & 0xFFFFFFFFn)
const tMid = Number((t >> 32n) & 0xFFFFn)
const tHiVer = Number((t >> 48n) & 0x0FFFn) | 0x1000 // version 1
const clockSeq = (buf[8] & 0x3F) | 0x80 // variant 10xxxxxx
const clockSeqLow = buf[9]
const hex = (n, pad) => n.toString(16).padStart(pad, '0')
const node = [...buf.slice(10)].map(b => b.toString(16).padStart(2, '0')).join('')
return `${hex(tLow,8)}-${hex(tMid,4)}-${hex(tHiVer,4)}-${hex(clockSeq,2)}${hex(clockSeqLow,2)}-${node}`
}
// Extract the embedded timestamp from a UUID v1
function extractTimestamp(uuid) {
const parts = uuid.split('-')
const tHex = parts[2].slice(1) + parts[1] + parts[0]
const t = BigInt('0x' + tHex)
const ms = (t - 122192928000000000n) / 10000n
return new Date(Number(ms))
}
const id = generateUuidV1()
console.log(id) // e.g. "1eb5e8b0-6b4d-11ee-9c45-a1f2b3c4d5e6"
console.log(extractTimestamp(id)) // e.g. 2023-10-15T12:34:56.789Zimport uuid from datetime import datetime, timezone # Generate UUID v1 (uses MAC address by default) uid = uuid.uuid1() print(uid) # Extract embedded timestamp # uuid.time is 100-ns intervals since Oct 15, 1582 GREGORIAN_OFFSET = 122192928000000000 # 100-ns intervals ts_100ns = uid.time ts_ms = (ts_100ns - GREGORIAN_OFFSET) // 10000 dt = datetime.fromtimestamp(ts_ms / 1000, tz=timezone.utc) print(dt.isoformat()) # e.g. "2023-10-15T12:34:56.789000+00:00"
package main
import (
"fmt"
"time"
"github.com/google/uuid" // go get github.com/google/uuid
)
func main() {
id, _ := uuid.NewUUID() // UUID v1
fmt.Println(id)
// Extract timestamp from UUID v1
// uuid.Time is 100-ns ticks since Oct 15, 1582
t := id.Time()
sec := int64(t)/1e7 - 12219292800 // convert to Unix seconds
nsec := (int64(t) % 1e7) * 100
ts := time.Unix(sec, nsec).UTC()
fmt.Println(ts.Format(time.RFC3339Nano))
}