Quand dois-je utiliser uuid.uuid1 () vs uuid.uuid4 () en python?

207

Je comprends les différences entre les deux de la documentation.

uuid1():
Générer un UUID à partir d'un ID d'hôte, d'un numéro de séquence et de l'heure actuelle

uuid4():
Générer un UUID aléatoire.

uuid1Utilise donc les informations machine / séquence / heure pour générer un UUID. Quels sont les avantages et les inconvénients de chacun?

Je sais que cela uuid1()peut avoir des problèmes de confidentialité, car il est basé sur des informations sur la machine. Je me demande s'il y a plus de subtilité dans le choix de l'un ou de l'autre. J'utilise juste en uuid4()ce moment, car c'est un UUID complètement aléatoire. Mais je me demande si je devrais utiliser uuid1pour réduire le risque de collisions.

Fondamentalement, je suis à la recherche de conseils sur les meilleures pratiques d'utilisation de l'un par rapport à l'autre. Merci!

rocketmonkeys
la source
3
Voici une approche alternative à UUID. Bien que le risque de collision soit infinitésimal, l'UUID ne garantit pas l'unicité. Pour garantir l'unicité, vous pouvez utiliser la clé composée comme [<id système>, <id local>]. Chaque système participant au partage de données doit avoir son propre ID unique du système, attribué lors de la configuration du système ou obtenu à partir d'un pool commun d'ID. L'identifiant local est un identifiant unique dans un système particulier. Cela implique plus de tracas mais garantit l'unicité. Désolé pour l'offtopic, j'essaye juste d'aider.
oᴉɹǝɥɔ
3
Ne s'occupe pas des "problèmes de confidentialité" qu'il a mentionnés
Shrey

Réponses:

253

