1.1 Co to jest sharding?

Jeśli uporczywie googlujesz, okazuje się, że istnieje dość niewyraźna granica między tzw. partycjonowaniem a tak zwanym shardingiem. Każdy dzwoni jak chce, jak chce. Niektórzy rozróżniają partycjonowanie poziome i sharding. Inni twierdzą, że sharding to pewien rodzaj partycjonowania poziomego.

Nie znalazłem ani jednej normy terminologicznej, która byłaby zatwierdzona przez ojców założycieli i certyfikowana przez ISO. Osobiste wewnętrzne przekonanie jest mniej więcej takie: podział to przeciętnie „cięcie podstawy na kawałki” w arbitralnie przyjęty sposób.

  • Partycjonowanie pionowe — według kolumn. Na przykład istnieje gigantyczna tabela z kilkoma miliardami rekordów w 60 kolumnach. Zamiast utrzymywać jedną taką gigantyczną tabelę, przechowujemy 60 co najmniej gigantycznych tabel po 2 miliardy rekordów każda - i to nie jest podstawa kolumny, ale podział pionowy (jako przykład terminologii).
  • Partycjonowanie poziome - wycinamy linia po linii, może wewnątrz serwera.

Niezręcznym momentem jest tutaj subtelna różnica między partycjonowaniem poziomym a dzieleniem na fragmenty. Mogę zostać pocięty na kawałki, ale nie mogę powiedzieć z całą pewnością, co to jest. Istnieje wrażenie, że sharding i partycjonowanie poziome to mniej więcej to samo.

Sharding jest generalnie wtedy, gdy duża tabela pod względem baz danych lub pro-kolekcji dokumentów, obiektów, jeśli nie masz bazy danych, ale magazyn dokumentów, jest wycinana dokładnie przez obiekty. Oznacza to, że z 2 miliardów obiektów wybierane są elementy bez względu na rozmiar. Same obiekty wewnątrz każdego obiektu nie są cięte na kawałki, nie układamy ich w osobne kolumny, a mianowicie układamy je partiami w różnych miejscach.

Istnieją subtelne różnice terminologiczne. Na przykład, relatywnie rzecz biorąc, programiści Postgres mogą powiedzieć, że partycjonowanie poziome ma miejsce, gdy wszystkie tabele, na które podzielona jest główna tabela, znajdują się w tym samym schemacie, a gdy na różnych komputerach jest to już sharding.

W ogólnym sensie, bez wiązania się z terminologią konkretnej bazy danych i konkretnego systemu zarządzania danymi, można odnieść wrażenie, że sharding to po prostu cięcie linia po linii / dokument po dokumencie i tak dalej - to wszystko.

Podkreślam typowe. W tym sensie, że robimy to wszystko nie tylko po to, aby podzielić 2 miliardy dokumentów na 20 tabel, z których każda byłaby łatwiejsza w zarządzaniu, ale po to, aby rozmieścić je na wielu rdzeniach, wielu dyskach lub wielu różnych serwerach fizycznych lub wirtualnych.

1.2 Podziel niepodzielne

Rozumie się, że robimy to po to, aby każdy shard – każdy fragment danych – był wielokrotnie replikowany. Ale naprawdę nie.

INSERT INTO docs00 
SELECT * FROM documents WHERE (id%16)=0 
... 
 
INSERT INTO docs15 
SELECT * FROM documents WHERE (id%16)=15 

W rzeczywistości, jeśli wykonasz takie cięcie danych iz jednej gigantycznej tabeli SQL w MySQL na swoim dzielnym laptopie, wygenerujesz 16 małych tabel, nie wychodząc poza jeden laptop, ani jeden schemat, ani jedną bazę danych itp. . i tak dalej. - to wszystko, masz już sharding.

Powoduje to, co następuje:

  • Zwiększa się przepustowość.
  • Opóźnienie się nie zmienia, to znaczy każdy, że tak powiem, pracownik lub konsument w tym przypadku dostaje swoje. Różne żądania są obsługiwane mniej więcej w tym samym czasie.
  • Lub jedno i drugie, a także wysoka dostępność (replikacja).

Dlaczego przepustowość? Czasami możemy mieć takie ilości danych, które się nie mieszczą – nie wiadomo gdzie, ale się nie mieszczą – na 1 {kernel | dysk | serwer | ...}. Po prostu nie ma wystarczających środków, to wszystko. Aby pracować z tym dużym zbiorem danych, musisz go wyciąć.

