Quando você é realmente forçado a usar o UUID como parte do design?


123

Eu realmente não vejo o objetivo do UUID . Eu sei que a probabilidade de uma colisão é efetivamente nula , mas efetivamente nula não é nem perto do impossível.

Alguém pode dar um exemplo em que você não tem escolha a não ser usar o UUID? De todos os usos que já vi, posso ver um design alternativo sem o UUID. Certamente, o design pode ser um pouco mais complicado, mas pelo menos não tem uma probabilidade de falha diferente de zero.

UUID cheira a variáveis ​​globais para mim. Existem várias maneiras pelas quais as variáveis ​​globais criam um design mais simples, mas é apenas um design preguiçoso.


23
Tudo tem uma chance diferente de zero de falha. Gostaria de concentrar-se em muito mais provável de ocorrer problemas (ou seja, quase qualquer coisa que você pode pensar) do que a colisão de UUIDs
DanSingerman

16
Na verdade, "efetivamente zero" é quase impossível.
Mqp 31/03/09

21
Não, a sua realidade infinitamente longe de ser impossível
Pyrolistical

32
@ Pirolistical, quando você começa a usar palavras como "infinito", sai do mundo do desenvolvimento de software. A teoria da ciência da computação é uma discussão totalmente diferente da escrita de software real.
Rex M

2
Vou fechar principalmente porque sha1 do git tem me convencido da bondade de um hash
Pyrolistical

Respostas:


617

Eu escrevi o gerador / analisador de UUID para Ruby, por isso considero-me razoavelmente bem informado sobre o assunto. Existem quatro versões principais de UUID:

Os UUIDs da versão 4 são essencialmente apenas 16 bytes de aleatoriedade extraídos de um gerador de números aleatórios criptograficamente seguro, com algumas correções de bits para identificar a versão e a variante do UUID. É extremamente improvável que colidam, mas isso pode acontecer se um PRNG for usado ou se você tiver realmente, realmente, muito, muito, muito azar.

Os UUIDs das versões 5 e 3 usam as funções de hash SHA1 e MD5, respectivamente, para combinar um espaço para nome com uma parte dos dados já exclusivos para gerar um UUID. Isso permitirá, por exemplo, que você produza um UUID a partir de uma URL. As colisões aqui só são possíveis se a função hash subjacente também tiver uma colisão.

Os UUIDs da versão 1 são os mais comuns. Eles usam o endereço MAC da placa de rede (que, a menos que seja falsificado, deve ser exclusivo), além de um carimbo de data / hora, além da variação de bits usual para gerar o UUID. No caso de uma máquina que não possui um endereço MAC, os bytes de 6 nós são gerados com um gerador de números aleatórios criptograficamente seguro. Se dois UUIDs forem gerados em sequência com rapidez suficiente para que o registro de data e hora corresponda ao UUID anterior, o registro de data e hora será incrementado em 1. Colisões não devem ocorrer a menos que ocorra uma das seguintes situações: O endereço MAC é falsificado; Uma máquina executando dois aplicativos geradores de UUID diferentes produz UUIDs no mesmo momento; Duas máquinas sem uma placa de rede ou sem acesso no nível de usuário ao endereço MAC recebem a mesma sequência aleatória de nós e geram UUIDs no exato momento;

Realisticamente, nenhum desses eventos ocorre acidentalmente no espaço de ID de um único aplicativo. A menos que você aceite IDs em, por exemplo, uma escala na Internet ou com um ambiente não confiável em que indivíduos mal-intencionados possam fazer algo ruim no caso de uma colisão de IDs, isso não é algo com que você deva se preocupar. É fundamental entender que, se você gerar a mesma versão 4 UUID que eu, na maioria dos casos, isso não importa. Eu criei o ID em um espaço de ID completamente diferente do seu. Meu aplicativo nunca saberá sobre a colisão, portanto a colisão não importa. 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 que você '

Além disso, 2 ^ 64 * 16 são 256 exabytes. Assim, você precisaria armazenar 256 exabytes de IDs antes de ter 50% de chance de uma colisão de IDs em um único espaço de aplicativo.


8
Esta é de longe a melhor explicação. Não sei por que isso não está sendo votado no topo. Parabéns a você Sporkmonger.
24411 Brad Barker

