MongoDB zużywa za dużo pamięci

28

Używamy MongoDB od kilku tygodni, ogólny trend, który widzieliśmy, był taki, że mongodb zużywa zbyt dużo pamięci (znacznie więcej niż cały rozmiar zestawu danych + indeksy).

Przeczytałem już to pytanie i to pytanie , ale wydaje się, że żadne nie odnosi się do problemu, z którym się spotkałem, w rzeczywistości wyjaśniają to, co zostało już wyjaśnione w dokumentacji.

Poniżej przedstawiono wyniki poleceń htop i show dbs .

wprowadź opis zdjęcia tutaj

pokaż dbs

Wiem, że mongodb używa IO odwzorowanego w pamięci, więc w zasadzie system operacyjny obsługuje buforowanie rzeczy w pamięci, a mongodb teoretycznie powinien zwolnić swoją pamięć podręczną, gdy inny proces żąda wolnej pamięci , ale z tego, co widzieliśmy, tak nie jest.

OOM zaczyna zabijać inne ważne procesy, np. Postgres, redis itp. (Jak widać, aby rozwiązać ten problem, zwiększyliśmy pamięć RAM do 183 GB, co teraz działa, ale jest dość drogie. Mongo używa ~ 87 GB pamięci RAM, prawie czterokrotnie większy niż cały zestaw danych)

Więc,

  1. Czy tak duże zużycie pamięci jest naprawdę oczekiwane i normalne? (Zgodnie z dokumentacją WiredTiger zużywa maksymalnie ~ 60% pamięci RAM na swoją pamięć podręczną, ale biorąc pod uwagę rozmiar zestawu danych, czy ma nawet wystarczającą ilość danych, aby móc zabrać 86 GB pamięci RAM?)
  2. Nawet jeśli spodziewane jest użycie pamięci, dlaczego Mongo nie zwolni przydzielonej pamięci, jeśli inny proces zacznie żądać więcej pamięci? Różne inne działające procesy były ciągle zabijane przez linux oom, w tym sam mongodb, zanim zwiększyliśmy pamięć RAM i spowodowało to, że system był całkowicie niestabilny.

Dzięki !

SpiXel
źródło
4
Być może niektóre prezentacje na temat wewnętrznych elementów WiredTiger, takie jak mongodb.com/presentations/… , mogą rzucić nieco światła. Oczekuję, że domyślne użycie 50% fizycznej pamięci RAM to tylko przypuszczenie, co jest prawdopodobnie wymagane na dedykowanym hoście MongoDB i wielu będzie musiało to zmienić. FWIW, nie sądzę, aby ustawienie cacheSizeGB „ograniczało” mongo - opcja istnieje, więc masz kontrolę nad wdrożeniami. Określenie, ile pamięci „mongo” potrzebuje na pamięć podręczną, wymagałoby monitorowania statystyk pamięci podręcznej serwera przy oczekiwanym obciążeniu serwera.

Odpowiedzi:

23

Okej, więc po zapoznaniu się ze wskazówkami podanymi przez loicmathieu i jstell i nieco pogrzebaniu, oto rzeczy, które dowiedziałem się o MongoDB za pomocą silnika pamięci WiredTiger. Kładę to tutaj, jeśli ktoś napotkał te same pytania.

Wątki użycia pamięci, o których wspomniałem, wszystkie należały do ​​2012-2014, wszystkie były wcześniejszymi WiredTiger i opisują zachowanie oryginalnego silnika pamięci MMAPV1, który nie ma osobnej pamięci podręcznej ani obsługi kompresji.

