Possibilidade de duplicar o Mongo ObjectId sendo gerado em duas coleções diferentes?

187

É possível que o mesmo Mongo ObjectId exato seja gerado para um documento em duas coleções diferentes? Percebo que é definitivamente muito improvável, mas é possível?

Sem ser muito específico, o motivo pelo qual pergunto é que, com um aplicativo em que estou trabalhando, mostramos perfis públicos de funcionários eleitos que esperamos converter em usuários de pleno direito do nosso site. Temos coleções separadas para usuários e funcionários eleitos que atualmente não são membros do nosso site. Existem vários outros documentos que contêm vários dados sobre os funcionários eleitos que são mapeados de volta para a pessoa usando seu ObjectId oficial eleito.

Após criar a conta, ainda destacamos os dados associados ao funcionário eleito, mas agora eles também fazem parte da coleção de usuários com um ObjectId de usuários correspondente para mapear seu perfil para interações com nosso aplicativo.

Começamos a converter nosso aplicativo do MySql para o Mongo há alguns meses e, enquanto estamos em transição, armazenamos o ID do MySql herdado para esses dois tipos de dados e também começamos a armazenar o Mongo ObjectId oficial eleito nos usuários documento para mapear de volta aos dados oficiais eleitos.

Eu estava pensando em especificar o novo usuário ObjectId como o ObjectId oficial eleito anterior para tornar as coisas mais simples, mas queria ter certeza de que não era possível ter uma colisão com qualquer usuário existente ObjectId.

Obrigado pela sua compreensão.

Edit: Logo após postar esta pergunta, percebi que minha solução proposta não era uma idéia muito boa. Seria melhor manter o esquema atual que possuímos e apenas vincular ao funcionário eleito '_id' no documento do usuário.

Anthony Jack
fonte
1
Eu li essa página antes. Ironicamente, na verdade, eu vinculei à mesma página em uma resposta anterior. E vi a isenção de responsabilidade "razoavelmente alta de ser único", mas não tinha certeza se a coleção inserida desempenhava algum fator nisso. Acho que não tenho certeza do que exatamente a parte de identificação de processo de 2 bytes do ObjectId realmente representa. Se isso tem algo a ver com a coleção, haveria exclusividade entre dois documentos diferentes criados ao mesmo tempo e exatamente na mesma máquina em coleções diferentes.
Anthony Jack
1
A identificação do processo de 2 bytes é o pid do processo que gera o ObjectID. Como exemplo, aqui está o código que o pymongo usa para gerar ObjectIDs
mstearn
Um problema que eu encontrei é a inserção em lote. Eu estava construindo lotes de 10 mil documentos e colidindo toda vez porque a parte do contador rolava toda vez.
fawce
Sei que já faz um tempo, mas os documentos de 10 mil não rolariam no balcão. A parte do contador é de três bytes, não de três dígitos. São mais de 16 milhões.
Asya Kamsky

Respostas:

318

Resposta curta

Apenas para adicionar uma resposta direta à sua pergunta inicial: SIM, se você usar a geração de BSON Object ID, para a maioria dos drivers, os IDs certamente serão únicos nas coleções. Veja abaixo o que "quase certamente" significa.

Resposta longa

É altamente provável que os IDs de objeto BSON gerados pelos drivers Mongo DB sejam únicos nas coleções. Isso ocorre principalmente pelos últimos 3 bytes do ID, que para a maioria dos drivers são gerados por meio de um contador estático de incremento. Esse contador é independente da coleção; é global. O driver Java, por exemplo, usa um AtomicInteger estático, inicializado aleatoriamente.

Então, por que, nos documentos do Mongo, eles dizem que os IDs têm "alta probabilidade" de serem únicos, em vez de dizerem claramente que eles serão únicos? Podem ocorrer três possibilidades em que você não obterá um ID exclusivo (entre em contato se houver mais):

Antes desta discussão, lembre-se de que o ID do objeto BSON consiste em:

[4 bytes segundos desde a época, hash da máquina de 3 bytes, ID do processo de 2 bytes, contador de 3 bytes]

