Les UUID peuvent-ils entrer en collision ? Les schémas d'implémentation et d'exploitation qui provoquent des doublons

· Mis à jour le: · · UUID, Identifiants, Systèmes distribués, Conception des données, Implémentation

Vous utilisiez des UUID comme clés primaires, et un jour, une erreur duplicate key apparaît. À cet instant, il y a de fortes chances que la conversation glisse vers : « En fin de compte, les UUID entrent-ils en collision ? »

Pourtant, la plupart des doublons de UUID rencontrés en pratique ne sont pas tant un problème de la norme UUID elle-même que des cas où l’implémentation ou l’exploitation brisent les conditions de génération que la norme suppose. Selon la RFC 9562, UUIDv4 dispose de 122 bits d’espace aléatoire, et UUIDv7 est lui aussi défini en supposant que les 74 bits hors horodatage servent à des données aléatoires ou à un compteur garantissant l’unicité. UUIDv8, en revanche, est explicitement décrit comme dépendant de l’implémentation, et son unicité ne doit jamais être présumée.123 La bibliothèque standard de Python indique elle aussi que uuid4() est générée par une méthode cryptographiquement sûre ; tant que l’on se contente d’« utiliser normalement une implémentation sérieuse », les garanties du côté du UUID sont donc assez solides.4

Cet article recense les schémas typiques où une exploitation ou une implémentation erronée fait entrer les UUID en collision, accompagnés des mesures de prévention correspondantes. Le contenu s’appuie sur la RFC 9562, la documentation officielle de Python et la documentation officielle de PostgreSQL, telles que vérifiables en mars 2026.546

1. La conclusion d’abord

Pour résumer brièvement dès le départ, voici les schémas dangereux.

Schéma Ce qui se produit Première mesure à prendre
Fabriquer soi-même des valeurs ressemblant à UUIDv4 avec un seed fixe ou un PRNG faible La même séquence se reproduit dans un autre processus ou un autre nœud Utiliser l’API UUID standard de l’OS / du runtime
Transmettre tel quel l’état du générateur après un fork, un snapshot de VM ou une duplication de conteneur L’état de l’aléa ou du compteur revient en arrière, provoquant des doublons Réamorcer (re-seed) après un fork, réinitialiser après un clone, revoir la gestion de l’état persistant
Utiliser UUIDv3 / v5 en croyant à tort obtenir « un nouvel ID à chaque fois » Le même UUID est régénéré à partir du même namespace et du même nom Comprendre qu’il s’agit d’ID déterministes et en restreindre l’usage
Implémenter soi-même UUIDv1 / v6 / v7 / v8 en traitant sans soin le clock rollback ou le node/compteur Les doublons deviennent probables en cas de génération à haute fréquence ou sur plusieurs nœuds Utiliser des bibliothèques existantes et réduire les générateurs maison
Tronquer le UUID en cours de route ou le compresser dans un autre format On abandonne soi-même l’unicité des 128 bits d’origine Toujours stocker et comparer à la longueur complète
Ne pas poser de UNIQUE / PRIMARY KEY côté base de données Les doublons s’infiltrent silencieusement, retardant l’analyse de la cause Maintenir une contrainte d’unicité au niveau du stockage

En somme, il ne s’agit pas tant du fait que « le UUID est entré en collision » que du fait que l’unicité attendue du UUID a été rognée quelque part dans la conception.

2. Il faut d’abord suspecter la génération et l’exploitation, pas les « mathématiques du UUID »

Les discussions sur les UUID deviennent confuses parce que les propriétés diffèrent selon la version.

  • UUIDv4 est basé sur l’aléa. Selon la RFC 9562, les 122 bits autres que la version / le variant sont remplis de données aléatoires.1
  • UUIDv7 a une structure qui se trie bien chronologiquement ; en plus d’un horodatage Unix en millisecondes, le reste est constitué d’aléa ou d’un compteur soigneusement initialisé (carefully seeded counter).2
  • UUIDv3 / v5 sont basés sur un nom (name-based). Pour un même namespace et un même canonical name, obtenir le même UUID est le comportement correct.7
  • UUIDv8 est destiné à un usage expérimental ou propre à un fournisseur, et son unicité dépend de l’implémentation. La RFC indique qu’« il ne faut pas présumer l’unicité ».3

Autrement dit, même en disant « nous utilisons des UUID », tout change complètement selon que le contenu réel est

  • le uuid4() de la bibliothèque standard,
  • un timestamp + random maison,
  • uuid5(namespace, name),
  • ou un format personnalisé qui ressemble seulement à UUIDv8.
