Gli UUID collidono? Pattern di implementazione e operazione che invitano duplicati

· Aggiornato il: · · UUID, Identificatori, Sistemi distribuiti, Data Design, Implementazione

Hai usato UUID come chiavi primarie, e un giorno compare duplicate key. In quel momento, c’è una buona probabilità che qualcuno dica: “Quindi gli UUID collidono.”

In pratica, però, la maggior parte dei duplicati UUID non è un problema della specifica UUID stessa, ma casi in cui l’implementazione o le operazioni rompono le condizioni di generazione che la specifica assume. Sotto RFC 9562, UUIDv4 ha 122 bit di spazio casuale, e UUIDv7 è definito assumendo che i 74 bit esterni al timestamp siano usati per casualità o contatori che forniscono unicità. UUIDv8, d’altra parte, è descritto esplicitamente come implementation-specific, e l’unicità non deve essere assunta.123 Anche la libreria standard Python documenta che uuid4() è generato usando un metodo criptograficamente sicuro, quindi, finché “si usa un’implementazione corretta nel modo normale”, le garanzie lato UUID sono piuttosto forti.4

In questo articolo organizziamo i pattern tipici in cui operazioni o implementazioni errate fanno collidere gli UUID, insieme alle misure per prevenire la ricorrenza. Il contenuto è basato su RFC 9562, la documentazione ufficiale Python e la documentazione ufficiale PostgreSQL come verificabili a marzo 2026.546

1. La conclusione prima di tutto

Per riassumere subito, questi sono i pattern pericolosi.

Pattern Cosa succede Prima contromisura
Creare a mano valori simili a UUIDv4 con seed fisso o PRNG debole La stessa sequenza viene riprodotta in un altro processo o nodo Usa l’API UUID standard del SO / runtime
Portare lo stato del generatore dopo fork, snapshot VM o clone container Lo stato casuale o del contatore torna indietro e compaiono duplicati Re-seed dopo fork, re-inizializza dopo clone, rivedi la gestione dello stato persistente
Usare UUIDv3 / v5 pensando che restituiscano “un nuovo ID ogni volta” Lo stesso UUID viene rigenerato dallo stesso namespace e stesso nome Capisci che sono ID deterministici e limitane l’uso
Implementare UUIDv1 / v6 / v7 / v8 a mano gestendo male rollback orologio o nodo/contatore I duplicati diventano probabili con generazione ad alta frequenza o tra nodi Usa librerie esistenti e riduci i generatori custom
Troncare gli UUID a metà o schiacciarli in un altro formato Butti tu stesso via l’unicità 128-bit originale Conserva e confronta a lunghezza piena
Non mettere UNIQUE / PRIMARY KEY sul lato DB I duplicati entrano silenziosamente e l’analisi root-cause si ritarda Mantieni un vincolo di unicità nello storage

In breve, piuttosto che “l’UUID ha colliso”, di solito è che l’unicità che ti aspettavi dall’UUID è stata intaccata da qualche parte nel design.

2. Sospetta prima generazione e operazioni, non la “matematica degli UUID”

Le discussioni sugli UUID diventano confuse perché le proprietà differiscono per versione.

  • UUIDv4 è basato su casualità. Sotto RFC 9562, i 122 bit diversi da versione / variant sono riempiti con dati casuali.1
  • UUIDv7 ha una struttura che ordina bene cronologicamente; oltre a un timestamp Unix in millisecondi, il resto è composto da casualità o da un contatore accuratamente seedato.2
  • UUIDv3 / v5 sono basati su nome. Dato lo stesso namespace e lo stesso nome canonico, produrre lo stesso UUID è il comportamento corretto.7
  • UUIDv8 è per uso sperimentale e vendor-specific, e la sua unicità è implementation-specific. La RFC dice esplicitamente che non si deve assumere unicità.3

Quindi anche se dici “usiamo UUID”, la storia cambia completamente a seconda che si tratti di

  • uuid4() della libreria standard
  • un timestamp + random fatto in casa
  • uuid5(namespace, name)
  • o un formato custom che sembra solo UUIDv8.
