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ément | UUID v4 | UUID v7 | ULID | Nano ID | |
|---|---|---|---|---|---|
| Exemple | 1042 | f47ac10b-58cc-4372-a567-0e02b2c3d479 | 01926f3a-7c2b-7d4e-9a1f-3b5c8d2e6f10 | 01J9QKMZ1BFN79M7SVBJ6JWVRG | V1StGXR8_Z5jdHi6B-myT |
| Taille binaire | 4 ou 8 octets | 16 octets | 16 octets | 16 octets | Stocké sous forme de texte |
| Longueur du texte | Jusqu'à 10 ou 19 chiffres | 36 | 36 | 26 | 21 par défaut |
| Bits aléatoires | Aucun | 122 | Jusqu'à 74 | 80 | 126 par défaut |
| Tri par date de création | Oui | Non | Oui, à la milliseconde | Oui, à la milliseconde | Non |
| Généré sans la base de données | Non | Oui | Oui | Oui | Oui |
| Spécification | Fonction de la base de données | RFC 9562 | RFC 9562 | Spécification communautaire | Bibliothèque |
| Révèle | Le nombre et l'ordre | Rien | La date de création | La date de création | Rien |
Comment chacun est construit
- L'auto-incrément est un compteur tenu par la base de données. Un
INTsigné de 32 bits s'arrête à 2 147 483 647 et unBIGINTsigné à 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ésrand_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 derand_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éma | Bits aléatoires | ID pour 50 % de risque de doublon | Risque de doublon parmi 1 milliard d'ID |
|---|---|---|---|
| UUID v4 | 122 | 2,7 × 10^18 | 9,4 × 10^-20 |
| Nano ID, 21 caractères | 126 | 1,1 × 10^19 | 5,9 × 10^-21 |
| Nano ID, 10 caractères | 60 | 1,3 × 10^9 | Environ 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 :
uuiddans PostgreSQL,UUIDdans 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
uniqueidentifierde 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
CHARouVARCHAR, 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 :
BIGINTen 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.