この投稿は、TOIメソッドを使用したGaleraでのオンラインスキーマアップグレードに関する以前の投稿の続きです。次に、Rolling Schema Upgrade(RSU)メソッドを使用してスキーマアップグレードを実行する方法を示します。
RSUとTOI
すでに説明したように、TOIを使用すると、すべてのノードで同時に変更が発生します。スキーマ変更を実行するこのような方法は、他のクエリを実行できないことを意味するため、これは重大な制限になる可能性があります。長いALTERステートメントの場合、クラスターは何時間も使用できない場合があります。明らかに、これは本番環境で受け入れることができるものではありません。 RSU方式は、この弱点に対処します。変更は一度に1つのノードで発生しますが、他のノードは影響を受けず、トラフィックを処理できます。 1つのノードでALTERが完了すると、ALTERはクラスターに再参加し、次のノードでスキーマ変更の実行を続行できます。
このような動作には、独自の制限があります。主なものは、スケジュールされたスキーマ変更に互換性がなければならないということです。どういう意味ですか?しばらく考えてみましょう。まず、クラスターが常に稼働していることを覚えておく必要があります。変更されたノードは、残りのノードに到達するすべてのトラフィックを受け入れることができる必要があります。つまり、古いスキーマで実行されたDMLは、新しいスキーマでも機能する必要があります(Galera Clusterでラウンドロビンのような接続分散を使用している場合は、その逆も同様です)。 MySQLの互換性に焦点を当てますが、アプリケーションは変更されたノードと変更されていないノードの両方で動作する必要があることも覚えておく必要があります。変更によってアプリケーションロジックが破損しないようにしてください。クエリに列名を明示的に渡すことをお勧めします。返される列の数がわからないため、「SELECT*」に依存しないでください。
ガレラおよび行ベースのバイナリログ形式
さて、DMLは新旧のスキーマで動作する必要があります。 DMLはGaleraノード間でどのように転送されますか?互換性のある変更と互換性のない変更に影響しますか?はい、確かに-そうです。 Galeraは通常のMySQLレプリケーションを使用しませんが、ノード間でイベントを転送するためにそれを使用します。正確には、GaleraはイベントにROW形式を使用します。行形式のイベント(デコード後)は次のようになります:
### INSERT INTO `schema`.`table`
### SET
### @1=1
### @2=1
### @3='88764053989'
### @4='14700597838'
または:
### UPDATE `schema`.`table`
### WHERE
### @1=1
### @2=1
### @3='88764053989'
### @4='14700597838'
### SET
### @1=2
### @2=2
### @3='88764053989'
### @4='81084251066'
ご覧のとおり、目に見えるパターンがあります。行はその内容によって識別されます。列名はなく、順序だけがあります。これだけで、いくつかの警告灯が点灯するはずです。「列の1つを削除するとどうなりますか?」さて、それが最後の列であれば、これは許容できます。途中の列を削除すると、列の順序が乱れ、その結果、レプリケーションが中断します。最後ではなく中央に列を追加すると、同様のことが起こります。ただし、さらに多くの制約があります。列定義の変更は、同じデータ型である限り機能します。INT列をBIGINTに変更できますが、INT列をVARCHARに変更することはできません。これによりレプリケーションが中断されます。互換性のある変更と互換性のない変更の詳細な説明は、MySQLのドキュメントに記載されています。ドキュメントに何が表示されていても、安全を確保するために、別の開発/ステージングクラスターでいくつかのテストを実行することをお勧めします。ドキュメントに従って機能するだけでなく、特定のセットアップでも正常に機能することを確認してください。
全体として、はっきりとわかるように、安全な方法でRSUを実行することは、いくつかのコマンドを実行するよりもはるかに複雑です。それでも、コマンドは重要なので、RSUを実行する方法と、プロセスで何がうまくいかない可能性があるかの例を見てみましょう。
RSUの例
初期設定
アプリケーションのかなり単純な例を想像してみましょう。コンテンツとトラフィックを生成するためにベンチマークツールであるSysbenchを使用しますが、フローはほぼすべてのアプリケーション(Wordpress、Joomla、Drupalなど)で同じです。アプリケーションと同じ場所に配置されたHAProxyを使用して、Galeraノード間でラウンドロビン方式で読み取りと書き込みを分割します。以下で、HAProxyがGaleraクラスターをどのように認識しているかを確認できます。
トポロジ全体は次のようになります:
トラフィックは次のコマンドを使用して生成されます:
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
スキーマは次のようになります:
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)
まず、このテーブルにインデックスを追加する方法を見てみましょう。インデックスの追加は互換性のある変更であり、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)
[ノード]タブでわかるように、変更を実行したホストは自動的にドナー/非同期状態に切り替わり、ALTERによって速度が低下した場合でも、このホストがクラスターの他の部分に影響を与えないようにします。
>スキーマがどのように見えるかを確認しましょう:
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)
ご覧のとおり、インデックスが追加されています。ただし、これはその特定のノードでのみ発生したことを覚えておいてください。完全なスキーマ変更を実行するには、Galeraクラスターの残りのノードでこのプロセスに従う必要があります。最初のノードを終了するために、wsrep_OSU_methodをTOIに戻すことができます:
SET SESSION wsrep_OSU_method=TOI;
Query OK, 0 rows affected (0.00 sec)
プロセスの残りの部分は同じであるため、ここでは示しません。セッションレベルでRSUを有効にし、ALTERを実行し、TOIを有効にします。さらに興味深いのは、変更に互換性がない場合にどうなるかです。スキーマをもう一度簡単に見てみましょう:
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)
1つのノードで列「k」のタイプをINTからVARCHAR(30)に変更するとします。
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
それでは、スキーマを見てみましょう:
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)
すべてが期待どおりです。「k」列がVARCHARに変更されました。これで、この変更がガレラクラスターに受け入れられるかどうかを確認できます。それをテストするために、残りの変更されていないノードの1つを使用して、次のクエリを実行します。
mysql> INSERT INTO sbtest1.sbtest1 (k, c, pad) VALUES (123, 'test', 'test');
Query OK, 1 row affected (0.19 sec)
何が起きたのか見てみましょう。 確かに見栄えがよくありません。ノードがダウンしています。ログには詳細が表示されます:
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…
ご覧のとおり、Galeraは、列をINTからVARCHAR(30)に変換できないという事実について不満を述べました。ライトセットを4回再実行しようとしましたが、当然のことながら失敗しました。そのため、Galeraは、ノードの整合性が損なわれ、ノードがクラスターから追い出されたと判断しました。ログの残りの内容は、このプロセスを示しています:
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.
もちろん、ClusterControlはそのようなノードの回復を試みます-回復にはSSTの実行が含まれるため、互換性のないスキーマの変更は削除されますが、元の状態に戻ります-スキーマの変更は元に戻されます。
ご覧のとおり、RSUの実行は非常に単純なプロセスですが、その下ではかなり複雑になる可能性があります。スキーマの変更に互換性がなかったという理由だけでノードが失われないようにするには、いくつかのテストと準備が必要です。