Ha valaha UUID-t használtál elsődleges kulcsként egy adatbázistáblában, valószínűleg gen_random_uuid()-t vagy crypto.randomUUID()-t hívtál, ami egy teljesen véletlenszerű UUIDv4-et ad vissza. Kényelmes, ütközésmentes, elosztottan generálható – és pont ez a véletlenszerűség az, ami tönkreteszi az adatbázis írási teljesítményét. 2024 májusában megjelent egy frissített UUID-specifikáció, ami erre ad natív megoldást, és mostanra a legfontosabb eszközök – Node.js, PostgreSQL – is leszállítják a támogatását.

Miért fáj egy random kulcs a B-fának
Egy relációs adatbázis az elsődleges kulcs indexét (jellemzően B-fát) a beszúrt értékek sorrendjében tartja karban. Ha a kulcs egy auto-increment szám, minden új sor a fa jobb szélére kerül – ez a legolcsóbb művelet, amit egy B-fa ismer. Ha viszont a kulcs egy egyenletesen véletlenszerű UUIDv4, minden beszúrás egy véletlen helyre kerül a fában. Ez azt jelenti, hogy a tábla méretének növekedésével egyre nagyobb eséllyel kell egy olyan indexlapot betölteni a memóriába (vagy lemezről), ami éppen nincs a cache-ben – az pedig lapfelosztásokhoz, extra write-ahead log (WAL) forgalomhoz és romló cache hit rate-hez vezet.
Ez nem elméleti probléma. Tomas Vondra 2018-as, sokat idézett mérésében azt mérte, hogy nagy, memóriát meghaladó méretű táblánál a random UUID kulcs a szekvenciális generátorokhoz képest kb. 20%-os áteresztőképességre esik vissza, a WAL-forgalom 20 GB-ról 2,5 GB-ra csökken sorrendtartó kulcsokkal, a cache hit ratio pedig 85%-ról közel 99%-ra javul. A mechanizmus – B-fa, lapfelosztás, WAL – azóta nem változott, ezért ezek a számok ma is jól illusztrálják, mekkora az ára a „csak legyen egyedi” típusú kulcsválasztásnak.
Mi az az UUIDv7
Az RFC 9562 (2024 május, ez váltotta le a régi RFC 4122-t) definiálja a 6-os, 7-es és 8-as UUID-verziókat. Az UUIDv7 128 bitje így oszlik meg:
- 48 bit – Unix-időbélyeg ezredmásodpercben, big-endian sorrendben
- 4 bit – verziómező, fix érték:
0111 - 12 bit –
rand_a, véletlen adat (vagy finomabb időfelbontás/számláló, lásd lentebb) - 2 bit – variánsmező, fix érték:
10 - 62 bit –
rand_b, további véletlen adat
A lényeg: az időbélyeg a legfelső biteken van, tehát az UUID-k szövegesen és bájtonként is nagyjából időrendi sorrendbe rendeződnek – pont úgy viselkednek a B-fában, mint egy auto-increment szám, miközben megmarad az az előny, hogy bármelyik kliens, szerver vagy háttérfolyamat központi koordináció nélkül tud generálni egyet.
Kipróbálva
A specifikáció annyira egyszerű, hogy pár sorban végig lehet írni – ez jól megmutatja, mi történik a kulisszák mögött (Node.js-ben futtatva, beépített node:crypto modullal):
const crypto = require('node:crypto');
function uuidv7() {
const bytes = crypto.randomBytes(16);
const ms = BigInt(Date.now());
// bit 0-47: unix_ts_ms
for (let i = 0; i < 6; i++) {
bytes[i] = Number((ms >> BigInt((5 - i) * 8)) & 0xffn);
}
// bit 48-51: verzió (0111), bit 52-63: rand_a (marad véletlen)
bytes[6] = 0x70 | (bytes[6] & 0x0f);
// bit 64-65: variáns (10), bit 66-127: rand_b (marad véletlen)
bytes[8] = 0x80 | (bytes[8] & 0x3f);
const hex = bytes.toString('hex');
return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`;
}
function extractTimestamp(uuid) {
const hex = uuid.replace(/-/g, '').slice(0, 12);
return new Date(parseInt(hex, 16));
}
console.log(uuidv7());
// pl. 019fe28a-e717-76a3-a5ec-937aa01c9291
console.log(extractTimestamp('019fe28a-e717-76a3-a5ec-937aa01c9291').toISOString());
// 2026-08-08T18:03:01.783Z
Az extractTimestamp function működését a PostgreSQL 18 dokumentációjának saját példa-UUID-jén is leellenőriztem: a 019535d9-3df7-79fb-b466-fa907fa17f9e első 12 hex karakteréből (019535d93df7) számolva pontosan 2025-02-24T02:46:24.503Z jön ki, ami a doksiban feltüntetett 2025-02-23 21:46:24.503-05 időponttal (UTC−5 zónaidő) egyezik. Vagyis az időbélyeg tényleg egyszerű bináris kivágással visszafejthető – ez nem mellékhatás, hanem szándékos tervezési döntés.
A monotonitás nem ingyenes
Van egy csapda, amibe könnyű belefutni. Ha a fenti generátorral egymás után, ugyanabban az ezredmásodpercben többször kérünk UUID-t, a sorrendjük nem garantált, mert a rand_a és rand_b mezők tisztán véletlenek:
const ids = Array.from({ length: 5 }, () => uuidv7());
const sorted = [...ids].sort();
console.log(JSON.stringify(ids) === JSON.stringify(sorted));
// false – ugyanazon az ezredmásodpercen belül nem stabil a sorrend
Ez pontosan megfelel annak, amit a Node.js dokumentációja is leír a saját, natív crypto.randomUUIDv7() implementációjáról (Node.js 26.1.0-tól elérhető): „a beágyazott időbélyeg nem monoton órán alapul, és nem garantált, hogy szigorúan növekvő legyen”. Az RFC emiatt megenged három különböző módszert a monotonitás javítására – a legegyszerűbb egy számláló beépítése a rand_a mezőbe. A PostgreSQL 18 saját uuidv7() függvénye pont ezt teszi: a 12 bites rand_a helyén egy ezredmásodperc-alatti finomabb időbélyeget tárol, így egy munkamenetben garantáltan növekvő sorozatot ad – ezért a gyakorlatban Postgres alatt jobb sorrendtartást kapunk, mint egy naiv, csak véletlen adatot használó implementációtól.
Natív támogatás mindenhol
A fenti kézzel írt kód főleg oktatási célra jó – éles kódban nincs értelme újraimplementálni, amit a platform már ad:
- Node.js 26.1.0+:
crypto.randomUUIDv7([options]),disableEntropyCacheopcióval, ha nem szeretnénk a beépített 128-as UUID-előgenerálási cache-t használni. - PostgreSQL 18 (2025 szeptemberében jelent meg):
uuidv7()a generáláshoz,uuid_extract_timestamp()ésuuid_extract_version()a visszafejtéshez – közvetlenül SQL-ből. - Régebbi Node-verzión vagy böngészőben a népszerű
uuidnpm csomagv7()függvénye ugyanezt adja userlandból.
Fontos: a natív crypto.randomUUID() (a v4-es verzió) böngészőben és Node-ban is évek óta elérhető, de az UUIDv7 egyelőre nem szabvány Web API – a böngészőben egyelőre a uuid csomagra kell hagyatkozni.
Amit elveszítünk: az időbélyeg kiszivárog
Az UUIDv7 pont attól hasznos, hogy elárulja a keletkezés időpontját – ez viszont adatvédelmi szempontból nem mindegy. Az RFC 9562 biztonsági megfontolásokról szóló szakasza kifejezetten kimondja: ha egy alkalmazásban az UUID-t bármilyen biztonsági művelethez használjuk, akkor UUIDv4-et kell választani, mert az időbélyeg (számlálóval kombinálva) elárulja a rekordok létrehozási sorrendjét. Gyakorlati példa: ha a felhasználói fiók ID-ja UUIDv7, egy támadó két ID összehasonlításából megbecsülheti, melyik fiók jött létre korábban, vagy durván beazonosíthatja a regisztráció időpontját. Meghívó tokeneknél, jelszó-visszaállító linkeknél, session azonosítóknál ez elég jó indok arra, hogy maradjunk a random v4-nél, és az UUIDv7-et csak ott vezessük be, ahol a rendezhetőség ér többet, mint a keletkezési idő elrejtése – tipikusan: elsődleges kulcsok, log- és eseményazonosítók, elosztottan generált rekordok.
Mikor éri meg
Meglévő, UUIDv4 kulcsokkal teli táblát nem lehet utólag „időrendbe rakni” – a régi sorok kulcsai megmaradnak véletlenszerűnek, így a migráció gyakorlatilag egy új tábla feltöltését jelenti. Új projekteknél viszont, ahol amúgy is UUID mellett döntenél elosztott generálhatóság miatt, az UUIDv7 szinte ingyen ad cserébe jobb index-lokalitást és rendezhetőséget – a fő ár mindössze az, hogy tudatosan kell dönteni arról, hol számít a keletkezési idő elrejtése.