Un doublon de UUID est découvertD'où vient réellement cette valeur identiqueGénérateur faibleÉtat revenu en arrièreMésusage d'un UUID name-basedTronqué au stockagePas de contrainte d'unicité côté DBErreur d'implémentation

En pratique, il est plus rapide de partir du côté droit de ce schéma.

3. Schéma 1 : se présenter comme UUIDv4 tout en utilisant en réalité un PRNG faible

C’est le cas le plus fréquent.

  • Construire 128 bits avec un PRNG à usage général équivalent à Math.random()
  • Amorcer (seed) au démarrage avec time() ou le PID
  • Assembler soi-même « 32 chiffres hexadécimaux qui ressemblent au format UUID »

Même si cela ressemble à un UUID, si la source d’aléa est faible, la même séquence se reproduit dans un autre processus ou un autre nœud.

La RFC 9562 indique qu’un CSPRNG devrait être utilisé, à la fois pour l’unicité et pour la difficulté de prédiction des UUID. Il s’agit d’une exigence recommandée (SHOULD) : des exceptions peuvent être envisagées selon l’usage, mais si vous fabriquez vous-même des UUID avec un PRNG à usage général, vous devez être en mesure d’en justifier la raison. Elle précise en outre que l’état du CSPRNG devrait être correctement réamorcé (re-seed) lors d’un changement d’état tel qu’un fork de processus.8 La fonction uuid.uuid4() de Python indique elle aussi qu’elle génère des UUID aléatoires par une méthode cryptographiquement sûre.4

La conclusion pratique ici est simple.

  • Ne fabriquez pas vous-même vos UUID
  • Ne manipulez pas manuellement le seed aléatoire
  • Utilisez tel quel la bibliothèque standard, ou une implémentation largement répandue

Conserver un générateur maison parce que « c’est léger » ou « on l’utilise depuis toujours » est ce qui coûtera le plus cher par la suite.

4. Schéma 2 : faire revenir en arrière l’état du générateur via fork, snapshot ou clone

Le deuxième point dangereux, ce sont les opérations où l’état du générateur est dupliqué ou remis en arrière.

La RFC 9562 recommande explicitement de réamorcer (re-seed) après un fork, et explique que les implémentations sans stockage stable (stable storage) doivent générer plus fréquemment la clock sequence, le compteur et les données aléatoires, ce qui augmente la probabilité de doublons.89

Un raisonnement pratique en découle naturellement.

  • Restaurer plusieurs instances de la même image après avoir pris un snapshot de VM
  • Un générateur maison qui redémarre depuis le même état initial à chaque démarrage d’image de conteneur
  • Partager l’état du PRNG ou du compteur entre les forks de worker

Dans ce type d’exploitation, la séquence de génération des UUID peut se reproduire involontairement. La RFC ne dit pas littéralement que « les snapshots sont dangereux », mais il s’agit d’une précaution très pratique que l’on peut déduire de ses remarques sur le réamorçage après un fork et sur la gestion de l’état du générateur.89

Les mesures possibles sont les suivantes.

  • Ne pas conserver longtemps un état de génération de UUID maison
  • Réinitialiser immédiatement après un fork / clone / restore
  • Si possible, privilégier une implémentation qui puise à chaque fois dans l’aléa fourni par l’OS
  • Pour un générateur à haute fréquence, formaliser dans la spécification la gestion de l’état et le réamorçage

5. Schéma 3 : croire à tort que UUIDv3 / v5 donnent « un nouvel ID à chaque fois »

UUIDv3 / v5 ne sont pas des ID aléatoires résistants aux collisions. Ce sont des ID déterministes capables de régénérer le même ID à partir du même nom.

La RFC 9562 indique que les UUID générés à partir du même nom en format canonique, dans le même namespace, doivent être égaux.7 Avec ce type d’usage, un doublon n’est donc pas un accident : c’est un comportement conforme à la spécification.

  • Utiliser uuid5(NAMESPACE_URL, "https://example.com/users/42") comme une « attribution d’un nouvel identifiant » à chaque fois
  • Attribuer des identifiants à partir d’un namespace commun à tous les clients plus l’email, sans y intégrer le tenant
  • Supposer qu’attribuer à nouveau le même nom logique à chaque tentative (retry) donnera un ID différent

