¿Cuándo debo usar uuid.uuid1 () vs.uuid.uuid4 () en Python?

207

Entiendo las diferencias entre los dos de los documentos.

uuid1():
Generar un UUID a partir de una ID de host, un número de secuencia y la hora actual

uuid4():
Genera un UUID aleatorio.

Entonces uuid1usa información de máquina / secuencia / tiempo para generar un UUID. ¿Cuáles son los pros y los contras de usar cada uno?

Sé que uuid1()puede tener problemas de privacidad, ya que se basa en la información de la máquina. Me pregunto si hay algo más sutil al elegir uno u otro. Solo lo uso en uuid4()este momento, ya que es un UUID completamente aleatorio. Pero me pregunto si debería usarlo uuid1para disminuir el riesgo de colisiones.

Básicamente, estoy buscando consejos de las personas para las mejores prácticas sobre el uso de uno frente al otro. ¡Gracias!

rocketmonkeys
fuente
3
Aquí hay un enfoque alternativo para UUID. Aunque la posibilidad de colisión es UUID infinitesimal no garantiza la unicidad. Para garantizar la unicidad, es posible que desee utilizar la clave compuesta como [<id del sistema>, <id local>]. Cada sistema que participa en el intercambio de datos debe tener su propia ID única del sistema, ya sea asignada durante la configuración del sistema u obtenida de un conjunto común de ID. La identificación local es una identificación única dentro de cualquier sistema en particular. Esto implica más molestias pero garantiza la unicidad. Perdón por el tema, solo trato de ayudar.
oᴉɹǝɥɔ
3
No se ocupa de las "preocupaciones de privacidad" que mencionó
Shrey

Respuestas:

253

uuid1()está garantizado para no producir colisiones (bajo el supuesto de que no crea demasiadas al mismo tiempo). No lo usaría si es importante que no haya conexión entre uuidla computadora y la computadora, ya que la dirección MAC se utiliza para que sea única en todas las computadoras.

Puede crear duplicados creando más de 2 14 uuid1 en menos de 100ns, pero esto no es un problema para la mayoría de los casos de uso.

uuid4()genera, como dijiste, un UUID aleatorio. La posibilidad de una colisión es muy, muy, muy pequeña. Lo suficientemente pequeño como para no preocuparse por eso. El problema es que un mal generador de números aleatorios hace que sea más probable que tenga colisiones.

Esta excelente respuesta de Bob Aman lo resume muy bien. (Recomiendo leer la respuesta completa).

Francamente, en un solo espacio de aplicación sin actores maliciosos, la extinción de toda la vida en la tierra ocurrirá mucho antes de que tenga una colisión, incluso en un UUID de versión 4, incluso si está generando bastantes UUID por segundo.

Georg Schölly
fuente
Lo siento, comenté sin investigar a fondo: hay partes reservadas para evitar que una versión 4 uuid choque con una versión 1 uuid. Eliminaré mi comentario original. Ver tools.ietf.org/html/rfc4122
Mark Ransom el
1
@gs Sí, tiene sentido con lo que estaba leyendo. uuid1 es "más exclusivo", mientras que uuid4 es más anónimo. Así que básicamente usa uuid1 a menos que tengas una razón para no hacerlo. @mark ransom: respuesta impresionante, no apareció cuando busqué uuid1 / uuid4. Directamente de la boca del caballo, parece.
rocketmonkeys el
66
uuid1no necesariamente producirá UUID únicos si produce varios por segundo en el mismo nodo. Ejemplo: [uuid.uuid1() for i in range(2)]. A menos que, por supuesto, ocurra algo extraño que me estoy perdiendo.
Michael Mior el
1
@Michael: uuid1tiene un número de secuencia (cuarto elemento en su ejemplo), por lo que, a menos que use todos los bits en el contador, no tiene ninguna colisión.
Georg Schölly
3
@ Michael: He intentado investigar las circunstancias en que ocurren las colisiones y he agregado la información que encontré.
Georg Schölly
32

Una instancia en la que puede considerar en uuid1()lugar de hacerlo uuid4()es cuando los UUID se producen en máquinas separadas , por ejemplo, cuando se procesan varias transacciones en línea en varias máquinas con fines de escala.

En tal situación, los riesgos de tener colisiones debido a malas elecciones en la forma en que se inicializan los generadores de números pseudoaleatorios, y también los números potencialmente más altos de UUID producidos hacen más probable la posibilidad de crear ID duplicadas.

Otro interés de uuid1(), en ese caso, es que la máquina donde se produjo inicialmente cada GUID se registra implícitamente (en la parte "nodo" de UUID). Esta y la información de tiempo pueden ayudar aunque solo sea con la depuración.

mjv
fuente
20

Mi equipo acaba de tener problemas al usar UUID1 para un script de actualización de base de datos donde generamos ~ 120k UUID en un par de minutos. La colisión UUID condujo a la violación de una restricción de clave primaria.

