Fragmentacja, znana również jako sharding, to technika podziału obszernego zbioru danych na mniejsze, zarządzalne części, które są rozlokowywane między różnymi instancjami MongoDB w środowisku rozproszonym.
Czym jest fragmentacja danych?
Sharding w MongoDB stanowi efektywną metodę skalowania, umożliwiającą przechowywanie ogromnych ilości danych na wielu serwerach, zamiast ograniczać się do jednego, centralnego serwera.
W praktyce, wraz ze wzrostem ilości danych, staje się niemożliwe ich przechowywanie na pojedynczej maszynie. Przeszukiwanie ogromnych baz danych na jednym serwerze może prowadzić do przeciążenia zasobów oraz niezadowalającej szybkości odczytu i zapisu.
Istnieją dwa główne podejścia do skalowania systemów gromadzenia danych:
Skalowanie wertykalne, czyli zwiększanie mocy obliczeniowej pojedynczego serwera poprzez dodawanie szybszych procesorów, zwiększanie pamięci RAM lub przestrzeni dyskowej. Jednakże, istnieją praktyczne ograniczenia w stosowaniu tej metody, wynikające z dostępnych technologii i konfiguracji sprzętowych.
Skalowanie horyzontalne, czyli rozkładanie obciążenia na wiele serwerów. Każdy serwer obsługuje podzbiór całości danych, co zapewnia wyższą wydajność i jest bardziej opłacalne niż inwestycja w zaawansowany sprzęt. Wymaga to jednak bardziej złożonej infrastruktury i jej konserwacji.
Sharding w MongoDB opiera się na koncepcji skalowania horyzontalnego.
Elementy składowe shardingu
Aby zaimplementować sharding w MongoDB, potrzebne są następujące komponenty:
Shard (fragment) – instancja MongoDB, która przechowuje podzbiór danych. Fragmenty powinny być wdrażane w ramach zestawu replik.
Mongos – instancja MongoDB działająca jako pośrednik między aplikacją kliencką a klastrem podzielonym na fragmenty. Działa jako router przekierowujący zapytania do odpowiednich fragmentów.
Config Server – instancja MongoDB przechowująca metadane oraz informacje konfiguracyjne klastra. Serwer konfiguracji musi być wdrożony jako zestaw replik.
Architektura shardingu
Klaster MongoDB składa się z kilku zestawów replik.
Każdy zestaw replik obejmuje co najmniej 3 instancje MongoDB. Klaster podzielony na fragmenty może składać się z wielu instancji fragmentów, przy czym każda z nich działa w swoim własnym zestawie replik. Aplikacje komunikują się z serwerem Mongos, który następnie kieruje zapytania do odpowiednich fragmentów. Aplikacje nie wchodzą w bezpośrednią interakcję z węzłami fragmentów. Router zapytań rozdziela dane między węzły fragmentów w oparciu o klucz fragmentu.
Implementacja shardingu
Poniżej przedstawiono kroki niezbędne do skonfigurowania shardingu:
Krok 1
- Uruchom serwer konfiguracyjny w zestawie replik i włącz replikację pomiędzy jego instancjami.
mongod –configsvr –port 27019 –replSet rs0 –dbpath C:datadata1 –bind_ip localhost
mongod –configsvr –port 27018 –replSet rs0 –dbpath C:datadata2 –bind_ip localhost
mongod –configsvr –port 27017 –replSet rs0 –dbpath C:datadata3 –bind_ip localhost
Krok 2
- Zainicjuj zestaw replik na jednym z serwerów konfiguracyjnych.
rs.initiate( { _id : “rs0”, configsvr: true, członkowie: [ { _id: 0, host: “IP:27017” }, { _id: 1, host: “IP:27018” }, { _id: 2, host: “IP:27019” } ] })
rs.initiate( { _id : "rs0", configsvr: true, members: [ { _id: 0, host: "IP:27017" }, { _id: 1, host: "IP:27018" }, { _id: 2, host: "IP:27019" } ] }) { "ok" : 1, "$gleStats" : { "lastOpTime" : Timestamp(1593569257, 1), "electionId" : ObjectId("000000000000000000000000") }, "lastCommittedOpTime" : Timestamp(0, 0), "$clusterTime" : { "clusterTime" : Timestamp(1593569257, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } }, "operationTime" : Timestamp(1593569257, 1) }
Krok 3
- Uruchom serwery fragmentów w zestawie replik i włącz replikację między nimi.
mongod –shardsvr –port 27020 –replSet rs1 –dbpath C:datadata4 –bind_ip localhost
mongod –shardsvr –port 27021 –replSet rs1 –dbpath C:datadata5 –bind_ip localhost
mongod –shardsvr –port 27022 –replSet rs1 –dbpath C:datadata6 –bind_ip localhost
MongoDB ustawi pierwszy serwer shardingu jako podstawowy. Użyj metody movePrimary, aby przenieść użycie podstawowego serwera shardingu.
Krok 4
- Zainicjuj zestaw replik na jednym z serwerów fragmentów.
rs.initiate( { _id : “rs0”, członkowie: [ { _id: 0, host: “IP:27020” }, { _id: 1, host: “IP:27021” }, { _id: 2, host: “IP:27022” } ] })
rs.initiate( { _id : "rs0", members: [ { _id: 0, host: "IP:27020" }, { _id: 1, host: "IP:27021" }, { _id: 2, host: "IP:27022" } ] }) { "ok" : 1, "$clusterTime" : { "clusterTime" : Timestamp(1593569748, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } }, "operationTime" : Timestamp(1593569748, 1) }
Krok 5
- Uruchom instancję mongos dla klastra podzielonego na fragmenty
mongos –port 40000 –configdb rs0/localhost:27019,localhost:27018,localhost:27017
Krok 6
- Połącz się z serwerem routingu mongos.
mongo – port 40000
- Teraz dodaj serwery shardingu.
sh.addShard( „rs1/hostlokalny:27020,hostlokalny:27021,hostlokalny:27022”)
sh.addShard( "rs1/localhost:27020,localhost:27021,localhost:27022") { "shardAdded" : "rs1", "ok" : 1, "operationTime" : Timestamp(1593570212, 2), "$clusterTime" : { "clusterTime" : Timestamp(1593570212, 2), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
Krok 7
- W powłoce mongo włącz sharding dla bazy danych i kolekcji.
- Włącz sharding dla bazy danych.
sh.enableSharding(„geekFlareDB”)
sh.enableSharding("geekFlareDB") { "ok" : 1, "operationTime" : Timestamp(1591630612, 1), "$clusterTime" : { "clusterTime" : Timestamp(1591630612, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
Krok 8
- Do fragmentacji kolekcji wymagany jest klucz fragmentu (opisany w dalszej części artykułu).
Składnia: sh.shardCollection(“dbName.collectionName”, { “key” : 1 } )
sh.shardCollection("geekFlareDB.geekFlareCollection", { "key" : 1 } ) { "collectionsharded" : "geekFlareDB.geekFlareCollection", "collectionUUID" : UUID("0d024925-e46c-472a-bf1a-13a8967e97c1"), "ok" : 1, "operationTime" : Timestamp(1593570389, 3), "$clusterTime" : { "clusterTime" : Timestamp(1593570389, 3), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
Uwaga: jeśli kolekcja nie istnieje, utwórz ją w następujący sposób:
db.createCollection("geekFlareCollection") { "ok" : 1, "operationTime" : Timestamp(1593570344, 4), "$clusterTime" : { "clusterTime" : Timestamp(1593570344, 5), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
Krok 9
Wstaw dane do kolekcji. Logi Mongo zaczną się rozrastać, sygnalizując aktywność mechanizmu równoważenia i próby rozłożenia danych między fragmentami.
Krok 10
Ostatnim krokiem jest sprawdzenie stanu shardingu. Można to zrobić, uruchamiając poniższe polecenie w węźle trasy Mongos.
Status fragmentowania
Sprawdź status fragmentowania za pomocą poniższego polecenia w węźle routingu mongo:
sh.status()
mongos> sh.status() --- Sharding Status --- sharding version: { "_id" : 1, "minCompatibleVersion" : 5, "currentVersion" : 6, "clusterId" : ObjectId("5ede66c22c3262378c706d21") } shards: { "_id" : "rs1", "host" : "rs1/localhost:27020,localhost:27021,localhost:27022", "state" : 1 } active mongoses: "4.2.7" : 1 autosplit: Currently enabled: yes balancer: Currently enabled: yes Currently running: no Failed balancer rounds in last 5 attempts: 5 Last reported error: Could not find host matching read preference { mode: "primary" } for set rs1 Time of Reported error: Tue Jun 09 2020 15:25:03 GMT+0530 (India Standard Time) Migration Results for the last 24 hours: No recent migrations databases: { "_id" : "config", "primary" : "config", "partitioned" : true } config.system.sessions shard key: { "_id" : 1 } unique: false balancing: true chunks: rs1 1024 too many chunks to print, use verbose if you want to force print { "_id" : "geekFlareDB", "primary" : "rs1", "partitioned" : true, "version" : { "uuid" : UUID("a770da01-1900-401e-9f34-35ce595a5d54"), "lastMod" : 1 } } geekFlareDB.geekFlareCol shard key: { "key" : 1 } unique: false balancing: true chunks: rs1 1 { "key" : { "$minKey" : 1 } } -->> { "key" : { "$maxKey" : 1 } } on : rs1 Timestamp(1, 0) geekFlareDB.geekFlareCollection shard key: { "product" : 1 } unique: false balancing: true chunks: rs1 1 { "product" : { "$minKey" : 1 } } -->> { "product" : { "$maxKey" : 1 } } on : rs1 Timestamp(1, 0) { "_id" : "test", "primary" : "rs1", "partitioned" : false, "version" : { "uuid" : UUID("fbc00f03-b5b5-4d13-9d09-259d7fdb7289"), "lastMod" : 1 } } mongos>
Dystrybucja danych
Mongos rozdziela obciążenie między fragmenty w oparciu o klucz fragmentu, a równomierną dystrybucją danych zajmuje się Balancer.
Kluczowe elementy dystrybucji danych między shardami:
- Balancer odpowiada za równomierne rozłożenie podzbiorów danych między węzłami fragmentów. Uruchamia się, gdy serwer Mongos zaczyna przekazywać obciążenia między shardami, i stara się zapewnić jak najbardziej równomierny rozkład. Aby sprawdzić stan balansera, można użyć polecenia sh.status(), sh.getBalancerState() lub
sh.isBalancerRunning()
.
mongos> sh.isBalancerRunning() true mongos>
LUB
mongos> sh.getBalancerState() true mongos>
Po wstawieniu danych można zaobserwować aktywność w logach demona Mongos, który informuje o przenoszeniu porcji danych między shardami. Balancer próbuje równoważyć dane, a jego działanie może wpływać na wydajność. Dlatego zaleca się uruchamianie balansera w określonym oknie balansowania.
mongos> sh.status() --- Sharding Status --- sharding version: { "_id" : 1, "minCompatibleVersion" : 5, "currentVersion" : 6, "clusterId" : ObjectId("5efbeff98a8bbb2d27231674") } shards: { "_id" : "rs1", "host" : "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022", "state" : 1 } { "_id" : "rs2", "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025", "state" : 1 } active mongoses: "4.2.7" : 1 autosplit: Currently enabled: yes balancer: Currently enabled: yes Currently running: yes Failed balancer rounds in last 5 attempts: 5 Last reported error: Could not find host matching read preference { mode: "primary" } for set rs2 Time of Reported error: Wed Jul 01 2020 14:39:59 GMT+0530 (India Standard Time) Migration Results for the last 24 hours: 1024 : Success databases: { "_id" : "config", "primary" : "config", "partitioned" : true } config.system.sessions shard key: { "_id" : 1 } unique: false balancing: true chunks: rs2 1024 too many chunks to print, use verbose if you want to force print { "_id" : "geekFlareDB", "primary" : "rs2", "partitioned" : true, "version" : { "uuid" : UUID("a8b8dc5c-85b0-4481-bda1-00e53f6f35cd"), "lastMod" : 1 } } geekFlareDB.geekFlareCollection shard key: { "key" : 1 } unique: false balancing: true chunks: rs2 1 { "key" : { "$minKey" : 1 } } -->> { "key" : { "$maxKey" : 1 } } on : rs2 Timestamp(1, 0) { "_id" : "test", "primary" : "rs2", "partitioned" : false, "version" : { "uuid" : UUID("a28d7504-1596-460e-9e09-0bdc6450028f"), "lastMod" : 1 } } mongos>
- Klucz fragmentu definiuje logikę dystrybucji dokumentów pomiędzy shardami. Może to być pole indeksowane lub indeksowane pole złożone, które musi występować we wszystkich dokumentach w danej kolekcji. Dane są dzielone na porcje, a każda z nich jest przypisywana do klucza fragmentu w oparciu o zakres. Na podstawie zakresu zapytania router wybiera shard, który będzie przechowywał daną porcję danych.
Wybierając klucz fragmentu, należy wziąć pod uwagę następujące pięć właściwości:
- Kardynalność
- Dystrybucja zapisu
- Dystrybucja odczytu
- Kierowanie odczytu
- Lokalność odczytu
Idealny klucz fragmentu sprawia, że MongoDB równomiernie rozkłada obciążenie pomiędzy wszystkie fragmenty. Dlatego właściwy wybór klucza jest bardzo istotny.
Obraz: MongoDB
Usuwanie węzła fragmentu
Przed usunięciem fragmentu z klastra należy upewnić się, że dane zostały bezpiecznie przeniesione do pozostałych fragmentów. MongoDB automatycznie przesyła dane do innych węzłów przed usunięciem wybranego węzła fragmentu.
Aby usunąć fragment, wykonaj poniższe kroki:
Krok 1
Najpierw należy zidentyfikować nazwę hosta fragmentu, który ma zostać usunięty. Poniższe polecenie wyświetli listę wszystkich fragmentów obecnych w klastrze, wraz z ich stanem.
db.adminCommand( { listShards: 1 } )
mongos> db.adminCommand( { listShards: 1 } ) { "shards" : [ { "_id" : "rs1", "host" : "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022", "state" : 1 }, { "_id" : "rs2", "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025", "state" : 1 } ], "ok" : 1, "operationTime" : Timestamp(1593572866, 15), "$clusterTime" : { "clusterTime" : Timestamp(1593572866, 15), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
Krok 2
Wydaj poniższe polecenie, aby usunąć wybrany fragment z klastra. Po jego wykonaniu Balancer zajmie się usunięciem porcji danych z opróżnianego węzła fragmentu i zrównoważy ich dystrybucję pomiędzy pozostałymi węzłami.
db.adminCommand( { removeShard: “shardedReplicaNodes” } )
mongos> db.adminCommand( { removeShard: "rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022" } ) { "msg" : "draining started successfully", "state" : "started", "shard" : "rs1", "note" : "you need to drop or movePrimary these databases", "dbsToMove" : [ ], "ok" : 1, "operationTime" : Timestamp(1593572385, 2), "$clusterTime" : { "clusterTime" : Timestamp(1593572385, 2), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
Krok 3
Aby sprawdzić stan opróżnianego fragmentu, wprowadź ponownie to samo polecenie.
db.adminCommand( { removeShard: “rs1/127.0.0.1:27020,127.0.0.1:27021,127.0.0.1:27022” } )
Należy poczekać, aż transfer danych zostanie zakończony. Pola „msg” i „state” informują o postępie procesu.
"msg" : "draining ongoing", "state" : "ongoing",
Możemy również sprawdzić status za pomocą polecenia sh.status(). Po usunięciu węzła fragmentu nie będzie on widoczny w wynikach. Jeśli proces opróżniania trwa, węzeł będzie oznaczony statusem „draining” jako true.
Krok 4
Kontynuuj sprawdzanie stanu opróżniania, aż do całkowitego usunięcia fragmentu. Po zakończeniu procesu dane wyjściowe polecenia powinny odzwierciedlać komunikat i stan jako zakończone.
"msg" : "removeshard completed successfully", "state" : "completed", "shard" : "rs1", "ok" : 1,
Krok 5
Na koniec należy sprawdzić, które fragmenty pozostały w klastrze. Aby sprawdzić stan, wpisz sh.status() lub db.adminCommand( { listShards: 1 } )
mongos> db.adminCommand( { listShards: 1 } ) { "shards" : [ { "_id" : "rs2", "host" : "rs2/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025", "state" : 1 } ], "ok" : 1, "operationTime" : Timestamp(1593575215, 3), "$clusterTime" : { "clusterTime" : Timestamp(1593575215, 3), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
Widać, że usunięty fragment nie jest już wyświetlany na liście.
Korzyści fragmentacji nad replikacją
- W przypadku replikacji węzeł podstawowy obsługuje wszystkie operacje zapisu, a serwery pomocnicze są przeznaczone do tworzenia kopii zapasowych lub obsługi zapytań tylko do odczytu. W shardingu obciążenie jest rozkładane na wiele serwerów.
- Pojedynczy zestaw replik ma ograniczenie do 12 węzłów, natomiast nie ma limitu liczby fragmentów.
- Replikacja wymaga zaawansowanego sprzętu do obsługi dużych zbiorów danych, a skalowanie pionowe jest kosztowne w porównaniu z dodawaniem serwerów w shardingu.
- W replikacji wydajność odczytu można zwiększyć, dodając serwery podrzędne, natomiast sharding pozwala zwiększyć zarówno wydajność odczytu, jak i zapisu przez dodawanie większej liczby węzłów fragmentów.
Ograniczenia shardingu
- Klaster podzielony na fragmenty nie obsługuje unikatowych indeksów w fragmentach, jeśli nie są one poprzedzone kluczem fragmentu.
- Wszystkie operacje aktualizacji dla kolekcji podzielonej na fragmenty muszą zawierać klucz fragmentu lub pole _id w zapytaniu.
- Kolekcje mogą być fragmentowane tylko wtedy, gdy ich rozmiar nie przekracza określonego progu. Próg ten zależy od średniego rozmiaru klucza fragmentu i skonfigurowanego rozmiaru fragmentu.
- Sharding ma limity operacyjne dotyczące maksymalnego rozmiaru kolekcji lub liczby podziałów.
- Nieprawidłowy wybór kluczy fragmentów może mieć negatywny wpływ na wydajność.
Podsumowanie
MongoDB oferuje wbudowany mechanizm shardingu do implementacji dużych baz danych bez obniżania wydajności. Mam nadzieję, że powyższe informacje pomogą w skonfigurowaniu shardingu w MongoDB. Warto również zapoznać się z popularnymi poleceniami MongoDB.
newsblog.pl
Maciej – redaktor, pasjonat technologii i samozwańczy pogromca błędów w systemie Windows. Zna Linuxa lepiej niż własną lodówkę, a kawa to jego główne źródło zasilania. Pisze, testuje, naprawia – i czasem nawet wyłącza i włącza ponownie. W wolnych chwilach udaje, że odpoczywa, ale i tak kończy z laptopem na kolanach.