UUID vs ULID vs Nano ID vs auto-incrément : le comparatif

Cinq schémas d'identifiants courants comparés sur ce qui compte en pratique : taille, ordre, risque de collision, comportement en base de données et ce qu'un ID laisse deviner.

Le choix d'un format d'identifiant se résume à trois questions. Les ID doivent-ils se trier par date de création ? Apparaîtront-ils dans des URL ou des API publiques ? Et la base de données les stockera-t-elle dans une colonne native de 128 bits ou sous forme de texte ? UUID v7, normalisé par la RFC 9562 en mai 2024, a changé la réponse pour de nombreux projets : il offre l'ordre chronologique qui a fait le succès de ULID, dans un format que toutes les colonnes et bibliothèques UUID acceptent déjà.

Vue d'ensemble

Auto-incrémentUUID v4UUID v7ULIDNano ID
Exemple1042f47ac10b-58cc-4372-a567-0e02b2c3d47901926f3a-7c2b-7d4e-9a1f-3b5c8d2e6f1001J9QKMZ1BFN79M7SVBJ6JWVRGV1StGXR8_Z5jdHi6B-myT
Taille binaire4 ou 8 octets16 octets16 octets16 octetsStocké sous forme de texte
Longueur du texteJusqu'à 10 ou 19 chiffres36362621 par défaut
Bits aléatoiresAucun122Jusqu'à 7480126 par défaut
Tri par date de créationOuiNonOui, à la millisecondeOui, à la millisecondeNon
Généré sans la base de donnéesNonOuiOuiOuiOui
SpécificationFonction de la base de donnéesRFC 9562RFC 9562Spécification communautaireBibliothèque
RévèleLe nombre et l'ordreRienLa date de créationLa date de créationRien

Comment chacun est construit

  • L'auto-incrément est un compteur tenu par la base de données. Un INT signé de 32 bits s'arrête à 2 147 483 647 et un BIGINT signé à 9 223 372 036 854 775 807. L'application ne connaît l'ID qu'après l'insertion, sauf si elle réserve d'abord des valeurs dans une séquence.
  • UUID v4 se compose de 122 bits aléatoires et de 6 bits fixes qui indiquent la version et la variante.
  • UUID v7 commence par un timestamp Unix de 48 bits en millisecondes, suivi de la version, de 12 bits appelés rand_a, de la variante et de 62 bits appelés rand_b. La RFC 9562 autorise un générateur à utiliser une partie de ces bits comme compteur, pour que les ID créés dans la même milliseconde sortent malgré tout dans l'ordre. Le générateur de ce site le fait avec les 12 bits de rand_a.
  • ULID associe un timestamp de 48 bits en millisecondes à 80 bits aléatoires, le tout écrit sur 26 caractères en Base32 de Crockford : 10 pour la date et 16 pour la partie aléatoire. L'alphabet exclut I, L, O et U et ne tient pas compte de la casse. En mode monotone, un ULID créé dans la même milliseconde que le précédent reprend la partie aléatoire de celui-ci et lui ajoute un.
  • Nano ID est une chaîne de caractères aléatoires, par défaut 21 caractères tirés de l'alphabet de 64 caractères A-Z a-z 0-9 _ -. Chaque caractère porte 6 bits, soit 126 bits au total. La longueur comme l'alphabet peuvent être modifiés, et le nombre de bits aléatoires change avec eux.

Un ULID et un UUID sont tous deux des nombres de 128 bits, simplement écrits différemment. 01J9QKMZ1BFN79M7SVBJ6JWVRG et 01926f3a-7c2b-7d4e-9a1f-3b5c8d2e6f10 représentent la même valeur, en Base32 et en hexadécimal. C'est pourquoi un ULID peut être stocké dans une colonne UUID native, comme le type uuid de PostgreSQL, et y conserve son ordre de tri. Un ULID qui n'est pas né sous forme d'UUID v7 porte toutefois des bits quelconques à l'emplacement de la version et de la variante : un validateur UUID strict peut donc le rejeter.

Le risque de collision, calculé

Pour des ID purement aléatoires, l'approximation du paradoxe des anniversaires s'applique : avec n ID tirés parmi N = 2 puissance le nombre de bits aléatoires, la probabilité d'au moins un doublon vaut environ 1 − e^(−n²/2N), et elle atteint 50 % vers n = 1,18 × √N.

SchémaBits aléatoiresID pour 50 % de risque de doublonRisque de doublon parmi 1 milliard d'ID
UUID v41222,7 × 10^189,4 × 10^-20
Nano ID, 21 caractères1261,1 × 10^195,9 × 10^-21
Nano ID, 10 caractères601,3 × 10^9Environ 35 %

C'est la dernière ligne qu'il faut retenir : raccourcir un Nano ID pour obtenir de plus jolies URL fait vite chuter la résistance aux collisions. Faites donc ce calcul avant de choisir une longueur.

Pour UUID v7 et ULID, deux ID ne peuvent entrer en collision que s'ils portent la même milliseconde : n est alors le nombre d'ID créés dans une même milliseconde par des générateurs qui ne partagent pas de compteur. Avec 1 000 ID de ce type dans la même milliseconde, la probabilité d'un doublon est d'environ 2,6 × 10^-17 avec 74 bits aléatoires, et de 4,1 × 10^-19 avec les 80 bits de ULID. En pratique, le vrai risque pour tous ces schémas est une source d'aléa faible, comme Math.random, et non l'arithmétique.

Comportement des index en base de données

