- Les UUID v4 sont très aléatoires, ce qui entraîne une inefficacité des index et des E/S excessives ; utilisés comme clés primaires dans PostgreSQL, ils provoquent donc une baisse de performances
- À cause des insertions aléatoires, les scissions de pages (page splits) et la fragmentation des index deviennent fréquentes, ce qui augmente la taille des journaux WAL et la latence en écriture
- Les UUID ont une taille de 16 octets, soit deux fois plus qu’un
bigint, ce qui réduit le taux de réussite du cache et entraîne un gaspillage de mémoire
- Ils sont souvent pris à tort pour des identifiants de sécurité, mais selon la RFC 4122, un UUID n’est pas un mécanisme de sécurité destiné à empêcher la devinette
- Pour une nouvelle base de données, il est recommandé d’utiliser des clés basées sur des séquences entières et, si ce n’est pas possible, des UUID v7 ordonnés dans le temps
Les problèmes de performance des UUID v4
- Les bases de données PostgreSQL utilisant des clés primaires UUID v4 ont montré de manière constante, ces dix dernières années, une dégradation des performances et des E/S excessives
- Un UUID v4 est généré avec 122 bits aléatoires, ce qui empêche tout tri efficace dans l’index
- Lors de l’insertion, il n’est pas stocké sur des pages séquentielles, ce qui provoque des accès aléatoires ; les mises à jour et suppressions exigent elles aussi des parcours inefficaces
- Les index B-Tree supposent des données triées, mais les UUID v4 n’ont aucun ordre naturel, d’où une faible efficacité à l’insertion
- Chaque insertion est écrite sur une page arbitraire, ce qui provoque souvent des scissions de pages intermédiaires
- Il en résulte une latence en écriture plus élevée et une augmentation du WAL
Structure des UUID et alternatives
- Un UUID est un identifiant de 128 bits (16 octets) stocké dans PostgreSQL sous forme de type
uuid binaire
- UUID v4 repose sur des bits aléatoires, tandis que UUID v7 inclut un horodatage dans les 48 premiers bits, ce qui améliore l’efficacité des index
- PostgreSQL 18, prévu pour 2025, doit prendre en charge nativement les UUID v7
- Les UUID v7 peuvent être triés chronologiquement, ce qui améliore la densité des pages et l’efficacité du cache
Pourquoi choisir un UUID, et ses limites
- Les UUID sont utilisés lorsqu’il faut générer des identifiants sans collision dans des environnements à plusieurs clients ou en microservices
- Exemple : génération simultanée d’ID sur plusieurs instances de base de données
- Toutefois, la RFC 4122 précise qu’« il ne faut pas supposer qu’un UUID est difficile à deviner », ce qui en fait un mauvais choix comme identifiant de sécurité
- La probabilité de collision atteint 50 % après la génération de 2.71×10¹⁸ UUID, donc le risque réel reste faible, mais le coût en performances est élevé
Inefficacité des UUID en espace et en E/S
- Un UUID occupe deux fois plus d’espace qu’un
bigint (8 octets) et quatre fois plus qu’un int (4 octets)
- Sur de très grandes tables, cela se traduit par une augmentation de l’espace de stockage et des temps de sauvegarde et de restauration plus longs
- Résultats d’une expérience sur la densité des pages d’index
- index
integer : 97,64 %
- index UUID v4 : 79,06 %
- index UUID v7 : 90,09 %
- Lors d’un test Cybertec, une recherche sur un index UUID v4 a nécessité 8,5 millions d’accès supplémentaires à des pages et montré une hausse des E/S de 31 229 %
- Dans les mêmes conditions, l’index
bigint a nécessité 27 332 accès au buffer, contre 8 562 960 pour l’UUID v4
Impact sur le cache et la mémoire
- En raison de leur distribution aléatoire, les UUID ont un faible taux de réussite du buffer cache (cache hit ratio)
- Il faut charger davantage de pages en cache, et les pages nécessaires sont fréquemment évincées (eviction)
- Cette baisse d’efficacité du cache provoque une latence des requêtes et une augmentation de l’utilisation mémoire
- Pour maintenir les performances, il est recommandé de reconstruire régulièrement les index (
REINDEX CONCURRENTLY) ou d’utiliser pg_repack
Mesures d’atténuation des performances
- Augmenter la mémoire : il est recommandé de disposer d’une RAM égale à 4 fois la taille de la base (ex. : base de 25 Go → 128 Go de mémoire)
- Ajuster
work_mem : allouer davantage de mémoire aux opérations de tri peut améliorer les performances
- Dans un environnement Rails, le paramètre
implicit_order_column permet d’utiliser un champ triable comme created_at à la place d’un UUID
- La commande
CLUSTER permet de réorganiser une table selon un champ triable, mais elle nécessite un verrou exclusif
Recommandation : clés entières et séquences
- Pour les nouvelles bases de données, il est recommandé d’utiliser des clés basées sur des séquences entières
integer (4 octets) permet environ 2 milliards de valeurs, et bigint (8 octets) en offre bien davantage
- Pour la plupart des applications métier,
integer suffit ; pour les services à grande échelle, bigint est plus adapté
- À la place des UUID v4, des UUID v7 ou l’extension
sequential_uuids constituent des alternatives réalistes
Résumé
- Les UUID v4 entraînent une inefficacité des index due à leur caractère aléatoire, des E/S élevées et une faible efficacité du cache
- Ils ne peuvent pas servir d’identifiants de sécurité et impliquent un gaspillage d’espace important
- Les clés de séquence entières conviennent mieux à la plupart des applications
- Si l’usage d’UUID est inévitable, il faut choisir des UUID v7 ordonnés dans le temps
- Il faut éviter d’utiliser
gen_random_uuid() comme clé primaire dans PostgreSQL
Aucun commentaire pour le moment.