Jag håller med om den allmänna uppfattningen om andra svar att detta är en gränslinje relationsproblem.
Nyckeln till MongoDB-datamodeller är skrivtyngd, men det kan vara knepigt för detta användningsfall, mest på grund av den bokföring som skulle krävas om du vill länka användare till objekt direkt (en förändring till en grupp som följs av massor av användare skulle få ett stort antal skrivningar, och du behöver någon arbetare för att göra detta).
Låt oss undersöka om den lästunga modellen är otillämplig här, eller om vi gör för tidig optimering.
The Read Heavy Approach
Din viktigaste fråga är följande användningsfall:
ett verkligt prestandaproblem kan vara när jag vill få alla grupper som en användare följer för ett specifikt objekt [...] eftersom jag då måste hitta alla grupper som användaren följer, och från det hitta alla item_groups med group_id
$in
och artikel-id.
Låt oss dissekera detta:
-
Hämta alla grupper som användaren följer
Det är en enkel fråga:
db.followers.find({userId : userId})
. Vi kommer att behöva ett index påuserId
vilket kommer att göra körtiden för denna operation O(log n), eller blixtsnabb även för stort n. -
från det hitta alla item_groups med group_id
$in
och artikel-idNu är det här den svårare delen. Låt oss för ett ögonblick anta att det är osannolikt att föremål ingår i ett stort antal grupper. Sedan ett sammansatt index
{ itemId, groupId }
skulle fungera bäst, eftersom vi kan minska kandidatuppsättningen dramatiskt genom det första kriteriet - om ett objekt delas i endast 800 grupper och användaren följer 220 grupper, behöver mongodb bara hitta skärningspunkten mellan dessa, vilket är förhållandevis enkelt eftersom både set är små.
Vi måste dock gå djupare än så här:
Strukturen på dina data är förmodligen det för ett komplext nätverk . Komplexa nätverk finns i många varianter, men det är vettigt att anta att ditt följarediagram är nästan skalfritt, vilket också är i stort sett det värsta fallet. I ett skalfritt nätverk lockar ett mycket litet antal noder (kändisar, super bowl, Wikipedia) en hel del "uppmärksamhet" (d.v.s. har många anslutningar), medan ett mycket större antal noder har problem med att få lika mycket uppmärksamhet kombinerat .
De små noderna är ingen anledning till oro , frågorna ovan, inklusive tur och retur till databasen ligger inom 2ms intervallet på min utvecklingsmaskin på en datauppsättning med tiotals miljoner anslutningar och> 5 GB data. Nu är den datamängden inte enorm, men oavsett vilken teknik du väljer, kommer den att vara RAM-bunden eftersom indexen måste vara i RAM i alla fall (datalokalitet och separerbarhet i nätverk är generellt sett dålig), och den uppställda skärningsstorleken är liten per definition. Med andra ord:denna regim domineras av hårdvaruflaskhalsar.
Hur är det med supernoderna dock?
Eftersom det vore gissningar och jag är mycket intresserad av nätverksmodeller, tog jag mig friheten att implementera ett dramatiskt förenklat nätverksverktyg baserat på din datamodell för att göra några mätningar. (Tyvärr är det i C#, men att skapa välstrukturerade nätverk är svårt nog på det språk jag är mest flytande i...).
När jag frågar efter supernoderna får jag resultat inom intervallet 7ms toppar (det är på 12 miljoner poster i en 1,3 GB db, med den största gruppen med 133 000 objekt i sig och en användare som följer 143 grupper.)
antagandet i den här koden är att antalet grupper som följs av en användare inte är enormt, men det verkar rimligt här. Om det inte är det, skulle jag gå för den skrivtunga metoden.
Lek gärna med koden. Tyvärr kommer det att behövas lite optimering om du vill prova detta med mer än ett par GB data, eftersom det helt enkelt inte är optimerat och gör några väldigt ineffektiva beräkningar här och där (särskilt den beta-vägda slumpmässiga blandningen kan förbättras ).
Med andra ord:Jag skulle inte oroa mig för prestandan för den lästunga metoden ännu . Problemet är ofta inte så mycket att antalet användare växer, utan att användare använder systemet på oväntade sätt.
The Write Heavy Approach
Det alternativa tillvägagångssättet är förmodligen att vända ordningen för länkning:
UserItemLinker
{
userId,
itemId,
groupIds[] // for faster retrieval of the linker. It's unlikely that this grows large
}
Det här är förmodligen den mest skalbara datamodellen, men jag skulle inte satsa på den om vi inte pratar om ENORMA mängder data där skärning är ett nyckelkrav. Den viktigaste skillnaden här är att vi nu effektivt kan dela upp data genom att använda användar-ID som en del av shard-nyckeln. Det hjälper till att parallellisera frågor, skärpa effektivt och förbättra datalokaliteten i scenarier för flera datacenter.
Detta skulle kunna testas med en mer utarbetad version av testbädden, men jag hittade inte tiden ännu, och ärligt talat tycker jag att det är överdrivet för de flesta applikationer.