Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Aktualizacja schematu online w klastrze MySQL Galera przy użyciu metody RSU

Ten post jest kontynuacją naszego poprzedniego postu na temat aktualizacji schematu online w Galera metodą TOI. Pokażemy teraz, jak wykonać aktualizację schematu przy użyciu metody Rolling Schema Upgrade (RSU).

RSU i TOI

Jak już wspomnieliśmy, podczas korzystania z TOI zmiana następuje jednocześnie na wszystkich węzłach. Może to stać się poważnym ograniczeniem, ponieważ taki sposób wykonywania zmian schematu oznacza, że ​​nie można wykonać innych zapytań. W przypadku długich instrukcji ALTER klaster może być niedostępny nawet przez kilka godzin. Oczywiście nie jest to coś, co można zaakceptować w produkcji. Metoda RSU rozwiązuje tę słabość — zmiany zachodzą na jednym węźle na raz, podczas gdy inne węzły nie są dotknięte i mogą obsługiwać ruch. Gdy ALTER zakończy się na jednym węźle, ponownie dołączy do klastra i będziesz mógł kontynuować zmianę schematu na następnym węźle.

Takie zachowanie ma swoje własne ograniczenia. Głównym z nich jest to, że zaplanowana zmiana schematu musi być zgodna. Co to znaczy? Zastanówmy się przez chwilę. Przede wszystkim musimy pamiętać, że klaster jest cały czas uruchomiony - zmieniony węzeł musi być w stanie zaakceptować cały ruch, który trafia do pozostałych węzłów. Krótko mówiąc, DML wykonany na starym schemacie musi działać również na nowym schemacie (i odwrotnie, jeśli używasz jakiegoś rodzaju dystrybucji połączeń typu round-robin w swoim klastrze Galera). Skupimy się na kompatybilności z MySQL, ale musisz również pamiętać, że Twoja aplikacja musi działać zarówno ze zmienionymi, jak i niezmienionymi węzłami - upewnij się, że Twoja zmiana nie złamie logiki aplikacji. Jedną z dobrych praktyk jest jawne przekazywanie nazw kolumn do zapytań – nie polegaj na „SELECT *”, ponieważ nigdy nie wiesz, ile kolumn otrzymasz w zamian.

Format dziennika binarnego Galera i oparty na wierszach

Ok, więc DML musi działać na starych i nowych schematach. Jak przesyłane są DML między węzłami Galera? Czy ma to wpływ na to, które zmiany są zgodne, a jakie nie? Tak, rzeczywiście – tak. Galera nie korzysta ze zwykłej replikacji MySQL, ale nadal wykorzystuje ją do przesyłania zdarzeń między węzłami. Aby być precyzyjnym, Galera używa formatu ROW dla wydarzeń. Zdarzenie w formacie wiersza (po zdekodowaniu) może wyglądać tak:

### INSERT INTO `schema`.`table`
### SET
###   @1=1
###   @2=1
###   @3='88764053989'
###   @4='14700597838'

Lub:

### UPDATE `schema`.`table`
### WHERE
###   @1=1
###   @2=1
###   @3='88764053989'
###   @4='14700597838'
### SET
###   @1=2
###   @2=2
###   @3='88764053989'
###   @4='81084251066'

Jak widać, widoczny jest wzór:wiersz jest identyfikowany przez jego zawartość. Nie ma nazw kolumn, tylko ich kolejność. Samo to powinno włączyć kilka lampek ostrzegawczych:„co by się stało, gdybym usunęła jedną z kolumn?” Cóż, jeśli to ostatnia kolumna, to jest do przyjęcia. Jeśli usuniesz kolumnę pośrodku, spowoduje to bałagan w kolejności kolumn iw rezultacie replikacja zostanie przerwana. Podobnie stanie się, jeśli dodasz kolumnę pośrodku, a nie na końcu. Jest jednak więcej ograniczeń. Zmiana definicji kolumny będzie działać, o ile jest to ten sam typ danych — możesz zmienić kolumnę INT na BIGINT, ale nie możesz zmienić kolumny INT na VARCHAR — spowoduje to przerwanie replikacji. Szczegółowy opis tego, jaka zmiana jest zgodna, a czego nie, znajdziesz w dokumentacji MySQL. Bez względu na to, co widzisz w dokumentacji, aby zachować bezpieczną stronę, lepiej przeprowadzić kilka testów na oddzielnym klastrze deweloperskim/stagingowym. Upewnij się, że będzie działać nie tylko zgodnie z dokumentacją, ale także w Twojej konkretnej konfiguracji.

