Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Online-Schema-Upgrade im MySQL Galera-Cluster mithilfe der RSU-Methode

Dieser Beitrag ist eine Fortsetzung unseres vorherigen Beitrags zum Online-Schema-Upgrade in Galera mithilfe der TOI-Methode. Wir zeigen Ihnen nun, wie Sie ein Schema-Upgrade mit der Rolling Schema Upgrade (RSU)-Methode durchführen.

RSU und TOI

Wie wir besprochen haben, findet bei der Verwendung von TOI eine Änderung gleichzeitig auf allen Knoten statt. Dies kann zu einer ernsthaften Einschränkung werden, da eine solche Art der Ausführung von Schemaänderungen impliziert, dass keine anderen Abfragen ausgeführt werden können. Bei langen ALTER-Anweisungen ist der Cluster möglicherweise sogar stundenlang nicht verfügbar. Dies ist natürlich nicht etwas, was Sie in der Produktion akzeptieren können. Die RSU-Methode behebt diese Schwachstelle – Änderungen erfolgen jeweils auf einem Knoten, während andere Knoten nicht betroffen sind und den Datenverkehr bedienen können. Sobald ALTER auf einem Knoten abgeschlossen ist, wird es dem Cluster wieder beitreten und Sie können mit der Ausführung einer Schemaänderung auf dem nächsten Knoten fortfahren.

Ein solches Verhalten hat seine eigenen Einschränkungen. Der wichtigste ist, dass geplante Schemaänderungen kompatibel sein müssen. Was bedeutet das? Denken wir eine Weile darüber nach. Zuallererst müssen wir bedenken, dass der Cluster die ganze Zeit aktiv ist und läuft – der geänderte Knoten muss in der Lage sein, den gesamten Datenverkehr zu akzeptieren, der die verbleibenden Knoten trifft. Kurz gesagt, eine auf dem alten Schema ausgeführte DML muss auch auf dem neuen Schema funktionieren (und umgekehrt, wenn Sie in Ihrem Galera-Cluster eine Art Round-Robin-ähnliche Verbindungsverteilung verwenden). Wir werden uns auf die MySQL-Kompatibilität konzentrieren, aber Sie müssen auch bedenken, dass Ihre Anwendung sowohl mit geänderten als auch mit nicht geänderten Knoten funktionieren muss – stellen Sie sicher, dass Ihre Änderung die Anwendungslogik nicht unterbricht. Eine bewährte Vorgehensweise besteht darin, Spaltennamen explizit an Abfragen zu übergeben – verlassen Sie sich nicht auf „SELECT *“, da Sie nie wissen, wie viele Spalten Sie zurückerhalten.

Galera- und zeilenbasiertes binäres Protokollformat

Ok, DML muss also mit alten und neuen Schemas arbeiten. Wie werden DMLs zwischen Galera-Knoten übertragen? Beeinflusst es, welche Änderungen kompatibel sind und welche nicht? Ja, in der Tat – das tut es. Galera verwendet keine reguläre MySQL-Replikation, verlässt sich aber dennoch darauf, um Ereignisse zwischen den Knoten zu übertragen. Um genau zu sein, verwendet Galera das ROW-Format für Ereignisse. Ein Ereignis im Zeilenformat (nach der Dekodierung) kann wie folgt aussehen:

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

Oder:

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

Wie Sie sehen können, gibt es ein sichtbares Muster:Eine Zeile wird durch ihren Inhalt identifiziert. Es gibt keine Spaltennamen, nur ihre Reihenfolge. Allein dies sollte einige Warnlichter aufleuchten lassen:„Was würde passieren, wenn ich eine der Säulen entferne?“ Nun, wenn es die letzte Spalte ist, ist dies akzeptabel. Wenn Sie eine Spalte in der Mitte entfernen würden, würde dies die Spaltenreihenfolge durcheinander bringen und als Ergebnis würde die Replikation abbrechen. Ähnliches passiert, wenn Sie eine Spalte in der Mitte statt am Ende hinzufügen. Es gibt jedoch noch mehr Einschränkungen. Das Ändern der Spaltendefinition funktioniert, solange es sich um den gleichen Datentyp handelt - Sie können die INT-Spalte ändern, um BIGINT zu werden, aber Sie können die INT-Spalte nicht in VARCHAR ändern - dies unterbricht die Replikation. Eine detaillierte Beschreibung, welche Änderungen kompatibel sind und welche nicht, finden Sie in der MySQL-Dokumentation. Egal, was Sie in der Dokumentation sehen können, um auf der sicheren Seite zu bleiben, ist es besser, einige Tests auf einem separaten Entwicklungs-/Staging-Cluster auszuführen. Stellen Sie sicher, dass es nicht nur gemäß der Dokumentation funktioniert, sondern dass es auch in Ihrem speziellen Setup gut funktioniert.

