Czy możliwe jest generowanie duplikatów Mongo ObjectId w dwóch różnych kolekcjach?

187

Czy możliwe jest wygenerowanie tego samego dokładnego obiektu Mongo ObjectId dla dokumentu w dwóch różnych kolekcjach? Zdaję sobie sprawę, że jest to na pewno bardzo mało prawdopodobne, ale czy jest to możliwe?

Nie sprecyzowując, powód, dla którego pytam, jest taki, że dzięki aplikacji, nad którą pracuję, pokazujemy publiczne profile wybranych urzędników, którzy mamy nadzieję przekształcić się w pełnoprawnych użytkowników naszej witryny. Mamy osobne kolekcje dla użytkowników i wybranych urzędników, którzy obecnie nie są członkami naszej witryny. Istnieją różne inne dokumenty zawierające różne dane o wybranych urzędnikach, które wszystkie odwzorowują z powrotem na osobę używającą wybranego przez siebie oficjalnego obiektu ObjectId.

Po utworzeniu konta nadal podkreślamy dane powiązane z wybranym urzędnikiem, ale teraz są one również częścią kolekcji użytkowników z odpowiednim ObjectId użytkownika, aby odwzorować ich profil na interakcje z naszą aplikacją.

Kilka miesięcy temu zaczęliśmy konwertować naszą aplikację z MySql na Mongo i podczas gdy przechodzimy, przechowujemy starszy identyfikator MySql dla obu tych typów danych, a także zaczynamy teraz przechowywać wybranych oficjalnych obiektów Mongo ObjectId u użytkowników dokument odwzorowujący z powrotem na wybrane oficjalne dane.

Zastanawiałem się nad określeniem nowego obiektu ObjectId jako poprzednio wybranego oficjalnego obiektu ObjectId, aby uprościć sprawę, ale chciałem się upewnić, że kolizja z żadnym istniejącym użytkownikiem ObjectId nie będzie możliwa.

Dzięki za wgląd.

Edycja: Wkrótce po opublikowaniu tego pytania zdałem sobie sprawę, że moje zaproponowane rozwiązanie nie było zbyt dobrym pomysłem. Lepiej byłoby po prostu zachować obecny schemat, który mamy na miejscu, i po prostu link do wybranego oficjalnego „_id” w dokumencie użytkownika.

Anthony Jack
źródło
1
Przeczytałem tę stronę wcześniej. Jak na ironię, faktycznie link do tej samej strony w poprzedniej odpowiedzi. I widziałem zastrzeżenie „dość wysokie prawdopodobieństwo bycia wyjątkowym”, ale nie byłem pewien, czy wstawiona kolekcja zagrała w tym jakiś czynnik. Chyba nie jestem pewien, co dokładnie reprezentuje 2-bajtowa część ID procesu ObjectId. Jeśli ma to coś wspólnego z kolekcją, to istniałaby wyjątkowość między dwoma różnymi dokumentami tworzonymi dokładnie w tym samym czasie na tej samej maszynie w różnych kolekcjach.
Anthony Jack
1
2-bajtowy identyfikator procesu jest pid procesu generującego ObjectID. Jako przykład podajemy kod, którego używa pymongo do generowania identyfikatorów ObjectID: github.com/mongodb/mongo-python-driver/blob/master/bson/…
mstearn
Jednym z problemów, na jakie wpadłem, jest wstawianie partii. Budowałem partie 10 000 dokumentów i kolidowałem za każdym razem, ponieważ część za każdym razem się przewracała.
fawce
Wiem, że minęło trochę czasu, ale dokumenty 10 000 nie przewróciłyby się bez recepty. Część licząca to trzy bajty, a nie trzy cyfry. To ponad 16 milionów.
Asya Kamsky

Odpowiedzi:

318

Krótka odpowiedź

Wystarczy dodać bezpośrednią odpowiedź na początkowe pytanie: TAK, jeśli używasz generowania identyfikatorów obiektów BSON, to w przypadku większości sterowników identyfikatory prawie na pewno będą unikalne we wszystkich kolekcjach. Zobacz poniżej, co oznacza „prawie na pewno”.

Długa odpowiedź

Identyfikatory obiektów BSON generowane przez sterowniki Mongo DB najprawdopodobniej będą unikalne we wszystkich kolekcjach. Wynika to głównie z ostatnich 3 bajtów identyfikatora, który dla większości sterowników jest generowany przez statyczny licznik przyrostowy. Ten licznik jest niezależny od kolekcji; jest globalny. Na przykład sterownik Java używa losowo inicjowanej, statycznej wartości AtomicInteger.

Dlaczego więc w dokumentach z Mongo mówią, że identyfikatory są „wysoce prawdopodobne”, że będą unikalne, zamiast wprost twierdzić, że BĘDĄ niepowtarzalne? Mogą wystąpić trzy możliwości, w których nie otrzymasz unikalnego identyfikatora (daj mi znać, jeśli jest więcej):

