Please enable JavaScript.
Coggle requires JavaScript to display documents.
Mongo - Coggle Diagram
Mongo
ADVANCED
-
jorunal (przechowuje historie zmian zawsze, nie jak w przypadku oplog) - to plik na dysku, stale na dysku - tracker zmian, więc jeśli jest failoure i zmiany z RAMu nie zdązyły się zapisac na dysk, to potem jak serwer wstaje to zczytuje to z jornal
(jeśli mamy replikacje) - oplog - log to przechowywania transkacji. Możemy zapytać optloga - daj mi znać co się zmieniło od wczoraj. głownie po to, żeby zrobić update secondary node'ów
storage engines - wiredTiger - mechanizm zapisu, wspier multiVersion concurenccy control (MVCC)
brak in-place update
czyli opercja waży tyle ile cały dokument, nawet jeśli to tylko zmiana id
-
nie mamy konieczności dodawania paddingów, czyli wolnych przestrzeni na ewentalne zmiany. kiedyś dokumenty były umiejscowione obok siebie w pamięci, dlatego potrzebne były wolne przestrzenia na jakieś updaty
wewnętrznie wspera mechnizm "delta updates", ale tylko w ramach modyfikacji danych w pamięci oraz przy zrzucania danych do Journala
-
RAM jest podzielony na checkpointy (co 60s), dane między checkpointami są snapshotami. Plus, kiedy dochodzimy do końca checkpointu, wtedy wszytskie snapshoty z tego checkpointa za zapisywane na dysk, z czego potem otwierane jest nowe okno checkpointu
Journaling
również można stracić dane :( maksymalnie możemy utrzcić 100ms ostatnich zmian, ale możemy wymusić zapis do journala (flagą j=true) [ale jest wolniej, ale mamy 100% pewności]
zapis na dysk jest robiony gdy: - co 10oms by default, - przy zmianach powyżej 128kB, - przy tworzeniu nowego pliku journala, - w przypadku opercji replikacji bazujących na poprzedniej opercji
Rodzaje kompresji dokumentów - poczytać artykuł - blog allegro "impact of the data model on the MongoDB"
-
OPTYMALIZACJA ZAPYTAŃ
FIND
find jest opercją, która operuje na kursorach
-
Należy unikać przesadanie długich odczytów kursora (np. wykonywania czasochłonnych opercji per dokument odczytywanych z kursora)
-
-
-
SKIP
-
Mongo nie jest w stanie ustalić położenia dokumentu, więc konieczne jest kążdorazowe ustalnie, które dokumenty pominąc
alternatywą jest użycie "keyset pagination" - konstruowanie zapytanie ogrniczjącego zbiór na podstawie poprzedniego zapytania
jeśli nie zalęzy nam na kolejności sortowania możemy użyć pola _id
Takie rozwiązanie nie pozwala jednak na losowy dostęp do danych w ramach stronnicowania (musimy znąc poprzednią stronę, żeby zapytać o kolejną)
W celu zmniejszenia ilości danych transportowanych po sieci wskazane jest używanie projekcji ograniczającej rozmiar dokumentu
-
db.indexTest.find({$and : [{a : 10, b: 10}]})
$where
-
-
-
-
-
db.indexTest.find({$where : function () { return this.a == 10 && this.b == 10}}) // bardzo wolne, bo nieużywa indeksów i ani mechanizów cachowania, kod JS jest wykonywany na każdy elemencie
jest wolny, nie używać - lepiej użyć db.indexTest.find({$and : [{a : 10, b: 10}]}), bo to się dzieje na indexach
$regex
-
Warto mieć na uwadze, że zapytania $regex są dość zasobożerne, w szczególności jesli nie jest znany początke ciągu (brak użycia indeksów)
-
-
poniważ indexy, są przechowywany w postaci drzewa indexów, w regex nalęzy podąż początek zdania, inaczej tracimy zalety drzewa i przeszukujemy całe drzewo
db.indexTest.createIndex({stringValue : 1})
db.indexTest.find({stringValue : {$regex : "25."}}).explain() vs
db.indexTest.find({stringValue : {$regex : "^25."}}).explain() [to jest mega wolne, bo jest robiony scan całego drzewa :unamused:]
-
EXPLAIN PLAN
db.indexTest.find({a : 10, b: 10}).explain()
-
Metoda .explain() wykonywana na kursorze zwraca informacje na temat planu zapytania użytego w zapytaniu
Jako parametr możliwe jest przekazanie jako parametru jednej z wartości:
- queryPlanner (domyślna) - informacja na temat planu zapytania wybranego przez optymalizator zapytań
- executionStats - zwraca informację queryPlanner + informacje o statystykach wykonania zapytania
- allPlansExceution - zwraca informację o wszytskich planach zapytań branych pod uwagę przez optymalizator zapytań
executionStats
-
-
-
-
-
-
-
-
executionStages.isEOF - informacja czy zapytania zakończyło strumień (zazwyczaj czy pobrało wszytskie dane)
executionStages.needTime - liczba wykonanych cykli, w których nie osiągnieto wyniku
executionStages.needYield - liczba wstrzymań - zazwyczaj dostępów do danyc, których nie ma w pamięci RAM lub liczba cykli oczekiwania na lock
-
-
-
-
executionStages.allPlansExecution - częściowe informacje o innyc, dostępnych planach zapytania
QueryPlanner
-
-
queryPlanner.namesapce - nazwa bazt danych i kolekcji, których dotyczyło zapytanie
-
-
-
-
- Założyć indeks na polach:
{a : 1, b:1, c:1}
- Sprawdzić plan zapytania dla:
db.indexTest.find({a : 10}) <- sprawdzić odrzucone plany zapytania (allPlansExecution)
db.indexTest.find({a : 10, b:20})
db.indexTest.find({b : 20})
db.indexTest.find({a : 20, c : 20})
SAVE vs UPDATE
lepiej używać update, jeśli możemy
-
skalowanie
replikacja = większa wydajność odczytów, trochę mniejsza wydajność zapisów
sharding = wieksza wydajność zapisów, teoretycznie większa wydajność odczytów (w praktyce nie zawsze)
INDEXY
-
index złożony, na max 32
db.indexTest.createIndex({a : 1, b : 1}) -
- jeśłi zapytamy o a - użyjemy indexu,
- jeśli zapytamy o a,b - użyjemy indexu
- jeślizapytamy o b - nie użyjemy indexu (kolejność ma znaczenie)
-
-
-
-
-
-
Optymalny model danych
-
-
sharding
Janso zaprojektowane klucze shardowania - nawet jeśli nie przewidujesz tego procesu na obecnym etapie, to musisz być gotowy na łatwe wdrożenie shardingu
Należy zwrócić uwagę, że atomowe update jest tylko z użyciem klucza shardowania
-
-
REPLIKACJA
-
-
replikacja z automatycznym failoverem - w przydaku utraty bazy Mastera dokonuje procesu elekcji nowego Mastera z wezłów Slave
W ramach replikacji może istnieć wiele punktów odczyyty (Slaves), zapis możliwy tylko w węźle Master
-
Replika Set
-
Primary
-
-
-
Wybrór Primary odbywa się w wyniku elekcji, w której elementami głosującymi są wszytskie uprawnione do tego węzły Replica Seta
-
OPLOG
-
wezeł primary przesyła oplog do secondary, które na jego podstawie dokonuje zmian w posiadanych danych
-
Musimy być pewni że oplog pomiści wystarczająca ilość infromacji. Zbyt mały oplog zostanie może prowadzić do stałej desynchronizacji Secondary
-
-
ARBITER
nie przechowuje danych, a co za tym idzie nie prowadzi operacji odczytu i zapisu
-
-
-
-
to jest node, na którym po prostu nie ma danych, ale chcemy mieć replica set i możliwość
KONFIGURACJA
- Uruchomienie wszystkich węzłow Replica Set z parametrem - replSet "nazwa_replica_setu" oraz -oplogSize <MB>
- Podłączenie się konsolą mongo do jednego z węzłów
- Inicjalizacja Replica set komendą rs.initiate()
- dodanie pozostałych elementów Replica Set poprzez komendę rs.add("host:port")
- jeśłi przewidujemy dodanie arbitra wykorzystujemy do tego metodę rs.addArb("host:port")
Komendy rs.add() oraz rs.addArb() należy wykonywać węzła Primary
Ćwiczenie 1
- Wybrać porty dla instancji:
-27018
-27019
-27020
- Wybrać nazwę dla replica set
-stacjaRs1
- Uruchomić 3 instancje mongod
mongod --dbpath stacja --port 27018 --replSet stacjaRs1
mongod --dbpath db2 --port 27019 --replSet stacjaRs1
mongod --dbpath db3 --port 27020 --replSet stacjaRs1"
- for docker - login do mongo shell docker exec -it instancje mongo mongo
- dodać config (https://www.sohamkamani.com/blog/2016/06/30/docker-mongo-replica-set/)
- rs.status()
- rs.initiate()
- Ubicie wszystkich instancji
- Stworzyć katalogi na dane:
- wykorzystamy katalog stacja
-
-
-
Ćwiczenie 2
- Zalogować się na dowolny serwer Secondary
- Sprawdzić czy przekopiowały się dane
- Wyłączyć serwer Primary
- Sprawdzić za pomocą rs.status() czy wybrany został nowy Primary
- Spróbować dodać nowy rekord do secondary
Ćwiczenie 3
zasymulować że wszytskie nody są slavem, nie ma quorum nie ma primary
KOMENDY
rs.status() - podstawowa komenda dostarczająca informacji o stanie Replica Set - podłączonych węzłów oraz ich stanu
-
-
-
rs.printReplicationInfo() - informacje na temat aktualnego oplogu daty pierwszego i ostatniego rekordu zapisanego w oplogu
-
WRITE CONCERN
write concern pozwala na wybór wydajności vs spójności zapisu. Może on być konfigurowany per zapytania. Dostępne wartości:
- 0..4 - określenie w ilu węzłach musi nastąpić zapis, żeby został uznany za zakończony (0 - fire and forgot) - jeśłi 0 możemy stracić wszytskie dane
- majority - zapis do większości węzłów
-
POTENCJALNE PROBLEMY
-
rożnice sprzętowe między Primary,a Secondary, trwała desynchronizacja
-
każda modyfikacja powoduje dodatkowe obciążenia (zapis do oplogu + przesyłanie po sieci do Secondary)
-
-
TYPY
ObjectId
unikalny identifikator, używany domyślnie dla pola _id
jakiś ciąg stringa gdzie, zajmuje: 4bajty - epoch(znacznik czasowy), losowa wartość i licznik
-
-
-
-
-
-
-
-
-
-
-
SHARDING
tnie dane, które lądują na różnych nodach
Ma "load balancer - query ruter???", który kontroluje za nody (jak zookeeper w kafce)
jeśli zależ nam na zapisie to sharding jest lepszym rozwiązeniem niż replikacja (daje nam możliwość parraler zapisu, gdzie w przypadku replikacji zapis jest robiony tylko na node mastera)
-
-
-
problem jumbo chunk - mając klucz jedno stajnie rosnący to mamy problem, że zapisaujemy tylko do jednego node, czyli tracimy plusy shardowania
Ćwiczenie 1
- Ubić wszystkie instancje
- Dodać flagę --shardsvr do sharda pierwszego (stacjaRs1)
mongod --dbpath stacja --port 27018 --replSet stacjaRs1 --shardsvr
mongod --dbpath db2 --port 27019 --replSet stacjaRs1 --shardsvr
mongod --dbpath db3 --port 27020 --replSet stacjaRs1 --shardsvr
- Postawić drugi, pust shard:
-stworzyć katalog rs2db
-uruchomić instancję mongod:
mongod --dbpath rs2db --port 27028 --replSet stacjaRs2 --shardsvr
-zalogować się na 27028 i odpalić rs.initiate()
- Postawić cfg server
-stworzyć katalog cfgdb
-uruchomić instancję mongod:
mongod --dbpath cfgdb --port 27038 --replSet cfgRs --configsvr
-zalogować się na 27038 i odpalić rs.initiate()
- postawić query router
mongos --configdb "cfgRs/localhost:27038" --port 27050
6.sh.addShard("stacjaRs1/localhost:27018,localhost:27019,localhost:27020")
- sh.enableSharding("test")
- sh.shardCollection("test.IndexTest", {a:1})
- sh.splitAt("test.IndexTest",{a : 50})
syntax
insert - jeśli nie ma bazy, to stworzy się sama automatycznie
-
-
-
save
-
-
db.users.save({_id:2, name : "John2", age : 20})
-
-
update
db.users.update({name : "John2"}, {$set : {age : 40}})
-
może dodawać dokument, jeśli nie został stworzony
find
-
find() nie zwraca wszytskich danych, bo jeśli resulta miałby 100GB to nie mogłoby się załadować do RAMu, więc używany jest stremowanie danych przy użyciu kursora (zwraca co 20 dokumentów, gdzie kolejny pobierany jest za pomocą metody next())
-
-
opercaj 2 etaopwa - najpierw szuka w RAM, a dopiero potem dysku
-
-
WHY TO USE IT?
it's designed to scale (skalowanie horyzontalne), thanks to topology of cluster
-
-
-
pole _id jest zawsze indexowane by default, ale możemy nadpisać
-
-
-