Ustawienia pamięci podręcznej WiredTiger kontrolują tylko rozmiar pamięci używanej bezpośrednio przez silnik pamięci WiredTiger (nie całkowitą pamięć używaną przez mongod). Wiele innych rzeczy potencjalnie zajmuje pamięć w konfiguracji MongoDB / WiredTiger, takich jak:

  • WiredTiger kompresuje pamięć dyskową, ale dane w pamięci są nieskompresowane.

  • WiredTiger domyślnie nie synchronizuje danych przy każdym zatwierdzeniu , więc pliki dziennika znajdują się również w pamięci RAM, co odbija się na pamięci. Wspomniano również, że w celu efektywnego wykorzystania We / Wy, WiredTiger łączy żądania We / Wy (brak pamięci podręcznej), co również wydaje się zajmować trochę pamięci RAM (w rzeczywistości brudne strony (strony, które uległy zmianie / aktualizacji) mają listę aktualizacji na nich przechowywane w współbieżnym SkipList ).

  • WiredTiger przechowuje wiele wersji rekordów w swojej pamięci podręcznej (Kontrola współbieżności wielu wersji, operacje odczytu uzyskują dostęp do ostatniej zatwierdzonej wersji przed ich działaniem).

  • WiredTiger Przechowuje sumy kontrolne danych w pamięci podręcznej.

  • Sam MongoDB zużywa pamięć do obsługi otwartych połączeń, agregacji, kodu na serwerze itp .

Biorąc pod uwagę te fakty, poleganie na show dbs;nim nie było technicznie poprawne, ponieważ pokazuje jedynie skompresowany rozmiar zestawów danych.

Aby uzyskać pełny rozmiar zestawu danych, można użyć następujących poleceń.

db.getSiblingDB('data_server').stats()
# OR
db.stats()

Wyniki są następujące:

{
    "db" : "data_server",
    "collections" : 11,
    "objects" : 266565289,
    "avgObjSize" : 224.8413545621088,
    "dataSize" : 59934900658, # 60GBs
    "storageSize" : 22959984640,
    "numExtents" : 0,
    "indexes" : 41,
    "indexSize" : 7757348864, # 7.7GBs
    "ok" : 1
}

Wygląda więc na to, że rzeczywisty rozmiar zestawu danych + jego indeksy zajmują około 68 GB tej pamięci.

Biorąc to wszystko pod uwagę, wydaje się, że użycie pamięci jest teraz dość spodziewane, dobrą rzeczą jest to, że można całkowicie ograniczyć rozmiar pamięci podręcznej WiredTiger, ponieważ dość skutecznie obsługuje operacje we / wy (jak opisano powyżej).

Pozostaje również problem OOM, aby rozwiązać ten problem, ponieważ nie mieliśmy wystarczających zasobów, aby zabrać mongodb, obniżyliśmy oom_score_adj, aby na razie uniemożliwić OOM zabijanie ważnych procesów (co oznacza, że ​​powiedzieliśmy OOM, aby nie zabijał naszego pożądane procesy ).

SpiXel
źródło
Mamy podobny problem. MongoDB wciąż zjada pamięć RAM. Podobne proporcje. Czy oom_score_adj rozwiązanie było najlepszą rzeczą, jaką udało ci się wymyślić?
Hartator
@Hartator Cóż, zmniejszyliśmy rozmiar pamięci podręcznej wiredtiger, włożyliśmy więcej wysiłku w zarządzanie naszymi indeksami i polityką indeksowania, a następnie w końcu zmniejszyliśmy oom_score_adj o rzeczy, na które nam zależało, to chyba wszystko, co można zrobić.
SpiXel,
4

Nie sądzę, żebyś miał problem z MongoDB, ponieważ jstell powiedział ci, że MongoDB z WiredTiger zużyje 50% dostępnej pamięci, więc jeśli zwiększysz pamięć RAM twojego serwera, zajmie to więcej pamięci.

Ponieważ jest to więcej niż rozmiar indeksów DB +, należy pamiętać, że WiredTiger kompresuje bazę danych na dysku, a także używa dzienników migawek do rejestrowania zmian w dokumentach. Tak więc rzeczywisty rozmiar WiredTiger to rozmiar za pomocą show dbs * kompresja_racja + rozmiar dzienników migawek. Tak więc prawie niemożliwe jest dokładne określenie oczekiwanego rozmiaru.

