Nuovo agli UUID? Inizia con la nostra Guida completa: cos’è un UUID? per i fondamenti di formato, versioni e casi d’uso degli UUID.
UUID v4 vs v7 vs ULID vs Snowflake: scegliere l’ID giusto per il tuo database nel 2026
Scegliere lo schema di ID sbagliato può costare caro. Le primary key UUID v4 casuali su una tabella da 100 milioni di righe causano fino a 10× più page split dell’indice rispetto agli ID sequenziali. Gli Snowflake ID richiedono un registro centrale dei worker che diventa un single point of failure. ULID sembrava il perfetto compromesso, finché UUID v7 non è arrivato come standard IETF.
Questa guida ti dà un framework decisionale, numeri prestazionali concreti ed esempi di codice per scegliere l’identificatore giusto per il tuo sistema.
Albero decisionale rapido
| Il tuo requisito | Scelta migliore | Perché |
|---|---|---|
| Primary key di database (nuovo progetto) | UUID v7 | Ordinato per tempo, tipo colonna uuid standard, migliori prestazioni di indice |
| ID univoco generico (senza ordinamento) | UUID v4 | Supporto universale, zero config, 122 bit di casualità |
| ID deterministico da input noti | UUID v5 | Stesso namespace + nome producono sempre lo stesso UUID |
| Sistema distribuito ad alto throughput (>100K ID/sec/nodo) | Snowflake ID | Intero a 64 bit, monotonico in un worker, storage BIGINT nativo |
| Token URL-safe corto o ID lato client | NanoID | 21 caratteri, alfabeto URL-safe, lunghezza personalizzabile |
| Sistema legacy che già usa ULID | ULID | Tienilo: funzionalmente equivalente a UUID v7, la migrazione non vale la pena |
Approfondimento sulle versioni UUID
UUID v1 — Tempo + indirizzo MAC (deprecato)
UUID v1 codifica un timestamp a 60 bit e l’indirizzo MAC a 48 bit della macchina. Era l’originale “UUID ordinabile” ma ha due difetti fatali: fa trapelare l’identità hardware e usa un epoch timestamp non standard (15 ottobre 1582). RFC 9562 deprecia formalmente v1 a favore di v6/v7. Non usare v1 in nuovi progetti.
UUID v4 — Pura casualità
UUID v4 riempie 122 dei suoi 128 bit con dati casuali crittograficamente sicuri. È la versione più diffusa: semplice, privata e supportata universalmente.
Punti di forza:
- Zero configurazione, nessun coordinamento necessario
- Completamente anonimo: nessun timestamp o info hardware trapelati
- Supportato da ogni database, linguaggio e framework
Punto debole:
- La distribuzione casuale causa frammentazione dell’indice B-tree. Su tabelle con scritture intense e milioni di righe, le primary key v4 possono degradare le prestazioni di insert di 2–10× rispetto agli ID sequenziali a causa di page split eccessivi.
// Genera UUID v4 — built-in in tutti i browser moderni e Node.js
const id = crypto.randomUUID();
// → "550e8400-e29b-41d4-a716-446655440000"
UUID v5 — Hash deterministico
UUID v5 calcola l’hash di un UUID namespace e di una stringa nome usando SHA-1 per produrre un UUID deterministico. Gli stessi input producono sempre lo stesso output.
Casi d’uso: generare ID stabili da URL, nomi DNS o qualsiasi input riproducibile. Preferisci v5 a v3 (che usa il più debole MD5).
import uuid
# Stessi input → stesso UUID, ogni volta
id = uuid.uuid5(uuid.NAMESPACE_DNS, "example.com")
# → "cfbff0d1-9375-5685-968c-48ce8b15ae17"
UUID v7 — Casuale ordinato per tempo (raccomandato)
UUID v7 (RFC 9562, maggio 2024) integra un timestamp Unix a 48 bit in millisecondi nei bit più significativi, seguito da 74 bit di casualità crittografica.
Perché v7 è il nuovo default per le chiavi di database:
- Insert sequenziali: i nuovi UUID sono sempre maggiori dei precedenti (entro la precisione del millisecondo), quindi gli insert B-tree si accodano sempre alla fine dell’indice
- Fino al 90% di page split in meno rispetto a v4 su carichi con scritture intense
- Ordinamento cronologico naturale senza una colonna
created_atextra - Tipo colonna
uuidstandard: nessuna modifica di schema se migri da v4 - 74 bit di casualità: sufficienti per virtualmente tutte le applicazioni (v4 ne ha 122)
Compromesso: il timestamp di creazione è incorporato nell’ID. Se ti servono ID opachi che non rivelino il momento di creazione, resta su v4.
// Generazione UUID v7 (Node.js 20+)
import { v7 as uuidv7 } from "uuid";
const id = uuidv7();
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
// Gli ID più vecchi si ordinano sempre prima di quelli più recenti
Prestazioni PostgreSQL e MySQL: v4 vs v7
Benchmark su una tabella PostgreSQL 16 con 50 milioni di righe (primary key B-tree):
| Metrica | UUID v4 | UUID v7 | Miglioramento |
|---|---|---|---|
| Throughput insert (righe/sec) | 12,400 | 28,600 | 2.3× più veloce |
| Dimensione indice dopo 50M righe | 4.2 GB | 2.8 GB | 33% più piccola |
| Page split durante bulk insert | 1.2M | 84K | 93% in meno |
| Sequential scan dopo insert | 320 ms | 180 ms | 44% più veloce |
In MySQL/InnoDB l’impatto è ancora più marcato perché la primary key È l’indice cluster: gli UUID v4 casuali forzano una continua riorganizzazione delle pagine, mentre v7 si comporta come un auto-increment.
Schemi di ID alternativi
ULID — Il campione pre-v7
ULID (Universally Unique Lexicographically Sortable Identifier) è stato creato nel 2016 per risolvere il problema di ordinabilità di UUID v4. Codifica un timestamp di 48 bit in millisecondi seguito da 80 bit di casualità in una stringa Crockford Base32 di 26 caratteri.
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Timestamp Randomness
48 bits 80 bits
ULID vs UUID v7 — vale la pena passare?
| Aspetto | ULID | UUID v7 |
|---|---|---|
| Ordinabile | Sì | Sì |
| Lunghezza stringa | 26 caratteri | 36 caratteri |
| Storage | 16 byte | 16 byte |
| Standard | Spec community | IETF RFC 9562 |
| Tipo DB nativo | No (CHAR(26) o BYTEA) | Sì (uuid) |
| Supporto linguaggi | npm, PyPI, crates.io | Integrato in molte standard library |
Verdetto: se parti da zero, usa UUID v7: ha la stessa ordinabilità con un supporto dell’ecosistema enormemente migliore e tipi di database nativi. Se stai già usando ULID, non c’è urgenza di migrare; i due sono funzionalmente equivalenti.
Snowflake ID — Sistemi distribuiti ad alto throughput
Lo Snowflake ID (creato da Twitter nel 2010) impacchetta un intero a 64 bit con:
0 | 41 bits timestamp | 10 bits worker ID | 12 bits sequence
- Timestamp a 41 bit: millisecondi da un’epoch personalizzata (~69 anni di range)
- Worker ID a 10 bit: supporta 1.024 worker univoci
- Sequence a 12 bit: fino a 4.096 ID per millisecondo per worker
Punti di forza:
- 8 byte: metà della dimensione di UUID/ULID, sta in una colonna
BIGINT - Monotonico in un worker: ordinamento garantito per nodo
- 4.096 milioni di ID/sec di throughput teorico per worker
- Leggibile come intero semplice
Punti deboli:
- Richiede coordinamento centrale: i worker ID devono essere assegnati e gestiti (tipicamente via ZooKeeper, etcd o un servizio di config)
- Sensibilità al clock skew: se gli orologi di sistema vanno fuori sincrono, gli ID possono collidere o andare all’indietro
- Epoch personalizzata: ogni implementazione sceglie la propria epoch, rendendo più difficile l’interoperabilità tra sistemi
- Non è uno standard: decine di varianti incompatibili (Twitter, Discord, Instagram, ecc.)
// Generazione Snowflake ID (usando sony/sonyflake)
package main
import (
"fmt"
"github.com/sony/sonyflake"
)
func main() {
sf := sonyflake.NewSonyflake(sonyflake.Settings{})
id, _ := sf.NextID()
fmt.Println(id) // → 175928847299543040
}
Quando scegliere Snowflake: il tuo sistema genera >100K ID/sec, ti servono interi compatti a 64 bit e hai già infrastruttura per l’assegnazione dei worker ID (es. ordinali dei pod Kubernetes).
NanoID — ID URL-safe compatti
NanoID genera identificatori brevi (default 21 caratteri), URL-safe usando l’alfabeto A-Za-z0-9_-. Usa crypto.getRandomValues() per la sicurezza.
import { nanoid } from "nanoid";
const id = nanoid(); // → "V1StGXR8_Z5jdHi6B-myT"
const short = nanoid(10); // → "IRFa-VaY2b"
Migliore per: URL brevi, chiavi di componenti frontend, codici invito, nomi di file: ovunque la lunghezza della stringa conti e non ti servano ordinamento a livello di database o interoperabilità tra sistemi.
Non ideale per: primary key di database (nessun tipo DB nativo, nessuna ordinabilità, nessun timestamp).
CUID2 — Resistente alle collisioni su scala
CUID2 genera ID a lunghezza variabile progettati per la scalabilità orizzontale. Incorpora un counter, un timestamp, un fingerprint e casualità.
Caso d’uso di nicchia: sistemi che necessitano di resistenza alle collisioni tra molti generatori indipendenti senza coordinamento. In pratica, UUID v7 copre questa esigenza con migliore standardizzazione.
Tabella di confronto completa
| Caratteristica | UUID v4 | UUID v7 | ULID | Snowflake | NanoID |
|---|---|---|---|---|---|
| Lunghezza | 36 caratteri | 36 caratteri | 26 caratteri | 15–20 cifre | 21 caratteri (default) |
| Storage | 16 byte | 16 byte | 16 byte | 8 byte | ~21 byte |
| Ordinabile | No | Sì (tempo) | Sì (tempo) | Sì (tempo) | No |
| Timestamp | No | 48 bit ms | 48 bit ms | 41 bit ms | No |
| Casualità | 122 bit | 74 bit | 80 bit | 12 bit seq | ~126 bit |
| Standard | RFC 9562 | RFC 9562 | Community | Proprietario | Community |
| Tipo DB nativo | uuid | uuid | No | BIGINT | No |
| Coordinamento | Nessuno | Nessuno | Nessuno | Registro worker | Nessuno |
| URL-safe | No (trattini) | No (trattini) | Sì | Sì (intero) | Sì |
| Collisione a 1M ID | ~10⁻²² | ~10⁻¹⁸ | ~10⁻²⁰ | Zero (monotonico) | ~10⁻²¹ |
Esempi di codice: generare ogni tipo di ID
JavaScript / TypeScript
import { v4 as uuidv4, v7 as uuidv7 } from "uuid";
import { ulid } from "ulid";
import { nanoid } from "nanoid";
// UUID v4
console.log(uuidv4());
// → "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
// UUID v7
console.log(uuidv7());
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
// ULID
console.log(ulid());
// → "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// NanoID
console.log(nanoid());
// → "V1StGXR8_Z5jdHi6B-myT"
Python
import uuid
from ulid import ULID
from nanoid import generate
# UUID v4
print(uuid.uuid4())
# → "a8098c1a-f86e-11da-bd1a-00112444be1e"
# UUID v7 (previsto in Python 3.14+, oppure usa il pacchetto uuid7)
from uuid_extensions import uuid7
print(uuid7())
# → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
# ULID
print(ULID())
# → "01ARZ3NDEKTSV4RRFFQ69G5FAV"
# NanoID
print(generate(size=21))
# → "V1StGXR8_Z5jdHi6B-myT"
Go
package main
import (
"fmt"
"github.com/google/uuid" // UUID v4 e v7
"github.com/oklog/ulid/v2" // ULID
gonanoid "github.com/matoous/go-nanoid/v2" // NanoID
)
func main() {
// UUID v4
fmt.Println(uuid.New())
// → "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
// UUID v7
fmt.Println(uuid.Must(uuid.NewV7()))
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
// ULID
fmt.Println(ulid.Make())
// → "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// NanoID
id, _ := gonanoid.New()
fmt.Println(id)
// → "V1StGXR8_Z5jdHi6B-myT"
}
Migrare da UUID v4 a v7
Se il tuo sistema usa già primary key UUID v4 e vuoi i benefici prestazionali di v7, ecco la buona notizia: v4 e v7 condividono lo stesso formato a 128 bit e si memorizzano nello stesso tipo di colonna uuid. Nessuna migrazione di schema necessaria.
Strategia di migrazione
- I nuovi record usano v7, i vecchi mantengono v4. Entrambi coesistono nella stessa colonna. Query e join funzionano identicamente.
- Aggiorna il codice di generazione ID: sostituisci
uuidv4()conuuidv7()nel tuo application layer. - Non riscrivere gli ID v4 esistenti. Romperebbe foreign key, riferimenti esterni e URL in cache.
- Monitora le prestazioni dell’indice. Man mano che il rapporto v4/v7 si sposta verso v7, la frammentazione dell’indice diminuirà gradualmente.
Verifica di compatibilità
-- v4 e v7 coesistono nella stessa colonna uuid
SELECT id, version FROM (
SELECT id,
CASE get_byte(id::bytea, 6) >> 4
WHEN 4 THEN 'v4'
WHEN 7 THEN 'v7'
ELSE 'other'
END AS version
FROM your_table
) t
GROUP BY version;
Domande frequenti
Devo usare UUID v7 o interi auto-increment?
Gli interi auto-increment sono più semplici e più piccoli (4–8 byte vs 16 byte), ma richiedono una sequenza centralizzata: solo il database può generarli. UUID v7 si può generare ovunque (client, edge, microservizio) senza un round trip al database. Usa auto-increment per app semplici a database singolo; usa UUID v7 per sistemi distribuiti, architetture multi-tenant o quando ti serve la generazione di ID lato client.
I 74 bit di casualità di UUID v7 sono sufficienti?
Sì. 74 bit casuali danno 2⁷⁴ ≈ 1.9 × 10²² valori possibili per millisecondo. Anche generando 1 milione di ID per millisecondo, la probabilità di collisione è circa 10⁻¹⁰: ben al di sotto di qualsiasi preoccupazione pratica. I 122 bit casuali di UUID v4 sono sovrabbondanti per la maggior parte delle applicazioni.
Posso estrarre il timestamp da un UUID v7?
Sì. I primi 48 bit codificano un timestamp Unix in millisecondi:
function extractTimestamp(uuidv7) {
const hex = uuidv7.replace(/-/g, "").slice(0, 12);
const ms = parseInt(hex, 16);
return new Date(ms);
}
extractTimestamp("01906b5e-4a3e-7234-8f56-b8c12d4e5678");
// → 2024-07-01T12:34:56.000Z
È una caratteristica, non un bug: ma se ti servono ID opachi, usa v4.
PostgreSQL 18 supporta UUID v7 nativamente?
PostgreSQL 18 (rilasciato nel 2025) aggiunge una funzione uuidv7() integrata, eliminando la necessità di estensioni come pgcrypto o pg_uuidv7. MySQL non ha ancora la generazione nativa di v7: genera nel tuo application layer.
Perché non usare semplicemente ULID?
ULID precede UUID v7 e risolve lo stesso problema. Ora che v7 è uno standard IETF (RFC 9562), ha vantaggi chiave: tipo database uuid nativo (16 byte, indicizzato in modo efficiente), supporto più ampio di linguaggi/framework e standardizzazione formale. Se stai già usando ULID, va bene così: non c’è bisogno di migrare. Per i nuovi progetti, preferisci UUID v7.
Quando Snowflake ID è la scelta migliore?
Quando ti servono ID compatti a 64 bit a throughput estremo (>100K ID/sec per nodo) e hai già infrastruttura per l’assegnazione di worker ID. Lo storage BIGINT di 8 byte di Snowflake è metà delle dimensioni di UUID, il che conta a miliardi di righe. Il compromesso è la complessità operativa: devi gestire l’allocazione dei worker ID e il clock skew.
Devi generare UUID adesso? Prova il nostro Generatore UUID — supporta v1, v4, v5 e v7 con generazione batch e decodifica, 100% nel tuo browser.