Podsumowując, jak widać, wykonywanie RSU w bezpieczny sposób jest znacznie bardziej złożone niż uruchamianie kilku poleceń. Mimo to, ponieważ polecenia są ważne, spójrzmy na przykład, jak możesz wykonać RSU i co może pójść nie tak w tym procesie.

Przykład RSU

Wstępna konfiguracja

Wyobraźmy sobie dość prosty przykład aplikacji. Do generowania treści i ruchu użyjemy narzędzia wzorcowego Sysbench, ale przepływ będzie taki sam dla prawie każdej aplikacji - Wordpress, Joomla, Drupal, co tylko chcesz. Użyjemy HAProxy kolokowanego z naszą aplikacją, aby podzielić odczyty i zapisy między węzłami Galera w sposób okrężny. Poniżej możesz sprawdzić, jak HAProxy widzi klaster Galera.

Cała topologia wygląda jak poniżej:

Ruch jest generowany za pomocą następującego polecenia:

while true ; do sysbench /root/sysbench/src/lua/oltp_read_write.lua --threads=4 --max-requests=0 --time=3600 --mysql-host=10.0.0.100 --mysql-user=sbtest --mysql-password=sbtest --mysql-port=3307 --tables=32 --report-interval=1 --skip-trx=on --table-size=100000 --db-ps-mode=disable run ; done

Schemat wygląda jak poniżej:

mysql> SHOW CREATE TABLE sbtest1.sbtest1\G
*************************** 1. row ***************************
       Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `k` int(11) NOT NULL DEFAULT '0',
  `c` char(120) NOT NULL DEFAULT '',
  `pad` char(60) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=29986632 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

Najpierw zobaczmy, jak możemy dodać indeks do tej tabeli. Dodanie indeksu jest kompatybilną zmianą, którą można łatwo wykonać za pomocą RSU.

mysql> SET SESSION wsrep_OSU_method=RSU;
Query OK, 0 rows affected (0.00 sec)
mysql> ALTER TABLE sbtest1.sbtest1 ADD INDEX idx_new (k, c); 
Query OK, 0 rows affected (5 min 19.59 sec)

Jak widać na karcie Node, host, na którym wprowadziliśmy zmianę, automatycznie przełączył się w stan Donor/Desynced, co zapewnia, że ​​ten host nie wpłynie na resztę klastra, jeśli zostanie spowolniony przez ALTER.

Sprawdźmy teraz, jak wygląda nasz schemat:

mysql> SHOW CREATE TABLE sbtest1.sbtest1\G
*************************** 1. row ***************************
       Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `k` int(11) NOT NULL DEFAULT '0',
  `c` char(120) NOT NULL DEFAULT '',
  `pad` char(60) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`),
  KEY `idx_new` (`k`,`c`)
) ENGINE=InnoDB AUTO_INCREMENT=29986632 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

Jak widać, indeks został dodany. Pamiętaj jednak, że stało się to tylko w tym konkretnym węźle. Aby dokonać pełnej zmiany schematu, musisz wykonać ten proces na pozostałych węzłach klastra Galera. Aby zakończyć z pierwszym węzłem, możemy przełączyć wsrep_OSU_method z powrotem na TOI:

SET SESSION wsrep_OSU_method=TOI;
Query OK, 0 rows affected (0.00 sec)

Nie będziemy pokazywać reszty procesu, ponieważ jest tak samo - włącz RSU na poziomie sesji, uruchom ALTER, włącz TOI. Bardziej interesujące jest to, co by się stało, gdyby zmiana była niekompatybilna. Przyjrzyjmy się jeszcze raz schematowi:

mysql> SHOW CREATE TABLE sbtest1.sbtest1\G
*************************** 1. row ***************************
       Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `k` int(11) NOT NULL DEFAULT '0',
  `c` char(120) NOT NULL DEFAULT '',
  `pad` char(60) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`),
  KEY `idx_new` (`k`,`c`)
) ENGINE=InnoDB AUTO_INCREMENT=29986632 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

