Problém(y)
Jak již bylo napsáno , při nadměrném vkládání dochází k několika problémům:
Problém 1:Limit velikosti BSON
V době psaní tohoto článku jsou dokumenty BSON omezeny na 16 MB . Pokud je tohoto limitu dosaženo, MongoDB vyvolá výjimku a vy byste jednoduše nemohli přidávat další komentáře a v nejhorším případě ani nezměnit (uživatelské) jméno nebo obrázek, pokud by změna zvětšila velikost dokumentu.
Problém 2:Omezení dotazu a výkon
Za určitých podmínek není snadné dotazovat se nebo třídit pole komentářů. Některé věci by vyžadovaly poměrně nákladnou agregaci, jiné dosti komplikované výpisy.
I když by se dalo namítnout, že jakmile jsou dotazy na místě, není to velký problém, musím se lišit. Za prvé, čím je dotaz složitější, tím hůře se optimalizuje, a to jak pro vývojáře, tak pro optimalizátor dotazů MongoDBs. Nejlepších výsledků jsem dosáhl se zjednodušením datových modelů a dotazů, zrychlením odpovědí o faktor 100 v jednom případě.
Při škálování mohou zdroje potřebné pro komplikované a/nebo nákladné dotazy dokonce sčítat celé stroje ve srovnání s jednodušším datovým modelem a podle dotazů.
Problém 3:Údržba
V neposlední řadě můžete narazit na problémy s údržbou kódu. Jako jednoduché pravidlo
V tomto kontextu se „drahý“ týká peněz (pro profesionální projekty) a času (pro hobby projekty).
(Moje!) řešení
Je to docela snadné:zjednodušte svůj datový model. V důsledku toho budou vaše dotazy méně komplikované a (doufejme) rychlejší.
Krok 1:Identifikujte své případy použití
Bude to pro mě divoký odhad, ale důležité je ukázat vám obecnou metodu. Vaše případy použití bych definoval následovně:
- U daného příspěvku by uživatelé měli mít možnost komentovat
- U daného příspěvku zobrazit autora a komentáře spolu s uživatelským jménem komentujících a autorů a jejich obrázkem
- U daného uživatele by mělo být snadné změnit jméno, uživatelské jméno a obrázek
Krok 2:Podle toho modelujte svá data
Uživatelé
Za prvé, máme přímočarý uživatelský model
{
_id: new ObjectId(),
name: "Joe Average",
username: "HotGrrrl96",
picture: "some_link"
}
Není zde nic nového, přidáno jen pro úplnost.
Příspěvky
{
_id: new ObjectId()
title: "A post",
content: " Interesting stuff",
picture: "some_link",
created: new ISODate(),
author: {
username: "HotGrrrl96",
picture: "some_link"
}
}
A to je k příspěvku asi vše. Zde je třeba poznamenat dvě věci:za prvé ukládáme data autora, která okamžitě potřebujeme při zobrazení příspěvku, protože nám to ušetří dotaz na velmi běžný, ne-li všudypřítomný případ použití. Proč neukládáme komentáře a údaje o komentářích podle toho? Kvůli limitu velikosti 16 MB , snažíme se zabránit ukládání referencí do jednoho dokumentu. Spíše ukládáme odkazy v dokumentech komentářů:
Komentáře
{
_id: new ObjectId(),
post: someObjectId,
created: new ISODate(),
commenter: {
username: "FooBar",
picture: "some_link"
},
comment: "Awesome!"
}
Stejně jako u příspěvků máme všechny potřebné údaje pro zobrazení příspěvku.
Dotazy
Nyní jsme dosáhli toho, že jsme obešli limit velikosti BSON a nepotřebujeme se odvolávat na uživatelská data, abychom mohli zobrazovat příspěvky a komentáře, což by nám mělo ušetřit spoustu dotazů. Ale vraťme se k případům použití a několika dalším dotazům
Přidání komentáře
To je nyní zcela jednoduché.
Získání všech nebo některých komentářů k danému příspěvku
Za všechny komentáře
db.comments.find({post:objectIdOfPost})
Za 3 poslední komentáře
db.comments.find({post:objectIdOfPost}).sort({created:-1}).limit(3)
Takže pro zobrazení příspěvku a všech (nebo některých) jeho komentářů včetně uživatelských jmen a obrázků jsme u dvou dotazů. Více, než jste dříve potřebovali, ale obešli jsme limit velikosti a v podstatě můžete mít ke každému příspěvku neomezený počet komentářů. Ale pojďme k něčemu skutečnému
Získání posledních 5 příspěvků a jejich posledních 3 komentářů
Toto je dvoukrokový proces. Při správném indexování (k tomu se vrátíme později) by to však mělo být rychlé (a tedy úspora zdrojů):
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);
}
)
Seřadit všechny příspěvky daného uživatele od nejnovějších po nejstarší a jejich komentáře
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});
Všimněte si, že zde máme pouze dva dotazy. Ačkoli musíte „ručně“ propojit příspěvky a jejich příslušné komentáře, mělo by to být docela jednoduché.
Změna uživatelského jména
Toto je pravděpodobně ojedinělý případ použití. S uvedeným datovým modelem to však není příliš složité
Nejprve změníme uživatelský dokument
db.users.update(
{ username: "HotGrrrl96"},
{
$set: { username: "Joe Cool"},
$push: {oldUsernames: "HotGrrrl96" }
},
{
writeConcern: {w: "majority"}
}
);
Staré uživatelské jméno přesuneme do příslušného pole. Toto je bezpečnostní opatření pro případ, že by se s následujícími operacemi něco pokazilo. Kromě toho jsme nastavili starost o zápis na poměrně vysokou úroveň, abychom zajistili trvanlivost dat.
db.posts.update(
{ "author.username": "HotGrrrl96"},
{ $set:{ "author.username": "Joe Cool"} },
{
multi:true,
writeConcern: {w:"majority"}
}
)
Tady nic zvláštního. Aktualizační prohlášení pro komentáře vypadá v podstatě stejně. I když tyto dotazy nějakou dobu trvají, jsou málokdy provedeny.
Indexy
Obecně lze říci, že MongoDB může použít pouze jeden index na dotaz. I když to není úplně pravda, protože existují průsečíky indexů, je snadné se s tím vypořádat. Další věcí je, že jednotlivá pole ve složeném indexu lze používat nezávisle. Jednoduchým přístupem k optimalizaci indexu je tedy najít dotaz s největším počtem polí používaných v operacích využívajících indexy a vytvořit z nich složený index. Všimněte si, že na pořadí výskytu v dotazu záleží. Tak pojďme dál.
Příspěvky
db.posts.createIndex({"author.username":1,"created":-1})
Komentáře
db.comments.createIndex({"post":1, "created":-1})
Závěr
Plně vložený dokument na příspěvek je nepochybně nejrychlejší způsob, jak jej načíst a jeho komentáře. Neškáluje se však dobře a vzhledem k povaze možná složitých dotazů nutných k jejímu zpracování může být tato výkonnostní výhoda využita nebo dokonce eliminována.
S výše uvedeným řešením vyměníte určitou rychlost (pokud!) za v podstatě neomezenou škálovatelnost a mnohem přímočařejší způsob nakládání s daty.
Hth.