Należy także pamiętać, że narzędzia, takie jak top, ps, htopnie wyświetla pamięć rzeczywiście używany przez aplikację, referen do tego SOW pytanie o szczegóły: https://stackoverflow.com/questions/131303/how-to-measure-actual-memory -usage-of-a-application-or-process

Wróćmy do problemu. Masz inne narzędzia uruchomione na tym samym hoście, a OOM je zabija. Nie znam Linux OOM, ale czy jesteś pewien, że zabija ich z powodu MongoDB lub… właśnie z ich powodu (może zabija Postgres, ponieważ Postgres zabrał zbyt dużo pamięci).

W każdym razie, jako najlepszą praktykę, jeśli masz dużą bazę danych Mongo, nie instaluj jej na hoście współdzielonym z innymi bazami danych lub będziesz miał wiele trudności, na wypadek, gdybyś miał problem taki jak ten, który tu opisujesz, aby wiedzieć kto naprawdę powoduje problem na hoście.

loicmathieu
źródło
4

Dokumenty

Możesz przeczytać podstawowe obawy dotyczące pamięci MongoDB, a także krótką dyskusję na temat sprawdzania użycia pamięci .

Przegląd wykorzystania pamięci

Polecenie db.serverStatus()( dokumenty ) może zapewnić przegląd użycia pamięci, w szczególności:

> db.serverStatus().mem
{ "bits" : 64, "resident" : 27, "virtual" : 397, "supported" : true }

> db.serverStatus().tcmalloc
... not easy to read! ...

> db.serverStatus().tcmalloc.tcmalloc.formattedString
------------------------------------------------
MALLOC:        3416192 (    3.3 MiB) Bytes in use by application
MALLOC: +      4788224 (    4.6 MiB) Bytes in page heap freelist
MALLOC: +       366816 (    0.3 MiB) Bytes in central cache freelist
...
... a bunch of stats in an easier to read format ...

Jak duże są twoje indeksy?

db.stats() możemy wyświetlić całkowity rozmiar wszystkich indeksów, ale możemy również uzyskać szczegółowe informacje o pojedynczej kolekcji przy użyciu db.myCollection.stats()

Na przykład to polecenie porówna rozmiary indeksów dla każdej kolekcji :

> db.getCollectionNames().map(name => ({totalIndexSize: db.getCollection(name).stats().totalIndexSize, name: name})).sort((a, b) => a.totalIndexSize - b.totalIndexSize).forEach(printjson)
...
{ "totalIndexSize" : 696320, "name" : "smallCollection" }
{ "totalIndexSize" : 135536640, "name" : "bigCollection" }
{ "totalIndexSize" : 382681088, "name" : "hugeCollection" }
{ "totalIndexSize" : 511901696, "name" : "massiveCollection" }

Teraz możemy spojrzeć na szczegóły tej ogromnej kolekcji, aby zobaczyć, który z jej indeksów jest najbardziej kosztowny:

> db.massiveCollection.stats().indexSizes
{
        "_id_" : 230862848,
        "groupId_1_userId_1" : 49971200,
        "createTime_1" : 180301824,
        "orderId_1" : 278528,
        "userId_1" : 50155520
}

Dzięki temu możemy lepiej zrozumieć, gdzie możliwe są oszczędności.

(W tym przypadku mieliśmy indeks, createTimektóry był raczej ogromny - jeden wpis na dokument - i zdecydowaliśmy, że możemy bez niego żyć).

joeytwiddle
źródło
Czy indeksy mają duży koszt pamięci?
Mathias Lykkegaard Lorenzen
@MathiasLykkegaardLorenzen Zależy to od liczby unikalnych wartości zindeksowanego pola w stosunku do pamięci RAM serwera. W naszym przypadku createTimeindeks był problematyczny, ponieważ był unikalny dla każdego dokumentu, a ta kolekcja była ogromna. Indeksowanie pozostałych pól było w porządku, ponieważ było mniej unikatowych wartości (wartości były grupowane).
joeytwiddle