Quando devo usar uuid.uuid1 () vs. uuid.uuid4 () em python?

207

Eu entendo as diferenças entre os dois dos documentos.

uuid1():
Gerar um UUID a partir de um ID do host, número de sequência e hora atual

uuid4():
Gera um UUID aleatório.

Então uuid1usa máquina / seqüência / info tempo para gerar um UUID. Quais são os prós e os contras de usar cada um?

Sei que uuid1()pode ter preocupações com a privacidade, pois é baseado em informações da máquina. Gostaria de saber se há mais sutil ao escolher um ou outro. Eu apenas uso uuid4()agora, já que é um UUID completamente aleatório. Mas me pergunto se devo usá-lo uuid1para diminuir o risco de colisões.

Basicamente, estou procurando dicas das pessoas sobre práticas recomendadas sobre como usar uma vs. a outra. Obrigado!

rocketmonkeys
fonte
3
Aqui está uma abordagem alternativa ao UUID. Embora a chance de colisão seja infinita, o UUID não garante exclusividade. Para garantir a exclusividade, convém usar a chave composta como [<id do sistema>, <id local>]. Cada sistema participante do compartilhamento de dados deve ter seu próprio ID exclusivo do sistema, atribuído durante a configuração do sistema ou obtido de um conjunto comum de IDs. ID local é um ID exclusivo em qualquer sistema específico. Isso envolve mais problemas, mas garante exclusividade. Desculpe pelo offtopic, apenas tentando ajudar.
oᴉɹǝɥɔ
3
Não cuidar das "preocupações com a privacidade", ele mencionou
Shrey

Respostas:

253

uuid1()é garantido que não produzirá colisões (sob a suposição de que você não crie muitas delas ao mesmo tempo). Eu não o usaria se fosse importante que não houvesse conexão entre uuido computador e o computador, pois o endereço mac é usado para torná-lo único nos computadores.

Você pode criar duplicatas criando mais de 2 14 uuid1 em menos de 100ns, mas isso não é um problema para a maioria dos casos de uso.

uuid4()gera, como você disse, um UUID aleatório. A chance de uma colisão é muito, muito, muito pequena. Pequeno o suficiente para não se preocupar com isso. O problema é que um gerador ruim de números aleatórios aumenta a probabilidade de colisões.

Esta excelente resposta de Bob Aman resume bem. (Eu recomendo a leitura da resposta completa.)

Francamente, em um único espaço de aplicativo sem atores maliciosos, a extinção de toda a vida na Terra ocorrerá muito antes de você ter uma colisão, mesmo em um UUID da versão 4, mesmo se você estiver gerando alguns UUIDs por segundo.

Georg Schölly
fonte
Desculpe, comentei sem pesquisar completamente - existem bits reservados para impedir que um uuid da versão 4 colide com um uuid da versão 1. Vou remover o meu comentário original. Veja tools.ietf.org/html/rfc4122
Mark Ransom
1
@gs Sim, faz sentido com o que eu estava lendo. uuid1 é "mais exclusivo", enquanto uuid4 é mais anônimo. Então, basicamente, use uuid1, a menos que você tenha um motivo para não fazê-lo. @mark resgate: resposta impressionante, não surgiu quando eu procurei por uuid1 / uuid4. Direto da boca do cavalo, ao que parece.
Rocketmonkeys 25/11/2009
6
uuid1não produzirá necessariamente UUIDs exclusivos se você produzir vários por segundo no mesmo nó. Exemplo: [uuid.uuid1() for i in range(2)]. A menos, claro, que algo estranho esteja acontecendo que eu esteja sentindo falta.
Michael Mior
1
@ Michael: uuid1tem um número de sequência (quarto elemento no seu exemplo), então, a menos que você use todos os bits do contador, não terá colisão.
Georg Schölly 17/11/2013
3
@ Michael: Eu tentei pesquisar as circunstâncias quando ocorrem colisões e adicionei as informações que encontrei.
Georg Schölly 18/11/2013
32

Um exemplo, quando você pode considerar uuid1()em vez de uuid4()é quando UUIDs são produzidos em máquinas separadas , por exemplo, quando várias transações on-line são processo em várias máquinas para fins de escala.

Em tal situação, os riscos de colisões devido a más escolhas na forma como os geradores de números pseudo-aleatórios são inicializados, por exemplo, e também o número potencialmente mais alto de UUIDs produzidos tornam mais provável a possibilidade de criação de IDs duplicados.

Outro interesse de uuid1(), nesse caso, é que a máquina onde cada GUID foi produzido inicialmente seja implicitamente registrada (na parte "nó" do UUID). Esta e as informações de tempo podem ajudar, mesmo que apenas com depuração.

mjv
fonte
20

Minha equipe teve problemas ao usar o UUID1 para um script de atualização de banco de dados, onde geramos ~ 120k UUIDs em alguns minutos. A colisão de UUID levou à violação de uma restrição de chave primária.