Dlaczego opóźnienie? Na jednym rdzeniu skanowanie tabeli zawierającej 2 miliardy wierszy jest 20 razy wolniejsze niż skanowanie 20 tabel na 20 rdzeniach, wykonywane równolegle. Dane są przetwarzane zbyt wolno w jednym zasobie.

Dlaczego wysoka dostępność? Lub wycinamy dane tak, aby zrobić jedno i drugie jednocześnie, a jednocześnie kilka kopii każdego sharda - replikacja zapewnia wysoką dostępność.

1.3 Prosty przykład „jak to zrobić ręcznie”

Warunkowe sharding można wyciąć za pomocą tabeli testowej test.documents dla 32 dokumentów i wygenerowania 16 tabel testowych z tej tabeli, po około 2 dokumenty każdy test.docs00, 01, 02, ..., 15.

INSERT INTO docs00 
SELECT * FROM documents WHERE (id%16)=0 
... 
 
INSERT INTO docs15 
SELECT * FROM documents WHERE (id%16)=15 

dlaczego o? Ponieważ a priori nie wiemy jak rozkładają się id, jeśli od 1 do 32 włącznie, to będą dokładnie po 2 dokumenty każdy, inaczej nie.

Robimy to tutaj, dlaczego. Po zrobieniu 16 stolików możemy „zgarnąć” 16 z tego, czego potrzebujemy. Niezależnie od tego, co trafimy, możemy zrównoleglić te zasoby. Na przykład, jeśli nie ma wystarczającej ilości miejsca na dysku, sensowne byłoby rozłożenie tych tabel na osobne dyski.

To wszystko niestety nie jest darmowe. Podejrzewam, że w przypadku kanonicznego standardu SQL (długo nie czytałem standardu SQL, być może nie był on aktualizowany przez długi czas) nie ma oficjalnej znormalizowanej składni do mówienia do dowolnego serwera SQL : „Drogi serwerze SQL, zrób mi 32 odłamki i podziel je na 4 dyski. Ale w poszczególnych implementacjach często istnieje specyficzna składnia do robienia zasadniczo tego samego. PostgreSQL ma mechanizmy partycjonowania, MySQL ma MariaDB, Oracle prawdopodobnie zrobił to wszystko dawno temu.

Niemniej jeśli robimy to ręcznie, bez wsparcia bazodanowego i w ramach standardu, to warunkowo płacimy złożonością dostępu do danych . Gdzie było proste SELECT * FROM dokumentów WHERE id=123, teraz 16 x SELECT * FROM docsXX. I dobrze, gdybyśmy spróbowali uzyskać nagranie po kluczu. O wiele bardziej interesujące, gdybyśmy próbowali uzyskać wczesny zakres rekordów. Teraz (jeśli my, podkreślam, jesteśmy niejako głupcami i pozostaniemy w ramach standardu), wyniki tych 16 SELECT * FROM trzeba będzie połączyć w aplikacji.

Jakiej zmiany wydajności można się spodziewać?

  • Intuicyjnie – liniowo.
  • Teoretycznie – podliniowe, bo prawo Amdahla.
  • Praktycznie, może prawie liniowo, może nie.

W rzeczywistości poprawna odpowiedź jest nieznana. Dzięki sprytnemu zastosowaniu techniki shardingu możesz osiągnąć znaczną superliniową degradację wydajności aplikacji, a nawet administrator bazy danych zacznie działać z rozpalonym do czerwoności pogrzebaczem.

Zobaczmy, jak można to osiągnąć. Oczywiste jest, że samo ustawienie PostgreSQL shards=16, a następnie samo startuje, nie jest interesujące. Zastanówmy się, jak możemy się upewnić, że zwolnimy od shardingu 16 razy o 32 - jest to interesujące z punktu widzenia tego, jak tego nie robić.

Nasze próby przyspieszenia lub spowolnienia zawsze trafią na klasykę – stare dobre prawo Amdahla, które mówi, że nie ma idealnej paralelizacji żadnego żądania, zawsze jest jakaś spójna część.

1.4 Prawo Amdahla

Zawsze jest część zserializowana.

Zawsze istnieje część wykonywania zapytania, która jest zrównoleglona i zawsze istnieje część, która nie jest zrównoleglona. Nawet jeśli wydaje ci się, że idealnie równoległe zapytanie, przynajmniej zbiór wiersza wyniku, który zamierzasz wysłać do klienta z wierszy otrzymanych z każdego sharda, zawsze istnieje i zawsze jest sekwencyjny.