Powiedzmy, że chcemy zmienić typ kolumny „k” z INT na VARCHAR(30) w jednym węźle.

mysql> SET SESSION wsrep_OSU_method=RSU;
Query OK, 0 rows affected (0.00 sec)
mysql> ALTER TABLE sbtest1.sbtest1 MODIFY COLUMN k VARCHAR(30) NOT NULL DEFAULT '';
Query OK, 10004785 rows affected (1 hour 14 min 51.89 sec)
Records: 10004785  Duplicates: 0  Warnings: 0

Przyjrzyjmy się teraz schematowi:

mysql> SHOW CREATE TABLE sbtest1.sbtest1\G
*************************** 1. row ***************************
       Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `k` varchar(30) NOT NULL DEFAULT '',
  `c` char(120) NOT NULL DEFAULT '',
  `pad` char(60) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `k_1` (`k`),
  KEY `idx_new` (`k`,`c`)
) ENGINE=InnoDB AUTO_INCREMENT=29986632 DEFAULT CHARSET=latin1
1 row in set (0.02 sec)

Wszystko jest zgodne z oczekiwaniami - kolumna „k” została zmieniona na VARCHAR. Teraz możemy sprawdzić, czy ta zmiana jest akceptowalna, czy nie dla klastra Galera. Aby to przetestować, użyjemy jednego z pozostałych, niezmienionych węzłów do wykonania następującego zapytania:

mysql> INSERT INTO sbtest1.sbtest1 (k, c, pad) VALUES (123, 'test', 'test');
Query OK, 1 row affected (0.19 sec)
Zobaczmy co się stało.

Zdecydowanie nie wygląda to dobrze – nasz węzeł jest wyłączony. Dzienniki podadzą więcej szczegółów:

2017-04-07T10:51:14.873524Z 5 [ERROR] Slave SQL: Column 1 of table 'sbtest1.sbtest1' cannot be converted from type 'int' to type 'varchar(30)', Error_code: 1677
2017-04-07T10:51:14.873560Z 5 [Warning] WSREP: RBR event 3 Write_rows apply warning: 3, 982675
2017-04-07T10:51:14.879120Z 5 [Warning] WSREP: Failed to apply app buffer: seqno: 982675, status: 1
         at galera/src/trx_handle.cpp:apply():351
Retrying 2th time
2017-04-07T10:51:14.879272Z 5 [ERROR] Slave SQL: Column 1 of table 'sbtest1.sbtest1' cannot be converted from type 'int' to type 'varchar(30)', Error_code: 1677
2017-04-07T10:51:14.879287Z 5 [Warning] WSREP: RBR event 3 Write_rows apply warning: 3, 982675
2017-04-07T10:51:14.879399Z 5 [Warning] WSREP: Failed to apply app buffer: seqno: 982675, status: 1
         at galera/src/trx_handle.cpp:apply():351
Retrying 3th time
2017-04-07T10:51:14.879618Z 5 [ERROR] Slave SQL: Column 1 of table 'sbtest1.sbtest1' cannot be converted from type 'int' to type 'varchar(30)', Error_code: 1677
2017-04-07T10:51:14.879633Z 5 [Warning] WSREP: RBR event 3 Write_rows apply warning: 3, 982675
2017-04-07T10:51:14.879730Z 5 [Warning] WSREP: Failed to apply app buffer: seqno: 982675, status: 1
         at galera/src/trx_handle.cpp:apply():351
Retrying 4th time
2017-04-07T10:51:14.879911Z 5 [ERROR] Slave SQL: Column 1 of table 'sbtest1.sbtest1' cannot be converted from type 'int' to type 'varchar(30)', Error_code: 1677
2017-04-07T10:51:14.879924Z 5 [Warning] WSREP: RBR event 3 Write_rows apply warning: 3, 982675
2017-04-07T10:51:14.885255Z 5 [ERROR] WSREP: Failed to apply trx: source: 938415a6-1aab-11e7-ac29-0a69a4a1dafe version: 3 local: 0 state: APPLYING flags: 1 conn_id: 125559 trx_id: 2856843 seqnos (l: 392283, g: 9
82675, s: 982674, d: 982563, ts: 146831275805149)
2017-04-07T10:51:14.885271Z 5 [ERROR] WSREP: Failed to apply trx 982675 4 times
2017-04-07T10:51:14.885281Z 5 [ERROR] WSREP: Node consistency compromized, aborting…