Atualizamos centenas de servidores, mas em nossas instâncias do Amazon EC2 encontramos esse problema algumas vezes. Eu suspeito que a resolução do relógio é ruim e a mudança para o UUID4 resolveu isso para nós.

Mattias Lagergren
fonte
5

Uma coisa a ser observada ao usar uuid1, se você usar a chamada padrão (sem fornecer o clock_seqparâmetro), terá chance de colidir: você tem apenas 14 bits de aleatoriedade (gerar 18 entradas dentro de 100ns oferece aproximadamente 1% de chance de colisão) paradoxo / ataque de aniversário). O problema nunca ocorrerá na maioria dos casos de uso, mas em uma máquina virtual com baixa resolução de relógio, ele o morderá.

Guillaume
fonte
7
@Guilaume que seria muito útil para ver um exemplo de boas práticas usando clock_seq....
eric
@Guilaume Como você calculou essa chance de 1%? 14 bits de meios aleatoriedade da colisão será garantido para acontecer se você gerar> = 2 ^ 14 ids por 100ns e isso significa que 1% de chance de uma colisão é quando você produzir cerca de 163 ids por 100 ns
maks
1
@ maks Como eu disse, você deve olhar para o paradoxo do aniversário .
Guillaume
3

Talvez algo que não tenha sido mencionado seja o da localidade.

Um endereço MAC ou um pedido com base no tempo (UUID1) pode proporcionar um desempenho aprimorado do banco de dados, pois é menos trabalhoso classificar números mais próximos do que os distribuídos aleatoriamente (UUID4) (veja aqui ).

Um segundo problema relacionado é que o uso do UUID1 pode ser útil na depuração, mesmo que os dados de origem sejam perdidos ou não sejam armazenados explicitamente (isso obviamente está em conflito com o problema de privacidade mencionado pelo OP).

cz
fonte
1

Além da resposta aceita, há uma terceira opção que pode ser útil em alguns casos:

v1 com MAC aleatório ("v1mc")

Você pode fazer um híbrido entre v1 e v4 gerando deliberadamente UUIDs v1 com um endereço MAC de transmissão aleatória (isso é permitido pela especificação v1). O UUID da v1 resultante depende do tempo (como a v1 normal), mas não possui todas as informações específicas do host (como a v4). Também está muito mais próximo da v4 em sua resistência à colisão: v1mc = 60 bits de tempo + 61 bits aleatórios = 121 bits únicos; v4 = 122 bits aleatórios.

O primeiro lugar que encontrei foi a função uuid_generate_v1mc () do Postgres . Desde então, usei o seguinte equivalente em 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: eu tenho uma versão mais longa + rápida que cria o objeto UUID diretamente; pode postar se alguém quiser)


No caso de grandes volumes de chamadas / segundo, isso pode esgotar a aleatoriedade do sistema. Você pode usar o randommódulo stdlib (provavelmente também será mais rápido). Mas lembre-se: são necessárias apenas algumas centenas de UUIDs antes que um invasor possa determinar o estado do RNG e, portanto, prever parcialmente UUIDs futuros.

import random
from uuid import uuid1

def uuid1mc_insecure():
    return uuid1(random.getrandbits(48) | 0x010000000000)
Eli Collins
fonte
Parece que esse método é "como" v4 (independente de host), mas pior (menos bits, dependência de urandom, etc). Existem vantagens em comparação com apenas o uuid4?
Rocketmonkeys
Isso é basicamente apenas uma atualização para os casos em que a v1 é útil por suas qualidades baseadas no tempo, mas é necessária uma maior resistência à colisão e privacidade do host. Um exemplo é como uma chave primária para um banco de dados - comparado à v4, os uuids v1 terão melhor localidade ao gravar no disco, terão uma classificação natural mais útil etc. Mas se você tiver um caso em que um invasor prevê 2 ** 61 bits é um problema de segurança (por exemplo, como uuid a nonce), então $ diety yes, use uuid4 (eu sei que sim!). Re: sendo pior porque usa urandom, não sei o que você quer dizer - em python, uuid4 () também usa urandom.
Eli Collins
Coisas boas, isso faz sentido. É bom ver não apenas o que você pode fazer (seu código), mas também porque você o deseja. Re: urandom, quero dizer que você está consumindo 2x a aleatoriedade (1 para uuid1, outra para o urandom), então poderia usar a entropia do sistema mais rapidamente.
Rocketmonkeys
Na verdade, é quase metade do que uuid4: uuid1 () usa 14 bits para clock_seq, que arredonda até 2 bytes de urandom. O wrapper uuid1mc usa 48 bits, que devem mapear para 6 bytes de urandom, para um total de urandom (8) consumido por chamada. enquanto uuid4 invoca diretamente urandom (16) para cada chamada.
Eli Collins