1
@ Chamnap eu escrevi UUIDTools. Os UUIDs podem ser convertidos em um número inteiro ou na forma de bytes brutos e seriam substancialmente menores como binários.
Bob Aman

1
O @Chamnap uuid.rawfornecerá a sequência de bytes. O hashmétodo não é útil para você. É usado para tabelas de hash e operações de comparação internamente no Ruby. Todos os métodos para converter de e para várias representações UUID são definidos como métodos de classe e devem ser prefixados com "parse".
Bob Aman

3
@BobAman em 1990, eu tive 12 colisões de UUID em um sistema Aegis, que acabaram sendo uma FPU defeituosa, mas pensei em avisar que isso pode acontecer (não aconteceu além disso nos últimos 30 anos de programação) . Boa explicação, também btw, este é agora o meu defacto UUID refence post para dar às pessoas :)
GMasucci

2
@kqr Você está absolutamente certo de que é o problema do aniversário, no entanto, para um código de n bits, o problema do paradoxo do aniversário reduz-se a 2 ^ (n / 2), que neste caso é 2 ^ 64, conforme indicado na minha resposta .
Bob Aman

69

O que os UUIDs compram para você que é muito difícil de fazer é obter um identificador exclusivo sem precisar consultar ou coordenar uma autoridade central . O problema geral de conseguir algo assim sem algum tipo de infraestrutura gerenciada é o problema que os UUIDs resolvem.

Eu li que, de acordo com o paradoxo do aniversário, a chance de uma colisão de UUID ocorrer é de 50% depois que 2 ^ 64 UUIDs são gerados. Agora 2 ^ 64 é um número bastante grande, mas 50% de chance de colisão parece muito arriscada (por exemplo, quantos UUIDs precisam existir antes que haja 5% de chance de colisão - mesmo que pareça uma probabilidade muito grande) .

O problema com essa análise é duplo:

  1. Os UUIDs não são totalmente aleatórios - existem componentes principais do UUID que são baseados no tempo e / ou no local. Portanto, para ter uma chance real de uma colisão, os UUIDs em colisão precisam ser gerados ao mesmo tempo a partir de diferentes geradores de UUID. Eu diria que, embora exista uma chance razoável de que vários UUIDs possam ser gerados ao mesmo tempo, há outras informações suficientes (incluindo informações de localização ou bits aleatórios) para tornar quase impossível a colisão entre esse conjunto muito pequeno de UUIDs. .

  2. estritamente falando, os UUIDs só precisam ser exclusivos entre o conjunto de outros UUIDs com os quais podem ser comparados. Se você estiver gerando um UUID para usar como chave de banco de dados, não importa se em algum outro lugar em um universo alternativo maligno o mesmo UUID está sendo usado para identificar uma interface COM. Assim como não causará confusão se houver alguém (ou algo) chamado "Michael Burr" em Alpha-Centauri.


1
Exemplo concreto? UUIDs COM / DCE - não há autoridade para atribuí-los e ninguém queria assumir a responsabilidade e / ou ninguém queria que houvesse uma autoridade. Bancos de dados distribuídos que não possuem links confiáveis ​​e sem mestre.
Michael Burr

3
Exemplo mais concreto - um aplicativo bancário. É instalado vários data centers, um para cada país, com cada data center com um banco de dados. As múltiplas instalações existem para obedecer a diferentes regulamentações. Só pode haver um registro do cliente em todo o conjunto para cada cliente .....
Vineet Reynolds

(Continuação do comentário anterior) Você precisa de um servidor central para gerar o ID do cliente para fins gerais de relatório e rastreamento (em todas as instalações) ou fazer com que as instalações individuais gerem UUIDs para servir como IDs do cliente (obviamente, os UUIDs não podem ser usados ​​como em nos relatórios).
Vineet Reynolds

Quando você tem 50% de chance de duplicação, já está se afogando. Alguém aponta o volume necessário para obter uma chance de 0,0000001%. Vários bancos de dados de incremento automático, iniciando em 1 e aumentando cada vez n, resolvem o mesmo problema com eficiência.
Gordon

