Ik ben het eens met het algemene idee van andere antwoorden dat dit een borderline is relationeel probleem.
De sleutel tot MongoDB-gegevensmodellen is schrijfzwaarte, maar dat kan lastig zijn voor dit gebruik, vooral vanwege de boekhouding die nodig zou zijn als u gebruikers rechtstreeks aan items wilt koppelen (een wijziging in een groep die wordt gevolgd door veel van de gebruikers zou een enorm aantal schrijfbewerkingen veroorzaken, en je hebt een medewerker nodig om dit te doen).
Laten we eens kijken of het lees-zware model hier niet van toepassing is, of dat we voortijdige optimalisatie doen.
De Lees Zware Aanpak
Uw belangrijkste zorg is de volgende use case:
een echt prestatieprobleem kan zijn wanneer ik alle groepen wil krijgen die een gebruiker volgt voor een specifiek item [...] omdat ik dan alle groepen moet vinden die de gebruiker volgt, en van daaruit alle groepen moet vinden de item_groups met de group_id $in
en de item-ID.
Laten we dit ontleden:
-
Alle groepen ophalen die de gebruiker volgt
Dat is een simpele vraag:
db.followers.find({userId : userId})
. We hebben een index nodig opuserId
waardoor de looptijd van deze bewerking O(log n) wordt, of razendsnel, zelfs voor grote n. -
van daaruit vind je alle item_groups met de group_id
$in
en de item-IDNu dit het lastiger deel. Laten we even aannemen dat het onwaarschijnlijk is dat items deel uitmaken van een groot aantal groepen. Dan een samengestelde index
{ itemId, groupId }
zou het beste werken, omdat we de kandidatenset drastisch kunnen verminderen door het eerste criterium - als een item in slechts 800 groepen wordt gedeeld en de gebruiker 220 groepen volgt, hoeft mongodb alleen de kruising hiervan te vinden, wat relatief eenvoudig is omdat beide sets zijn klein.
We moeten echter dieper gaan dan dit:
De structuur van uw gegevens is waarschijnlijk die van een complex netwerk . Complexe netwerken zijn er in vele smaken, maar het is logisch om aan te nemen dat uw volgersgrafiek bijna schaalvrij is, wat ook vrijwel het slechtste geval is. In een schaalvrij netwerk trekt een zeer klein aantal knooppunten (beroemdheden, super bowl, Wikipedia) heel veel 'aandacht' (d.w.z. hebben veel verbindingen), terwijl een veel groter aantal knooppunten moeite hebben om dezelfde hoeveelheid aandacht te krijgen gecombineerd .
De kleine knooppunten zijn geen reden tot bezorgdheid , de bovenstaande zoekopdrachten, inclusief retouren naar de database, liggen in het bereik van 2 ms op mijn ontwikkelmachine op een dataset met tientallen miljoenen verbindingen en> 5GB aan data. Nu die dataset niet enorm is, maar welke technologie je ook kiest, je zal RAM-gebonden zijn omdat de indices in ieder geval in RAM moeten staan (datalokaliteit en scheidbaarheid in netwerken is over het algemeen slecht), en de ingestelde intersectiegrootte is per definitie klein. Met andere woorden:dit regime wordt gedomineerd door hardware-knelpunten.
Hoe zit het met de supernodes hoewel?
Aangezien dat giswerk zou zijn en ik erg geïnteresseerd ben in netwerkmodellen, heb ik de vrijheid genomen om een drastisch vereenvoudigde netwerktool te implementeren op basis van uw gegevensmodel om enkele metingen te doen. (Sorry, het is in C#, maar het genereren van goed gestructureerde netwerken is al moeilijk genoeg in de taal die ik het meest machtig ben...).
Bij het doorzoeken van de supernodes krijg ik resultaten in het bereik van 7ms tops (dat is op 12 miljoen inzendingen in een db van 1,3 GB, met de grootste groep met 133.000 items erin en een gebruiker die 143 groepen volgt.)
De aanname in deze code is dat het aantal groepen gevolgd door een gebruiker niet enorm is, maar dat lijkt hier redelijk. Als dat niet het geval is, zou ik voor de schrijfzware aanpak gaan.
Speel gerust met de code. Helaas heeft het een beetje optimalisatie nodig als je dit met meer dan een paar GB aan gegevens wilt proberen, omdat het gewoon niet is geoptimaliseerd en hier en daar een aantal zeer inefficiënte berekeningen doet (vooral de bèta-gewogen willekeurige shuffle kan worden verbeterd ).
Met andere woorden:ik zou me nog geen zorgen maken over de prestaties van de leesintensieve aanpak . Het probleem is vaak niet zozeer dat het aantal gebruikers groeit, maar dat gebruikers het systeem op onverwachte manieren gebruiken.
De zware schrijfbenadering
De alternatieve benadering is waarschijnlijk om de volgorde van koppelen om te keren:
UserItemLinker
{
userId,
itemId,
groupIds[] // for faster retrieval of the linker. It's unlikely that this grows large
}
Dit is waarschijnlijk het meest schaalbare datamodel, maar ik zou er niet voor gaan tenzij we het hebben over ENORME hoeveelheden data waarbij sharding een belangrijke vereiste is. Het belangrijkste verschil hier is dat we de gegevens nu efficiënt kunnen compartimenteren door de userId te gebruiken als onderdeel van de Shard-sleutel. Dat helpt om query's te parallelliseren, efficiënt te sharden en de gegevenslocatie te verbeteren in scenario's met meerdere datacenters.
Dit zou getest kunnen worden met een uitgebreidere versie van het testbed, maar ik heb de tijd nog niet gevonden, en eerlijk gezegd vind ik het voor de meeste toepassingen overkill.