Przed tą dyskusją pamiętaj, że identyfikator obiektu BSON składa się z:

[4 bajty sekund od epoki, 3-bajtowy hasz maszynowy, 2-bajtowy identyfikator procesu, 3-bajtowy licznik]

Oto trzy możliwości, więc sam ocenisz, jak prawdopodobne jest uzyskanie duplikatu:

1) Przepełnienie licznika: w liczniku są 3 bajty. Jeśli zdarzy się, że wstawisz ponad 16 777 216 (2 ^ 24) dokumentów w ciągu jednej sekundy, na tej samej maszynie, w tym samym procesie, możesz przepełnić inkrementujące bajty licznika i otrzymać dwa identyfikatory obiektów, które współużytkują ten sam czas, maszyna , wartości procesowe i licznikowe.

2) Licznik nie inkrementujący: niektórzy kierowcy Mongo używają liczb losowych zamiast inkrementujących liczby dla bajtów liczących. W takich przypadkach istnieje szansa 1/16 777 216 wygenerowania nieunikalnego identyfikatora, ale tylko wtedy, gdy te dwa identyfikatory zostaną wygenerowane w tej samej sekundzie (tj. Przed aktualizacją sekcji czasu identyfikatora do następnej sekundy), w tym samym maszyna w tym samym procesie.

3) Skrót maszyny i procesu do tych samych wartości. Identyfikator maszyny i wartości identyfikatora procesu mogą, w bardzo mało prawdopodobnym scenariuszu, odwzorować na te same wartości dla dwóch różnych maszyn. Jeśli tak się stanie, a jednocześnie dwa liczniki na dwóch różnych komputerach, w tej samej sekundzie, wygenerują tę samą wartość, to otrzymasz duplikat ID.

Oto trzy scenariusze, na które należy uważać. Scenariusz 1 i 3 wydają się bardzo mało prawdopodobne, a scenariusza 2 można całkowicie uniknąć, jeśli używasz odpowiedniego sterownika. Będziesz musiał sprawdzić źródło sterownika, aby się upewnić.

Raj Advani
źródło
Czy licznik 3 bajtów nie jest w stanie zaakceptować 2 ^ 24 = 16777216 liczby dokumentów wstawianych na sekundę na proces na maszynę?
Forrest Ye
Masz absolutną rację, przypadkowo zmniejszyłem o połowę liczbę bitów - odpowiedź została zmieniona.
Raj Advani,
Ponieważ właśnie do tego dołączyłem, dodam, że niektórzy kierowcy (np. C), chociaż stosują przyrosty, nie zwiększają atomowo, więc od czasu do czasu generują tę samą ilość oleju ze względu na warunki wyścigowe
Paweł Veselov
39
Całkowicie pominąłeś fakt, że za 136 lat będziesz miał kolejny zastrzyk, aby wygenerować to samo ObjectId, co wcześniej, o ile skrót maszyny, identyfikator procesu i licznik wszystko to samo
jamylak
25
@jamylak Zajmiemy się tym problemem, gdy stanie się on pilny (powiedzieli ci, którzy w latach 70. znormalizowali formaty dat YYMMDD)
Philipp
14

ObjectId są generowane po stronie klienta w sposób podobny do UUID, ale z pewnymi ładniejszymi właściwościami do przechowywania w bazie danych, takimi jak z grubsza zwiększanie kolejności i kodowanie czasu ich tworzenia za darmo. Kluczową sprawą w twoim przypadku użycia jest to, że zostały zaprojektowane w celu zagwarantowania wyjątkowości z dużym prawdopodobieństwem, nawet jeśli są generowane na różnych komputerach.

Teraz, jeśli ogólnie odwołujesz się do pola _id, nie wymagamy unikatowości między kolekcjami, więc można bezpiecznie użyć starego _id. Jako konkretny przykład, jeśli masz dwie kolekcje colorsi fruitsobie mogą jednocześnie mieć taki obiekt {_id: 'orange'}.

Jeśli chcesz dowiedzieć się więcej o tworzeniu ObjectIds, oto specyfikacja: http://www.mongodb.org/display/DOCS/Object+IDs#ObjectIDs-BSONObjectIDSpecification

mstearn
źródło
11

Jeśli ktoś ma problemy ze zduplikowanymi identyfikatorami obiektów Mongo, powinieneś wiedzieć, że pomimo mało prawdopodobnego wystąpienia duplikatów w samym Mongo, możliwe jest wygenerowanie duplikatów _id w PHP w Mongo.

Przypadek użycia, w którym zdarzało mi się to regularnie, dotyczy mnie, gdy przeglądam zestaw danych i próbuję wstrzyknąć dane do kolekcji.

