Aller au contenu
Toova
Tous les outils

UUID v4 vs v7 — Différences et quand utiliser chacun

Toova

UUID v4 est le choix par défaut pour les clés primaires distribuées depuis plus d'une décennie. Générez une valeur aléatoire de 128 bits, formatez-la en huit groupes hexadécimaux, c'est fait. Aucune coordination requise, probabilité de collision pratiquement nulle, fonctionne partout. Alors pourquoi UUID v7 — standardisé dans la RFC 9562 en 2024 — se répand-il rapidement dans les systèmes de production en 2026 ?

La réponse courte : les performances des bases de données. UUID v4 détruit la localité des index B-tree. UUID v7 corrige cela tout en conservant tout ce que les développeurs aiment des UUID. Cet article explique ce qu'est réellement chaque version, pourquoi la différence compte à grande échelle, quand choisir chacune, et comment migrer sans interruption de service.

Besoin de générer des UUID maintenant ? Essayez le générateur UUID Toova, qui prend en charge v4 et v7 en lot. Pour d'autres identifiants aléatoires, le générateur de chaînes aléatoires et le générateur de mots de passe couvrent les jetons plus courts.

UUID v4 — Pure aléatoire

UUID version 4 utilise un générateur de nombres pseudo-aléatoires cryptographiquement sûr (CSPRNG) pour remplir 122 des 128 bits. Les six bits restants sont fixes : quatre bits encodent la version (0100) et deux bits encodent la variante (10). Le résultat ressemble à ceci :

f47ac10b-58cc-4372-a567-0e02b2c3d479
              ^^^^
              version = 4 (aléatoire)

L'aléatoire est la fonctionnalité. Deux systèmes indépendants peuvent générer des UUID sans coordination et ne jamais entrer en collision — la probabilité d'une collision dans un ensemble de 2,71 quadrillions d'UUID v4 est d'environ 50 %, ce qui signifie que pour toute application pratique le risque est négligeable. Vous n'avez pas besoin d'un serveur d'ID centralisé, d'une séquence de base de données ou d'un verrou distribué.

Comment v4 est généré

import { v4 as uuidv4 } from 'uuid';
const id = uuidv4();
// => 'f47ac10b-58cc-4372-a567-0e02b2c3d479'

La bibliothèque uuid (JavaScript), le uuid.uuid4() de Python, le google/uuid de Go, et chaque environnement d'exécution majeur délèguent au CSPRNG du système d'exploitation — /dev/urandom sur Linux, CryptGenRandom sur Windows. Le coût de génération est effectivement nul.

Le problème : l'insertion aléatoire détruit les index B-tree