À l’inverse, si la canonicalisation du nom varie, on obtient des UUID différents pour un même objet. La RFC insiste elle aussi à plusieurs reprises sur le traitement de la représentation canonique.710

Dans cette famille, trois points comptent :

  • UUIDv3 / v5 ne sont pas une « attribution sans doublon », mais un principe « même entrée, même ID »
  • Ne pas laisser la conception du namespace dans le flou
  • Formaliser la canonicalisation du nom dans la spécification

6. Schéma 4 : implémenter soi-même des UUID temporels ou UUIDv8

UUIDv1 / v6 / v7 / v8 sont dangereux si l’on se contente d’en imiter l’apparence.

6.1 Traiter sans soin le node ou la clock sequence dans UUIDv1 / v6

Selon la RFC 9562, UUIDv6 est un réarrangement des champs de UUIDv1 destiné à améliorer la localité en base de données (DB locality) ; il manipule la clock sequence et le node. La RFC comporte en outre plusieurs mises en garde sur la résistance aux collisions de node en environnement distribué et sur la conservation de l’état.11912

La RFC va même jusqu’à écrire qu’avec l’avènement des machines virtuelles et des conteneurs, l’unicité de l’adresse MAC n’est plus garantie.5

Des conceptions comme

  • présumer que « c’est une adresse MAC, donc c’est forcément unique »
  • dupliquer le node ID en le figeant dans une image
  • remettre la clock sequence à une valeur fixe à chaque redémarrage

sont donc dangereuses.

6.2 Implémenter soi-même UUIDv7 en négligeant le counter rollover ou le clock rollback

UUIDv7 est très pratique, mais la RFC détaille avec soin la monotonicité (monotonicity) et la gestion du compteur lors d’une génération à haute fréquence. Elle précise aussi explicitement qu’il ne faut jamais renvoyer sciemment (knowingly) un doublon en cas de clock rollback ou de counter rollover.213

Cela signifie que des implémentations comme

  • attribuer un grand nombre d’ID dans la même milliseconde sans conception de compteur
  • continuer à générer sans rien faire lorsque l’horloge revient en arrière
  • plusieurs processus initialisant chacun séparément le même compteur interne

sont risquées.

6.3 Utiliser UUIDv8 avec la légèreté d’un « simple nouveau format de UUID »

UUIDv8 semble pratique, mais la RFC 9562 est tout à fait claire : l’unicité de UUIDv8 dépend de l’implémentation et ne doit jamais être présumée.3

Ainsi, un « UUID maison » qui

  • embarque un horodatage
  • embarque un shard ID
  • embarque une signification métier quelconque
  • et remplit le reste avec de l’aléa au petit bonheur

signifie que le document de conception devient lui-même la spécification d’unicité du UUID. C’est bien trop dangereux à introduire sans revue.

7. Schéma 5 : raccourcir le UUID en cours de route

Même si la génération est correcte, on peut tout casser au stade du stockage ou de la comparaison.

Voici des exemples typiques.

  • Utiliser seulement les 8 premiers caractères comme substitut de clé étrangère
  • Compresser un UUID de 128 bits dans un entier 64 bits
  • Une colonne de type chaîne trop courte, dont la fin se retrouve tronquée
  • Traiter la représentation abrégée utilisée dans les journaux ou à l’écran comme la clé unique elle-même

Ce qui compte ici, c’est que changer la représentation n’est pas mauvais en soi.

  • Retirer les tirets
  • Uniformiser en minuscules / majuscules
  • Conserver la valeur sous forme de 16 octets binaires

Des transformations comme celles-ci, qui ne perdent aucun des 128 bits, ne posent pas de problème. Ce qui est dangereux, c’est une transformation qui rogne la matière même de l’unicité.

C’est en particulier le cas lorsqu’un « ID abrégé lisible par un humain » créé séparément finit, sans qu’on s’en rende compte, par prendre le pas sur le UUID d’origine : ce genre de conception est propice aux incidents.

8. Schéma 6 : pas de contrainte d’unicité côté base de données

Et ce point est particulièrement important.

Même si les UUID sont suffisamment résistants aux collisions, si les doublons sont réellement inacceptables, la destination de stockage devrait elle aussi porter une contrainte d’unicité.

La documentation officielle de PostgreSQL explique qu’une contrainte unique (unique constraint) garantit que la valeur d’une colonne ou d’un ensemble de colonnes est unique sur l’ensemble de la table, et qu’une clé primaire (primary key) est un identifiant de ligne à la fois unique et non nul.6