Trovato un UUID duplicatoDove è venuto davvero lo stesso valoreGeneratore deboleStato riavvoltoUso errato di UUID basati su nomeTroncato al momento dello storageNessun vincolo di unicità sul DBErrore di implementazione

In pratica, partire dal lato destro di questo diagramma è più veloce.

3. Pattern 1: Chiamarlo UUIDv4 mentre si usa un PRNG debole

Questo è il più comune.

  • Costruire 128 bit con un PRNG generico equivalente a Math.random()
  • Seedare all’avvio con time() o il PID
  • Assemblare a mano “32 cifre esadecimali che sembrano formato UUID”

Può sembrare un UUID, ma se la sorgente casuale è debole, la stessa sequenza viene riprodotta in un altro processo o nodo.

RFC 9562 raccomanda di usare un CSPRNG, sia per l’unicità UUID che per l’impossibilità di indovinare. Questa è una raccomandazione (SHOULD), quindi eccezioni possono essere progettate per alcuni casi d’uso, ma se crei a mano UUID con un PRNG generico devi essere in grado di spiegare perché. Inoltre, afferma che lo stato del CSPRNG dovrebbe essere re-seedato correttamente al cambiamento di stato come un fork di processo.8 Anche Python uuid.uuid4() è documentata come generazione di UUID casuali in modo criptograficamente sicuro.4

La conclusione pratica qui è semplice.

  • Non creare UUID a mano
  • Non giocherellare con seed casuali a mano
  • Usa la libreria standard o un’implementazione ampiamente usata così com’è

Tenere un generatore custom “perché è leggero” o “perché l’abbiamo sempre usato” è ciò che costa di più in seguito.

4. Pattern 2: Riavvolgere lo stato del generatore tramite fork, snapshot o clone

La seconda cosa più pericolosa è operazioni in cui lo stato del generatore viene duplicato o riavvolto.

RFC 9562 raccomanda esplicitamente di re-seedare dopo fork, e spiega che implementazioni senza storage stabile devono generare sequenze di clock, contatori e dati casuali più frequentemente, il che aumenta la probabilità di duplicati.89

Da questo deriva naturalmente una linea pratica.

  • Ripristinare più istanze della stessa immagine dopo aver fatto uno snapshot VM
  • Un generatore custom che parte dallo stesso stato iniziale ogni volta che un’immagine container si avvia
  • Condividere lo stato PRNG o contatore tra fork di worker

Sotto queste operazioni, la sequenza di generazione UUID può essere riprodotta involontariamente. La RFC non dice letteralmente “gli snapshot sono pericolosi”, ma questa è una cautela pratica molto reale che si ricava dalle sue note sul re-seed dopo fork e sulla gestione dello stato del generatore.89

Le contromisure appaiono così.

  • Non conservare a lungo lo stato custom del generatore UUID
  • Re-inizializza subito dopo fork / clone / restore
  • Dove possibile, appoggiati a implementazioni che pescano casualità fornita dal SO ogni volta
  • Per generatori ad alta frequenza, documenta esplicitamente la gestione dello stato e le specifiche di re-seed

5. Pattern 3: Leggere male UUIDv3 / v5 come “un nuovo ID ogni volta”

UUIDv3 / v5 non sono ID casuali che resistono alla collisione. Sono ID deterministici che possono rigenerare lo stesso ID dallo stesso nome.

RFC 9562 afferma che UUID generati dallo stesso nome in forma canonica all’interno dello stesso namespace devono essere uguali.7 Quindi con un uso come il seguente, i duplicati non sono un incidente — sono il comportamento specificato.

  • Usare uuid5(NAMESPACE_URL, "https://example.com/users/42") come “assegnazione di ID fresco” ogni volta
  • Emettere ID da un namespace condiviso tra tutti i clienti più un’email, senza mettere il tenant nel namespace
  • Assumere che ri-emettere lo stesso nome logico a ogni retry dia un ID diverso