2
As chances de conseguir uma duplicata são muito, muito menor do que as chances da autoridade central na falta de alguma forma de missão crítica
std''OrgnlDave

33

Tudo tem uma chance diferente de zero de falha. Eu me concentraria em problemas muito mais prováveis ​​de ocorrer (ou seja, quase tudo o que você possa imaginar) do que a colisão de UUIDs


Adicionado como resposta a pedido de
Pyrolistical

16

Uma ênfase em "razoavelmente" ou, como você diz, "efetivamente": bom o suficiente é como o mundo real funciona. A quantidade de trabalho computacional envolvido na cobertura dessa lacuna entre "praticamente único" e "verdadeiramente único" é enorme. Exclusividade é uma curva com retornos decrescentes. Em algum momento dessa curva, há uma linha entre onde "o suficiente" ainda é acessível e, em seguida, fazemos uma curva muito acentuada. O custo de adicionar mais exclusividade se torna bastante grande. Exclusividade infinita tem custo infinito.

UUID / GUID é, relativamente falando, uma maneira computacionalmente rápida e fácil de gerar um ID que pode ser razoavelmente considerado universalmente único. Isso é muito importante em muitos sistemas que precisam integrar dados de sistemas anteriormente desconectados. Por exemplo: se você possui um Sistema de Gerenciamento de Conteúdo que é executado em duas plataformas diferentes, mas em algum momento precisa importar o conteúdo de um sistema para outro. Você não deseja que os IDs sejam alterados, portanto suas referências entre os dados do sistema A permanecem intactas, mas não deseja colisões com os dados criados no sistema B. Um UUID resolve isso.


Solução. Não seja preguiçoso e atualize as referências. Faça certo.
Pyrolistical

8
Isso não tem nada a ver com preguiça - se a política é que um ID para um item seja considerado permanente e imutável, o ID não muda. Portanto, você deseja que os IDs sejam únicos desde o início e faça isso sem exigir que todos os sistemas sejam conectados de alguma forma desde o início.
Michael Burr

Você precisa de contexto então. Se você tem dois grupos de ids únicos que podem entrar em conflito, é necessário um alto nível de contexto para separá-los
Pyrolistical

23
Ou você pode simplesmente criar o sistema para usar UUIDs e enviá-lo, vendê-lo, ganhar um milhão de dólares e nunca ouvir uma única reclamação de que dois IDs colidiram porque isso não acontecerá.
Rex M

16

Nunca é absolutamente necessário criar um UUID. No entanto, é conveniente ter um padrão em que os usuários offline possam gerar uma chave para algo com uma probabilidade muito baixa de colisão.

Isso pode ajudar na resolução de replicação de banco de dados, etc ...

Seria fácil para os usuários online gerar chaves exclusivas para algo sem sobrecarga ou possibilidade de colisão, mas não é para isso que servem os UUIDs.

De qualquer forma, uma palavra sobre a probabilidade de colisão, retirada da Wikipedia:

Para colocar esses números em perspectiva, estima-se que o risco anual de ser atingido por um meteorito seja uma chance em 17 bilhões, equivalente às chances de criar algumas dezenas de trilhões de UUIDs em um ano e ter uma duplicata. Em outras palavras, somente após gerar 1 bilhão de UUIDs a cada segundo nos próximos 100 anos, a probabilidade de criar apenas uma duplicata seria de cerca de 50%.


4
Simples, não permita que usuários offline gerem chaves. Atribua as chaves temporárias até o sistema ficar on-line para que as chaves reais possam ser geradas.
Pyrolistical

Esta é uma resposta muito útil, na minha opinião ... ofereceria algum tipo de analogia à probabilidade, pois parecia que o OP não entendia bem o significado, mas você parece ter feito isso.
Noldorin

Eu sei que a probabilidade é efetivamente nula. Para mim, o uso de UUID é design preguiçoso, e eu só queria ver se você pode sempre evitá-lo
Pyrolistical

Isso é justo o suficiente, desde que você veja que a baixa probabilidade precisa ser considerada nas circunstâncias mais extremas, como presumirei agora.
Noldorin

13

Um exemplo clássico é quando você está replicando entre dois bancos de dados.

O DB (A) insere um registro com o ID int 10 e, ao mesmo tempo, o DB (B) cria um registro no ID 10. Isso é uma colisão.

