Het(de) probleem(en)
Zoals voorheen geschreven , zijn er verschillende problemen bij over-inbedding:
Probleem 1:BSON-groottelimiet
Op het moment van schrijven zijn BSON-documenten beperkt tot 16 MB . Als die limiet wordt bereikt, zou MongoDB een uitzondering maken en zou je gewoon geen opmerkingen meer kunnen toevoegen en in het ergste geval zelfs de (gebruikers-)naam of de afbeelding niet wijzigen als de wijziging de grootte van het document zou vergroten.
Probleem 2:Beperkingen en prestaties van zoekopdrachten
Onder bepaalde voorwaarden is het niet eenvoudig om de array met opmerkingen te doorzoeken of te sorteren. Sommige dingen zouden een nogal kostbare aggregatie vereisen, andere nogal ingewikkelde verklaringen.
Hoewel je zou kunnen stellen dat als de query's eenmaal op hun plaats zijn, dit niet zo'n probleem is, ben ik het daar niet mee eens. Ten eerste, hoe ingewikkelder een query is, hoe moeilijker het is om te optimaliseren, zowel voor de ontwikkelaar als voor de MongoDB's query-optimizer. Ik heb de beste resultaten behaald met het eenvoudig maken van gegevensmodellen en query's, waardoor reacties in één keer met een factor 100 werden versneld.
Bij het schalen kunnen de middelen die nodig zijn voor gecompliceerde en/of kostbare query's zelfs tot hele machines oplopen in vergelijking met een eenvoudiger gegevensmodel en bijbehorende query's.
Probleem 3:Onderhoudbaarheid
Last but not least kunt u problemen tegenkomen bij het onderhouden van uw code. Als een eenvoudige vuistregel
In deze context verwijst 'duur' zowel naar geld (voor professionele projecten) als tijd (voor hobbyprojecten).
(Mijn!) Oplossing
Het is vrij eenvoudig:vereenvoudig uw datamodel. Hierdoor worden uw zoekopdrachten minder ingewikkeld en (hopelijk) sneller.
Stap 1:Identificeer uw gebruiksscenario's
Dat wordt een wilde gok voor mij, maar het belangrijkste hier is om je de algemene methode te laten zien. Ik zou uw gebruiksscenario's als volgt definiëren:
- Voor een bepaald bericht moeten gebruikers kunnen reageren
- Toon voor een bepaald bericht de auteur en de opmerkingen, samen met de gebruikersnaam van de commentatoren en auteurs en hun foto
- Voor een bepaalde gebruiker moet het gemakkelijk mogelijk zijn om de naam, gebruikersnaam en afbeelding te wijzigen
Stap 2:Modelleer uw gegevens dienovereenkomstig
Gebruikers
Allereerst hebben we een eenvoudig gebruikersmodel
{
_id: new ObjectId(),
name: "Joe Average",
username: "HotGrrrl96",
picture: "some_link"
}
Niets nieuws hier, voor de volledigheid toegevoegd.
Berichten
{
_id: new ObjectId()
title: "A post",
content: " Interesting stuff",
picture: "some_link",
created: new ISODate(),
author: {
username: "HotGrrrl96",
picture: "some_link"
}
}
En dat is het zowat voor een post. Er zijn hier twee dingen om op te merken:ten eerste slaan we de auteursgegevens op die we onmiddellijk nodig hebben bij het weergeven van een bericht, omdat dit ons een zoekopdracht bespaart voor een veel voorkomende, zo niet alomtegenwoordige gebruikssituatie. Waarom slaan we de gegevens van de opmerkingen en commentatoren niet dienovereenkomstig op? Vanwege de limiet van 16 MB , proberen we het opslaan van referenties in één document te voorkomen. In plaats daarvan slaan we de referenties op in commentaardocumenten:
Opmerkingen
{
_id: new ObjectId(),
post: someObjectId,
created: new ISODate(),
commenter: {
username: "FooBar",
picture: "some_link"
},
comment: "Awesome!"
}
Hetzelfde als bij berichten, we hebben alle benodigde gegevens om een bericht weer te geven.
De vragen
Wat we nu hebben bereikt, is dat we de BSON-groottelimiet hebben omzeild en dat we niet naar de gebruikersgegevens hoeven te verwijzen om berichten en opmerkingen te kunnen weergeven, wat ons veel vragen zou moeten besparen. Maar laten we terugkomen op de gebruiksscenario's en nog wat vragen
Een opmerking toevoegen
Dat is nu helemaal duidelijk.
Alle of enkele opmerkingen krijgen voor een bepaald bericht
Voor alle reacties
db.comments.find({post:objectIdOfPost})
Voor de 3 laatste reacties
db.comments.find({post:objectIdOfPost}).sort({created:-1}).limit(3)
Dus voor het weergeven van een bericht en alle (of sommige) opmerkingen, inclusief de gebruikersnamen en afbeeldingen, hebben we twee vragen. Meer dan je eerder nodig had, maar we hebben de maximale grootte omzeild en in principe kun je een onbepaald aantal reacties voor elk bericht hebben. Maar laten we tot iets echts komen
De laatste 5 berichten en hun laatste 3 reacties ontvangen
Dit is een proces in twee stappen. Met de juiste indexering (kom daar later op terug) zou dit echter nog steeds snel moeten zijn (en dus middelen besparen):
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);
}
)
Alle berichten van een bepaalde gebruiker sorteren van nieuw naar oud en hun opmerkingen
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});
Merk op dat we hier slechts twee vragen hebben. Hoewel je "handmatig" de verbinding tussen berichten en hun respectievelijke opmerkingen moet maken, zou dat vrij eenvoudig moeten zijn.
Een gebruikersnaam wijzigen
Dit is vermoedelijk een zeldzame use case uitgevoerd. Het is echter niet erg ingewikkeld met genoemd datamodel
Eerst wijzigen we het gebruikersdocument
db.users.update(
{ username: "HotGrrrl96"},
{
$set: { username: "Joe Cool"},
$push: {oldUsernames: "HotGrrrl96" }
},
{
writeConcern: {w: "majority"}
}
);
We pushen de oude gebruikersnaam naar een overeenkomstige array. Dit is een veiligheidsmaatregel voor het geval er iets misgaat met de volgende handelingen. Bovendien hebben we de schrijfzorg op een vrij hoog niveau gezet om ervoor te zorgen dat de gegevens duurzaam zijn.
db.posts.update(
{ "author.username": "HotGrrrl96"},
{ $set:{ "author.username": "Joe Cool"} },
{
multi:true,
writeConcern: {w:"majority"}
}
)
Niets bijzonders hier. De update-verklaring voor de opmerkingen ziet er vrijwel hetzelfde uit. Hoewel deze zoekopdrachten enige tijd in beslag nemen, worden ze zelden uitgevoerd.
De indices
Als vuistregel kan men zeggen dat MongoDB slechts één index per zoekopdracht kan gebruiken. Hoewel dit niet helemaal waar is, omdat er indexkruisingen zijn, is het gemakkelijk om mee om te gaan. Een ander ding is dat individuele velden in een samengestelde index onafhankelijk van elkaar kunnen worden gebruikt. Een gemakkelijke benadering voor indexoptimalisatie is dus om de query te vinden met de meeste velden die worden gebruikt in bewerkingen die gebruik maken van indices en er een samengestelde index van te maken. Houd er rekening mee dat de volgorde van voorkomen in de query van belang is. Dus laten we doorgaan.
Berichten
db.posts.createIndex({"author.username":1,"created":-1})
Opmerkingen
db.comments.createIndex({"post":1, "created":-1})
Conclusie
Een volledig ingesloten document per bericht is weliswaar de snelste manier om het en de opmerkingen te laden. Het schaalt echter niet goed en vanwege de aard van mogelijk complexe query's die nodig zijn om ermee om te gaan, kan dit prestatievoordeel worden benut of zelfs worden geëlimineerd.
Met de bovenstaande oplossing ruilt u wat snelheid (als!) tegen in principe onbeperkte schaalbaarheid en een veel eenvoudigere manier om met de gegevens om te gaan.
Hth.