Viceversa, se la canonicalizzazione del nome è inconsistente, ottieni UUID diversi per lo stesso soggetto. La RFC sottolinea ripetutamente la gestione della rappresentazione canonica.710

Quel che conta in questa famiglia sono tre cose:

  • UUIDv3 / v5 non sono “assegnazione di ID senza collisioni” ma “stesso input, stesso ID”
  • Non lasciare vago il design del namespace
  • Specifica esplicitamente la canonicalizzazione dei nomi

6. Pattern 4: Implementare UUID basati sul tempo o UUIDv8 a mano

UUIDv1 / v6 / v7 / v8 sono pericolosi da imitare solo per l’aspetto.

6.1 Gestire node o clock sequence con noncuranza in UUIDv1 / v6

Sotto RFC 9562, UUIDv6 è un UUIDv1 con campi riordinati progettato per migliorare la località DB, e tratta clock sequence e node. La RFC porta anche molteplici cautele sulla resistenza alle collisioni di node in ambienti distribuiti e sulla conservazione dello stato.11912

Inoltre, la RFC arriva a dire che con l’avvento di macchine virtuali e container, l’unicità degli indirizzi MAC non può più essere garantita.5

Quindi design come

  • assumere “è un indirizzo MAC, quindi deve essere unico”
  • replicare un node ID cotto in un’immagine
  • resettare la clock sequence a un valore fisso a ogni riavvio

sono pericolosi.

6.2 Costruire UUIDv7 a mano ignorando rollover del contatore o rollback dell’orologio

UUIDv7 è abbastanza pratico, ma la RFC è accurata su monotonicità e gestione del contatore sotto generazione ad alta frequenza. Afferma inoltre esplicitamente che le implementazioni non devono restituire consapevolmente duplicati su rollback dell’orologio o rollover del contatore.213

Il che significa che implementazioni come

  • emettere grandi volumi nello stesso millisecondo senza un design del contatore
  • continuare a generare senza fare nulla quando l’orologio va indietro
  • processi multipli che inizializzano ciascuno lo stesso contatore interno in modo indipendente

sono rischiose.

6.3 Trattare UUIDv8 alla leggera, come se fosse “la nuova specifica UUID”

UUIDv8 sembra comodo, ma RFC 9562 è piuttosto chiara: l’unicità di UUIDv8 è implementation-specific e non deve essere assunta.3

Quindi un “UUID proprietario dell’azienda” che

  • incorpora un timestamp
  • incorpora uno shard ID
  • incorpora qualche significato di business
  • e riempie il resto con qualunque casualità

significa che quel documento di design è in sé la tua specifica di unicità UUID. È troppo pericoloso introdurlo senza revisione.

7. Pattern 5: Accorciare l’UUID lungo la strada

Anche se la generazione è corretta, le cose possono rompersi nello stage di storage o confronto.

Esempi tipici:

  • Usare solo i primi 8 caratteri come surrogato di foreign key
  • Schiacciare un UUID 128-bit in un intero 64-bit
  • Una colonna stringa troppo corta, così la coda viene tagliata
  • Trattare la rappresentazione accorciata usata nei log o a schermo come chiave unica

Quel che conta qui è che cambiare la rappresentazione non è di per sé cattivo.

  • Rimuovere i trattini
  • Normalizzare a lower / upper case
  • Conservare come 16 byte binari

Trasformazioni come queste, che non scartano nessuno dei 128 bit, vanno bene. Pericolose sono le trasformazioni che raschiano via la stessa sostanza dell’unicità.

In particolare, un design in cui è stato creato un “short ID amico dell’umano” separato e poi silenziosamente ha iniziato a prevalere sul vero UUID è incline agli incidenti.

8. Pattern 6: Nessun vincolo di unicità sul lato DB

E questo è particolarmente importante.

Anche se gli UUID sono sufficientemente resistenti alle collisioni, se non puoi tollerare davvero duplicati, la destinazione di storage dovrebbe portare anche un vincolo di unicità.

La documentazione ufficiale di PostgreSQL spiega che un vincolo unique garantisce che il valore di una colonna o gruppo di colonne sia unico in tutta la tabella, e che una primary key è un identificatore di riga unico e not null.6

