Utiliser des UUID comme primary key avec l’ORM de Django
jeudi 6 décembre 2012 à 15:59Par défaut Django ajoute automatiquement un champ id
à tous les modèles, et le configure pour être un entier qui s’auto incrémente puis le désigne comme la clé primaire. Il est néanmoins possible d’utiliser un autre champ comme clé primaire pour sa table: un slug ou un identifiant métier. Dans notre cas, on va voir comment utiliser un UUID.
Un UUID est un identifiant généré de manière pseudo aléatoire qui a une forte probabilité d’être unique dans le monde entier, il y a 4×10³⁷ combinaisons possibles pour les versions des algos récents. Selon le paradoxe des anniversaires, il faudrait créer un milliard de UUID par seconde pendant 100 ans pour que le prochain ait 50% de chance d’être un doublon. Normalement pour votre site de fans club des limules hermaphrodites, ça devrait être suffisant.
Utiliser des UUID possède de nombreux avantages car ils sont plus ou moins garantis d’être uniques, et ainsi:
- Vos serveurs peuvent les générer indépendemment les uns des autres.
- .
/manage.py loadata
ne va pas vous crasher à la gueule à cause d’une duplicate key. - On peut faire du sharding sur la clé très facilement.
- Faire des réplications et des synchronisations est beaucoup plus simple qu’avec un
AUTO INT
. - Votre modèle n’a pas besoin d’un champ significatif pour être unique.
- Vous pouvez générer l’ID côté client et l’envoyer au serveur.
- Faire communiquer des systèmes complètement séparés, différents ou par API interposée ne pose aucun problème de référence.
Pour toutes ces raisons, les bases de données NoSQL (CouchDB, MongoDB, etc) utilisent depuis longtemps les UUID comme clés primaires par défaut. Le moteur de base de données de Google, Big Table, utilise des UUID.
Pourtant les UUID ne sont pas exempts de défauts:
- Les JOINS sont beaucoup plus lents.
- Il prennent plus d’espace disque et en mémoire.
- On ne peut pas les mettre dans des URL sans que ce soit très très moche.
- Ils sont difficiles à retenir, dicter et rechercher manuellement.
- Ils sont non significatifs. Regardez un UUID ne vous donne aucune info sur les données sous-jacentes.
- Ils ne sont pas ordonnés. Cela a un impact sur la manipulation des données, et sur certains moteurs de BDD à l’insertion.
Les UUID ne sont donc pas la solution miracle à tous les soucis, mais ils sont tout de même mon choix par défaut en ce moment ne serait-ce que pour les fixtures. Je gagne les performances sur le caching et le parallélisme, et un peu de lenteur sur les JOINS est quelque chose que je peux supporter. Pour les gros sites, les JOINS sont de tout façon votre ennemi juré et on finit toujours par tweaker à grand coup de dé-normalisation.
On peut faire passer ses modèles Django aux UUID en faisant:
from uuid import uuid4 class MotDElle(object): ... id = models.CharField(max_length=36, primary_key=True, default=lambda: str(uuid.uuid4()), editable=False)
Ou, si vous utilisez django_extensions (et vous devriez, ne serait-ce que pour shell_plus
et runserver_plus
):
from django_extensions.db.fields import UUIDField class MotDElle(object): ... id = UUIDField(primary_key=True)
Ce qui a l’avantage de caster la valeur en un objet UUID, et non une bête string, à la lecture.
Malheureusement, pour le moment il n’y a aucune implémentation officielle de Django pour utiliser les UUID. Cela a des conséquences fort ennuyeuses:
- Les implémentations actuelles utilisent un
VARCHAR
pour stocker l’UUID au lieu d’un type natif quand c’est possible, et donc ce n’est pas du tout optimisé. L’exception étant django-pdfield, mais dans ce cas, adieu la compatibilité entre plusieurs SGBD. django.contrib.auth.models.User
n’a aucun moyen d’utiliser proprement les UUID (il ne vous reste que le monkey patching). Cela pourrait changer avec Django 1.5 qui prévoit un modèleUser
extensible. \o/
Un ticket pendouille depuis 5 ans sur la question. Je vous invite d’ailleurs à le spammer jusqu’à ce que réouverture s’en suive. Et le premier qui me dit “t’as qu’à l’implémenter, toi”, je lui retourne un tampon dans la poire.