Un index B-tree maintient les données triées. Lorsque vous insérez une nouvelle ligne, la base de données trouve où la nouvelle clé s'insère dans l'ordre trié et la place à cet endroit. Si chaque nouvelle clé est aléatoire, elle atterrit à une position aléatoire dans l'index — ce qui signifie que chaque insertion doit charger une page différente du disque dans le pool de tampons. À faible volume c'est invisible. À fort volume (des millions de lignes, un taux d'INSERT élevé), cela crée un schéma appelé fragmentation d'index : l'index se remplit de pages à moitié vides car chaque insertion frappe un emplacement différent, et le pool de tampons change constamment parce que l'ensemble de travail actif couvre tout l'index plutôt qu'une tranche récente prévisible.

Les symptômes en production : la latence des INSERT augmente, la charge d'autovacuum augmente (PostgreSQL), la pression des points de contrôle croît, et les performances de lecture des enregistrements récents se dégradent parce que « récent » ne correspond plus à aucune localité dans l'index. Ce n'est pas hypothétique — c'est un point de douleur bien documenté sur de grandes tables PostgreSQL et MySQL avec des clés primaires UUID v4.

UUID v7 — Aléatoire ordonné dans le temps

UUID v7 a été conçu explicitement pour résoudre le problème de fragmentation B-tree. Il encode un timestamp Unix en millisecondes de 48 bits dans les bits les plus significatifs, suivi des bits de version, 12 bits aléatoires, des bits de variante et 62 autres bits aléatoires. Aléatoire total : 74 bits — encore bien plus que nécessaire pour prévenir les collisions.

018f4e6b-a23c-7d45-9abc-0e02b2c3d479
 ^^^^^^^^^^^^^^
 Timestamp Unix 48 bits (précision ms)
                   ^
                   version = 7

Parce que le timestamp occupe les bits de poids fort, les UUID générés plus tard se trient après les UUID générés plus tôt. Les insertions sont toujours ajoutées au bord droit de l'index. La base de données n'a besoin de maintenir que les pages d'index les plus récentes dans le pool de tampons, pas l'index entier. Aux taux d'INSERT élevés, cela seul peut réduire la latence d'écriture de 30 à 60 % et réduire les E/S d'un ordre de grandeur sur des tables de dizaines de millions de lignes.

Comment v7 est généré

import { v7 as uuidv7 } from 'uuid';
const id = uuidv7();
// => '018f4e6b-a23c-7d45-9abc-0e02b2c3d479'

La même bibliothèque uuid a ajouté le support v7 dans la version 10. La bibliothèque standard de Python l'a ajouté dans la 3.14. PostgreSQL 17 inclut uuidv7() comme fonction intégrée. Si vous êtes sur une stack plus ancienne, plusieurs petites bibliothèques fournissent la génération v7 sans dépendances.

Monotonicité sub-milliseconde

Que se passe-t-il lorsque deux UUID v7 sont générés dans la même milliseconde ? La RFC 9562 permet aux implémentations d'incrémenter un compteur monotone dans les bits aléatoires pour garantir l'ordre dans la même milliseconde. La bibliothèque uuid le fait par défaut. Résultat : même si 10 000 ID sont générés en une milliseconde, ils se trient encore correctement.

Le problème de fragmentation d'index B-tree en détail

Pour comprendre pourquoi cela compte, considérez ce qui se passe à 10 000 insertions par seconde avec une clé primaire UUID v4 sur une table de 100 millions de lignes existantes :

  • Chaque insertion génère une clé aléatoire de 128 bits qui tombe à une position aléatoire parmi les 100 millions d'entrées existantes.
  • La base de données doit charger la page B-tree spécifique contenant cette position dans le pool de tampons.
  • Avec 100 millions de lignes et des pages de 8 Ko, l'index couvre environ 100 000 pages. Chaque seconde, 10 000 pages différentes sont nécessaires — bien plus que ce qu'un shared_buffers typique de 8 Go peut contenir pour cet index seul.
  • Chaque défaut de cache entraîne une lecture disque. À 10 000 insertions/s, cela peut générer des milliers de lectures disque aléatoires par seconde, saturant même les SSD sur de grandes tables.

Avec UUID v7, les 10 000 insertions par seconde atterrissent toutes sur la page feuille la plus à droite (ou une poignée de pages récentes). Le pool de tampons n'a besoin de maintenir que ces quelques pages actives. Le taux de succès du cache pour les écritures approche 100 %. L'amplification des écritures chute considérablement.

Le même avantage s'applique aux analyses par plage : WHERE created_at BETWEEN x AND y sur une table v4 nécessite un balayage complet de l'index ou un index de timestamp séparé. Sur une table v7, la clé primaire elle-même est l'index de timestamp — la requête peut aller directement à la bonne plage.

Comment utiliser UUID v7 dans PostgreSQL

-- Fonctionne nativement avec le type UUID dans PostgreSQL 16+
CREATE TABLE events (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- v4 (aléatoire)
  -- Passer à UUIDv7 via extension ou génération côté application
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Avec UUIDv7 : le timestamp est déjà intégré, donc
-- cette colonne created_at séparée est souvent redondante.

PostgreSQL 17 embarque uuidv7() nativement. Pour PostgreSQL 14–16, l'extension pg_uuidv7 fournit la même fonction. Le type de données UUID stocke v4 et v7 de manière identique — 16 octets, sans surcoût. La seule différence est le motif de bits qui détermine l'ordre de tri.

Une conséquence utile : puisque le timestamp est intégré, de nombreuses tables n'ont plus besoin d'une colonne created_at séparée pour l'ordonnancement ou l'affichage. Vous pouvez extraire le timestamp d'un UUID v7 avec un seul appel de fonction. Cela réduit la complexité du schéma et élimine une écriture par insertion.

UUID v4 vs v7 — Compromis

Propriété UUID v4 UUID v7
Bits d'aléatoire 122 74
Ordre de tri Aléatoire Chronologique
Perf INSERT B-tree Médiocre (fragmentation) Excellente (séquentiel)
Divulgation timestamp Aucune Oui (précision ms)
Norme RFC RFC 4122 (2005), RFC 9562 (2024) RFC 9562 (2024)
Support bibliothèque Universel En forte croissance (2024–2026)
created_at intégré Non Oui

Guide de migration — v4 vers v7

Migrer une table existante des clés primaires UUID v4 vers v7 est une opération en plusieurs étapes. La contrainte clé : vous ne pouvez pas modifier la valeur d'une clé primaire référencée par des clés étrangères sans mettre à jour toutes les tables référentes également. Prévoyez une fenêtre de maintenance ou utilisez une approche en double écriture.

-- 1. Ajouter la nouvelle colonne
ALTER TABLE orders ADD COLUMN id_v7 UUID;

-- 2. Remplissage des lignes existantes (utiliser created_at comme
--    source du timestamp ; utiliser une lib UUIDv7 dans l'application)
UPDATE orders SET id_v7 = generate_uuidv7(created_at);

-- 3. Vérifier qu'il ne reste pas de NULL
SELECT COUNT(*) FROM orders WHERE id_v7 IS NULL;

-- 4. Échanger les colonnes (nécessite une courte fenêtre de maintenance)
ALTER TABLE orders ALTER COLUMN id_v7 SET NOT NULL;
ALTER TABLE orders ALTER COLUMN id_v7 SET DEFAULT generate_uuidv7(now());
ALTER TABLE orders RENAME COLUMN id TO id_v4_old;
ALTER TABLE orders RENAME COLUMN id_v7 TO id;
ALTER TABLE orders ADD PRIMARY KEY (id);

Pour les migrations sans interruption sur les tables à fort trafic, l'approche recommandée est :

  1. Ajoutez la nouvelle colonne id_v7 avec une valeur par défaut côté serveur qui génère des UUID v7 dorénavant.
  2. Remplissez les anciennes lignes par lots pendant les périodes de faible trafic, en utilisant le timestamp created_at existant comme graine pour la partie timestamp du v7.
  3. Mettez à jour toutes les colonnes de clés étrangères dans les tables référentes pour pointer vers id_v7.
  4. Renommez les colonnes et supprimez l'ancienne contrainte de clé primaire lors d'une courte fenêtre de maintenance.

L'étape de remplissage est la plus longue. À 10 000 lignes par lot avec 50 ms de pause entre les lots, une table de 100 millions de lignes prend environ 8 heures. Commencez tôt.

Impact réel

Plusieurs équipes d'ingénierie ont publié des benchmarks comparant UUID v4 et v7 sur des ensembles de données de taille production. Les résultats constants :

  • Débit INSERT : amélioration de 2 à 5x sur les tables de 50M+ lignes lors du passage de v4 à v7, le gain augmentant avec la taille de la table.
  • Latence d'écriture p99 : Passe de centaines de millisecondes (v4, sous charge) à quelques millisecondes (v7) sur le même matériel.
  • Taille de l'index : Les index B-tree sur les colonnes v7 sont 15 à 30 % plus petits que les index v4 équivalents sur les mêmes données car la fragmentation laisse moins de pages à moitié vides.
  • Efficacité du pool de tampons : Le taux de succès du shared_buffers pour l'index de clé primaire passe de ~40 % (v4, grande table) à ~99 % (v7), car seules les pages récentes doivent rester actives.

Les gains sont négligeables en dessous d'environ 1 million de lignes. Si votre table reste petite, gardez v4 pour la simplicité. Au-dessus de 10 millions de lignes avec un taux d'INSERT significatif, v7 est le meilleur défaut.

Quand utiliser chacun

Utilisez UUID v7 lorsque :

  • Vous concevez un nouveau schéma et la table grandira beaucoup (10M+ lignes).
  • La table a un taux d'INSERT élevé — événements, logs, commandes, messages, notifications.
  • Vous souhaitez une clé primaire qui double comme timestamp de création (élimine une colonne).
  • Vous êtes sur PostgreSQL 17, MySQL 8.0+, ou un ORM moderne prenant en charge la génération v7.
  • Trier par ID est sémantiquement équivalent à trier par heure de création — ce qui sera vrai pour la plupart des tables à ajouts fréquents.

Utilisez UUID v4 lorsque :

  • L'identifiant est exposé aux utilisateurs et ne doit pas révéler l'heure de création (codes d'invitation, liens de partage, identifiants de facturation).
  • La table est petite et stable — aucun gain de performance ne justifie le coût de migration.
  • Vous générez des ID dans un contexte où le timestamp de création est sensible (enregistrements utilisateur privés dans un produit qui se distingue par ses métriques de croissance).
  • Vous avez besoin de quelque chose comme identifiant ou jeton à usage unique — utilisez un générateur de secrets dédié, pas une version UUID quelconque.

Résumé

UUID v4 est aléatoire, confidentiel et universellement supporté. UUID v7 est ordonné dans le temps, optimisé pour les bases de données, et est maintenant standard dans les principales bibliothèques et bases de données. Pour les nouveaux schémas avec des tables grandes ou en forte croissance, v7 est le meilleur défaut. Pour les ID exposés aux utilisateurs où la divulgation du timestamp est une préoccupation, v4 reste le bon choix.

Générez les deux versions instantanément avec le générateur UUID Toova — sans compte requis. Pour les jetons plus courts, le générateur de chaînes aléatoires produit des ID alphanumériques de n'importe quelle longueur.