Hemos actualizado cientos de servidores, pero en nuestras instancias de Amazon EC2 nos encontramos con este problema varias veces. Sospecho que la baja resolución del reloj y el cambio a UUID4 lo resolvieron por nosotros.

Mattias Lagergren
fuente
5

Una cosa a tener en cuenta al usar uuid1, si usa la llamada predeterminada (sin dar clock_seqparámetro), tiene la posibilidad de encontrarse con colisiones: tiene solo 14 bits de aleatoriedad (generar 18 entradas dentro de 100ns le da aproximadamente un 1% de probabilidad de una colisión. cumpleaños paradoja / ataque). El problema nunca ocurrirá en la mayoría de los casos de uso, pero en una máquina virtual con una resolución de reloj pobre, lo morderá.

Guillaume
fuente
77
@Guilaume sería realmente útil ver un ejemplo de buenas prácticas usando clock_seq....
eric
@Guilaume ¿Cómo calculó esta posibilidad de 1%? 14 bits de medios de aleatoriedad se garantiza la colisión que ocurra si generas> = 2 ^ 14 por identificadores de 100 ns y significa esto que el 1% de probabilidad de una colisión es cuando se producen aproximadamente 163 identificadores de cada 100 ns
Maks
1
@maks Como dije, deberías mirar la paradoja del cumpleaños .
Guillaume
3

Quizás algo que no se ha mencionado es el de la localidad.

Una dirección MAC o un pedido basado en el tiempo (UUID1) puede permitir un mayor rendimiento de la base de datos, ya que es menos difícil ordenar los números de forma más cercana que los distribuidos aleatoriamente (UUID4) (ver aquí ).

Un segundo problema relacionado es que usar UUID1 puede ser útil en la depuración, incluso si los datos de origen se pierden o no se almacenan explícitamente (esto obviamente está en conflicto con el problema de privacidad mencionado por el OP).

cz
fuente
1

Además de la respuesta aceptada, hay una tercera opción que puede ser útil en algunos casos:

v1 con MAC aleatorio ("v1mc")

Puede hacer un híbrido entre v1 y v4 generando deliberadamente UUID v1 con una dirección MAC de transmisión aleatoria (esto está permitido por la especificación v1). El UUID v1 resultante depende del tiempo (como v1 normal), pero carece de toda la información específica del host (como v4). También está mucho más cerca de v4 en su resistencia a la colisión: v1mc = 60 bits de tiempo + 61 bits aleatorios = 121 bits únicos; v4 = 122 bits aleatorios.

El primer lugar donde encontré esto fue la función uuid_generate_v1mc () de Postgres . Desde entonces he usado el siguiente equivalente de Python:

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)

(nota: tengo una versión más larga + más rápida que crea el objeto UUID directamente; puede publicar si alguien lo desea)


En el caso de GRANDES volúmenes de llamadas / segundo, esto tiene el potencial de agotar la aleatoriedad del sistema. En su lugar, podría usar el randommódulo stdlib (probablemente también será más rápido). Pero tenga cuidado: solo se necesitan unos pocos cientos de UUID antes de que un atacante pueda determinar el estado de RNG y, por lo tanto, predecir parcialmente futuros UUID.

import random
from uuid import uuid1

def uuid1mc_insecure():
    return uuid1(random.getrandbits(48) | 0x010000000000)
Eli Collins
fuente
Parece que este método es "like" v4 (host-agnostic), pero peor (menos bits, dependencia de urandom, etc.). ¿Hay alguna ventaja en comparación con solo uuid4?
rocketmonkeys
Esto es principalmente solo una actualización para los casos en que v1 es útil por sus cualidades basadas en el tiempo, pero se desea una mayor resistencia a la colisión y privacidad del host. Un ejemplo es como clave principal para una base de datos: en comparación con v4, los uuids v1 tendrán una mejor ubicación al escribir en el disco, tendrán un tipo natural más útil, etc. Pero si tiene un caso en el que un atacante predice 2 ** 61 bits es un problema de seguridad (por ejemplo, como uuid a nonce), luego $ diety sí, use uuid4 en su lugar (¡sé que lo hago!) Re: siendo peor porque usa urandom, no estoy seguro de lo que quieres decir: bajo python, uuid4 () también usa urandom.
Eli Collins el
Cosas buenas, eso tiene sentido. Es bueno ver no solo lo que puede hacer (su código), sino también por qué lo querría. Re: urandom, quiero decir que estás consumiendo el doble de aleatoriedad (1 para uuid1, otro para el urandom), por lo que podría agotar la entropía del sistema más rápido.
rocketmonkeys
En realidad, es aproximadamente la mitad de uuid4: uuid1 () usa 14 bits para clock_seq, que redondea hasta 2 bytes de urandom. El contenedor uuid1mc usa 48 bits, que deberían mapearse a 6 bytes de urandom, para un total de urandom (8) consumidos por llamada. mientras que uuid4 invoca directamente urandom (16) para cada llamada.
Eli Collins el