Anche RFC 9562 dice che, sebbene gli UUID possano fornire un’unicità sufficiente in pratica, l’unicità globale vera non può mai essere garantita assolutamente, e che per usi in cui l’impatto di una collisione è alto andrebbero prese contromisure più forti.14

In pratica, questa combinazione è la baseline.

  • Usa gli UUID come ID che difficilmente collidono
  • Mantieni UNIQUE / PRIMARY KEY nel DB come ultima linea di difesa
  • Progetta retry / idempotenza / incident logging per il caso duplicato

Usare UUID e omettere vincoli di unicità non sono la stessa cosa.

9. Una checklist pratica

Infine, ecco un modulo che puoi usare direttamente per adozione o audit.

  1. Verifica se stai generando UUID da solo Se puoi passare ad API standard come uuid4() / uuid7(), fallo per primo.
  2. Decidi la versione UUID come parte della specifica Dichiara esplicitamente che v4/v7 sono basati su casualità, v3/v5 sono deterministici, e v8 è una specifica custom.
  3. Fai l’inventario di come vengono gestiti seed e stato del generatore Assicurati che lo stesso stato non venga portato oltre fork, riavvio worker, snapshot o clone.
  4. Conferma che a storage venga conservata la lunghezza piena Non usare confronti per prefisso o display accorciati come chiave effettiva.
  5. Metti UNIQUE / PRIMARY KEY nel DB Un UUID è un meccanismo che abbassa la probabilità; non è un vincolo in sé.
  6. Rendi i duplicati osservabili Non ingoiare gli errori di chiave duplicata; rendi tracciabile quale generatore / nodo / deployment li ha prodotti.

10. Riassunto

Gli incidenti di collisione UUID di solito iniziano non perché l’UUID sia debole, ma perché l’implementazione o le operazioni rompono le assunzioni su cui l’UUID si basa.

  • Creare a mano con casualità debole
  • Riavvolgere lo stato dopo fork o snapshot
  • Usare UUID basati su nome per assegnazione di ID freschi
  • Implementare casualmente v7 o v8
  • Troncare lungo la strada scartando l’unicità
  • Rimuovere il vincolo di unicità sul lato DB

Farne anche solo uno equivale quasi a costruire attivamente una situazione in cui le collisioni diventano probabili.

Quando trovi un duplicato, quello che dovresti sospettare per primo non è la matematica degli UUID, ma il generatore, la gestione dello stato, il formato di storage e il design dei vincoli. Guarda in quell’ordine, e la causa di solito si restringe rapidamente.

11. Articoli correlati

12. Riferimenti

  1. IETF RFC 9562, Section 5.4 UUID Version 4. Sugli 122 bit di spazio casuale di UUIDv4.  2

  2. IETF RFC 9562, Section 5.7 UUID Version 7. Sul design di timestamp, bit casuali e contatori di UUIDv7.  2 3

  3. IETF RFC 9562, Section 5.8 UUID Version 8. Sull’unicità implementation-specific di UUIDv8 e sul fatto che non deve essere assunta.  2 3

  4. Python 3.14 documentation, uuid module. Su uuid4() generazione criptograficamente sicura, comportamento deterministico di uuid5(), e proprietà di uuid7() / uuid8() 2 3

  5. IETF RFC 9562, Universally Unique IDentifiers (UUIDs). Il documento di base per il formato UUID, ogni versione e le best practice complessive.  2

  6. PostgreSQL documentation, Constraints. Sul garantire unicità tramite vincoli UNIQUE e PRIMARY KEY.  2

  7. IETF RFC 9562, Section 6.5 Name-Based UUID Generation. Sullo stesso namespace + stesso nome che danno lo stesso UUID, e sull’importanza della canonicalizzazione.  2 3

  8. IETF RFC 9562, Section 6.9 Unguessability. Sull’uso di CSPRNG e re-seed dopo fork.  2 3

  9. IETF RFC 9562, Section 6.3 UUID Generator States. Sulla gestione dello storage stabile e dello stato del generatore.  2 3

  10. IETF RFC 9562, Section 5.5 UUID Version 5. Sulla specifica degli UUID basati su nome costruiti da namespace più nome canonico. 

  11. IETF RFC 9562, Section 5.6 UUID Version 6. Su UUIDv6: node / clock sequence / località DB. 

  12. IETF RFC 9562, Section 6.4 Distributed UUID Generation. Sulla resistenza alle collisioni di node in ambienti distribuiti. 

  13. IETF RFC 9562, Section 6.2 Monotonicity and Counters. Sulle cautele di rollback dell’orologio, rollover del contatore e generazione batch. 

  14. IETF RFC 9562, Sections 6.7 and 6.8. Sul ragionamento dietro resistenza alle collisioni e unicità globale. 

