Generator UUID v7
Generuje posortowane czasowo UUID v7 dla kluczy głównych bazy danych
…
Formatuj
Czym jest UUID v7?
UUID v7 to format UUID nowej generacji standaryzowany w RFC 9562 (maj 2024). Koduje 48-bitowy uniksowy znacznik czasu w milisekundach w bitach o największym znaczeniu, a następnie znaczniki wersji i wariantu, a pozostałe bity wypełnia kryptograficznie bezpiecznymi losowymi danymi.
Ponieważ znacznik czasu zajmuje wysokie bity, wartości UUID v7 sortują się chronologicznie — zarówno leksykograficznie, jak i numerycznie. Dzięki temu doskonale nadają się do kluczy głównych bazy danych, gdzie losowe UUID (v4) powodują fragmentację indeksu B-tree.
Dlaczego losowe UUID fragmentują indeksy bazy danych
Indeksy B-tree — używane przez PostgreSQL, MySQL, SQLite i większość innych baz danych — przechowują wiersze posortowane według wartości klucza. Podczas wstawiania nowego wiersza baza danych musi umieścić go na właściwej posortowanej pozycji w indeksie.
W przypadku UUID v4 (w pełni losowego) każde nowe wstawianie trafia do zasadniczo losowej pozycji w drzewie indeksów. Zmusza to bazę danych do ciągłego odczytywania i przepisywania wewnętrznych stron indeksu, dzielenia pełnych stron i pozostawiania innych do połowy pustych. Wynikiem jest pofragmentowany, nadmiernie rozrośnięty indeks, który spowalnia zarówno zapisy, jak i odczyty w miarę wzrostu tabeli.
Układ bitów UUID v7
UUID v7 ma 128 bitów szerokości, podzielonych na sześć pól:
| Bity | Pole | Cel |
|---|---|---|
| 48 | unix_ts_ms | 48-bitowy uniksowy znacznik czasu w milisekundach — zajmuje całą górną połowę |
| 4 | ver | Numer wersji — zawsze 0111 (dziesiętnie 7) |
| 12 | rand_a | 12 bitów kryptograficznie bezpiecznych losowych danych |
| 2 | var | Znacznik wariantu — zawsze 10 (wariant RFC 4122) |
| 62 | rand_b | 62 bity kryptograficznie bezpiecznych losowych danych |
Precyzja znacznika czasu wynosi 1 milisekundę. W obrębie tej samej milisekundy wartości UUID v7 są uporządkowane według losowego sufiksu — nie ma gwarancji monotonicznego wzrostu poniżej milisekundy, ale są k-sortowalne: identyfikatory generowane blisko siebie w czasie będą sortowały się blisko siebie w indeksie.
UUID v7 a UUID v1
Zarówno UUID v1, jak i UUID v7 zawierają znacznik czasu, ale znacznie różnią się projektem:
| Cecha | UUID v7 | UUID v1 |
|---|---|---|
| Epoka / Podstawa czasu | Epoka uniksowa (1 stycznia 1970) | Epoka gregoriańska (15 października 1582) |
| Precyzja czasu | 1 milisekunda | 100 nanosekund |
| Sortowalny | Tak — k-sortowalny z założenia | Nie — pola czasu są pomieszane w układzie UUID |
| Prywatność | Brak wycieków informacji o hoście | Zawiera adres MAC generującego hosta |
| Wydajność indeksu bazy danych | Doskonała — sekwencyjne wstawiania, minimalna fragmentacja | Słaba — niesequencyjne mimo znacznika czasu |
| Standard | RFC 9562 (2024) | RFC 4122 (2005) |
| Natywna obsługa przeglądarki | Jeszcze nie (brak crypto.randomUUID v7) | Niedostępne natywnie |
W przypadku każdego nowego projektu wymagającego UUID posortowanych według czasu, preferuj UUID v7 nad UUID v1. UUID v1 jest przestarzały i ujawnia informacje o hoście.
UUID v7 a ULID
ULID (Universally Unique Lexicographically Sortable Identifier) rozwiązuje podobny problem co UUID v7. Oto porównanie:
- Zgodny ze standardem UUID RFC 9562 — kompatybilny ze wszystkimi narzędziami UUID
- Format szesnastkowy z myślnikami — powszechnie rozpoznawany
- Natywna obsługa kolumn UUID w bazach danych
- 128 bitów łącznie
- Kodowanie Crockford Base32 — 26 znaków, nieco bardziej kompaktowe
- Bez rozróżniania wielkości liter, bez niejednoznacznych znaków (I, L, O, U)
- Bardziej czytelny dla człowieka na pierwszy rzut oka
- Wymaga biblioteki — brak natywnej obsługi platformy
Jeśli jesteś już w ekosystemie UUID (kolumna uuid PostgreSQL, REST API zwracające UUID), użyj UUID v7. Jeśli zaczynasz od nowa i wolisz bardziej przyjazne dla człowieka kodowanie, ULID jest rozsądną alternatywą.
Używanie UUID v7 w bazach danych
UUID v7 nie jest jeszcze natywnie generowany przez większość baz danych, ale może być przechowywany w standardowych kolumnach UUID i generowany w kodzie aplikacji lub przez rozszerzenia:
uuid. Rozszerzenie pg-uuidv7 dodaje funkcję po stronie serwera uuid_generate_v7(), jeśli potrzebujesz identyfikatorów generowanych przez bazę danych.BINARY(16) lub CHAR(36). Generuj w kodzie aplikacji. MySQL 8.0+ obsługuje posortowane UUID za pomocą UUID_TO_BIN(UUID(), 1) dla v1, ale v7 wymaga generowania na poziomie aplikacji.TEXT (36 znaków) lub BLOB (16 bajtów). Generuj w kodzie aplikacji. Leksykograficzne sortowanie na TEXT działa poprawnie, ponieważ UUID v7 używa prefiksu znacznika czasu o stałej szerokości.Przykłady kodu
UUID v7 nie jest jeszcze dostępny przez crypto.randomUUID(). Używaj biblioteki takiej jak uuidv7 (npm) do czasu pojawienia się natywnej obsługi:
function generateUuidV7() {
const buf = new Uint8Array(16)
crypto.getRandomValues(buf)
const ms = BigInt(Date.now())
// Embed 48-bit Unix ms timestamp
buf[0] = Number((ms >> 40n) & 0xFFn)
buf[1] = Number((ms >> 32n) & 0xFFn)
buf[2] = Number((ms >> 24n) & 0xFFn)
buf[3] = Number((ms >> 16n) & 0xFFn)
buf[4] = Number((ms >> 8n) & 0xFFn)
buf[5] = Number(ms & 0xFFn)
// Set version 7 (0111xxxx)
buf[6] = (buf[6] & 0x0F) | 0x70
// Set variant (10xxxxxx)
buf[8] = (buf[8] & 0x3F) | 0x80
const hex = [...buf].map(b => b.toString(16).padStart(2, '0')).join('')
return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`
}
// Node.js 20+ built-in
// import { randomUUID } from 'node:crypto' // v4 only — no v7 yet in stdlib# pip install uuid7 import uuid_extensions uid = uuid_extensions.uuid7() print(uid) # e.g. 018e2b3d-1a2b-7000-8000-abc123456789 print(uid.time) # Unix ms timestamp embedded in the UUID # Or as a plain string from uuid_extensions import uuid7str print(uuid7str())
-- PostgreSQL 13+ extension-free implementation
CREATE OR REPLACE FUNCTION uuid_generate_v7()
RETURNS uuid
LANGUAGE sql
AS $$
SELECT encode(
set_bit(
set_bit(
overlay(
uuid_send(gen_random_uuid())
PLACING substring(int8send(floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint) FROM 3)
FROM 1 FOR 6
),
52, 1
),
53, 1
),
'hex'
)::uuid;
$$;
-- Usage as a default primary key
CREATE TABLE events (
id uuid PRIMARY KEY DEFAULT uuid_generate_v7(),
payload jsonb,
created_at timestamptz DEFAULT now()
);function extractTimestamp(uuid: string): Date {
const hex = uuid.replace(/-/g, '')
const ms = parseInt(hex.slice(0, 12), 16) // first 48 bits = ms timestamp
return new Date(ms)
}
const uid = '018e2b3d-1a2b-7000-8000-abc123456789'
console.log(extractTimestamp(uid).toISOString())
// → "2024-03-15T10:22:05.259Z"