Radzenie sobie z dużymi transakcjami zawsze było bolesnym punktem w Galera Cluster. Sposób, w jaki działa certyfikacja zbioru zapisu Galera, powoduje problemy, gdy transakcje są długie lub gdy jeden wiersz jest często modyfikowany na wielu węzłach. W rezultacie transakcje muszą być wycofywane i ponawiane, co powoduje spadki wydajności. Na szczęście ten problem został rozwiązany w Galera 4, nowej wersji Galery od Codership. Ta biblioteka jest używana w MariaDB 10.4, więc zainstalowanie MariaDB 10.4 to najprostszy sposób testowania nowo wprowadzonych funkcji. W tym poście na blogu przyjrzymy się, jak można wykorzystać replikację strumieniową do łagodzenia problemów, które były standardowym problemem w poprzednich wersjach Galera.
Użyjemy trzech węzłów klastra MariaDB Galera w wersji 10.4.6, który jest dostarczany z Galera w wersji 26.4.2.
MariaDB [(none)]> show global status like 'wsrep_provider%';
+-----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+
| Variable_name | Value |
+-----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+
| wsrep_provider_capabilities | :MULTI_MASTER:CERTIFICATION:PARALLEL_APPLYING:TRX_REPLAY:ISOLATION:PAUSE:CAUSAL_READS:INCREMENTAL_WRITESET:UNORDERED:PREORDERED:STREAMING:NBO: |
| wsrep_provider_name | Galera |
| wsrep_provider_vendor | Codership Oy <[email protected]> |
| wsrep_provider_version | 26.4.2(r4498) |
+-----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------+
4 rows in set (0.001 sec)
Istnieją trzy główne problemy, z którymi ma się zajmować replikacja strumieniowa:
- Długie transakcje
- Duże transakcje
- Gorące miejsca w tabelach
Rozważmy je jeden po drugim i zobaczmy, jak replikacja strumieniowa może nam pomóc w radzeniu sobie z nimi, ale najpierw skupmy się na certyfikacji zbioru zapisu – głównej przyczynie pojawiania się tych problemów.
Certyfikacja zestawu zapisu w klastrze Galera
Klaster Galera składa się z wielu zapisywalnych węzłów. Każda transakcja wykonywana na klastrze Galera tworzy zapis. Każdy zapis musi zostać wysłany do wszystkich węzłów w klastrze w celu certyfikacji - proces ten zapewnia, że wszystkie węzły mogą zastosować daną transakcję. Zestawy zapisu muszą być wykonywane na wszystkich węzłach klastra, więc w przypadku jakiegokolwiek konfliktu transakcja nie może zostać zatwierdzona. Jakie są typowe powody, dla których transakcja nie może zostać popełniona? Cóż, trzy punkty, które wymieniliśmy wcześniej:
- Długie transakcje - dłużej trwa transakcja, tym bardziej prawdopodobne jest, że w międzyczasie inny węzeł wykona aktualizacje, które ostatecznie wejdą w konflikt z zestawem zapisów i uniemożliwią przejście certyfikacji
- Duże transakcje - przede wszystkim duże transakcje są również dłuższe niż małe, co powoduje pierwszy problem. Drugim problemem, ściśle związanym z dużymi transakcjami, jest wielkość zmian. Więcej wierszy zostanie zaktualizowanych, bardziej prawdopodobne jest, że niektóre zapisy w innym węźle spowodują konflikt i cała transakcja będzie musiała zostać wycofana.
- Hot spoty w tabelach - bardziej prawdopodobne jest, że dany wiersz zostanie zaktualizowany, bardziej prawdopodobne, że taka aktualizacja nastąpi jednocześnie na wielu węzłach, co spowoduje wycofanie niektórych transakcji
Głównym problemem jest to, że Galera nie wprowadza żadnego blokowania na węzłach innych niż węzeł początkowy, na którym została otwarta transakcja. Proces certyfikacji opiera się na nadziei, że jeśli jeden węzeł będzie w stanie wykonać transakcję, to inne też będą w stanie to zrobić. To prawda, ale jak już wspomnieliśmy, istnieją przypadki skrajne, w których prawdopodobieństwo takiego zdarzenia jest znacznie zmniejszone.
W Galera 4, z replikacją strumieniową, zachowanie uległo zmianie i wszystkie blokady są pobierane we wszystkich węzłach. Transakcje zostaną podzielone na części, a każda część będzie certyfikowana na wszystkich węzłach. Po pomyślnej certyfikacji wiersze zostaną zablokowane na wszystkich węzłach w klastrze. Istnieje kilka zmiennych, które decydują o tym, jak dokładnie to się robi - wsrep_trx_fragment_size i wsrep_trx_fragment_unit definiują, jak duży powinien być fragment i jak powinien być zdefiniowany. Jest to bardzo szczegółowa kontrola:jednostkę fragmentu można zdefiniować jako bajty, zestawienia lub wiersze, co umożliwia uruchomienie certyfikacji dla każdego wiersza zmodyfikowanego w transakcji. Rzućmy okiem na to, jak możesz skorzystać z replikacji strumieniowej w prawdziwym życiu.
Praca z replikacją strumieniową
Rozważmy następujący scenariusz. Mamy do wykonania transakcję, która zajmuje co najmniej 30 sekund:
BEGIN; UPDATE sbtest.sbtest1 SET k = k - 2 WHERE id < 2000 ; UPDATE sbtest.sbtest1 SET k = k + 1 WHERE id < 2000 ; UPDATE sbtest.sbtest1 SET k = k + 1 WHERE id < 2000 ; SELECT SLEEP(30); COMMIT;
Następnie, podczas jego działania, wykonamy SQL, który dotyka podobnych wierszy. Zostanie to wykonane na innym węźle:
BEGIN; UPDATE sbtest.sbtest1 SET k = k - 1 WHERE id < 20 ; UPDATE sbtest.sbtest1 SET k = k + 1 WHERE id < 20 ; COMMIT;
Jaki byłby wynik?
Pierwsza transakcja jest wycofywana natychmiast po wykonaniu drugiej:
MariaDB [sbtest]> BEGIN; UPDATE sbtest.sbtest1 SET k = k - 2 WHERE id < 2000 ; UPDATE sbtest.sbtest1 SET k = k + 1 WHERE id < 2000 ; UPDATE sbtest.sbtest1 SET k = k + 1 WHERE id < 2000 ; SELECT SLEEP(30); COMMIT;
Query OK, 0 rows affected (0.001 sec)
Query OK, 667 rows affected (0.020 sec)
Rows matched: 667 Changed: 667 Warnings: 0
Query OK, 667 rows affected (0.010 sec)
Rows matched: 667 Changed: 667 Warnings: 0
Query OK, 667 rows affected (0.009 sec)
Rows matched: 667 Changed: 667 Warnings: 0
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
Query OK, 0 rows affected (0.001 sec)
Transakcja na drugim węźle powiodła się:
MariaDB [(none)]> BEGIN; UPDATE sbtest.sbtest1 SET k = k - 1 WHERE id < 20 ; UPDATE sbtest.sbtest1 SET k = k + 1 WHERE id < 20 ; COMMIT;
Query OK, 0 rows affected (0.000 sec)
Query OK, 7 rows affected (0.002 sec)
Rows matched: 7 Changed: 7 Warnings: 0
Query OK, 7 rows affected (0.001 sec)
Rows matched: 7 Changed: 7 Warnings: 0
Query OK, 0 rows affected (0.004 sec)
Aby tego uniknąć, możemy użyć replikacji strumieniowej dla pierwszej transakcji. Poprosimy firmę Galera o poświadczenie każdej zmiany w rzędzie:
MariaDB [sbtest]> BEGIN; SET SESSION wsrep_trx_fragment_size=1 ; SET SESSION wsrep_trx_fragment_unit='rows' ; UPDATE sbtest.sbtest1 SET k = k - 2 WHERE id < 2000 ; UPDATE sbtest.sbtest1 SET k = k + 1 WHERE id < 2000 ; UPDATE sbtest.sbtest1 SET k = k + 1 WHERE id < 2000 ; SELECT SLEEP(30); COMMIT; SET SESSION wsrep_trx_fragment_size=0;
Query OK, 0 rows affected (0.001 sec)
Query OK, 0 rows affected (0.000 sec)
Query OK, 0 rows affected (0.000 sec)
Query OK, 667 rows affected (1.757 sec)
Rows matched: 667 Changed: 667 Warnings: 0
Query OK, 667 rows affected (1.708 sec)
Rows matched: 667 Changed: 667 Warnings: 0
Query OK, 667 rows affected (1.685 sec)
Rows matched: 667 Changed: 667 Warnings: 0
Jak widać, tym razem zadziałało bez zarzutu. W drugim węźle:
MariaDB [(none)]> BEGIN; UPDATE sbtest.sbtest1 SET k = k - 1 WHERE id < 20 ; UPDATE sbtest.sbtest1 SET k = k + 1 WHERE id < 20 ; COMMIT;
Query OK, 0 rows affected (0.000 sec)
Query OK, 7 rows affected (33.942 sec)
Rows matched: 7 Changed: 7 Warnings: 0
Query OK, 7 rows affected (0.001 sec)
Rows matched: 7 Changed: 7 Warnings: 0
Query OK, 0 rows affected (0.026 sec)
Co ciekawe widać, że wykonanie UPDATE zajęło prawie 34 sekundy - było to spowodowane tym, że początkowa transakcja, poprzez replikację strumieniową, zablokowała wszystkie zmodyfikowane wiersze na wszystkich węzłach i nasza druga transakcja musiała czekać na pierwszy do zakończenia, mimo że obie transakcje zostały wykonane na różnych węzłach.
To w zasadzie wszystko, jeśli chodzi o replikację strumieniową. W zależności od wymagań i natężenia ruchu możesz z niego korzystać w mniej rygorystyczny sposób - certyfikowaliśmy każdy wiersz, ale możesz to zmienić na każdy n wiersz lub każde zestawienie. Możesz nawet zdecydować o ilości danych do certyfikacji. To powinno wystarczyć, aby spełnić wymagania Twojego środowiska.
Jest jeszcze kilka rzeczy, o których chcielibyśmy pamiętać i pamiętać. Po pierwsze, replikacja strumieniowa w żadnym wypadku nie jest rozwiązaniem, które powinno być używane domyślnie. To jest powód, dla którego jest domyślnie wyłączony. Zalecanym przypadkiem użycia jest ręczne decydowanie o transakcjach, które skorzystałyby z replikacji strumieniowej i włączenie jej na poziomie sesji. To jest powód, dla którego nasze przykłady kończą się na:
SET SESSION wsrep_trx_fragment_size=0;
Ta instrukcja (ustawienie wsrep_trx_fragment_size na 0) wyłącza replikację strumieniową dla bieżącej sesji.
Kolejna rzecz, o której warto pamiętać - jeśli zdarzy ci się korzystać z replikacji strumieniowej, użyje ona tabeli „wsrep_streaming_log” w schemacie „mysql” do trwałego przechowywania danych, które są przesyłane strumieniowo. Korzystając z tej tabeli, możesz zorientować się, jakie dane są przesyłane przez klaster za pomocą replikacji strumieniowej.
Wreszcie występ. Jest to również jeden z powodów, dla których nie chcesz używać replikacji strumieniowej przez cały czas. Głównym tego powodem jest blokowanie - przy replikacji strumieniowej musisz nabyć blokady wierszy na wszystkich węzłach. Uzyskanie blokad zajmuje trochę czasu, a jeśli będziesz musiał wycofać transakcję, spowoduje to również presję na wszystkie węzły, aby wykonały wycofanie. Przeprowadziliśmy bardzo szybki test wpływu na wydajność replikacji strumieniowej. Środowisko jest ściśle testowe, więc nie zakładaj, że te wyniki będą takie same na sprzęcie klasy produkcyjnej, bardziej powinieneś zobaczyć, jaki może być wpływ.
Przetestowaliśmy cztery scenariusze:
- Podstawa, ustaw globalny wsrep_trx_fragment_size=0;
- ustaw globalne wsrep_trx_fragment_unit='wiersze'; ustaw globalne wsrep_trx_fragment_size=1;
- ustaw globalne wsrep_trx_fragment_unit='oświadczenia'; ustaw globalne wsrep_trx_fragment_size=1;
- ustaw globalne wsrep_trx_fragment_unit='oświadczenia'; ustaw globalne wsrep_trx_fragment_size=5;
Użyliśmy testu r/w sysbench:
sysbench /root/sysbench/src/lua/oltp_read_write.lua --threads=4 --events=0 --time=300 --mysql-host=10.0.0.141 --mysql-user=sbtest --mysql-password=sbtest --mysql-port=3306 --tables=32 --report-interval=1 --skip-trx=off --table-size=100000 --db-ps-mode=disable run
Wyniki to:
- Transakcje:82,91 na sekundę, zapytania:1658,27 na sekundę. (100%)
- Transakcje:54,72 na sekundę, zapytania:1094,43 na sekundę. (66%)
- Transakcje:54,76 na sekundę, zapytania:1095,18 na sekundę. (66%)
- Transakcje:70,93 na sekundę, zapytania:1418,55 na sekundę. (86%)
Jak widać, wpływ jest znaczny, wydajność spada nawet o 33%.
Mamy nadzieję, że ten post na blogu okazał się dla Ciebie pouczający i dał ci pewne informacje na temat replikacji strumieniowej, która jest dostępna w Galera 4 i MariaDB 10.4. Staraliśmy się omówić przypadki użycia i potencjalne wady związane z tą nową technologią.