Problemet/problemen
Som skrivet tidigare , det finns flera problem vid överinbäddning:
Problem 1:BSON-storleksgräns
När detta skrivs är BSON-dokument begränsade till 16 MB . Om den gränsen nås, skulle MongoDB göra ett undantag och du kunde helt enkelt inte lägga till fler kommentarer och i värsta fall inte ens ändra (användar-)namnet eller bilden om ändringen skulle öka storleken på dokumentet.
Problem 2:Frågabegränsningar och prestanda
Det är inte lätt möjligt att fråga eller sortera kommentarsfältet under vissa förutsättningar. Vissa saker skulle kräva en ganska kostsam sammanställning, andra ganska komplicerade påståenden.
Även om man skulle kunna hävda att när frågorna väl är på plats är detta inte ett stort problem, men jag ber att skilja. För det första, ju mer komplicerad en fråga är, desto svårare är den att optimera, både för utvecklaren och därefter MongoDBs frågeoptimerare. Jag har fått de bästa resultaten med att förenkla datamodeller och frågor, påskynda svaren med en faktor på 100 i en instans.
Vid skalning kan resurserna som behövs för komplicerade och/eller kostsamma frågor till och med summeras till hela maskiner jämfört med en enklare datamodell och enligt frågor.
Problem 3:Underhållbarhet
Sist men inte minst kan du mycket väl stöta på problem med att underhålla din kod. Som en enkel tumregel
I detta sammanhang avser "dyrt" både pengar (för professionella projekt) och tid (för hobbyprojekt).
(Min!) Lösning
Det är ganska enkelt:förenkla din datamodell. Följaktligen kommer dina frågor att bli mindre komplicerade och (förhoppningsvis) snabbare.
Steg 1:Identifiera dina användningsfall
Det kommer att vara en vild gissning för mig, men det viktiga här är att visa dig den allmänna metoden. Jag skulle definiera dina användningsfall enligt följande:
- För ett givet inlägg bör användare kunna kommentera
- För ett givet inlägg, visa författaren och kommentarerna, tillsammans med kommentatorernas och författarens användarnamn och deras bild
- För en given användare bör det vara lätt att ändra namn, användarnamn och bild
Steg 2:Modellera dina data i enlighet med detta
Användare
Först och främst har vi en enkel användarmodell
{
_id: new ObjectId(),
name: "Joe Average",
username: "HotGrrrl96",
picture: "some_link"
}
Inget nytt här, lagt till bara för fullständighetens skull.
Inlägg
{
_id: new ObjectId()
title: "A post",
content: " Interesting stuff",
picture: "some_link",
created: new ISODate(),
author: {
username: "HotGrrrl96",
picture: "some_link"
}
}
Och det är ungefär det för ett inlägg. Det finns två saker att notera här:för det första lagrar vi författardata vi omedelbart behöver när vi visar ett inlägg, eftersom detta sparar oss en fråga för ett mycket vanligt, om inte allmänt förekommande användningsfall. Varför sparar vi inte kommentarer och kommentatordata i enlighet med detta? På grund av 16 MB storleksgräns , försöker vi förhindra lagring av referenser i ett enda dokument. Snarare lagrar vi referenserna i kommentarsdokument:
Kommentarer
{
_id: new ObjectId(),
post: someObjectId,
created: new ISODate(),
commenter: {
username: "FooBar",
picture: "some_link"
},
comment: "Awesome!"
}
På samma sätt som med inlägg har vi all nödvändig data för att visa ett inlägg.
Frågorna
Det vi har uppnått nu är att vi kringgick BSON-storleksgränsen och vi behöver inte hänvisa till användardata för att kunna visa inlägg och kommentarer, vilket borde spara oss en hel del frågor. Men låt oss återkomma till användningsfallen och några fler frågor
Lägga till en kommentar
Det är helt okomplicerat nu.
Få alla eller några kommentarer för ett givet inlägg
För alla kommentarer
db.comments.find({post:objectIdOfPost})
För de 3 senaste kommentarerna
db.comments.find({post:objectIdOfPost}).sort({created:-1}).limit(3)
Så för att visa ett inlägg och alla (eller några) av dess kommentarer inklusive användarnamn och bilder har vi två frågor. Mer än du behövde tidigare, men vi kringgick storleksgränsen och i princip kan du ha ett obestämt antal kommentarer för varje inlägg. Men låt oss komma till något verkligt
Hämta de senaste 5 inläggen och deras senaste 3 kommentarer
Detta är en tvåstegsprocess. Men med korrekt indexering (återkommer till det senare) bör detta fortfarande vara snabbt (och därmed resursbesparing):
var posts = db.posts.find().sort({created:-1}).limit(5)
posts.forEach(
function(post) {
doSomethingWith(post);
var comments = db.comments.find({"post":post._id}).sort("created":-1).limit(3);
doSomethingElseWith(comments);
}
)
Få alla inlägg från en given användare sorterade från senaste till äldsta och deras kommentarer
var posts = db.posts.find({"author.username": "HotGrrrl96"},{_id:1}).sort({"created":-1});
var postIds = [];
posts.forEach(
function(post){
postIds.push(post._id);
}
)
var comments = db.comments.find({post: {$in: postIds}}).sort({post:1, created:-1});
Observera att vi bara har två frågor här. Även om du måste "manuellt" göra kopplingen mellan inlägg och deras respektive kommentarer, borde det vara ganska enkelt.
Ändra ett användarnamn
Detta är förmodligen ett sällsynt användningsfall. Det är dock inte särskilt komplicerat med nämnda datamodell
Först ändrar vi användardokumentet
db.users.update(
{ username: "HotGrrrl96"},
{
$set: { username: "Joe Cool"},
$push: {oldUsernames: "HotGrrrl96" }
},
{
writeConcern: {w: "majority"}
}
);
Vi skickar det gamla användarnamnet till en motsvarande array. Detta är en säkerhetsåtgärd om något går fel med följande operationer. Dessutom sätter vi skrivproblemet på en ganska hög nivå för att säkerställa att data är hållbara.
db.posts.update(
{ "author.username": "HotGrrrl96"},
{ $set:{ "author.username": "Joe Cool"} },
{
multi:true,
writeConcern: {w:"majority"}
}
)
Inget speciellt här. Uppdateringsförklaringen för kommentarerna ser ungefär likadan ut. Även om dessa frågor tar lite tid, körs de sällan.
Indeksen
Som en tumregel kan man säga att MongoDB bara kan använda ett index per fråga. Även om detta inte är helt sant eftersom det finns indexkorsningar, är det lätt att hantera. En annan sak är att enskilda fält i ett sammansatt index kan användas oberoende. Så ett enkelt tillvägagångssätt för indexoptimering är att hitta frågan med flest fält som används i operationer som använder index och skapa ett sammansatt index av dem. Observera att förekomstordningen i frågan har betydelse. Så, låt oss gå vidare.
Inlägg
db.posts.createIndex({"author.username":1,"created":-1})
Kommentarer
db.comments.createIndex({"post":1, "created":-1})
Slutsats
Ett helt inbäddat dokument per inlägg är visserligen det snabbaste sättet att ladda det och dess kommentarer. Den skalas dock inte bra och på grund av möjliga komplexa frågor som krävs för att hantera den, kan denna prestandafördel utnyttjas eller till och med elimineras.
Med ovanstående lösning byter du lite hastighet (om!) mot i princip obegränsad skalbarhet och ett mycket enklare sätt att hantera data.
Hth.