Test e QA · 6 min di lettura

UUID v4 vs v7 nel 2026: layout dei bit, ordinamento, costo dell'indice

UUID v4 vs v7 spiegati dai loro bit: 122 bit casuali contro un timestamp di 48 bit in millisecondi, comportamento dell'ordinamento, costo dell'indice B-tree, privacy e dimensione della codifica.

Di Charlie Simmon ·

UUID v4 e UUID v7 sono entrambi identificatori a 128 bit, e la somiglianza finisce qui. v4 spende 122 bit in casualità. v7 mette davanti un timestamp Unix di 48 bit in millisecondi, poi 4 bit di versione, 74 bit casuali e 2 bit di variante. Il numero di versione non è una classifica. È una dichiarazione su cosa l'identificatore può rivelare e su come si ordina.

Entrambi sono definiti nella RFC 9562, che nel 2024 ha sostituito la RFC 4122. La scelta pratica si riduce a tre domande: l'identificatore deve ordinarsi per data di creazione, finirà in una chiave primaria B-tree, ed è accettabile che la data di creazione sia leggibile da chiunque possieda il valore. Questa guida risponde con il layout dei byte anziché con una regola empirica.

Il layout a 128 bit, a confronto

Ogni UUID è di 16 byte, e i campi sono definiti per posizione, non dai trattini del testo. Il byte 6 contiene la versione nel suo nibble alto. Il byte 8 contiene la variante nei suoi due bit più alti. Tutto il resto dipende dalla versione. Nella stringa canonica, il 13° carattere esadecimale è la versione e il 17° è la variante.

Per v4, i 122 bit che non sono versione o variante sono casuali. Per v7, i primi 48 bit sono il timestamp Unix in millisecondi, big-endian, ed è per questo che i valori v7 si ordinano per data di creazione come byte grezzi. I restanti 74 bit sono casuali per impostazione predefinita, anche se la RFC 9562 consente ad alcuni di essi di portare un contatore per la monotonicità all'interno di un millisecondo.

CampoBitv4v7
Byte 0-548Casualiunix_ts_ms, big-endian
Nibble alto del byte 640100 (versione 4)0111 (versione 7)
Nibble basso del byte 6 e byte 712Casualirand_a
Bit alti del byte 82variante 10xvariante 10x
Byte 8-15 rimanenti62Casualirand_b
Totale bit casuali12212274

v4 in dettaglio

Un UUID v4 è un numero casuale con sei bit fissi. Il nibble alto del byte 6 è 0100, quindi il 13° carattere della stringa canonica è sempre 4. I due bit più alti del byte 8 sono 10, quindi il 17° carattere è sempre 8, 9, a o b. Questi sono gli unici caratteri prevedibili; gli altri 30 caratteri esadecimali sono casuali.

La casualità è la proprietà di sicurezza. Un valore v4 non porta timestamp, sequenza né identificatore di macchina, quindi non rivela nulla su quando o dove è stato creato. Questo lo rende una scelta ragionevole per un identificatore pubblico che non deve essere enumerabile, a condizione che sia generato da una sorgente casuale crittograficamente sicura. Uno spazio di 122 bit rende le collisioni trascurabili: un miliardo di valori dà una probabilità di collisione di circa 2^-63.

  • Nibble di versione: sempre 4 alla posizione 13 della stringa canonica
  • Variante: sempre 8, 9, a o b alla posizione 17
  • Bit casuali: 122, da un CSPRNG
  • Non si ordina per nulla di significativo; l'ordine è casuale
  • Non rivela data di creazione né sequenza

v7 in dettaglio

Un UUID v7 è ordinato nel tempo. I byte da 0 a 5 contengono il numero di millisecondi dall'epoca Unix, big-endian. Il nibble alto del byte 6 è 0111. Il nibble basso del byte 6 e tutto il byte 7 formano rand_a, i due bit più alti del byte 8 sono la variante, e i restanti 62 bit sono rand_b. Sono 74 bit casuali più un timestamp leggibile a colpo d'occhio.

Il timestamp è di 48 bit, sufficienti per i millisecondi fino all'anno 10889, quindi l'overflow non è un problema pratico. Due valori v7 creati nello stesso millisecondo differiscono solo nei loro bit casuali o di contatore, e un valore creato dopo si confronta sempre come maggiore, come stringa di byte, rispetto a uno creato prima, purché l'orologio non torni indietro. Quell'ordinamento è tutto il punto.