La RFC 9562 indique elle aussi que, si les UUID peuvent offrir une unicité suffisante en pratique, une unicité globale véritable ne peut jamais être garantie de façon absolue. Elle ajoute que les usages où l’impact d’une collision est élevé devraient prévoir des mesures plus strictes.14

En pratique, cette combinaison constitue la base.

  • Utiliser les UUID comme des ID peu susceptibles d’entrer en collision
  • Conserver en base de données une dernière ligne de défense via UNIQUE / PRIMARY KEY
  • Concevoir le retry, l’idempotence et la journalisation d’incident pour le cas de doublon

Utiliser des UUID et se passer d’une contrainte d’unicité ne sont pas la même chose.

9. Une checklist pratique

Pour finir, voici une synthèse directement exploitable pour l’adoption ou un audit.

  1. Vérifier si vous générez vous-même les UUID Si vous pouvez migrer vers une API standard comme uuid4() / uuid7(), faites-le en priorité.
  2. Fixer la version de UUID dans la spécification Indiquez explicitement que v4/v7 sont basés sur l’aléa, v3/v5 sont déterministes, et v8 relève d’une spécification propriétaire.
  3. Faire l’inventaire de la gestion du seed et de l’état du générateur Assurez-vous de ne pas transmettre le même état après un fork, un redémarrage de worker, un snapshot ou un clone.
  4. Confirmer que la longueur complète est préservée au stockage Évitez d’utiliser une comparaison de préfixe ou un affichage abrégé comme clé réelle.
  5. Poser UNIQUE / PRIMARY KEY en base de données Un UUID est un mécanisme qui réduit une probabilité ; ce n’est pas une contrainte en soi.
  6. Rendre les doublons observables Ne masquez pas les erreurs de duplicate key ; rendez traçable le générateur / le nœud / le déploiement qui les a produites.

10. Conclusion

Les incidents de collision de UUID commencent généralement non pas parce que le UUID est faible, mais parce que l’implémentation ou l’exploitation brisent les hypothèses sur lesquelles repose le UUID.

  • Fabriquer soi-même des UUID avec un aléa faible
  • Faire revenir en arrière l’état après un fork ou un snapshot
  • Utiliser des UUID name-based pour attribuer de nouveaux identifiants
  • Implémenter soi-même v7 ou v8 à la légère
  • Raccourcir en cours de route et abandonner l’unicité
  • Retirer la contrainte d’unicité côté base de données

Faire l’une de ces choses revient presque à créer soi-même une situation propice aux collisions.

Lorsque vous découvrez un doublon, ce qu’il faut suspecter en premier n’est pas les mathématiques du UUID, mais le générateur, la gestion de l’état, le format de stockage et la conception des contraintes. En procédant dans cet ordre, la cause se réduit généralement assez vite.

11. Articles connexes

12. Références

  1. IETF RFC 9562, Section 5.4 UUID Version 4. Sur l’espace aléatoire de 122 bits de UUIDv4.  2

  2. IETF RFC 9562, Section 5.7 UUID Version 7. Sur la conception de l’horodatage, des bits aléatoires et du compteur de UUIDv7.  2 3

  3. IETF RFC 9562, Section 5.8 UUID Version 8. Sur le fait que l’unicité de UUIDv8 dépend de l’implémentation et ne doit pas être présumée.  2 3

  4. Python 3.14 documentation, uuid module. Sur la génération cryptographiquement sûre de uuid4(), le comportement déterministe de uuid5(), et les propriétés de uuid7() / uuid8() 2 3

  5. IETF RFC 9562, Universally Unique IDentifiers (UUIDs). Document de référence pour le format des UUID, chaque version, et l’ensemble des bonnes pratiques.  2

  6. PostgreSQL documentation, Constraints. Sur la garantie de l’unicité via les contraintes UNIQUE et PRIMARY KEY.  2

  7. IETF RFC 9562, Section 6.5 Name-Based UUID Generation. Sur le fait qu’un même namespace + un même nom produisent le même UUID, et sur l’importance de la canonicalisation.  2 3

  8. IETF RFC 9562, Section 6.9 Unguessability. Sur l’usage d’un CSPRNG et le réamorçage après un fork.  2 3

  9. IETF RFC 9562, Section 6.3 UUID Generator States. Sur la gestion du stockage stable et de l’état du générateur.  2 3

  10. IETF RFC 9562, Section 5.5 UUID Version 5. Sur la spécification des UUID name-based construits à partir d’un namespace et d’un nom canonique. 

  11. IETF RFC 9562, Section 5.6 UUID Version 6. Sur le node / la clock sequence / la localité en base de données de UUIDv6. 

  12. IETF RFC 9562, Section 6.4 Distributed UUID Generation. Sur la résistance aux collisions de node en environnement distribué. 

  13. IETF RFC 9562, Section 6.2 Monotonicity and Counters. Sur les précautions relatives au clock rollback, au counter rollover et à la génération par lots. 

  14. IETF RFC 9562, Sections 6.7 and 6.8. Sur la logique de la résistance aux collisions et de l’unicité globale. 

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Conseil technique et revue de conception

