Problem(y)
Jak napisane wcześniej , istnieje kilka problemów z nadmiernym osadzaniem:
Problem 1:Limit rozmiaru BSON
W chwili pisania tego tekstu Dokumenty BSON są ograniczone do 16 MB . Jeśli ten limit zostanie osiągnięty, MongoDB zgłosi wyjątek i po prostu nie będziesz mógł dodać więcej komentarzy, aw najgorszym przypadku nawet nie zmienisz nazwy (użytkownika) ani obrazu, jeśli zmiana zwiększyłaby rozmiar dokumentu.
Problem 2:Ograniczenia i wydajność zapytań
Pod pewnymi warunkami nie jest łatwo wykonać zapytanie lub posortować tablicę komentarzy. Niektóre rzeczy wymagałyby dość kosztownej agregacji, inne raczej skomplikowanych instrukcji.
Chociaż można argumentować, że gdy zapytania są już na miejscu, nie stanowi to większego problemu, błagam o różnicę. Po pierwsze, im bardziej skomplikowane jest zapytanie, tym trudniej jest je zoptymalizować, zarówno dla dewelopera, jak i później optymalizatora zapytań MongoDBs. Osiągnąłem najlepsze wyniki z uproszczeniem modeli danych i zapytań, przyspieszeniem odpowiedzi o współczynnik 100 w jednym przypadku.
Podczas skalowania zasoby potrzebne do skomplikowanych i/lub kosztownych zapytań mogą nawet sumować się na całe maszyny w porównaniu z prostszym modelem danych i odpowiednimi zapytaniami.
Problem 3:Utrzymanie
Na koniec możesz napotkać problemy z utrzymaniem kodu. Prosta zasada
W tym kontekście „drogie” odnosi się zarówno do pieniędzy (w przypadku projektów profesjonalnych), jak i czasu (w przypadku projektów hobbystycznych).
(Moje!) Rozwiązanie
To całkiem proste:uprość swój model danych. W rezultacie Twoje zapytania staną się mniej skomplikowane i (miejmy nadzieję) szybsze.
Krok 1:Zidentyfikuj swoje przypadki użycia
To będzie dla mnie szalone przypuszczenie, ale ważne jest, aby pokazać ogólną metodę. Zdefiniowałbym twoje przypadki użycia w następujący sposób:
- Dla danego posta użytkownicy powinni mieć możliwość komentowania
- Dla danego posta pokaż autora i komentarze, wraz z komentującymi i nazwą użytkownika autora oraz ich zdjęciem
- Dla danego użytkownika powinna być łatwo możliwa zmiana nazwy, nazwy użytkownika i zdjęcia
Krok 2:odpowiednio modeluj swoje dane
Użytkownicy
Przede wszystkim mamy prosty model użytkownika
{
_id: new ObjectId(),
name: "Joe Average",
username: "HotGrrrl96",
picture: "some_link"
}
Nie ma tu nic nowego, dodane tylko dla kompletności.
Posty
{
_id: new ObjectId()
title: "A post",
content: " Interesting stuff",
picture: "some_link",
created: new ISODate(),
author: {
username: "HotGrrrl96",
picture: "some_link"
}
}
I o to chodzi w poście. Należy zwrócić uwagę na dwie rzeczy:po pierwsze, przechowujemy dane autora, których potrzebujemy natychmiast podczas wyświetlania posta, ponieważ oszczędza nam to zapytania o bardzo powszechny, jeśli nie wszechobecny przypadek użycia. Dlaczego nie zapisujemy odpowiednio komentarzy i danych komentatorów? Ze względu na limit 16 MB , staramy się zapobiec przechowywaniu referencji w jednym dokumencie. Zamiast tego przechowujemy odniesienia w dokumentach komentarzy:
Komentarze
{
_id: new ObjectId(),
post: someObjectId,
created: new ISODate(),
commenter: {
username: "FooBar",
picture: "some_link"
},
comment: "Awesome!"
}
Podobnie jak w przypadku postów, mamy wszystkie niezbędne dane do wyświetlenia posta.
Zapytania
To, co osiągnęliśmy teraz, to ominięcie limitu rozmiaru BSON i nie musimy odwoływać się do danych użytkownika, aby móc wyświetlać posty i komentarze, co powinno oszczędzić nam wielu zapytań. Wróćmy jednak do przypadków użycia i kilku innych zapytań
Dodawanie komentarza
Teraz jest to całkowicie proste.
Pobieranie wszystkich lub niektórych komentarzy do danego posta
Dla wszystkich komentarzy
db.comments.find({post:objectIdOfPost})
3 ostatnie komentarze
db.comments.find({post:objectIdOfPost}).sort({created:-1}).limit(3)
Tak więc za wyświetlanie postu i wszystkich (lub niektórych) jego komentarzy, w tym nazw użytkowników i zdjęć, jesteśmy na dwa pytania. Więcej niż potrzebowałeś wcześniej, ale obeszliśmy limit rozmiaru i w zasadzie możesz mieć nieskończoną liczbę komentarzy do każdego posta. Ale przejdźmy do czegoś prawdziwego
Pobieranie ostatnich 5 postów i ich 3 ostatnie komentarze
Jest to proces dwuetapowy. Jednak przy odpowiednim indeksowaniu (wrócimy do tego później) nadal powinno to być szybkie (a tym samym oszczędzać zasoby):
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);
}
)
Pobierz wszystkie posty danego użytkownika posortowane od najnowszych do najstarszych i ich komentarze
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});
Zauważ, że mamy tu tylko dwa zapytania. Chociaż musisz „ręcznie” nawiązać połączenie między postami i ich komentarzami, powinno to być całkiem proste.
Zmień nazwę użytkownika
Przypuszczalnie jest to rzadki przypadek użycia. Jednak nie jest to zbyt skomplikowane w przypadku wspomnianego modelu danych
Najpierw zmieniamy dokument użytkownika
db.users.update(
{ username: "HotGrrrl96"},
{
$set: { username: "Joe Cool"},
$push: {oldUsernames: "HotGrrrl96" }
},
{
writeConcern: {w: "majority"}
}
);
Wstawiamy starą nazwę użytkownika do odpowiedniej tablicy. Jest to środek bezpieczeństwa na wypadek, gdyby coś poszło nie tak z następującymi operacjami. Ponadto ustawiliśmy problem zapisu na dość wysokim poziomie, aby upewnić się, że dane są trwałe.
db.posts.update(
{ "author.username": "HotGrrrl96"},
{ $set:{ "author.username": "Joe Cool"} },
{
multi:true,
writeConcern: {w:"majority"}
}
)
Nic specjalnego tutaj. Oświadczenie o aktualizacji komentarzy wygląda prawie tak samo. Chociaż te zapytania zajmują trochę czasu, rzadko są wykonywane.
Indeksy
Z reguły można powiedzieć, że MongoDB może używać tylko jednego indeksu na zapytanie. Chociaż nie jest to do końca prawdą, ponieważ istnieją przecięcia indeksów, łatwo sobie z tym poradzić. Inną rzeczą jest to, że poszczególne pola w indeksie złożonym mogą być używane niezależnie. Tak więc łatwym podejściem do optymalizacji indeksów jest znalezienie zapytania z największą liczbą pól używanych w operacjach wykorzystujących indeksy i utworzenie z nich indeksu złożonego. Zwróć uwagę, że kolejność występowania w zapytaniu ma znaczenie. Więc chodźmy dalej.
Posty
db.posts.createIndex({"author.username":1,"created":-1})
Komentarze
db.comments.createIndex({"post":1, "created":-1})
Wniosek
W pełni osadzony dokument na post jest wprawdzie najszybszym sposobem jego załadowania i komentarzy. Jednak nie skaluje się dobrze, a ze względu na charakter potencjalnie złożonych zapytań niezbędnych do poradzenia sobie z nim, ta przewaga wydajności może zostać wykorzystana, a nawet wyeliminowana.
Dzięki powyższemu rozwiązaniu handlujesz pewną szybkością (jeśli!) z zasadniczo nieograniczoną skalowalnością i znacznie prostszym sposobem radzenia sobie z danymi.
H.