Jak widać, Galera skarżył się na to, że nie można przekonwertować kolumny z INT na VARCHAR(30). Próbował on ponownie wykonać zapis cztery razy, ale nic dziwnego, że się nie udało. W związku z tym Galera ustaliła, że ​​spójność węzła jest naruszona i węzeł jest wyrzucany z klastra. Pozostała zawartość dzienników pokazuje ten proces:

2017-04-07T10:51:14.885560Z 5 [Note] WSREP: Closing send monitor...
2017-04-07T10:51:14.885630Z 5 [Note] WSREP: Closed send monitor.
2017-04-07T10:51:14.885644Z 5 [Note] WSREP: gcomm: terminating thread
2017-04-07T10:51:14.885828Z 5 [Note] WSREP: gcomm: joining thread
2017-04-07T10:51:14.885842Z 5 [Note] WSREP: gcomm: closing backend
2017-04-07T10:51:14.896654Z 5 [Note] WSREP: view(view_id(NON_PRIM,6fcd492a,37) memb {
        b13499a8,0
} joined {
} left {
} partitioned {
        6fcd492a,0
        938415a6,0
})
2017-04-07T10:51:14.896746Z 5 [Note] WSREP: view((empty))
2017-04-07T10:51:14.901477Z 5 [Note] WSREP: gcomm: closed
2017-04-07T10:51:14.901512Z 0 [Note] WSREP: New COMPONENT: primary = no, bootstrap = no, my_idx = 0, memb_num = 1
2017-04-07T10:51:14.901531Z 0 [Note] WSREP: Flow-control interval: [16, 16]
2017-04-07T10:51:14.901541Z 0 [Note] WSREP: Received NON-PRIMARY.
2017-04-07T10:51:14.901550Z 0 [Note] WSREP: Shifting SYNCED -> OPEN (TO: 982675)
2017-04-07T10:51:14.901563Z 0 [Note] WSREP: Received self-leave message.
2017-04-07T10:51:14.901573Z 0 [Note] WSREP: Flow-control interval: [0, 0]
2017-04-07T10:51:14.901581Z 0 [Note] WSREP: Received SELF-LEAVE. Closing connection.
2017-04-07T10:51:14.901589Z 0 [Note] WSREP: Shifting OPEN -> CLOSED (TO: 982675)
2017-04-07T10:51:14.901602Z 0 [Note] WSREP: RECV thread exiting 0: Success
2017-04-07T10:51:14.902701Z 5 [Note] WSREP: recv_thread() joined.
2017-04-07T10:51:14.902720Z 5 [Note] WSREP: Closing replication queue.
2017-04-07T10:51:14.902730Z 5 [Note] WSREP: Closing slave action queue.
2017-04-07T10:51:14.902742Z 5 [Note] WSREP: /usr/sbin/mysqld: Terminated.

Oczywiście ClusterControl spróbuje odzyskać taki węzeł — odzyskiwanie obejmuje uruchomienie SST, więc niekompatybilne zmiany w schemacie zostaną usunięte, ale wrócimy do punktu wyjścia — nasza zmiana schematu zostanie odwrócona.

Jak widać, o ile uruchamianie RSU jest bardzo prostym procesem, to pod spodem może być dość złożony. Wymaga to pewnych testów i przygotowań, aby upewnić się, że nie stracisz węzła tylko dlatego, że zmiana schematu nie była zgodna.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak wyświetlić status i zmienne systemowe w MySQL Workbench za pomocą GUI?

  2. Błąd PHP (MySQL):Ostrzeżenie:mysql_num_rows() oczekuje, że parametr 1 będzie zasobem

  3. Pobieranie nieprzetworzonego ciągu zapytania SQL z przygotowanych instrukcji PDO

  4. Różnice między bazami danych SQL i NoSQL — porównanie MySQL i MongoDB

  5. Mysql:Wybierz wiersze z tabeli, których nie ma w innej