Com os UUIDs, isso não acontece, pois eles não coincidem. (quase certamente)


1
Ok, faça com que o DB A use o ID par e o DB B use os IDs ímpares. Feito, sem UUID.
Pyrolistical

2
Com três
bancos de

20
Se você usa múltiplos 2/3 / o que quer que seja, o que acontece quando você adiciona um novo servidor ao mix posteriormente? Você precisa coordenar um comutador para usar múltiplos n + 1 no novo servidor e mover todos os servidores antigos para o novo algoritmo, além de desligar tudo enquanto faz isso para evitar colisões durante a chave do algoritmo. Ou ... você pode simplesmente usar UUIDs como TODOS.
24620 Bob Aman

3
É ainda pior do que isso, porque como você diferenciaria entre múltiplos de 2 e múltiplos de 4? Ou múltiplos de 3 vs. múltiplos de 6? Na verdade, você teria que ficar com múltiplos números primos. Blech! Basta usar o UUID, ele funciona. Microsoft, Apple e inúmeras outras pessoas confiam neles e confiam neles.
sidewinderguy

2
@sidewinderguy, em GUID confiamos! :)
Ron Klein

13

Também existe uma probabilidade diferente de zero de que todas as partículas do seu corpo esculpam simultaneamente a cadeira em que você está sentado e de repente você se encontrará sentado no chão.

Você se preocupa com isso?


7
Claro que não, isso não é algo que eu possa controlar, mas sim projetos.
Pyrolistical

4
@Pyrolistical Is que realmente, eu quero dizer realmente a razão que você não se preocupe com isso? Então você é bem estranho. Além disso, você não está certo. Você pode controlar isso. Se você ganha alguns quilos, diminui significativamente a probabilidade de um evento desse tipo. Você considera que deveria ganhar peso, então? :-)
Veky

8

Eu tenho um esquema para evitar UUIDs. Configure um servidor em algum lugar e faça com que, toda vez que algum software deseje um identificador universal único, eles entrem em contato com esse servidor e ele o entregue. Simples!

Exceto que existem alguns problemas práticos reais com isso, mesmo que ignoremos a malícia total. Em particular, esse servidor pode falhar ou tornar-se inacessível em parte da Internet. Lidar com falhas do servidor requer replicação, e isso é muito difícil de acertar (consulte a literatura sobre o algoritmo Paxos para saber por que a construção de consenso é incômoda) e também é muito lenta. Além disso, se todos os servidores estiverem inacessíveis a partir de uma parte específica da rede, nenhum dos clientes conectados a essa sub-rede poderá fazer qualquer coisa, porque todos estarão aguardando novos IDs.

Então ... use um algoritmo probabilístico simples para gerá-los com probabilidade de falhar durante a vida útil da Terra ou (financiar e) construir uma infraestrutura importante que será uma PITA de implantação e tenha falhas frequentes. Eu sei qual eu escolheria.


2
Na verdade, o objetivo principal da invenção dos UUIDs era evitar sua abordagem. Se você pesquisar a história dos UUIDs, verá que ela deriva das primeiras experiências na criação de redes sofisticadas e significativas de computadores. Eles sabiam que as redes são inerentemente não confiáveis ​​e complicadas. Os UUIDs responderam à questão de como coordenar dados entre computadores quando você sabia que eles não podiam estar em comunicação constante.
Basil Bourque

7
@BasilBourque Eu estava usando sarcasmo no primeiro parágrafo, caso não fosse óbvio.
Donal Fellows

5

Não recebo toda a conversa sobre a probabilidade de colisão. Eu não ligo para colisão. Eu me preocupo com o desempenho.

https://dba.stackexchange.com/a/119129/33649

UUIDs são um desastre de desempenho para tabelas muito grandes. (200 mil linhas não são "muito grandes".)

Seu número 3 é realmente ruim quando o CHARCTER SET é utf8 - CHAR (36) ocupa 108 bytes!