Les clés primaires se trouvent en général dans un index B-tree. Quand chaque nouvelle clé est plus grande que la précédente, comme avec l'auto-incrément, UUID v7 et ULID, les insertions arrivent sur le bord droit de l'index, les pages se remplissent complètement et les lignes écrites récemment restent regroupées. Les clés aléatoires, UUID v4 et Nano ID, arrivent n'importe où : les pages se divisent et restent en partie vides, et dès que l'index ne tient plus en mémoire, chaque insertion peut entraîner une lecture aléatoire.

L'effet est plus marqué dans MySQL et MariaDB avec InnoDB, qui stockent les lignes elles-mêmes dans l'ordre de la clé primaire : des clés aléatoires dispersent alors la table, et pas seulement l'index. PostgreSQL conserve les lignes dans un heap séparé, si bien que la différence se limite à l'index.

Le format de stockage compte autant que le schéma choisi :

  • Stockez les UUID et les ULID dans un type natif de 16 octets quand il existe : uuid dans PostgreSQL, UUID dans MariaDB 10.7 et versions ultérieures, BINARY(16) dans MySQL. CHAR(36) occupe plus de deux fois plus de place et alourdit chaque index.
  • Dans MySQL, ne passez pas l'option d'inversion (swap flag) à UUID_TO_BIN(uuid, 1) pour des valeurs v7. Elle déplace les champs de temps au profit des UUID v1 et casserait l'ordre des v7.
  • Le type uniqueidentifier de SQL Server compare d'abord les six derniers octets : des valeurs v7 stockées dans ce type ne se trient donc pas par date. Les détails se trouvent dans UUID vs GUID.
  • Les Nano ID sont des chaînes et se stockent en CHAR ou VARCHAR, avec une collation binaire si la casse doit être prise en compte.

Les URL et ce qu'un ID laisse deviner

Les cinq formats peuvent figurer dans une URL sans encodage pourcent : les chiffres hexadécimaux, les tirets, le Base32 de Crockford et l'alphabet de Nano ID sont tous des caractères non réservés au sens de la RFC 3986. La différence tient à la longueur (36, 26 ou 21 caractères) et à la casse. Les UUID et les ULID sont insensibles à la casse et survivent donc aux systèmes qui la modifient ; les Nano ID, non.

Ce qu'un ID révèle est souvent le critère décisif :

  • L'auto-incrément révèle le volume. Si votre facture porte le numéro 1042 aujourd'hui et 1342 une semaine plus tard, l'entreprise a émis environ 300 factures cette semaine-là. Les ID séquentiels rendent aussi trivial l'essai des enregistrements voisins, ce qui transforme le moindre contrôle d'accès manquant en fuite de données.
  • UUID v7 et ULID révèlent la date de création de l'enregistrement, à la milliseconde près. Pour des commandes, c'est rarement un problème ; pour des comptes utilisateurs ou des dossiers médicaux, cela peut l'être. Collez un UUID v7 dans l'inspecteur du générateur d'UUID v7 : il affiche le timestamp.
  • UUID v4 et Nano ID ne révèlent rien du moment ni du lieu de leur création.

Aucun de ces ID n'est un secret. Un ID impossible à deviner rend le sondage plus difficile, mais il ne remplace pas la vérification du droit de l'utilisateur à consulter l'enregistrement.

Lequel choisir

  • Tables internes d'une seule base de données, jamais montrées aux utilisateurs : BIGINT en auto-incrément. C'est la clé la plus petite et la plus simple.
  • Clés primaires créées par l'application ou partagées entre plusieurs services, lorsqu'une colonne UUID native est disponible : UUID v7.
  • Identifiants publics dont la date de création doit rester privée, ID de corrélation et noms de fichiers : UUID v4, avec le générateur d'UUID v4 ou crypto.randomUUID().
  • ID courts dans les URL, quand l'ordre n'a pas d'importance : Nano ID à sa longueur par défaut ou presque.
  • ULID lorsque votre stack l'utilise déjà ou que vous voulez sa forme texte plus courte et insensible à la casse. Pour un nouveau projet, UUID v7 couvre les mêmes besoins dans un format standard.

Une combinaison courante réunit le meilleur des deux : une clé primaire BIGINT ou UUID v7 pour les jointures et les index, plus un ID public aléatoire distinct, doté d'un index unique, pour les URL et les API.

Questions Fréquentes

ULID est-il meilleur que UUID v7 ?

Ils sont construits de la même façon : un timestamp de 48 bits en millisecondes suivi de bits aléatoires, soit 128 bits au total. UUID v7 est un standard de l'IETF compatible avec les colonnes et les validateurs UUID. ULID a une forme texte plus courte et insensible à la casse, et 80 bits aléatoires au lieu de 74 au maximum.

Un Nano ID est-il aussi unique qu'un UUID ?

À sa longueur par défaut de 21 caractères, oui : il possède 126 bits aléatoires contre 122 pour UUID v4. Les Nano ID plus courts perdent vite cet avantage : avec 10 caractères, le risque de doublon atteint 50 % après environ 1,3 milliard d'ID.

Des UUID v7 peuvent-ils entrer en collision ?

Seuls des ID créés dans la même milliseconde par des générateurs indépendants peuvent entrer en collision. Avec 1 000 ID dans une milliseconde, la probabilité est d'environ 2,6 × 10^-17 : une source d'aléa fiable compte donc bien plus que le format.

Pourquoi les UUID aléatoires sont-ils lents comme clés primaires ?

Chaque nouvelle clé arrive à un endroit aléatoire de l'index B-tree, ce qui provoque des divisions de pages, des pages à moitié vides et davantage de lectures disque sur les grandes tables. Les ID ordonnés dans le temps, comme UUID v7 ou ULID, sont au contraire ajoutés à la fin.