ByteContenutoEsempio
0-5Millisecondi Unix, big-endian0192f3c4a1b2
Nibble alto del byte 6Versione 77
Nibble basso del byte 6 + 7rand_a, 12 bita1b
2 bit alti del byte 8Variante 108, 9, a o b
Byte 8 (6 bassi) a 15rand_b, 62 bitCasuali o contatore

Ordinamento e monotonicità all'interno di un millisecondo

L'ordinamento lessicografico del testo canonico corrisponde all'ordine dei byte, perché i trattini stanno in posizioni fisse e le cifre esadecimali si ordinano nello stesso ordine dei loro valori quando sono minuscole. Ordina per la forma testuale o per i 16 byte e ottieni l'ordine di creazione. Questo vale tra macchine senza coordinamento, ed è per questo che v7 è attraente per i log degli eventi e per gli identificatori che diventano parte di una chiave di ordinamento.

Il divario è all'interno di un singolo millisecondo. Per impostazione predefinita, due valori creati nello stesso millisecondo sono ordinati dai loro bit casuali, che non è l'ordine di creazione. La RFC 9562 descrive l'uso del campo rand_a, o dei bit casuali più a sinistra, come contatore inizializzato casualmente a ogni scatto del timestamp. Con quel metodo, i valori sono monotoni ai fini pratici all'interno di un millisecondo, al costo di un po' meno casualità e di un contatore condiviso che i produttori concorrenti devono gestire con attenzione.

  • Tra millisecondi: l'ordinamento è garantito finché l'orologio è monotono
  • All'interno di un millisecondo: ordine casuale a meno che non si usi un contatore
  • Metodo del contatore: inizializza casualmente a ogni scatto, incrementa per valore
  • Rollback dell'orologio: può rompere l'ordinamento, quindi usa una sorgente di orologio monotona
  • Concorrenza: incrementa il contatore in modo atomico o perdi la proprietà

Costo dell'indice in un B-tree

Una chiave primaria v4 casuale si inserisce in una posizione casuale dell'indice. Su un B-tree questo significa toccare una pagina foglia diversa quasi a ogni inserimento, espellere pagine dal buffer pool e dividere le pagine a metà, lasciandole semivuote. L'indice cresce, la write amplification aumenta e i tassi di cache hit calano man mano che la tabella cresce.

Una chiave v7 si inserisce vicino al bordo destro, quindi il working set resta piccolo e le divisioni di pagina avvengono alla fine, dove costano poco. Il compromesso è la contesa: ogni scrittore vuole la stessa pagina più a destra. Su un singolo database di solito va bene con qualche centinaio di scritture al secondo; a concorrenza molto alta diventa un hotspot, e le mitigazioni abituali sono il batching, l'interleaving a livello di connessione o un po' di jitter nei bit alti.

ProprietàChiave v4 casualeChiave v7 ordinata nel tempo
Posizione di inserimentoPagina foglia casualePagina foglia più a destra
Divisioni di paginaFrequenti, a metà paginaRare, alla fine
Località del buffer poolScarsa su indici grandiBuona
Dimensione dell'indiceMaggiore, fattore di riempimento più bassoCompatta
Hotspot di scritturaDistribuito nell'alberoConcentrato sul bordo destro
Scansione per intervallo di tempoRichiede una colonna separataNativa

Privacy e fuga del timestamp

Un valore v7 dice a chiunque lo possieda all'incirca quando è stato creato, al millisecondo, e quindi quanti altri record sono stati creati in quel periodo. Va bene per chiavi interne e id di eventi. Non va bene per un identificatore pubblico che non deve rivelare il volume di iscrizioni, il tasso di ordini o l'età di un account.

Se hai bisogno sia di ordinamento che di opacità, le opzioni sono tenere v7 interno ed esporre un id pubblico casuale separato, oppure usare v4 per qualsiasi cosa che esca dal tuo sistema. Non trattare un UUID come token di capacità in nessuna delle due versioni; entrambi sono identificatori, non autorizzazioni. Un id v4 è impraticabile da indovinare, ma va comunque verificato contro il controllo degli accessi a ogni richiesta.

  • v7 rivela la data di creazione al millisecondo
  • Enumerare valori v7 vicini rivela il volume dei record
  • Tieni v7 interno, esponi v4 o un id pubblico separato
  • Nessuna delle due versioni è un token di accesso o un segreto
  • Un v7 randomizzato non esiste; il timestamp è obbligatorio