Tablica przechowująca dane wstrzyknięcia musi zostać wyraźnie zresetowana przy każdej iteracji - nawet jeśli nie określono wartości _id. Z jakiegoś powodu proces INSERT dodaje Mongo _id do tablicy tak, jakby była zmienną globalną (nawet jeśli tablica nie ma zasięgu globalnego). Może to wpłynąć na ciebie, nawet jeśli wywołujesz wstawienie w oddzielnym wywołaniu funkcji, w którym normalnie oczekiwałbyś, że wartości tablicy nie będą wracać do funkcji wywołującej.

Istnieją trzy rozwiązania tego:

  1. Możesz unset()z pola _id z tablicy
  2. Możesz ponownie zainicjować całą tablicę za array()każdym razem, gdy przeglądasz zestaw danych
  3. Możesz samodzielnie zdefiniować wartość _id (uważając, aby zdefiniować ją w taki sposób, abyś sam nie generował duplikatów).

Domyślam się, że jest to błąd w interfejsie PHP i nie tyle problem z Mongo, ale jeśli napotkasz ten problem, po prostu usuń _id i powinieneś być w porządku.

DenverMatt
źródło
patrz tutaj: php.net/manual/en/mongocollection.insert.php : "Uwaga: Jeśli parametr nie ma klucza lub właściwości _id, zostanie utworzona i przypisana do niej nowa instancja MongoId. To specjalne zachowanie nie oznacza że parametr jest przekazywany przez referencję. ”, jest to funkcja, a nie błąd, tak powinno być
Oliver Konig
1
Nie rozumiem scenariusza, który tu opisujesz; może mógłbyś pokazać kod, który pokazuje błąd?
Mark Amery
-7

Nie ma żadnej gwarancji unikalności ObjectId we wszystkich kolekcjach. Nawet jeśli jest to bardzo mało prawdopodobne, byłby to bardzo słaby projekt aplikacji, który polegałby na wyjątkowości _id we wszystkich kolekcjach.

Można to łatwo przetestować w skorupce mangowej:

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" }

Więc absolutnie nie polegaj na tym, że _id jest unikatowy w różnych kolekcjach, a ponieważ nie kontrolujesz funkcji generowania ObjectId, nie polegaj na niej.

Możliwe jest stworzenie czegoś, co bardziej przypomina UUID, a jeśli zrobisz to ręcznie, możesz mieć lepszą gwarancję wyjątkowości.

Pamiętaj, że możesz umieszczać obiekty różnych „typów” w tej samej kolekcji, więc dlaczego nie po prostu umieścić dwóch „tabel” w tej samej kolekcji. Będą dzielić tę samą przestrzeń id, a tym samym będą gwarantowane unikalne. Przejście z „potencjalnego” na „zarejestrowanego” byłoby prostym przerzuceniem pola ...

slacy
źródło
1
Myślę, że ogólnie mylisz pole _id z typem ObjectID. Typ ObjectID został specjalnie zaprojektowany pod kątem wyjątkowości, aby mógł być traktowany jak UUID. Jednak pole _id może być dowolnego typu i gwarantuje unikatowość pojedynczej kolekcji, jeśli użyjesz innych typów dla klucza, takich jak ciąg znaków w przykładzie.
mstearn 13.01.11
@mstearn (Nitpick) Pojęcie, że identyfikator UUID jest z natury unikatowy, jest błędne. Dobra strategia UUID / generowania sekwencji może sprawić, że kolizja jest mało prawdopodobna, ale musi uwzględniać unikalne generatory (np. Unikalne lokalizacje), aby zagwarantować absolutną unikalność między generatorami. Oczywiście większość ma tak małe prawdopodobieństwo, że nie ma to żadnego znaczenia :-) GUID . Jedną z kwestii, które nie pojawią się jednak, jest powielanie / kopiowanie identyfikatorów zamiast nowej generacji.
1
@pst: MongoDB Identyfikatory obiektów obejmują zarówno pid procesu generowania, jak i niektóre bajty na podstawie skrótu nazwy hosta. Te w połączeniu z datownikiem i inkrementującym licznikiem sprawiają, że niezwykle prawdopodobne jest, że dowolne dwa oddzielnie generowane ObjectID będą unikalne na całym świecie. Oczywiście, jak powiedziałeś, dotyczy to tylko świeżo wygenerowanych ObjectID.
mstearn 13.01.11
1
Mam na myśli typ ObjectId. Brak określenia wartości ciągu dla „_id”. Oczywiście będą one takie same i będą powodować konflikty, jeśli ręcznie ustawisz dokładnie ten sam ciąg.
Anthony Jack
Tak, wyjaśniłem rzeczy w swoim poście. _id z pewnością nie są unikalne, a ponieważ nie kontrolujesz funkcji generowania ObjectId, prawdopodobnie złym pomysłem jest poleganie na niej.
slacy 13.01.11