Articoli recenti con gli stessi tag per approfondire argomenti vicini.

Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.

L’articolo è direttamente collegato ai servizi seguenti.

Analisi dei bug e delle cause

Negli incidenti reali di ID duplicati bisogna determinare se la colpa è dell'UUID stesso o di implementazione e operazioni, quindi organizzare gli angoli di indagine e il design di prevenzione della ricorrenza è essenziale.

Domande frequenti

Domande che ricorrono nelle consulenze sull’argomento dell’articolo.

Gli UUID collidono effettivamente in pratica?
Quando generati con un'implementazione corretta usata nel modo normale, le collisioni sono estremamente improbabili: sotto RFC 9562, UUIDv4 ha 122 bit di spazio casuale, e librerie standard come Python uuid4() usano generazione criptograficamente sicura. La maggior parte degli incidenti reali di duplicati non è un fallimento della specifica UUID, ma casi in cui implementazione o operazioni hanno rotto le condizioni di generazione che la specifica assume. Quando trovi un duplicato, sospetta prima il generatore, la gestione dello stato, il formato di storage e il design dei vincoli, prima della matematica.
Quali errori di implementazione causano UUID duplicati?
I pattern ricorrenti sono: creare a mano valori simili a UUIDv4 con un PRNG generico o un seed fisso, che riproduce la stessa sequenza in un altro processo o nodo; portare lo stato del generatore attraverso fork, snapshot VM o clone di container facendo tornare indietro lo stato casuale o del contatore; implementare a mano UUID basati sul tempo (v1, v6, v7) senza gestire il rollback dell'orologio o il rollover del contatore; troncare gli UUID a un breve prefisso o schiacciarli in 64 bit, buttando via l'unicità; e omettere il vincolo UNIQUE sul lato database così che duplicati entrino silenziosamente.
Perché UUIDv3 e UUIDv5 restituiscono lo stesso UUID ogni volta?
Perché è il comportamento specificato, non un bug. UUIDv3 e v5 sono identificatori deterministici basati su nome: RFC 9562 afferma che UUID generati dallo stesso nome in forma canonica all'interno dello stesso namespace devono essere uguali. Sono "stesso input, stesso ID" — non assegnazione di ID freschi. Se serve un nuovo ID ogni volta, usa UUIDv4 basato su casualità o UUIDv7 basato sul tempo, e se usi le versioni basate su nome, progetta esplicitamente namespace e canonicalizzazione dei nomi.
Dovrei comunque aggiungere un vincolo UNIQUE se uso UUID come chiavi?
Sì. RFC 9562 stesso dice che, sebbene gli UUID forniscano un'unicità sufficiente in pratica, l'unicità globale vera non può mai essere garantita assolutamente, e che gli usi in cui una collisione ha alto impatto dovrebbero adottare contromisure più forti. La baseline pratica è usare gli UUID come ID che difficilmente collidono, mantenere UNIQUE o PRIMARY KEY nel database come ultima linea di difesa, e progettare retry, idempotenza e logging incident per il caso di duplicato. Usare UUID e omettere i vincoli di unicità non sono la stessa cosa.

Profilo dell’autore

Pagina di presentazione dell’autore dell’articolo.

Go Komura

Rappresentante di KomuraSoft LLC

Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.

Torna al blog