UUID, ULID en ObjectId: wat er in de bits van een id staat
Een id van 36 tekens lijkt een willekeurig rijtje hex, maar een UUID is een formulier: RFC 9562 wijst zes van de 128 bits aan voor versie en variant, en bij de tijdvormen staat er ook een klok in. Met UUID en ULID lees je die velden bit voor bit in je eigen browser — versie, variant, tijd, clock-seq, node-adres, machine-id, teller en de random-velden — en weigert alles wat geen vorm is met de reden erbij.
Naast UUID (versies 1 tot en met 7, nil en max) leest de tool ULID: 26 tekens Crockford base32, waarvan de eerste tien een 48-bits millisecond-klok dragen en de overige zestien random zijn. Twee ULID’s in één plaksel beantwoorden daarmee de monotone-vraag: welke id staat er ná welke, en bepaalde de tijd dat of de random.
Dit artikel leest wat die bits betekenen en waar ze ophouden betekenis te hebben: waarom random-bits geen random-heid bewijzen, waarom de tijd in een id niet de tijd van de gebeurtenis is, en waarom een UUIDv1 meer over je machine prijsgeeft dan je intentie was. De generators van de tool bouwen er zelf bij: UUID v4 en v7, ULID en ObjectId, uit de CSPRNG van je browser.
1. Honderdachtenentwintig bits met zes bits afspraak
Een UUID is 8-4-4-4-12 hex-tekens: 128 bits, waarvan RFC 9562 er zes vast zet. De bovenste vier bits van byte 6 dragen het versienibble, de bovenste twee bits van byte 8 de variant — en die variant zegt in welke familie de id thuishoort: 10xx is RFC 9562 zelf, 0xxx het oude NCS, 110x Microsoft, 111x gereserveerd. De overige 122 bits zijn precies wat de versie er in zet: bij v4 en v7 random, bij v1 en v6 tijd en een node-adres, bij v3 en v5 de hash van een naam. Buiten die zes bits draagt een UUID geen afspraak: geen checksum, geen herkomst, geen eigendom.
Twee id’s hebben een eigen naam in plaats van een versie: de nil-UUID, elk bit nul, en de max-UUID, elk bit 1 — beide formeel benoemd in RFC 9562 en dus geen versiefout. Een versienibble dat RFC 9562 niet kent (8, 9, A tot en met F), is wél leesbaar als vorm maar blijft zonder betekenis: de anatomie leest hem als onbekend, niet als fout en niet als iets wat ze verzint.
2. De tijd in een id — en wíeks klok het is
UUIDv1 timestep in 60 bits: 100-nanoseconden sedert 15 oktober 1582, de Gregoriaanse herkalendring, uitgesmeerd over time_low, time_mid en time_hi — en de tool rekent die klok terug tot op de Unix-millisecond. UUIDv6 is dezelfde klok, herschikt zodat sorteren op hex volgorde op tijd volgt; v7 laat de Gregoriaanse ring staan en zet in de bovenste 48 bits gewone Unix-tijd in milliseconden. ULID doet dat laatste in base32: 48 bits, met een plafond dat in het jaar +10889 ligt — een ULID die daarvoor een timestamp-teken nodig heeft, bestaat niet. Een ObjectId ten slotte draagt slechts seconden, in de eerste acht hex-tekens.
Waar de tijd staat, staat de tijd van het id — niet van de gebeurtenis. Een rij die morgen wordt gemaakt, kan vandaag al een id hebben gekregen of uit een ouder bestand zijn meegenomen; uit de bits is dat niet te scheiden. En de tijdvormen zijn niet anoniem: v1 en v6 dragen er een clock-seq en het node-adres bij, in de praktijk het MAC-adres van de machine die de id bouwde — persoonlijk of bedrijfsgebonden, en uit de id zelf niet aan een naam te koppelen.
3. Crockford base32 en de monotone-vraag
Een ULID is 26 tekens uit Crockford base32: 32 tekens van het alfabet zonder de vier die verward worden met andere — I, L, O en U — en dus verboden in een id. Een geplakte ULID met een I is geen id en wordt niet half gelezen: de anatomie noemt de positie en de letter die de encoder wél bedoelde, want Crockford zelf legt die suggesties vast (I en L naar 1, O naar 0, U naar V). Lowercase is gewoon dezelfde id: de lezing uppercasest voor ze oordeelt.
Omdat de tien timestamp-tekens vooraan staan en base32 gewone tekensortering volgt, is de lexicografische volgorde van ULID’s hun tijdsvolgorde — zolang de id’s niet in dezelfde millisecond zijn gebouwd. De tool beantwoordt die vraag per paar: de tweede id staat ná, vóór of is identiek aan de eerste, en bij gelijke millisecond zegt de kaart erbij dat de random-tekens de volgorde bepaalden. Over drie of meer id’s of over hoe een database sorteert, zwijgt de tool — dat is niet wat een paar bits dragen.
4. Wat geen enkele id over zichzelf zegt
Random-velden bewijzen geen random-heid. Een UUIDv4, ULID of ObjectId heeft random-bits — 122, 80 of 64 van de — maar of die uit een goede bron komen, is uit de id zelf niet af te lezen: een id uit een voorspelbare bron ziet er identiek uit. Een id die door een bug twee keer werd gebouwd, draagt geen spoor van die bug. Daarom bouwen de generators van de tool uit crypto.getRandomValues en zeggen ze dat erbij; de herkomst van id’s die je uit een systeem kopiëert, laten we ongeclaimd.
Begin bij de meetkant: UUID en ULID voor de anatomie en de generators, Beveiligingsheaders voor één plek waar zo’n random id echt onvervangbaar moet zijn (de CSP-nonce, die niet voorspelbaar mag zijn), en E-mailkoppen lezen voor id’s in het wild: Message-IDs en selector-vingerafdrukken, waar dezelfde vraag — wat zegt deze id, en wat niet — over de hele kop terugkomt.