UUIDs (GUIDs) são muito "aleatórios". Usá-los como uma chave UNIQUE ou PRIMARY em tabelas grandes é muito ineficiente. Isso ocorre porque você precisa pular a tabela / índice toda vez que você INSERIR um novo UUID ou SELECT by UUID. Quando a tabela / índice é muito grande para caber no cache (consulte innodb_buffer_pool_size, que deve ser menor que a RAM, geralmente 70%), o UUID 'próximo' pode não ser armazenado em cache, portanto, um disco lento é atingido. Quando a tabela / índice é 20 vezes maior que o cache, apenas 1/20 (5%) das ocorrências são armazenadas em cache - você está vinculado à E / S.

Portanto, não use UUIDs, a menos que

você tem tabelas "pequenas" ou precisa delas devido à geração de IDs únicos de lugares diferentes (e não descobriu outra maneira de fazê-lo). Mais sobre UUIDs: http://mysql.rjweb.org/doc.php/uuid (inclui funções para converter entre UUIDs padrão de 36 caracteres e BINARY (16).)

Ter um AUTO_INCREMENT UNIQUE e um UUID UNIQUE na mesma tabela é um desperdício.

Quando um INSERT ocorre, todas as chaves exclusivas / primárias devem ser verificadas quanto a duplicatas. Qualquer chave exclusiva é suficiente para o requisito do InnoDB de possuir uma PRIMARY KEY. BINARY (16) (16 bytes) é um pouco volumoso (um argumento contra torná-lo o PK), mas não é tão ruim assim. O volume é importante quando você possui chaves secundárias. O InnoDB coloca a PK silenciosamente no final de cada chave secundária. A principal lição aqui é minimizar o número de chaves secundárias, especialmente para tabelas muito grandes. Para comparação: INT UNSIGNED é de 4 bytes com intervalo de 0 a 4 bilhões. BIGINT é de 8 bytes.


4

Se você apenas olhar as alternativas, por exemplo, para um aplicativo de banco de dados simples, para consultar o banco de dados toda vez antes de criar um novo objeto, em breve descobrirá que o uso do UUID pode efetivamente reduzir a complexidade do seu sistema. Concedido - se você usar as chaves int, elas são de 32 bits, que serão armazenadas em um quarto do UUID de 128 bits. Concedido - os algoritmos de geração de UUID ocupam mais poder computacional do que simplesmente incrementar um número. Mas quem se importa? A sobrecarga de gerenciar uma "autoridade" para atribuir números únicos de outra maneira supera facilmente essa ordem de magnitude, dependendo do espaço de ID de exclusividade pretendido.


3

No UUID == design lento

Eu discordo é sobre escolher suas lutas. Se um UUID duplicado é estatisticamente impossível e a matemática é comprovada, por que se preocupar? Gastar tempo projetando em torno do seu pequeno sistema de geração de N UUID é impraticável; sempre há uma dúzia de outras maneiras de melhorar seu sistema.


1

No meu último trabalho, estávamos recebendo objetos de terceiros identificados exclusivamente com o UUID. Coloquei uma tabela de pesquisa de número inteiro longo UUID-> e usei o número inteiro longo como minhas chaves primárias, porque era muito mais rápido assim.


Sim, com certeza, terceiros forçando você a usar o UUID é outro problema em que não quero entrar. Supondo que você tenha controle para usar UUID ou não.
Pyrolistical

Bem, um "número inteiro longo" (128 bits) é realmente o que é um UUID. É apenas mostrado como uma cadeia para consumo humano. Às vezes, pode ser transmitido dessa maneira, mas, para armazenamento e indexação, certamente será mais rápido em número inteiro, como você encontrou.
1016 Nicole

1

Usando o algoritmo da versão 1, parece impossível colisão sob a restrição de que menos de 10 UUIDs por milissegundo são gerados a partir do mesmo endereço MAC

Conceitualmente, o esquema de geração original (versão 1) para UUIDs era concatenar a versão UUID com o endereço MAC do computador que está gerando o UUID e com o número de intervalos de 100 nanossegundos desde a adoção do calendário gregoriano no Ocidente. . Na prática, o algoritmo real é mais complicado. Esse esquema foi criticado por não ser suficientemente "opaco"; revela a identidade do computador que gerou o UUID e a hora em que ele o fez.

Alguém me corrija se eu interpretar mal como funciona


