Doskonała wydajność bazy danych jest ważna podczas tworzenia aplikacji za pomocą MongoDB. Czasami ogólny proces dostarczania danych może ulec pogorszeniu z wielu powodów, z których niektóre obejmują:
- Nieodpowiednie wzorce projektowe schematów
- Niewłaściwe użycie lub niestosowanie strategii indeksowania
- Nieodpowiedni sprzęt
- Opóźnienie replikacji
- Słaba wydajność technik zapytań
Niektóre z tych niepowodzeń mogą zmusić Cię do zwiększenia zasobów sprzętowych, podczas gdy inne mogą nie. Na przykład słabe struktury zapytań mogą powodować, że przetwarzanie zapytania zajmie dużo czasu, powodując opóźnienie repliki, a może nawet utratę danych. W tym przypadku można by pomyśleć, że może pamięć do przechowywania nie wystarczy i prawdopodobnie wymaga skalowania. W tym artykule omówiono najbardziej odpowiednie procedury, które można zastosować, aby zwiększyć wydajność bazy danych MongoDB.
Projektowanie schematu
Zasadniczo dwie najczęściej stosowane relacje schematów to...
- Jeden do kilku
- Jeden do wielu
Chociaż najbardziej wydajnym projektem schematu jest relacja „jeden do wielu”, każdy ma swoje zalety i ograniczenia.
Jeden do kilku
W tym przypadku dla danego pola istnieją dokumenty osadzone, ale nie są one indeksowane tożsamością obiektu.
Oto prosty przykład:
{
userName: "Brian Henry",
Email : "[email protected]",
grades: [
{subject: ‘Mathematics’, grade: ‘A’},
{subject: English, grade: ‘B’},
]
}
Jedną z zalet korzystania z tej relacji jest to, że można uzyskać osadzone dokumenty za pomocą tylko jednego zapytania. Jednak z punktu widzenia zapytań nie można uzyskać dostępu do pojedynczego osadzonego dokumentu. Jeśli więc nie zamierzasz oddzielnie odwoływać się do osadzonych dokumentów, optymalnym rozwiązaniem będzie użycie tego projektu schematu.
Jeden do wielu
W przypadku tej relacji dane w jednej bazie danych są powiązane z danymi w innej bazie danych. Na przykład możesz mieć bazę danych dla użytkowników i inną dla postów. Więc jeśli użytkownik utworzy post, zostanie on zarejestrowany z identyfikatorem użytkownika.
Schemat użytkowników
{
Full_name: “John Doh”,
User_id: 1518787459607.0
}
Schemat postów
{
"_id" : ObjectId("5aa136f0789cf124388c1955"),
"postTime" : "16:13",
"postDate" : "8/3/2018",
"postOwnerNames" : "John Doh",
"postOwner" : 1518787459607.0,
"postId" : "1520514800139"
}
Zaletą tego projektu schematu jest to, że dokumenty są traktowane jako samodzielne (można je wybrać osobno). Kolejną zaletą jest to, że ten projekt umożliwia użytkownikom o różnych identyfikatorach dzielenie się informacjami ze schematu postów (stąd nazwa Jeden-do-Wielu), a czasami może być schematem „N-do-N” - w zasadzie bez użycia łączenia tabel. Ograniczeniem tego projektu schematu jest to, że musisz wykonać co najmniej dwa zapytania, aby pobrać lub wybrać dane w drugiej kolekcji.
Sposób modelowania danych będzie zatem zależeć od wzorca dostępu aplikacji. Poza tym musisz wziąć pod uwagę projekt schematu, który omówiliśmy powyżej.
Techniki optymalizacji projektowania schematów
-
Wykorzystaj osadzanie dokumentów tak często, jak to możliwe, ponieważ zmniejsza to liczbę zapytań, które musisz uruchomić dla określonego zestawu danych.
-
Nie używaj denormalizacji w przypadku dokumentów, które są często aktualizowane. Jeśli pole będzie często aktualizowane, będzie zadaniem odnalezienia wszystkich instancji, które wymagają aktualizacji. Spowoduje to powolne przetwarzanie zapytań, a zatem przytłacza nawet zalety związane z denormalizacją.
-
Jeśli istnieje potrzeba oddzielnego pobrania dokumentu, nie ma potrzeby korzystania z osadzania, ponieważ wykonanie złożonych zapytań, takich jak agregowanie potoków, zajmuje więcej czasu.
-
Jeśli tablica dokumentów do osadzenia jest wystarczająco duża, nie osadzaj ich. Wzrost tablicy powinien mieć przynajmniej ograniczony limit.
Właściwe indeksowanie
Jest to bardziej krytyczna część dostrajania wydajności i wymaga wszechstronnego zrozumienia zapytań aplikacji, stosunku odczytów do zapisów oraz ilości wolnej pamięci w systemie. Jeśli używasz indeksu, zapytanie przeskanuje indeks, a nie kolekcję.
Doskonały indeks to taki, który obejmuje wszystkie pola skanowane przez zapytanie. Nazywa się to indeksem złożonym.
Aby utworzyć pojedynczy indeks dla pól, możesz użyć tego kodu:
db.collection.createIndex({“fields”: 1})
W przypadku indeksu złożonego, aby utworzyć indeksowanie:
db.collection.createIndex({“filed1”: 1, “field2”: 1})
Oprócz szybszego odpytywania za pomocą indeksowania, dodatkową zaletą są inne operacje, takie jak sortowanie, próbkowanie i limitowanie. Na przykład, jeśli zaprojektuję swój schemat jako {f:1, m:1}, mogę wykonać dodatkową operację oprócz wyszukiwania jako
db.collection.find( {f: 1} ).sort( {m: 1} )
Odczytywanie danych z pamięci RAM jest wydajniejsze niż odczytywanie tych samych danych z dysku. Z tego powodu zawsze zaleca się upewnienie się, że indeks mieści się w całości w pamięci RAM. Aby uzyskać aktualny rozmiar indeksu swojej kolekcji, uruchom polecenie :
db.collection.totalIndexSize()
Otrzymasz wartość taką jak 36864 bajtów. Ta wartość nie powinna również zajmować dużego procentu całkowitego rozmiaru pamięci RAM, ponieważ musisz zaspokoić potrzeby całego zestawu roboczego serwera.
Wydajne zapytanie powinno również zwiększyć selektywność. Selektywność można zdefiniować jako zdolność zapytania do zawężenia wyniku za pomocą indeksu. Aby być bardziej secantnym, Twoje zapytania powinny ograniczać liczbę możliwych dokumentów z indeksowanym polem. Selektywność jest głównie związana ze złożonym indeksem, który obejmuje pole o niskiej selektywności i inne pole. Na przykład, jeśli masz te dane:
{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 7, b: "cd", c: 58 }
{ _id: ObjectId(), a: 8, b: "kt", c: 33 }
Zapytanie {a:7, b:“cd”} przeskanuje 2 dokumenty, aby zwrócić 1 pasujący dokument. Jeśli jednak dane dla wartości a są równomiernie rozłożone, tj.
{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 8, b: "cd", c: 58 }
{ _id: ObjectId(), a: 9, b: "kt", c: 33 }
Zapytanie {a:7, b:„cd”} przeskanuje 1 dokument i zwróci ten dokument. Dlatego zajmie to krócej niż pierwsza struktura danych.
ClusterControlSingle Console dla całej infrastruktury bazy danychDowiedz się, co jeszcze nowego w ClusterControlZainstaluj ClusterControl ZA DARMOZarządzanie zasobami
Niewystarczająca pamięć, pamięć RAM i inne parametry operacyjne mogą drastycznie obniżyć wydajność MongoDB. Na przykład, jeśli liczba połączeń użytkowników jest bardzo duża, utrudni to aplikacji serwera terminową obsługę żądań. Jak omówiono w Kluczowe rzeczy do monitorowania w MongoDB, możesz uzyskać przegląd tego, jakie masz ograniczone zasoby i jak możesz je skalować, aby dopasować je do swoich specyfikacji. W przypadku dużej liczby jednoczesnych żądań aplikacji system bazy danych będzie przeciążony nadążaniem za zapotrzebowaniem.
Opóźnienie replikacji
Czasami możesz zauważyć, że brakuje niektórych danych w Twojej bazie danych lub gdy coś usuniesz, pojawi się ponownie. O ile możesz mieć dobrze zaprojektowany schemat, odpowiednie indeksowanie i wystarczającą ilość zasobów, na początku Twoja aplikacja będzie działać płynnie bez żadnych problemów, ale w pewnym momencie zauważysz te ostatnie problemy. MongoDB opiera się na koncepcji replikacji, w której dane są nadmiarowo kopiowane, aby spełnić pewne kryteria projektowe. Założenie z tym jest takie, że proces jest natychmiastowy. Jednak mogą wystąpić pewne opóźnienia, które mogą być spowodowane awarią sieci lub nieobsługiwanymi błędami. Krótko mówiąc, będzie duża przerwa między czasem przetwarzania operacji w węźle podstawowym a czasem jej zastosowania w węźle drugorzędnym.
Niepowodzenia związane z opóźnieniami replik
-
Niespójne dane. Jest to szczególnie związane z operacjami odczytu, które są rozprowadzane na serwerach pomocniczych.
-
Jeśli różnica w opóźnieniu jest wystarczająco duża, wiele niereplikowanych danych może znajdować się w węźle głównym i trzeba będzie je uzgodnić w węźle drugorzędnym. W pewnym momencie może to być niemożliwe, zwłaszcza gdy nie można odzyskać głównego węzła.
-
Niemożność odzyskania węzła podstawowego może zmusić go do uruchomienia węzła z nieaktualnymi danymi, a w konsekwencji może usunąć całą bazę danych, aby przywrócić główny węzeł.
Przyczyny awarii węzła wtórnego
-
Przewyższająca moc pierwotną nad drugorzędną pod względem specyfikacji procesora, IOPS dysków i sieci we/wy.
-
Złożone operacje zapisu. Na przykład polecenie takie jak
db.collection.update( { a: 7} , {$set: {m: 4} }, {multi: true} )
Węzeł podstawowy wystarczająco szybko zarejestruje tę operację w oplogu. Jednak w przypadku węzła drugorzędnego musi pobrać te operacje, wczytać do pamięci RAM wszelkie indeksy i strony danych, aby spełnić pewne specyfikacje kryteriów, takie jak identyfikator. Ponieważ musi to zrobić wystarczająco szybko, aby utrzymać szybkość z głównym węzłem wykonuje operację, jeśli liczba operacji jest wystarczająco duża, nastąpi oczekiwane opóźnienie.
-
Blokowanie wtórnego podczas tworzenia kopii zapasowej. W takim przypadku możemy zapomnieć o wyłączeniu podstawowego, dlatego będziemy kontynuować jego normalne działanie. W momencie zwolnienia blokady opóźnienie replikacji będzie miało dużą lukę, zwłaszcza w przypadku dużej ilości kopii zapasowych danych.
-
Budowanie indeksu. Jeśli indeks gromadzi się w węźle drugorzędnym, wszystkie inne powiązane z nim operacje są blokowane. Jeśli indeks jest długotrwały, wystąpi czkawka z opóźnieniem replikacji.
-
Niepodłączony drugorzędny. Czasami węzeł dodatkowy może ulec awarii z powodu rozłączenia sieci, co powoduje opóźnienie replikacji po ponownym połączeniu.
Jak zminimalizować opóźnienie replikacji
-
Używaj unikalnych indeksów oprócz kolekcji posiadającej pole _id. Ma to na celu uniknięcie całkowitego niepowodzenia procesu replikacji.
-
Rozważ inne rodzaje kopii zapasowych, takie jak migawki z określonego punktu w czasie i systemu plików, które niekoniecznie wymagają blokowania.
-
Unikaj tworzenia dużych indeksów, ponieważ powodują one blokowanie w tle.
-
Spraw, aby drugorzędny był wystarczająco silny. Jeśli operacja zapisu jest lekka, użycie słabszych części wtórnych będzie opłacalne. Jednak w przypadku dużych obciążeń zapisu węzeł pomocniczy może pozostawać w tyle za węzłem podstawowym. Aby być bardziej seccant, wtórny powinien mieć wystarczającą przepustowość, aby pomóc w odczytywaniu oplogów wystarczająco szybko, aby utrzymać szybkość z węzłem podstawowym.
Wydajne techniki zapytań
Oprócz tworzenia zindeksowanych zapytań i używania selektywności zapytań, jak omówiono powyżej, istnieją inne koncepcje, które możesz zastosować, aby przyspieszyć i zwiększyć skuteczność zapytań.
Optymalizacja zapytań
-
Korzystanie z objętego zapytania. Zakryte zapytanie to takie, które jest zawsze całkowicie spełniane przez indeks, dlatego nie ma potrzeby sprawdzania żadnego dokumentu. W związku z tym objęte zapytanie powinno zawierać wszystkie pola jako część indeksu, a zatem wynik powinien zawierać wszystkie te pola.
Rozważmy ten przykład:
{_id: 1, product: { price: 50 }
Jeśli utworzymy indeks dla tej kolekcji jako
{“product.price”: 1}
Biorąc pod uwagę operację wyszukiwania, ten indeks pokryje to zapytanie;
db.collection.find( {“product.price”: 50}, {“product.price”: 1, _id: 0} )
i zwróć tylko pole product.price i wartość.
-
W przypadku dokumentów osadzonych użyj notacji kropkowej (.). Notacja z kropkami pomaga w dostępie do elementów tablicy i pól osadzonego dokumentu.
Dostęp do tablicy:
{ prices: [12, 40, 100, 50, 40] }
Aby określić na przykład czwarty element, możesz napisać to polecenie:
“prices.3”
Dostęp do tablicy obiektów:
{ vehicles: [{name: toyota, quantity: 50}, {name: bmw, quantity: 100}, {name: subaru, quantity: 300} }
Aby określić pole nazwy w tablicy pojazdów, możesz użyć tego polecenia
“vehicles.name”
-
Sprawdź, czy zapytanie jest objęte. W tym celu użyj funkcji db.collection.explain(). Funkcja ta dostarczy informacji o wykonaniu innych operacji -np. db.collection.explain().aggregate(). Aby dowiedzieć się więcej o funkcji wyjaśniania, skorzystaj z funkcji explain().
Ogólnie rzecz biorąc, nadrzędną techniką, jeśli chodzi o zapytania, jest użycie indeksów. Wysyłanie zapytań tylko do indeksu jest znacznie szybsze niż wykonywanie zapytań dotyczących dokumentów poza indeksem. Mogą zmieścić się w pamięci, dlatego są dostępne w pamięci RAM, a nie na dysku. Dzięki temu pobieranie ich z pamięci jest łatwe i szybkie.