Dimensione di archiviazione e codifica

Sedici byte è la risposta di archiviazione per entrambe le versioni. La forma testuale canonica è di 36 caratteri, di cui 32 cifre esadecimali e 4 trattini. Archiviare la forma testuale raddoppia la dimensione e rallenta i confronti, quindi usa un tipo UUID nativo dove esiste, BINARY(16) dove non esiste, e formatta solo al confine.

SQL Server ha una particolarità che vale la pena testare: il suo tipo uniqueidentifier si ordina in un ordine di byte diverso dalla stringa canonica, e l'ordine interno dei byte differisce dalla RFC 4122. Il codice che si basa sull'ordinamento v7 deve verificare il comportamento dell'ordinamento su quella piattaforma invece di darlo per scontato. Dove serve una forma testuale compatta, base64url senza padding dà 22 caratteri, ma non è una rappresentazione UUID standard e confonderà gli strumenti che si aspettano la forma canonica.

RappresentazioneDimensioneNote
Binaria16 byteArchiviazione nativa, confronto più veloce
Testo canonico36 caratteri8-4-4-4-12 con trattini
Esadecimale senza trattini32 caratteriUsato in alcune API e log
Base64url, senza padding22 caratteriNon è una forma UUID standard
Base3226 caratteriRaro, usato da alcuni sistemi di ticketing

Scegliere, migrare e testare

Scegli v7 quando l'identificatore è una chiave di database, quando vuoi ordinare per data di creazione senza una seconda colonna o quando stai costruendo una tabella a prevalenza di append. Scegli v4 quando il valore è esposto pubblicamente, quando la data di creazione è sensibile o quando il sistema archivia già una colonna timestamp e non guadagna nulla dall'ordinamento. Entrambe sono scelte corrette per vincoli diversi, e mescolarle in una tabella è di solito un errore.

I test devono verificare la struttura, non i valori. Controlla il nibble di versione, il nibble di variante e la lunghezza in byte dopo il parsing. Per v7, verifica che il timestamp incorporato cada nella finestra del test e che due valori creati in ordine si confrontino correttamente. Non fare mai snapshot di un UUID letterale in un test, e non verificare mai che un v7 sia strettamente maggiore del precedente a meno che il generatore non usi un contatore; senza contatore, l'ordine nello stesso millisecondo è casuale.

SituazionePreferisciMotivo
Chiave primaria su una tabella grandev7Località e indice più piccolo
Identificatore rivolto al pubblicov4Nessuna divulgazione del timestamp
Log degli eventi ordinato per tempov7Ordinamento nativo senza una colonna
Token di sessione o di resetNessuno dei dueUsa un token casuale creato allo scopo
Tabella esistente con una colonna temporalev4Ordinamento già disponibile
Scambio tra sistemiUno dei dueEntrambi sono UUID standard a 128 bit

Domande

UUID v4 e v7 hanno la stessa dimensione?+
Sì. Entrambi sono 128 bit, 16 byte e 36 caratteri nel testo canonico. Cambia solo il layout interno.
UUID v7 si ordina correttamente?+
Tra millisecondi, sì, come byte grezzi e come testo canonico. All'interno di un singolo millisecondo l'ordine predefinito è casuale a meno che il generatore non usi un contatore.
Quale è meglio per una chiave primaria di database?+
v7, perché gli inserimenti restano vicino al bordo destro del B-tree invece di sparpagliarsi. Il compromesso è un hotspot di scrittura sull'ultima pagina a concorrenza molto alta.
UUID v7 divulga informazioni?+
Rivela la data di creazione al millisecondo, e i valori vicini rivelano il volume dei record. Tienilo interno o usa v4 per gli identificatori che escono dal tuo sistema.
Posso usare un UUID come token di accesso?+
No. Un UUID è un identificatore. Anche un valore v4 va verificato contro il controllo degli accessi a ogni richiesta, e un token dovrebbe venire da un generatore creato allo scopo con le proprie regole di durata.

Gli esempi sono sintetici. Non usare un indirizzo generato per verifiche di identità, registrazione di account o consegne.

Standard di riferimento

Leggi dopo

Tutte le guide