uuid1()est garanti de ne pas produire de collisions (en supposant que vous n'en créez pas trop en même temps). Je ne l'utiliserais pas s'il est important qu'il n'y ait pas de connexion entre uuidl'ordinateur et l'ordinateur, car l'adresse mac est utilisée pour la rendre unique sur tous les ordinateurs.

Vous pouvez créer des doublons en créant plus de 2 14 uuid1 en moins de 100ns, mais ce n'est pas un problème pour la plupart des cas d'utilisation.

uuid4()génère, comme vous l'avez dit, un UUID aléatoire. Le risque de collision est vraiment, vraiment, vraiment très faible. Assez petit, que vous ne devriez pas vous en soucier. Le problème est qu'un mauvais générateur de nombres aléatoires le rend plus susceptible d'avoir des collisions.

Cette excellente réponse de Bob Aman le résume bien. (Je recommande de lire toute la réponse.)

Franchement, dans un seul espace d'application sans acteurs malveillants, l'extinction de toute vie sur terre se produira bien avant une collision, même sur un UUID version 4, même si vous générez pas mal d'UUID par seconde.

Georg Schölly
la source
Désolé, j'ai commenté sans rechercher complètement - il y a des bits réservés pour empêcher un uuid de la version 4 d'entrer en collision avec un uuid de la version 1. Je supprimerai mon commentaire d'origine. Voir tools.ietf.org/html/rfc4122
Mark Ransom
1
@gs Oui, c'est logique avec ce que je lisais. uuid1 est "plus unique", tandis que uuid4 est plus anonyme. Donc, utilisez essentiellement uuid1, sauf si vous avez une raison de ne pas le faire. @mark ransom: Une réponse géniale, n'est pas venue lorsque j'ai recherché uuid1 / uuid4. Directement de la bouche du cheval, semble-t-il.
rocketmonkeys
6
uuid1ne produira pas nécessairement des UUID uniques si vous en produisez plusieurs par seconde sur le même nœud. Exemple: [uuid.uuid1() for i in range(2)]. À moins bien sûr qu'il se passe quelque chose d'étrange qui me manque.
Michael Mior
1
@Michael: uuid1a un numéro de séquence (4ème élément dans votre exemple), donc à moins que vous n'utilisiez tous les bits du compteur, vous n'avez pas de collision.
Georg Schölly
3
@Michael: J'ai essayé de rechercher les circonstances dans lesquelles des collisions se produisent et j'ai ajouté les informations que j'ai trouvées.
Georg Schölly
32

Un exemple , lorsque vous pouvez envisager uuid1()plutôt que uuid4()est quand UUID sont produits sur des machines séparées , par exemple lorsque plusieurs transactions en ligne sont processus sur plusieurs machines à des fins d' échelle.

Dans une telle situation, les risques d'avoir des collisions en raison de mauvais choix dans la façon dont les générateurs de nombres pseudo-aléatoires sont initialisés, par exemple, et aussi le nombre potentiellement plus élevé d'UUID produits rendent plus probable la possibilité de créer des ID en double.

Un autre intérêt de uuid1(), dans ce cas, est que la machine sur laquelle chaque GUID a été initialement produit est implicitement enregistrée (dans la partie "nœud" de l'UUID). Ceci et les informations de temps peuvent aider, ne serait-ce qu'avec le débogage.

mjv
la source
20

Mon équipe vient de rencontrer des problèmes en utilisant UUID1 pour un script de mise à niveau de base de données où nous avons généré ~ 120k UUID en quelques minutes. La collision UUID a entraîné la violation d'une contrainte de clé primaire.

Nous avons mis à niveau des centaines de serveurs, mais sur nos instances Amazon EC2, nous avons rencontré ce problème à plusieurs reprises. Je soupçonne une mauvaise résolution d'horloge et le passage à UUID4 l'a résolu pour nous.

Mattias Lagergren
la source
5

Une chose à noter lors de l'utilisation uuid1, si vous utilisez l'appel par défaut (sans donner de clock_seqparamètre), vous avez une chance de tomber dans des collisions: vous n'avez que 14 bits d'aléatoire (générer 18 entrées en 100ns vous donne environ 1% de chance de collision voir paradoxe anniversaire / attaque). Le problème ne se produira jamais dans la plupart des cas d'utilisation, mais sur une machine virtuelle avec une mauvaise résolution d'horloge, il vous mordra.

Guillaume
la source
7
@Guilaume, il serait vraiment utile de voir un exemple de bonne pratique en utilisant clock_seq....
Eric
@Guilaume Comment avez-vous calculé cette chance de 1%? 14 bits d'aléa signifie que la collision se produira à coup sûr si vous générez> = 2 ^ 14 ids pour 100 ns, ce qui signifie que 1% de chance de collision survient lorsque vous produisez environ 163 ids pour 100 ns
marque
1
@maks Comme je l'ai dit, vous devriez regarder le paradoxe de l' anniversaire .
Guillaume
3

Peut-être que quelque chose qui n'a pas été mentionné est celui de la localité.

Une adresse MAC ou un ordre temporel (UUID1) peut offrir des performances de base de données accrues, car il est moins difficile de trier les nombres plus étroitement que ceux distribués de manière aléatoire (UUID4) (voir ici ).

Un deuxième problème connexe est que l'utilisation de UUID1 peut être utile dans le débogage, même si les données d'origine sont perdues ou ne sont pas explicitement stockées (cela est évidemment en conflit avec le problème de confidentialité mentionné par l'OP).

cz
la source
1

En plus de la réponse acceptée, il existe une troisième option qui peut être utile dans certains cas:

v1 avec MAC aléatoire ("v1mc")

Vous pouvez créer un hybride entre v1 et v4 en générant délibérément des UUID v1 avec une adresse MAC de diffusion aléatoire (cela est autorisé par la spécification v1). L'UUID v1 résultant dépend du temps (comme la v1 normale), mais il manque toutes les informations spécifiques à l'hôte (comme la v4). Il est également beaucoup plus proche de la v4 dans sa résistance aux collisions: v1mc = 60 bits de temps + 61 bits aléatoires = 121 bits uniques; v4 = 122 bits aléatoires.

Le premier endroit où j'ai rencontré cela était la fonction uuid_generate_v1mc () de Postgres . J'ai depuis utilisé l'équivalent python suivant:

from os import urandom
from uuid import uuid1
_int_from_bytes = int.from_bytes  # py3 only

def uuid1mc():
    # NOTE: The constant here is required by the UUIDv1 spec...
    return uuid1(_int_from_bytes(urandom(6), "big") | 0x010000000000)

(Remarque: j'ai une version plus longue + plus rapide qui crée directement l'objet UUID; peut publier si quelqu'un le veut)


En cas de grands volumes d'appels / seconde, cela a le potentiel d'épuiser le caractère aléatoire du système. Vous pouvez utiliser le randommodule stdlib à la place (il sera probablement aussi plus rapide). Mais ATTENTION: il ne faut que quelques centaines d'UUID avant qu'un attaquant puisse déterminer l'état RNG, et donc prédire partiellement les futurs UUID.

import random
from uuid import uuid1

def uuid1mc_insecure():
    return uuid1(random.getrandbits(48) | 0x010000000000)
Eli Collins
la source
On dirait que cette méthode est "similaire" à v4 (host-agnostic), mais pire (moins de bits, dépendance à l'urandom, etc.). Y a-t-il des avantages par rapport à juste uuid4?
rocketmonkeys
Il s'agit principalement d'une mise à niveau pour les cas où la v1 est utile pour ses qualités temporelles, mais une résistance aux collisions et une confidentialité de l'hôte plus importantes sont souhaitées. Un exemple est comme clé primaire pour une base de données - par rapport à v4, les uuids v1 auront une meilleure localisation lors de l'écriture sur le disque, auront un tri naturel plus utile, etc. Mais si vous avez un cas où un attaquant prédit 2 ** 61 bits est un problème de sécurité (par exemple, comme uuid a nonce), alors $ diety oui, utilisez uuid4 à la place (je sais que je le fais!). Re: étant pire parce qu'il utilise urandom, je ne sais pas ce que vous voulez dire - sous python, uuid4 () utilise également urandom.
Eli Collins
Bon, ça a du sens. Il est bon de voir non seulement ce que vous pouvez faire (votre code), mais aussi pourquoi vous le souhaitez. Re: urandom, je veux dire que vous consommez 2x le caractère aléatoire (1 pour uuid1, un autre pour l'urandom), donc pourrait utiliser l'entropie du système plus rapidement.
rocketmonkeys
C'est en fait environ la moitié de uuid4: uuid1 () utilise 14 bits pour clock_seq, qui arrondit à 2 octets d'urandom. L'encapsuleur uuid1mc utilise 48 bits, qui doivent correspondre à 6 octets d'urandom, pour un total d'urandom (8) consommé par appel. tandis que uuid4 invoque directement urandom (16) pour chaque appel.
Eli Collins