Zgadzam się z ogólnym pojęciem innych odpowiedzi, że jest to granica problem relacyjny.
Kluczem do modeli danych MongoDB jest ciężkość zapisu, ale może to być trudne w tym przypadku użycia, głównie ze względu na księgowość, która byłaby wymagana, jeśli chcesz połączyć użytkowników bezpośrednio z elementami (zmiana w grupie, po której następuje wiele użytkowników poniosłoby ogromną liczbę zapisów, a do tego potrzebny jest pracownik).
Zbadajmy, czy model z dużym obciążeniem odczytu nie ma tu zastosowania, czy też przeprowadzamy przedwczesną optymalizację.
Podejście do ciężkiego czytania
Twoim głównym zmartwieniem jest następujący przypadek użycia:
prawdziwym problemem z wydajnością może być to, że chcę uzyskać wszystkie grupy, które obserwuje użytkownik dla określonego elementu [...], ponieważ wtedy muszę znaleźć wszystkie grupy, które obserwuje użytkownik, a następnie znaleźć wszystkie item_groups z group_id $in
i identyfikator przedmiotu.
Przeanalizujmy to:
-
Pobierz wszystkie grupy, które obserwuje użytkownik
To jest proste zapytanie:
db.followers.find({userId : userId})
. Będziemy potrzebować indeksuuserId
co sprawi, że czas wykonywania tej operacji będzie O(log n) lub bardzo szybki nawet dla dużych n. -
z tego znajdź wszystkie item_groups z group_id
$in
i identyfikator przedmiotuTeraz to trudniejsza część. Załóżmy na chwilę, że jest mało prawdopodobne, aby przedmioty należały do dużej liczby grup. Następnie indeks złożony
{ itemId, groupId }
działa najlepiej, ponieważ możemy drastycznie zredukować zestaw kandydatów przez pierwsze kryterium - jeśli element jest udostępniany tylko w 800 grupach, a użytkownik śledzi 220 grup, mongodb musi tylko znaleźć przecięcie tych, co jest stosunkowo łatwe, ponieważ oba zestawy są małe.
Musimy jednak zejść głębiej:
Struktura Twoich danych jest prawdopodobnie że złożonej sieci . Złożone sieci mają wiele odmian, ale warto założyć, że wykres obserwujących jest prawie pozbawiony skali, co również jest najgorszym przypadkiem. W sieci wolnej od skali bardzo mała liczba węzłów (celebryci, super bowl, Wikipedia) przyciąga dużo uwagi (tj. ma wiele połączeń), podczas gdy znacznie większa liczba węzłów ma problemy z uzyskaniem takiej samej uwagi połączone .
Małe węzły nie są powodem do niepokoju , powyższe zapytania, w tym podróże w obie strony do bazy danych, mieszczą się w zakresie 2 ms na moim komputerze deweloperskim na zestawie danych z dziesiątkami milionów połączeń i> 5 GB danych. Teraz, gdy zestaw danych nie jest ogromny, ale bez względu na wybraną technologię, będzie on powiązany z pamięcią RAM, ponieważ indeksy i tak muszą znajdować się w pamięci RAM (lokalizacja danych i rozdzielność w sieciach jest ogólnie słaba), a ustawiony rozmiar przecięcia to mały z definicji. Innymi słowy:ten system jest zdominowany przez wąskie gardła sprzętowe.
A co z superwęzłami chociaż?
Ponieważ byłoby to zgadywanie i bardzo interesują mnie modele sieciowe, pozwoliłem sobie na zaimplementowanie radykalnie uproszczonego narzędzia sieciowego opartego na twoim modelu danych, aby dokonać pewnych pomiarów. (Przepraszam, że jest w C#, ale generowanie dobrze zorganizowanych sieci jest wystarczająco trudne w języku, którym posługuję się najbardziej biegle...).
Podczas wysyłania zapytań do superwęzłów otrzymuję wyniki w zakresie szczytów 7 ms (to jest na 12 mln wpisów w db 1,3 GB, z największą grupą zawierającą 133 000 elementów i użytkownika, który obserwuje 143 grupy).
Założenie w tym kodzie jest to, że liczba grup, za którymi podąża użytkownik, nie jest duża, ale wydaje się to rozsądne tutaj. Jeśli tak nie jest, wybrałbym podejście wymagające dużej ilości pisania.
Zapraszam do gry z kodem. Niestety, będzie wymagać trochę optymalizacji, jeśli chcesz spróbować tego z więcej niż kilkoma GB danych, ponieważ po prostu nie jest zoptymalizowany i wykonuje bardzo nieefektywne obliczenia tu i tam (zwłaszcza losowe tasowanie ważone beta może zostać ulepszone ).
Innymi słowy:nie martwię się o wydajność podejścia z dużym obciążeniem odczytu jeszcze . Problem często nie polega na tym, że liczba użytkowników rośnie, ale na tym, że użytkownicy korzystają z systemu w nieoczekiwany sposób.
Podejście do intensywnego zapisu
Alternatywnym podejściem jest prawdopodobnie odwrócenie kolejności linkowania:
UserItemLinker
{
userId,
itemId,
groupIds[] // for faster retrieval of the linker. It's unlikely that this grows large
}
Jest to prawdopodobnie najbardziej skalowalny model danych, ale nie wybrałbym go, chyba że mówimy o OGROMNYCH ilościach danych, gdzie sharding jest kluczowym wymaganiem. Kluczową różnicą jest to, że możemy teraz efektywnie posegmentować dane, używając identyfikatora użytkownika jako części klucza fragmentu. Pomaga to zrównoleglać zapytania, wydajnie shardować i poprawiać lokalizację danych w scenariuszach obejmujących wiele centrów danych.
Można to przetestować z bardziej rozbudowaną wersją środowiska testowego, ale nie znalazłem jeszcze na to czasu i szczerze mówiąc, uważam, że to przesada w przypadku większości zastosowań.