La question des collisions de UUID dépasse la simple compréhension de la norme : elle touche aussi aux sources d'aléa, à l'exploitation des snapshots, aux contraintes de base de données et à l'idempotence, ce qui en fait un sujet pertinent pour une revue de conception ou une consultation technique.

Analyse des bugs et des causes

Lors d'un incident réel de doublons, il faut déterminer si « le UUID lui-même est en cause » ou si « l'implémentation ou l'exploitation sont en cause » ; structurer les angles d'investigation et concevoir des mesures de prévention de la récidive sont essentiels.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Les UUID n'entrent-ils vraiment jamais en collision ?
En usage normal, ils sont suffisamment résistants aux collisions. Selon la RFC 9562, UUIDv4 dispose de 122 bits d'espace aléatoire, et UUIDv7 est lui aussi défini en supposant que les 74 bits hors horodatage servent à des données aléatoires ou à un compteur garantissant l'unicité. Tant que l'on utilise normalement une implémentation sérieuse, comme uuid4() en Python, ces garanties sont assez solides. Cependant, la RFC 9562 elle-même précise qu'un UUID ne peut jamais garantir de façon absolue une unicité globale véritable, et que les usages où l'impact d'une collision est élevé doivent prévoir des mesures plus strictes. C'est précisément pour cela que la contrainte d'unicité côté base de données constitue la dernière ligne de défense.
Pourquoi des doublons de UUID se produisent-ils ?
La plupart des doublons de UUID observés en pratique ne viennent pas d'un problème de la norme elle-même, mais de cas où l'implémentation ou l'exploitation brisent les conditions de génération que la norme suppose. Les schémas typiques sont au nombre de six : fabriquer soi-même des UUID avec un seed fixe ou un PRNG faible ; faire revenir en arrière l'état du générateur après un fork, un snapshot de VM ou une duplication de conteneur ; utiliser UUIDv3 / v5 en croyant à tort obtenir « un nouvel ID à chaque fois » ; implémenter soi-même des UUID temporels ou UUIDv8 en traitant sans soin le clock rollback ou le compteur ; tronquer le UUID en cours de route et ainsi abandonner son unicité ; et ne pas poser de contrainte d'unicité en base de données, ce qui laisse les doublons s'infiltrer silencieusement.
L'usage de UUIDv3 ou UUIDv5 provoque-t-il des doublons ?
UUIDv3 / v5 ne sont pas des ID aléatoires résistants aux collisions, mais des ID déterministes capables de régénérer le même ID à partir du même nom. La RFC 9562 stipule qu'un UUID généré à partir du même namespace et du même canonical name doit être identique ; obtenir le même UUID à partir de la même entrée n'est donc pas un accident, mais un comportement conforme à la spécification. Les utiliser pour attribuer de nouveaux identifiants est donc un mésusage. À l'inverse, si la canonicalisation du nom varie, on obtient un UUID différent pour un même objet. Il est essentiel de formaliser, dans la spécification, la conception du namespace et les règles de normalisation du nom.
Que faire pour éviter les doublons de UUID ?
Tout d'abord, ne générez pas vous-même les UUID : appuyez-vous sur une API standard comme uuid4() / uuid7(), ou sur une implémentation largement utilisée. Fixez, dans la spécification, la version de UUID employée, et veillez à ne pas transmettre l'état du générateur après un fork, un redémarrage de worker, un snapshot ou un clone. Conservez et comparez toujours la longueur complète de 128 bits, sans jamais utiliser une comparaison de préfixe ou un affichage tronqué comme clé réelle. Ensuite, si les doublons sont réellement inacceptables, posez une contrainte UNIQUE / PRIMARY KEY en base de données, et ne masquez pas les erreurs de duplicate key : rendez traçable le générateur ou le nœud qui les a produites.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog