Per 15 anni, UUID v4 era l’unica risposta. Identificatori casuali a 128 bit, generati nel tuo browser, copia-incollati in una colonna del database, usati ovunque. Funzionava. Poi nel 2024, RFC 9562 ha standardizzato v7, e la conversazione è cambiata.
La versione breve: usa v7 per le nuove chiavi primarie del database, usa v4 per i token di sicurezza e gli ID sessione. Sotto c’è il perché.
Cosa è realmente un UUID
Un UUID è un identificativo a 128 bit, solitamente scritto come 32 caratteri hex separati da trattini: 550e8400-e29b-41d4-a716-446655440000. I trattini sono estetici; i byte sono ciò che conta. I 128 bit ti danno 2¹²⁸ valori possibili — circa 340 undicilioni. Non finirai.
Il numero di versione (la prima cifra dopo il secondo trattino — 4 in 550e8400-e29b-**4**1d4-a716-446655440000) ti dice come è stato generato l’UUID. Questa è la parte che conta.
UUID v4: puro casuale
UUID v4 è ciò che la maggior parte delle persone intende quando dice “UUID”. 122 dei 128 bit sono casuali, gli altri 6 codificano la versione e la variante. Il caso è tutto il punto — non c’è pattern da sfruttare, nessun modo di indovinare il prossimo ID, nessuna correlazione temporale.
Generane uno in qualsiasi browser moderno:
crypto.randomUUID() // → "550e8400-e29b-41d4-a716-446655440000"
È bellamente semplice. Ed è anche, come si è scoperto, terribile per i database.
Il problema è l’indicizzazione B-tree. Un B-tree è la struttura dati alla base della maggior parte degli indici (Postgres, MySQL, SQLite, MongoDB). Mantiene i dati ordinati in modo che le query di intervallo siano veloci e gli inserimenti vadano approssimativamente nello stesso punto su disco. Quando inserisci un UUID casuale, il database deve metterlo da qualche parte nel mezzo dell’albero — ogni inserimento diventa un’operazione di I/O casuale. Con milioni di righe, questo uccide il throughput di scrittura.
La soluzione storica era “usa UUID v4 ma con ordinamento sequenziale” — tranne che v4 è, per definizione, casuale. La gente ha trovato workaround (UUID_TO_BIN(..., 1) di MySQL sposta i bit temporali davanti). RFC 9562 ha reso l’hack lo standard.
UUID v7: ordinato temporalmente
UUID v7 mette un timestamp con precisione al millisecondo nei bit alti e casualità nei bit bassi:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
| unix_ts_ms |
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
| unix_ts_ms | ver | rand_a |
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
|var| rand_b |
├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤
| rand_b |
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
I primi 48 bit sono il timestamp Unix in millisecondi. I successivi 4 bit sono la versione (7). Poi 74 bit casuali, poi 2 bit di variante, poi altri 48 bit casuali.
Il risultato: gli UUID generati nello stesso millisecondo si ordinano per ordine di inserimento. Gli UUID generati dopo si ordinano dopo. Gli inserimenti vanno al lato destro del B-tree, non al mezzo. Il throughput di scrittura aumenta di 2-10x a seconda del database.
Il benchmark che conta
PlanetScale ha pubblicato un benchmark nel 2024 che ha dato i numeri. Con MySQL 8 su un indice a chiave primaria:
| Formato | Inserts/sec | Note |
|---|---|---|
| INT auto-increment | 35.000 | Il più veloce, ma espone informazioni |
| UUID v7 | 18.000 | Paragonabile a INT per la maggior parte delle app |
| UUID v4 | 4.500 | Inserimenti casuali = split di pagina |
| UUID v1 (con timestamp) | 14.000 | Variante temporale più vecchia |
La differenza di 4x tra v4 e v7 è il costo delle operazioni di I/O casuali. Per un servizio ad alto traffico, questo si traduce direttamente in dollari.
Quando usare v4
UUID v4 è ancora la scelta giusta quando l’ID stesso è il confine di sicurezza:
- Token di sessione — non vuoi che un ID sessione riveli il suo momento di creazione. UUID v7 si ordina per tempo, il che significa che un attaccante che conosce un ID può indovinare gli ID vicini. (L’exploitabilità pratica è bassa — 74 bit di casualità sono ancora tanti — ma il principio conta.)
- Token di reset password — stessa ragione.
- Chiavi API distribuite a terze parti — quando l’ID deve essere non indovinabile, l’ordinamento temporale è una funzionalità che non vuoi.
- Qualsiasi cosa usata come output CSPRNG — v4 è ciò che
crypto.randomUUID()restituisce. Se usi l’ID per casualità crittografica, v4 è la scelta naturale.
Quando usare v7
UUID v7 è il default giusto per:
- Chiavi primarie del database — questo è il caso d’uso principale. La maggior parte dei nuovi schemi dovrebbe scegliere v7.
- ID degli event log — ordinabili per tempo senza una colonna timestamp separata.
- ID dei messaggi nelle code — ordinamento FIFO con unicità.
- Identificatori di sistemi distribuiti dove vuoi un ordinamento grossolano senza coordinare una sequenza.
Come generarli
Nel browser, v4 è integrato:
crypto.randomUUID() // v4
Per v7, hai bisogno di un piccolo polyfill — crypto.getRandomValues più un prefisso timestamp. Il generatore UUID su DevSpeedTools produce entrambi v4 e v7 nel browser usando direttamente crypto.getRandomValues, in modo da poter incollare gli ID direttamente nello schema senza installare nulla.
In Node.js 22+, crypto.randomUUID() integrato restituisce ancora solo v4. Il pacchetto npm uuid (v9+) ha aggiunto v7(). In Python, uuid.uuid7() è arrivato in 3.14 (rilasciato giugno 2025) ed è ora la scelta consigliata nella documentazione della libreria standard. In Postgres, la funzione gen_random_uuid() restituisce ancora v4 — per ottenere v7, genera lato client e passalo. MySQL 9 (rilasciato 2024) ha aggiunto UUID_V7_BIN() come tipo di prima classe, e MariaDB ha seguito in 11.7. Cloudflare D1, PlanetScale e Supabase generano tutti v7 come default per le nuove colonne chiave primaria nel 2026.
Migrazione da v4 a v7
Se hai una tabella esistente con chiavi primarie v4, non devi migrare. Le due versioni coesistono nella stessa colonna (il tipo uuid accetta entrambe). Il miglioramento delle prestazioni è sugli inserimenti, quindi conta solo se stai aggiungendo nuove righe. La migrazione più semplice: inizia a generare v7 per i nuovi inserimenti, lascia le righe esistenti come v4. Si ordineranno leggermente fuori ordine con le nuove righe v7 (poiché v4 non ha timestamp), ma l’indice sarà a posto.
Per uno schema nuovo nel 2026, v7 è il default. Il guadagno di 4x nel throughput di scrittura è reale, la specifica è stabile, ogni principale runtime linguistico ha un generatore v7, e i provider di database gestiti stanno impostando v7 come default per le nuove chiavi primarie. Usa v4 solo quando hai specificamente bisogno di ID non indovinabili e privi di correlazione temporale.