Existem muitas versões e muitos sistemas de software (Java, por exemplo) não podem usar a versão 1, pois não possui uma maneira pura de Java para acessar o endereço mac.
Pyrolistical

Sobre a incapacidade do Java em obter o endereço MAC: Não é inteiramente verdade. Existem soluções alternativas para isso. Você pode definir manualmente o endereço MAC usado pelo gerador através de um arquivo de configuração. Você também pode chamar ifconfig e analisar a saída. O gerador Ruby UUID que escrevi usa as duas abordagens.
24620 Bob Aman

Além disso, como mencionado na minha resposta, se você não conseguir obter um endereço MAC para um UUID da versão 1, use 6 bytes aleatórios, conforme a seção 4.5 da RFC 4122. Portanto, mesmo que não queira usar nenhum dos Nas duas soluções alternativas para Java, você ainda pode gerar um UUID versão 1 válido.
24620 Bob Aman

Os GUIDs da MS são apenas números aleatórios. Eles não têm mais nenhuma parte do MAC, porque isso possibilitou a engenharia reversa do endereço MAC do servidor (o que acabou sendo muito perigoso).
Stefan Steiger

1

Para aqueles que dizem que os UUIDs são de design ruim porque podem (com uma probabilidade ridiculamente pequena) colidir, enquanto as chaves geradas pelo DB não ... você sabe a chance de um erro humano causar uma colisão nas chaves geradas pelo DB por causa de algumas A necessidade prevista é MUITO MUITO MAIS alta do que a chance de colisão com o UUID4. Nós sabemos que se o db é recriado ele vai começar ids em 1 de novo, e como muitos de nós tiveram que recriar uma mesa quando estávamos certeza de que nunca iria precisar? Eu colocaria meu dinheiro na segurança do UUID quando as coisas começam a dar errado com o desconhecido-desconhecido a qualquer dia.


0

Além dos casos em que você precisa usar a API de outra pessoa que exige um UUID, é claro que sempre há outra solução. Mas essas alternativas resolverão todos os problemas que os UUIDs fazem? Você acabará adicionando mais camadas de hacks, cada uma para resolver um problema diferente, quando você poderia ter resolvido todas elas de uma vez?

Sim, é teoricamente possível que os UUIDs colidam. Como outros observaram, é ridiculamente improvável que não valha a pena considerar. Isso nunca aconteceu até agora e provavelmente nunca acontecerá. Esqueça isso.

A maneira mais "óbvia" de evitar colisões é permitir que um único servidor gere IDs únicos em cada inserção, o que obviamente cria sérios problemas de desempenho e não resolve o problema de geração offline. Opa

A outra solução "óbvia" é uma autoridade central que distribui blocos de números exclusivos com antecedência, que é essencialmente o que o UUID V1 faz usando o endereço MAC da máquina geradora (via IEEE OUI). Mas endereços MAC duplicados acontecem porque todas as autoridades centrais estragam eventualmente; portanto, na prática, isso é muito mais provável do que uma colisão UUID V4. Opa

O melhor argumento contra o uso de UUIDs é que eles são "grandes demais", mas um esquema (significativamente) menor inevitavelmente falhará na solução dos problemas mais interessantes; O tamanho dos UUIDs é um efeito colateral inerente à sua utilidade na solução desses mesmos problemas.

É possível que seu problema não seja grande o suficiente para precisar do que os UUIDs oferecem e, nesse caso, fique à vontade para usar outra coisa. Mas se o seu problema aumentar inesperadamente (e a maioria ocorre), você acabará mudando mais tarde - e se arrependerá por não usá-lo. Por que projetar para o fracasso quando é tão fácil projetar para o sucesso?


-10

Os UUIDs incorporam todas as práticas ruins de codificação associadas a variáveis ​​globais, apenas pior, pois são variáveis ​​superglobais que podem ser distribuídas por diferentes peças do kit.

Recentemente, ocorreu um problema com a substituição de uma impressora por um modelo de substituição exato e constatou que nenhum software cliente funcionaria.


2
Ainda bem que vivemos em uma sociedade que ainda se concentra nos fatos e não em opiniões aleatórias; caso contrário, todos nós, no excesso de pilha, ficaríamos sem empregos. :)
Makarand
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.