Znamy początkowy rozmiar danych (120 GB) i wiemy, że domyślny maksymalny rozmiar porcji w MongoDB to 64 MB. Jeśli podzielimy 64 MB na 120 GB, otrzymamy 1920 - jest to więc minimalna liczba fragmentów, od których powinniśmy zacząć. Tak się składa, że 2048 jest potęgą 16 podzieloną przez 2, a biorąc pod uwagę, że GUID (nasz klucz odłamkowy) jest oparty na szesnastce, jest to znacznie łatwiejsza liczba niż 1920 (patrz poniżej).
UWAGA: Ten wstępny podział należy wykonać przed wszelkie dane są dodawane do kolekcji. Jeśli użyjesz polecenia enableSharding() w kolekcji zawierającej dane, MongoDB podzieli same dane, a następnie uruchomisz to, gdy fragmenty już istnieją - może to prowadzić do dość dziwnej dystrybucji fragmentów, więc uważaj.
Na potrzeby tej odpowiedzi załóżmy, że baza danych będzie nazywać się users
a kolekcja nazywa się userInfo
. Załóżmy również, że identyfikator GUID zostanie zapisany w _id
pole. Z tymi parametrami połączylibyśmy się z mongos
i uruchom następujące polecenia:
// first switch to the users DB
use users;
// now enable sharding for the users DB
sh.enableSharding("users");
// enable sharding on the relevant collection
sh.shardCollection("users.userInfo", {"_id" : 1});
// finally, disable the balancer (see below for options on a per-collection basis)
// this prevents migrations from kicking off and interfering with the splits by competing for meta data locks
sh.stopBalancer();
Teraz, zgodnie z powyższymi obliczeniami, musimy podzielić zakres GUID na 2048 części. Aby to zrobić, potrzebujemy co najmniej 3 cyfr szesnastkowych (16 ^ 3 =4096) i umieścimy je w najbardziej znaczących cyfrach (tj. 3 skrajnie lewej) dla zakresów. Ponownie, powinno to być uruchamiane z mongos
powłoka
// Simply use a for loop for each digit
for ( var x=0; x < 16; x++ ){
for( var y=0; y<16; y++ ) {
// for the innermost loop we will increment by 2 to get 2048 total iterations
// make this z++ for 4096 - that would give ~30MB chunks based on the original figures
for ( var z=0; z<16; z+=2 ) {
// now construct the GUID with zeroes for padding - handily the toString method takes an argument to specify the base
var prefix = "" + x.toString(16) + y.toString(16) + z.toString(16) + "00000000000000000000000000000";
// finally, use the split command to create the appropriate chunk
db.adminCommand( { split : "users.userInfo" , middle : { _id : prefix } } );
}
}
}
Gdy to zrobisz, sprawdźmy stan gry za pomocą sh.status()
pomocnik:
mongos> sh.status()
--- Sharding Status ---
sharding version: {
"_id" : 1,
"version" : 3,
"minCompatibleVersion" : 3,
"currentVersion" : 4,
"clusterId" : ObjectId("527056b8f6985e1bcce4c4cb")
}
shards:
{ "_id" : "shard0000", "host" : "localhost:30000" }
{ "_id" : "shard0001", "host" : "localhost:30001" }
{ "_id" : "shard0002", "host" : "localhost:30002" }
{ "_id" : "shard0003", "host" : "localhost:30003" }
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "users", "partitioned" : true, "primary" : "shard0001" }
users.userInfo
shard key: { "_id" : 1 }
chunks:
shard0001 2049
too many chunks to print, use verbose if you want to force print
Mamy nasze 2048 porcji (plus jeden dodatkowy dzięki porcji min/maks), ale wszystkie są nadal w oryginalnym fragmencie, ponieważ balanser jest wyłączony. Więc włączmy ponownie balanser:
sh.startBalancer();
To natychmiast zacznie się równoważyć i będzie stosunkowo szybkie, ponieważ wszystkie porcje są puste, ale nadal zajmie to trochę czasu (znacznie wolniej, jeśli konkuruje z migracjami z innych kolekcji). Po upływie pewnego czasu uruchom sh.status()
znowu i tam (powinieneś) to mieć - 2048 porcji, wszystkie ładnie podzielone na 4 fragmenty i gotowe do wstępnego ładowania danych:
mongos> sh.status()
--- Sharding Status ---
sharding version: {
"_id" : 1,
"version" : 3,
"minCompatibleVersion" : 3,
"currentVersion" : 4,
"clusterId" : ObjectId("527056b8f6985e1bcce4c4cb")
}
shards:
{ "_id" : "shard0000", "host" : "localhost:30000" }
{ "_id" : "shard0001", "host" : "localhost:30001" }
{ "_id" : "shard0002", "host" : "localhost:30002" }
{ "_id" : "shard0003", "host" : "localhost:30003" }
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "users", "partitioned" : true, "primary" : "shard0001" }
users.userInfo
shard key: { "_id" : 1 }
chunks:
shard0000 512
shard0002 512
shard0003 512
shard0001 513
too many chunks to print, use verbose if you want to force print
{ "_id" : "test", "partitioned" : false, "primary" : "shard0002" }
Jesteś teraz gotowy do rozpoczęcia ładowania danych, ale aby absolutnie zagwarantować, że do momentu zakończenia ładowania danych nie nastąpią żadne podziały ani migracje, musisz zrobić jeszcze jedną rzecz - wyłączyć funkcję równoważenia i autodzielenia na czas importu:
- Aby wyłączyć całe równoważenie, uruchom to polecenie z mongos:
sh.stopBalancer()
- Jeśli chcesz pozostawić uruchomione inne operacje równoważenia, możesz wyłączyć konkretną kolekcję. Używając powyższej przestrzeni nazw jako przykładu:
sh.disableBalancing("users.userInfo")
- Aby wyłączyć automatyczne dzielenie podczas ładowania, musisz ponownie uruchomić każdy
mongos
będziesz używać do ładowania danych za pomocą--noAutoSplit
opcja.
Po zakończeniu importowania odwróć kroki zgodnie z potrzebami (sh.startBalancer()
, sh.enableBalancing("users.userInfo")
i uruchom ponownie mongos
bez --noAutoSplit
), aby przywrócić wszystko do ustawień domyślnych.
**
Aktualizacja:optymalizacja pod kątem szybkości
**
Powyższe podejście jest w porządku, jeśli się nie spieszysz. W obecnym stanie rzeczy i jak odkryjesz, jeśli to przetestujesz, balanser nie jest zbyt szybki – nawet przy pustych porcjach. W związku z tym, gdy zwiększasz liczbę tworzonych kawałków, tym dłużej trwa zrównoważenie. Zauważyłem, że zakończenie balansowania 2048 porcji zajmuje więcej niż 30 minut, chociaż będzie to się różnić w zależności od wdrożenia.
To może być w porządku do testowania lub dla stosunkowo cichego klastra, ale wyłączenie balansera i niewymaganie innych aktualizacji będzie znacznie trudniejsze do zapewnienia w zajętym klastrze. Jak więc możemy to przyspieszyć?
Odpowiedzią jest wcześniejsze wykonanie kilku ruchów ręcznych, a następnie rozdzielenie porcji, gdy znajdą się na odpowiednich odłamkach. Zwróć uwagę, że jest to pożądane tylko w przypadku niektórych kluczy fragmentów (takich jak losowo rozproszony UUID) lub pewnych wzorców dostępu do danych, więc uważaj, aby nie doprowadzić do słabej dystrybucji danych.
Korzystając z powyższego przykładu, mamy 4 odłamki, więc zamiast robić wszystkie podziały, a następnie równoważyć, dzielimy się na 4 zamiast tego. Następnie umieszczamy po jednym kawałku na każdym odłamku, ręcznie je przesuwając, a następnie dzielimy te kawałki na wymaganą liczbę.
Zakresy w powyższym przykładzie wyglądałyby tak:
$min --> "40000000000000000000000000000000"
"40000000000000000000000000000000" --> "80000000000000000000000000000000"
"80000000000000000000000000000000" --> "c0000000000000000000000000000000"
"c0000000000000000000000000000000" --> $max
Do ich utworzenia są tylko 4 polecenia, ale skoro już je mamy, dlaczego nie użyć powyższej pętli w uproszczonej/zmodyfikowanej formie:
for ( var x=4; x < 16; x+=4){
var prefix = "" + x.toString(16) + "0000000000000000000000000000000";
db.adminCommand( { split : "users.userInfo" , middle : { _id : prefix } } );
}
Oto jak teraz wyglądają myśli - mamy nasze 4 kawałki, wszystkie na shard0001:
mongos> sh.status()
--- Sharding Status ---
sharding version: {
"_id" : 1,
"version" : 4,
"minCompatibleVersion" : 4,
"currentVersion" : 5,
"clusterId" : ObjectId("53467e59aea36af7b82a75c1")
}
shards:
{ "_id" : "shard0000", "host" : "localhost:30000" }
{ "_id" : "shard0001", "host" : "localhost:30001" }
{ "_id" : "shard0002", "host" : "localhost:30002" }
{ "_id" : "shard0003", "host" : "localhost:30003" }
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : false, "primary" : "shard0001" }
{ "_id" : "users", "partitioned" : true, "primary" : "shard0001" }
users.userInfo
shard key: { "_id" : 1 }
chunks:
shard0001 4
{ "_id" : { "$minKey" : 1 } } -->> { "_id" : "40000000000000000000000000000000" } on : shard0001 Timestamp(1, 1)
{ "_id" : "40000000000000000000000000000000" } -->> { "_id" : "80000000000000000000000000000000" } on : shard0001 Timestamp(1, 3)
{ "_id" : "80000000000000000000000000000000" } -->> { "_id" : "c0000000000000000000000000000000" } on : shard0001 Timestamp(1, 5)
{ "_id" : "c0000000000000000000000000000000" } -->> { "_id" : { "$maxKey" : 1 } } on : shard0001 Timestamp(1, 6)
Zostawimy $min
kawałek tam, gdzie jest i przesuń pozostałe trzy. Możesz to zrobić programowo, ale zależy to od tego, gdzie początkowo znajdują się fragmenty, jak nazwałeś swoje fragmenty itp., więc na razie zostawię ten podręcznik, nie jest to zbyt uciążliwe - po prostu 3 moveChunk
polecenia:
mongos> sh.moveChunk("users.userInfo", {"_id" : "40000000000000000000000000000000"}, "shard0000")
{ "millis" : 1091, "ok" : 1 }
mongos> sh.moveChunk("users.userInfo", {"_id" : "80000000000000000000000000000000"}, "shard0002")
{ "millis" : 1078, "ok" : 1 }
mongos> sh.moveChunk("users.userInfo", {"_id" : "c0000000000000000000000000000000"}, "shard0003")
{ "millis" : 1083, "ok" : 1 }
Sprawdźmy dokładnie i upewnijmy się, że porcje znajdują się tam, gdzie oczekujemy:
mongos> sh.status()
--- Sharding Status ---
sharding version: {
"_id" : 1,
"version" : 4,
"minCompatibleVersion" : 4,
"currentVersion" : 5,
"clusterId" : ObjectId("53467e59aea36af7b82a75c1")
}
shards:
{ "_id" : "shard0000", "host" : "localhost:30000" }
{ "_id" : "shard0001", "host" : "localhost:30001" }
{ "_id" : "shard0002", "host" : "localhost:30002" }
{ "_id" : "shard0003", "host" : "localhost:30003" }
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : false, "primary" : "shard0001" }
{ "_id" : "users", "partitioned" : true, "primary" : "shard0001" }
users.userInfo
shard key: { "_id" : 1 }
chunks:
shard0001 1
shard0000 1
shard0002 1
shard0003 1
{ "_id" : { "$minKey" : 1 } } -->> { "_id" : "40000000000000000000000000000000" } on : shard0001 Timestamp(4, 1)
{ "_id" : "40000000000000000000000000000000" } -->> { "_id" : "80000000000000000000000000000000" } on : shard0000 Timestamp(2, 0)
{ "_id" : "80000000000000000000000000000000" } -->> { "_id" : "c0000000000000000000000000000000" } on : shard0002 Timestamp(3, 0)
{ "_id" : "c0000000000000000000000000000000" } -->> { "_id" : { "$maxKey" : 1 } } on : shard0003 Timestamp(4, 0)
To pasuje do naszych proponowanych powyżej zakresów, więc wszystko wygląda dobrze. Teraz uruchom oryginalną pętlę powyżej, aby podzielić je „na miejscu” na każdym fragmencie i powinniśmy mieć zrównoważoną dystrybucję, gdy tylko pętla się zakończy. Jeszcze jedna sh.status()
powinien potwierdzić rzeczy:
mongos> for ( var x=0; x < 16; x++ ){
... for( var y=0; y<16; y++ ) {
... // for the innermost loop we will increment by 2 to get 2048 total iterations
... // make this z++ for 4096 - that would give ~30MB chunks based on the original figures
... for ( var z=0; z<16; z+=2 ) {
... // now construct the GUID with zeroes for padding - handily the toString method takes an argument to specify the base
... var prefix = "" + x.toString(16) + y.toString(16) + z.toString(16) + "00000000000000000000000000000";
... // finally, use the split command to create the appropriate chunk
... db.adminCommand( { split : "users.userInfo" , middle : { _id : prefix } } );
... }
... }
... }
{ "ok" : 1 }
mongos> sh.status()
--- Sharding Status ---
sharding version: {
"_id" : 1,
"version" : 4,
"minCompatibleVersion" : 4,
"currentVersion" : 5,
"clusterId" : ObjectId("53467e59aea36af7b82a75c1")
}
shards:
{ "_id" : "shard0000", "host" : "localhost:30000" }
{ "_id" : "shard0001", "host" : "localhost:30001" }
{ "_id" : "shard0002", "host" : "localhost:30002" }
{ "_id" : "shard0003", "host" : "localhost:30003" }
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : false, "primary" : "shard0001" }
{ "_id" : "users", "partitioned" : true, "primary" : "shard0001" }
users.userInfo
shard key: { "_id" : 1 }
chunks:
shard0001 513
shard0000 512
shard0002 512
shard0003 512
too many chunks to print, use verbose if you want to force print
I masz to - nie czekaj na wyważenie, rozkład jest już równy.