Zawsze jest jakaś spójna część. Może być malutki, zupełnie niewidoczny na tle ogólnym, może być gigantyczny i odpowiednio silnie wpływać na zrównoleglenie, ale zawsze istnieje.

Dodatkowo jego wpływ się zmienia i może znacznie wzrosnąć, np. jeśli zmniejszymy naszą tabelę – podnieśmy stawkę – z 64 rekordów do 16 tabel po 4 rekordy, ta część ulegnie zmianie. Oczywiście, sądząc po tak gigantycznych ilościach danych, pracujemy na telefonie komórkowym i procesorze 2 MHz 86, a nie mamy wystarczającej liczby plików, które można otworzyć w tym samym czasie. Najwyraźniej przy takich wejściach otwieramy jeden plik na raz.

  • To było Total = Serial + Parallel . Gdzie, na przykład, równoległa to cała praca wewnątrz DB, a szeregowy wysyła wynik do klienta.
  • Stało się Total2 = Serial + Parallel/N + Xserial . Na przykład, gdy ogólny ORDER BY, Xserial>0.

Za pomocą tego prostego przykładu próbuję pokazać, że pojawia się jakiś Xserial. Oprócz faktu, że zawsze istnieje część serializowana i fakt, że próbujemy pracować z danymi równolegle, istnieje dodatkowa część zapewniająca cięcie danych. Z grubsza możemy potrzebować:

  • znajdź te 16 tabel w wewnętrznym słowniku bazy danych;
  • Otwórz pliki;
  • przydzielać pamięć;
  • anulować przydział pamięci;
  • scalić wyniki;
  • synchronizować między rdzeniami.

Nadal pojawiają się niektóre niezsynchronizowane efekty. Mogą być nieistotne i zajmować jedną miliardową całkowitego czasu, ale zawsze są niezerowe i zawsze obecne. Z ich pomocą możemy drastycznie stracić wydajność po shardingu.

To jest standardowy obraz prawa Amdahla. Ważne jest tutaj, aby linie, które idealnie powinny być proste i rosnąć liniowo, biegły w asymptocie. Ponieważ jednak wykres z internetu jest nieczytelny, zrobiłem moim zdaniem bardziej wizualne tabele z liczbami.

Załóżmy, że mamy pewną serializowaną część przetwarzania żądania, która zajmuje tylko 5%: serial = 0.05 = 1 / 20 .

Intuicyjnie wydaje się, że przy części serializowanej, która zajmuje tylko 1/20 przetwarzania żądania, jeśli zrównoleglimy przetwarzanie żądania dla 20 rdzeni, stanie się ono około 20, aw najgorszym przypadku 18 razy szybsze.

W rzeczywistości matematyka jest bezduszną rzeczą :

ściana = 0,05 + 0,95/liczba_rdzeni, przyspieszenie = 1 / (0,05 + 0,95/liczba_rdzeni)

Okazuje się, że jeśli dokładnie obliczyć, z serializowaną częścią 5%, przyspieszenie wyniesie 10 razy (10,3), czyli 51% w porównaniu z teoretycznym ideałem.

8 rdzeni = 5,9 = 74%
10 rdzeni = 6,9 = 69%
20 rdzeni = 10,3 = 51%
40 rdzeni = 13,6 = 34%
128 rdzeni = 17,4 = 14%

Używając 20 rdzeni (jak kto woli 20 dysków) do zadania, nad którym pracował jeden, nigdy nawet teoretycznie nie uzyskamy przyspieszenia ponad 20-krotnego, ale w praktyce znacznie mniejszego. Co więcej, wraz ze wzrostem liczby podobieństw nieefektywność znacznie wzrasta.

Kiedy pozostaje tylko 1% serializowanej pracy, a 99% jest zrównoleglone, wartości przyspieszenia nieco się poprawiają:

8 rdzeni = 7,5 = 93%
16 rdzeni = 13,9 = 87%
32 rdzenie = 24,4 = 76%
64 rdzenie = 39,3 = 61%

Dla idealnie termojądrowego zapytania, którego wykonanie naturalnie zajmuje godziny, a prace przygotowawcze i złożenie wyniku zajmują bardzo mało czasu (serial = 0,001), zobaczymy już dobrą wydajność:

8 rdzeni = 7,94 = 99%
16 rdzeni = 15,76 = 99%
32 rdzenie = 31,04 = 97%
64 rdzenie = 60,20 = 94%

Pamiętaj, że nigdy nie zobaczymy 100% . W szczególnie dobrych przypadkach widać na przykład 99,999%, ale nie dokładnie 100%.