Aqui estão as três possibilidades, para que você julgue por si mesmo qual a probabilidade de ser burro:

1) Estouro do contador: existem 3 bytes no contador. Se você inserir mais de 16.777.216 (2 ^ 24) documentos em um único segundo, na mesma máquina, no mesmo processo, poderá exceder os bytes de contador incrementais e acabar com dois IDs de Objeto que compartilham o mesmo tempo, máquina , processo e valores de contador.

2) Contador sem incremento: alguns drivers Mongo usam números aleatórios em vez de incrementar números para os bytes do contador. Nesses casos, existe uma chance de 1 / 16.777.216 de gerar um ID não exclusivo, mas apenas se esses dois IDs forem gerados no mesmo segundo (ou seja, antes da seção de tempo do ID ser atualizada para o próximo segundo), no mesmo máquina, no mesmo processo.

3) Hash da máquina e do processo com os mesmos valores. Os valores de ID de máquina e de processo podem, em algum cenário altamente improvável, mapear para os mesmos valores para duas máquinas diferentes. Se isso ocorrer e, ao mesmo tempo, os dois contadores nas duas máquinas diferentes, durante o mesmo segundo, gerarem o mesmo valor, você terá um ID duplicado.

Esses são os três cenários a serem observados. Os cenários 1 e 3 parecem altamente improváveis ​​e o cenário 2 é totalmente evitável se você estiver usando o driver correto. Você terá que verificar a fonte do driver para ter certeza.

Raj Advani
fonte
O contador de 3 bytes não representa uma capacidade de aceitar 2 ^ 24 = 16777216 número de documentos inseridos por segundo por processo por máquina?
Forrest Ye
Você está absolutamente certo, acidentalmente reduzi pela metade o número de bits - a resposta foi alterada.
Raj Advani
Desde Eu só entrou isso, deixe-me acrescentar que alguns drivers (por exemplo C), embora incrementos usos, não incremento atomicamente, então tempos em tempos, ele gera o mesmo OID devido à condição de corrida
Pawel Veselov
39
Você completamente ignorado o fato de que em 136 anos você teria outra chance para gerar a mesma ObjectIdque você tinha antes, enquanto o hash máquina, ID do processo, e contra todos o mesmo resultado
jamylak
25
@jamylak Vamos cuidar desse problema quando se torna urgente (disse aquelas pessoas que padronizados formatos de data YYMMDD nos anos 70)
Philipp
14

Os ObjectIds são gerados no lado do cliente de maneira semelhante ao UUID, mas com algumas propriedades mais agradáveis ​​para armazenamento em um banco de dados, como aumentar aproximadamente a ordem e codificar seu tempo de criação gratuitamente. O principal para o seu caso de uso é que eles são projetados para garantir exclusividade a uma alta probabilidade, mesmo que sejam gerados em máquinas diferentes.

Agora, se você estava se referindo ao campo _id em geral, não exigimos exclusividade entre as coleções, portanto é seguro reutilizar o antigo _id. Como um exemplo concreto, se você tiver duas coleções colorse fruits, ambas podem ter simultaneamente um objeto como {_id: 'orange'}.

Caso você queira saber mais sobre como os ObjectIds são criados, aqui está a especificação: http://www.mongodb.org/display/DOCS/Object+IDs#ObjectIDs-BSONObjectIDSpecification

mstearn
fonte
11

Caso alguém esteja tendo problemas com os ObjectIDs do Mongo duplicados, você deve saber que, apesar da improvabilidade de dups acontecendo no próprio Mongo, é possível ter _id's duplicados gerados com PHP no Mongo.

O caso de uso em que isso aconteceu com regularidade para mim é quando estou percorrendo um conjunto de dados e tentando injetar os dados em uma coleção.

A matriz que contém os dados da injeção deve ser redefinida explicitamente a cada iteração - mesmo se você não estiver especificando o valor _id. Por alguma razão, o processo INSERT adiciona o Mongo _id à matriz como se fosse uma variável global (mesmo que a matriz não tenha escopo global). Isso pode afetá-lo mesmo se você estiver chamando a inserção em uma chamada de função separada, na qual normalmente espera que os valores da matriz não persistam de volta à função de chamada.