Alles in allem, wie Sie deutlich sehen können, ist die sichere Ausführung von RSU viel komplexer, als nur ein paar Befehle auszuführen. Da Befehle dennoch wichtig sind, werfen wir einen Blick auf das Beispiel, wie Sie die RSU ausführen können und was dabei schief gehen kann.

RSU-Beispiel

Erste Einrichtung

Stellen wir uns ein recht einfaches Anwendungsbeispiel vor. Wir werden ein Benchmark-Tool, Sysbench, verwenden, um Inhalte und Traffic zu generieren, aber der Ablauf wird für fast jede Anwendung gleich sein – Wordpress, Joomla, Drupal, was auch immer. Wir werden HAProxy in Verbindung mit unserer Anwendung verwenden, um Lese- und Schreibvorgänge zwischen Galera-Knoten im Round-Robin-Verfahren aufzuteilen. Sie können unten überprüfen, wie HAProxy den Galera-Cluster sieht.

Die gesamte Topologie sieht wie folgt aus:

Datenverkehr wird mit dem folgenden Befehl generiert:

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

Das Schema sieht wie folgt aus:

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)

Sehen wir uns zunächst an, wie wir dieser Tabelle einen Index hinzufügen können. Das Hinzufügen eines Index ist eine kompatible Änderung, die einfach mit RSU durchgeführt werden kann.

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)

Wie Sie auf der Registerkarte Node sehen können, hat der Host, auf dem wir die Änderung durchgeführt haben, automatisch in den Donor/Desynced-Status gewechselt, wodurch sichergestellt wird, dass dieser Host den Rest des Clusters nicht beeinträchtigt, wenn er durch ALTER verlangsamt wird.

Sehen wir uns an, wie unser Schema jetzt aussieht:

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)

Wie Sie sehen können, wurde der Index hinzugefügt. Bitte beachten Sie jedoch, dass dies nur auf diesem bestimmten Knoten passiert ist. Um eine vollständige Schemaänderung durchzuführen, müssen Sie diesem Prozess auf den verbleibenden Knoten des Galera-Clusters folgen. Um mit dem ersten Knoten fertig zu werden, können wir wsrep_OSU_method wieder auf TOI:

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

Wir werden den Rest des Prozesses nicht zeigen, weil es derselbe ist – aktivieren Sie RSU auf Sitzungsebene, führen Sie ALTER aus, aktivieren Sie TOI. Interessanter ist, was passieren würde, wenn die Änderung nicht kompatibel wäre. Werfen wir noch einmal einen kurzen Blick auf das Schema:

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)

Angenommen, wir möchten den Typ der Spalte „k“ auf einem Knoten von INT in VARCHAR(30) ändern.

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

Schauen wir uns nun das Schema an:

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)

Alles ist wie erwartet – die Spalte „k“ wurde in VARCHAR geändert. Jetzt können wir prüfen, ob diese Änderung für das Galera-Cluster akzeptabel ist oder nicht. Um dies zu testen, verwenden wir einen der verbleibenden, unveränderten Knoten, um die folgende Abfrage auszuführen:

mysql> INSERT INTO sbtest1.sbtest1 (k, c, pad) VALUES (123, 'test', 'test');
Query OK, 1 row affected (0.19 sec)
Lass uns nachsehen, was passiert ist.

Es sieht definitiv nicht gut aus – unser Knoten ist ausgefallen. Protokolle geben Ihnen weitere Details:

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…

Wie man sieht, hat Galera bemängelt, dass die Spalte nicht von INT nach VARCHAR(30) konvertiert werden kann. Es wurde viermal versucht, das Writeset erneut auszuführen, aber es war nicht überraschend, dass es fehlschlug. Daher hat Galera festgestellt, dass die Knotenkonsistenz gefährdet ist und der Knoten aus dem Cluster geworfen wurde. Der verbleibende Inhalt der Protokolle zeigt diesen Prozess:

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.

Natürlich wird ClusterControl versuchen, einen solchen Knoten wiederherzustellen - die Wiederherstellung beinhaltet das Ausführen von SST, damit inkompatible Schemaänderungen entfernt werden, aber wir werden wieder am Anfang stehen - unsere Schemaänderung wird rückgängig gemacht.

Wie Sie sehen können, ist das Ausführen von RSU ein sehr einfacher Prozess, der darunter jedoch ziemlich komplex sein kann. Es erfordert einige Tests und Vorbereitungen, um sicherzustellen, dass Sie keinen Knoten verlieren, nur weil die Schemaänderung nicht kompatibel war.