Existem três soluções para isso:

  1. Você pode unset()o campo _id da matriz
  2. Você pode reinicializar toda a matriz a array()cada vez que percorrer seu conjunto de dados
  3. Você pode definir explicitamente o valor _id (cuidando de defini-lo de forma a não gerar dups).

Meu palpite é que isso é um bug na interface do PHP, e não um problema com o Mongo, mas se você encontrar esse problema, desative o _id e você ficará bem.

DenverMatt
fonte
veja aqui: php.net/manual/en/mongocollection.insert.php : "Nota: Se o parâmetro não possuir uma chave ou propriedade _id, uma nova instância do MongoId será criada e atribuída a ele. Esse comportamento especial não significa que o parâmetro é passado por referência. ", é um recurso, não um bug, deve ser assim
Oliver Konig 4/14
1
Eu não entendo o cenário que você está descrevendo aqui; talvez você possa mostrar algum código que exibe o bug?
Marque Amery
-7

Não há garantia alguma sobre a exclusividade do ObjectId entre as coleções. Mesmo que seja probabilisticamente muito improvável, seria um design de aplicativo muito ruim que dependesse da exclusividade _id entre as coleções.

Pode-se facilmente testar isso no shell mongo:

MongoDB shell version: 1.6.5
connecting to: test
> db.foo.insert({_id: 'abc'})
> db.bar.insert({_id: 'abc'})
> db.foo.find({_id: 'abc'})
{ "_id" : "abc" }
> db.bar.find({_id: 'abc'})
{ "_id" : "abc" }
> db.foo.insert({_id: 'abc', data:'xyz'})
E11000 duplicate key error index: test.foo.$_id_  dup key: { : "abc" }

Portanto, não confie absolutamente em _id sendo único nas coleções e, como você não controla a função de geração do ObjectId, não confie nela.

É possível criar algo mais parecido com um uuid e, se você fizer isso manualmente, poderá ter uma garantia melhor de exclusividade.

Lembre-se de que você pode colocar objetos de "tipos" diferentes na mesma coleção. Por que não colocar apenas suas duas "tabelas" na mesma coleção? Eles compartilhariam o mesmo espaço _id e, portanto, teriam garantia única. Mudar de "prospectivo" para "registrado" seria um simples movimento de um campo ...

slacy
fonte
1
Eu acho que você pode estar confundindo o campo _id em geral com o tipo ObjectID. O tipo ObjectID foi projetado especificamente para exclusividade com o objetivo de poder ser tratado como um UUID. No entanto, o campo _id pode ser de qualquer tipo e só garante exclusividade em uma única coleção se você usar outros tipos para a chave, como uma sequência no seu exemplo.
mstearn
@mstearn (Nitpick) A noção de que um UUID é inerentemente único é falha. Uma boa estratégia de geração de UUID / sequência pode tornar a colisão improvável, mas é necessário levar em consideração geradores únicos (por exemplo, locais únicos) para garantir exclusividade absoluta entre os geradores. É verdade que a maioria tem probabilidades tão baixas que não são uma preocupação aplicável :-) GUID . Uma questão que faz surgir, porém, é a duplicação / cópia de ids em vez de uma nova geração.
1
@pst: os ObjectIDs do MongoDBs incluem o pid do processo de geração e alguns bytes com base em um hash do nome do host. Estes, combinados com um carimbo de data e hora e um contador de incremento, tornam extremamente provável que quaisquer dois ObjectIDs gerados separadamente sejam global / universalmente únicos. Obviamente, como você disse, isso só se aplica a ObjectIDs gerados recentemente.
mstearn
1
Estou me referindo ao tipo ObjectId. Não especificando um valor de sequência para '_id'. É claro que eles serão os mesmos e entrarão em conflito se você os definir exatamente na mesma string manualmente.
Anthony Jack
Sim, eu esclareci as coisas no meu post. _id's certamente não são únicos e, como você não controla a função de geração do ObjectId, provavelmente é uma má idéia confiar nela.
slacy