UCO-Tech(MySQL GTIDレプリケーションが崩れたら)


たまには新しい技術じゃなくて、ちょっと前に学んだ技術についても、
復習の意味を兼ねて書いてみます。
今回のテーマはGTIDレプリケーションです。

MySQLのレプリケーションは非常に簡易で便利な高可用性ソリューションですが、

・間違ってスレーブでクエリを実行してしまった
・マスターのバイナリログが欠損した
・バグを踏んだ

などの理由で、レプリケーションが崩れてしまった!
ということを経験した方も多いのではないでしょうか。
特に、5.6から導入されたGTIDレプリケーションについては、
まだそれほどノウハウがたまっていない、ということもあるかと思うので、
今回は、GTIDレプリケーションの復旧方法について書いてみました。

復旧方法として、一番シンプルなのは、
スレーブのデータディレクトリを一旦削除し、
マスターのデータディレクトリと置き換えることです。
この方法は、シンプルで間違いがないですが、
マスター・スレーブ共にサービス停止を伴うこと、
データの転送にはデータ容量に応じた時間がかかることが
デメリットです。
この方法をとる場合、

1.スレーブ停止
2.スレーブのデータディレクトリをrm -r
3.マスターのデータディレクトリをスレーブにscp
4.スレーブにコピーしたデータディレクトリ中のauto.cnfを削除
5.スレーブのMySQLを起動
6.スレーブでchange master to でマスターを指定、start slave

で復旧は完了となります。
これは、GTIDレプリケーションであろうが、
以前のポジションベースのレプリケーションであろうが、同じ事です(4を除いて)。

他には、マスターでmysqldumpコマンドを実行してバックアップを取得し、
これをスレーブに流し込む、という方法もあります。
マスターがInnoDBテーブルしか利用していない、
もしくはMyISAMテーブルを利用していても、このテーブルには確実に更新が来ない時間を確保できる場合、
mysqldumpコマンドで一貫性のあるバックアップを取得できますので、
これを、空っぽにしたスレーブのMySQLに読ませ、マスターに接続し直せば
レプリケーション復旧です。

この方法であればマスターを停止せずにスレーブを再構築できますが、
mysqldumpコマンドでのバックアップ取得は時間を要する上、
ダンプファイルの転送にも時間がかかってしまうため、
所要時間はデータディレクトリの再配置よりも長くなります。
これも、ほとんどポジションベースのレプリケーションでも同じです。

しかし、現実問題として、
些細なクエリを誤って実行してしまっただけ、だとか、
このクエリさえなかったことにできれば、とかいうシチュエーションでは、
それだけのためにデータベースを全て再構築するのは悔しいですよね。

そこで、今回は、GTIDレプリケーションでクエリを飛ばす方法を書いてみようと思います。

ここで、GTIDレプリケーションを簡単におさらいしておきます。
GTIDというのは、
65e0efe3-0561-11e4-ab46-000c298f7114:1
のような形式で表される、トランザクションに対して振られるIDです。
この例で言えば、
65e0efe3-0561-11e4-ab46-000c298f7114
の部分はサーバのUUIDであり、
どのMySQLサーバで実行されたトランザクションであるかを示します。
:1
の部分は、そのトランザクションの番号です。
例えば、
65e0efe3-0561-11e4-ab46-000c298f7114:1-3835
であれば、
65e0efe3-0561-11e4-ab46-000c298f7114のUUIDを持つMySQLサーバで実行された、
1番から3835番までのトランザクション、
ということを意味するわけです。
ちなみに、UUIDは各MySQLサーバのデータディレクトリにある、auto.cnfファイルに記載されています。

マスターから来たクエリを飛ばしたい時には、
飛ばしたいGTIDを特定し、スレーブでそのGTIDに対応するダミーのトランザクションを実行することによって、
スレーブのMySQLは、マスターから来たクエリのGTID(飛ばしたいクエリのもの)は既に自分では実行済み、
と判断してくれますので、
これによってクエリを飛ばす、と言うことになります。

さて、では実践に入っていきましょう。
マスターにのみhogeデータベースが存在する状態を作り、
これをdrop databaseするコマンドがマスターで成功、スレーブで失敗する、
という状態にすることによって、
レプリケーションを崩します。

つまり、こんな状態です。

mysql-master> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| hoge               |
| mysql              |
| performance_schema |
| sbtest             |
| test               |
+--------------------+
6 rows in set (0.00 sec)

mysql-slave> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sbtest             |
| test               |
+--------------------+
5 rows in set (0.01 sec)

mysql-slave> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.2.201
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-failover1-bin.000001
          Read_Master_Log_Pos: 16102402
               Relay_Log_File: mysql-failover2-relay-bin.000002
                Relay_Log_Pos: 16102632
        Relay_Master_Log_File: mysql-failover1-bin.000001
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
~~~~~~~~~~(略)~~~~~~~~~~
                  Master_UUID: 65e0efe3-0561-11e4-ab46-000c298f7114
~~~~~~~~~~(略)~~~~~~~~~~
           Retrieved_Gtid_Set: 65e0efe3-0561-11e4-ab46-000c298f7114:1-18243
            Executed_Gtid_Set: 65e0efe3-0561-11e4-ab46-000c298f7114:1-18243
                Auto_Position: 1
1 row in set (0.00 sec)

マスターにのみ「hoge」データベースが存在しますが、
まだレプリケーションは正常に稼動しています。
※ MySQLではデータ不整合があっても、
その不整合を突くようなクエリが投げられない限りはエラーは発生しません。

ここでマスターでdrop database hogeを実行します。
すると、スレーブのshow slave status結果ではエラーが表示されます。

mysql-slave> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.2.201
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-failover1-bin.000001
          Read_Master_Log_Pos: 16407917
               Relay_Log_File: mysql-failover2-relay-bin.000002
                Relay_Log_Pos: 16102632
        Relay_Master_Log_File: mysql-failover1-bin.000001
             Slave_IO_Running: Yes
            Slave_SQL_Running: No
~~~~~~~~~~(略)~~~~~~~~~~
                   Last_Errno: 1008
                   Last_Error: Error 'Can't drop database 'hoge'; database doesn't exist' on query. Default database: 'hoge'. Query: 'drop database hoge'
~~~~~~~~~~(略)~~~~~~~~~~
                  Master_UUID: 65e0efe3-0561-11e4-ab46-000c298f7114
             Master_Info_File: mysql.slave_master_info
~~~~~~~~~~(略)~~~~~~~~~~
           Retrieved_Gtid_Set: 65e0efe3-0561-11e4-ab46-000c298f7114:1-18590
            Executed_Gtid_Set: 65e0efe3-0561-11e4-ab46-000c298f7114:1-18243
                Auto_Position: 1
1 row in set (0.00 sec)

これで無事(?)レプリケーション障害が発生したので、
ここからがレプリケーションの復旧手順です。
(ここで、マスターへの更新は続いているものとします)

まず、スレーブの状態を確認しましょう。
上で確認したshow slave statusの結果から、以下のことが分かります。

・バイナリログの受け取りはできているが、実行の段になってつまづいている(Slave_IO_Running、Slave_SQL_Runningより)。
・スレーブではGTID:18243番までのクエリが実行済みである(Executed_Gtid_Setより)。
・スレーブではGTID:18590番までのクエリを受け取っている(Retrieved_Gtid_Setより、必ずしもクエリは実行されていない)。

このことから、マスターで実行された、18244番のクエリが問題になっている可能性が高い、と
推察できます。
さらに、「Last_Error」の項目から、そのクエリは「drop database hoge」であることも分かります。

ここで、復旧方針決定にあたってはデータをどう整合させるか、という判断が必要になります。
今回は、失敗したクエリがdrop database hogeであり、
スレーブにはもうすでにhogeデータベースがないことから、
このdrop database hogeのSQLを飛ばせば無事復旧することが見込まれます。

状況判断のための情報取得の手段として、
バイナリログを確認して前後に実行されたクエリを確認することは有用であると言えます。

mysqlbinlog --no-defaults バイナリログファイル名 | less

DBのREDOログを覗いているわけなので、大量のデータを開くことになる点に注意してください。
mysqlbinlogコマンドはリレーログに対してでも実行できるので、
マスターで実行するのがためらわれる場合は、
スレーブのリレーログを確認しても良いでしょう。

ちなみに、他にも復旧方法はあって、
例えば、直後に削除されるのを承知で、スレーブにhogeデータベースを作成する手もあります。
ですが、スレーブにhogeデータベースを作成する方法は、
スレーブでcreate database hoge、start slave、とするだけで面白くないので、
今回はクエリを飛ばす方法での復旧をしてみたいと思います。

実際に復旧のためのオペレーションをするにあたっては、
一度スレーブでstop slaveコマンドを実行して、
レプリケーションを完全停止させておいたほうがいいでしょう。
(上の例で言えば、SQLスレッドは停止していますが、I/Oスレッドはまだ健在です)

mysql-slave> stop slave;
Query OK, 0 rows affected (0.00 sec)

mysql-slave> show slave status\G
~~~~~~~~~~(略)~~~~~~~~~~
             Slave_IO_Running: No
            Slave_SQL_Running: No
~~~~~~~~~~(略)~~~~~~~~~~

さて、それではスレーブでの18244番トランザクション実行を飛ばしましょう。
考え方としては、スレーブで18244番に空トランザクションをダミーとして入れておく、
ということになります。
通常は自動でセットされるGTID_NEXTパラメータを、一時的に手動設定します。

mysql-slave> set @@session.gtid_next='65e0efe3-0561-11e4-ab46-000c298f7114:18244';
Query OK, 0 rows affected (0.00 sec)

これで、次に実行されるトランザクションのGTIDは18244番、となっていますので、
ここで空トランザクションを実行します。

mysql-slave> commit;
Query OK, 0 rows affected (0.00 sec)

GTID_NEXTが自動設定されるよう、設定を元に戻します。

mysql-slave> set @@session.gtid_next=automatic;
Query OK, 0 rows affected (0.00 sec)

ここで、show slave statusの結果をみると、、、

mysql-slave> show slave status\G
*************************** 1. row ***************************
~~~~~~~~~~(略)~~~~~~~~~~
           Retrieved_Gtid_Set: 65e0efe3-0561-11e4-ab46-000c298f7114:1-18590
            Executed_Gtid_Set: 65e0efe3-0561-11e4-ab46-000c298f7114:1-18244
~~~~~~~~~~(略)~~~~~~~~~~

となっており、スレーブのExecuted_Gtid_Setの値から、
18244番トランザクションが実行されたと認識されていることが分かります。

これでスレーブのトランザクションを再開しても、
マスターの18244番トランザクション、
すなわち今回のデータ不整合の原因になった、
「drop database hoge」
クエリは実行されないはずです。

mysql-slave> start slave;
Query OK, 0 rows affected (0.00 sec)

さて、ちゃんとレプリケーションが再開されているかどうか見てみると、、、?

mysql-slave> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.2.201
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-failover1-bin.000001
          Read_Master_Log_Pos: 16407917
               Relay_Log_File: mysql-failover2-relay-bin.000003
                Relay_Log_Pos: 1362
        Relay_Master_Log_File: mysql-failover1-bin.000001
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
~~~~~~~~~~(略)~~~~~~~~~~
                  Master_UUID: 65e0efe3-0561-11e4-ab46-000c298f7114
             Master_Info_File: mysql.slave_master_info
~~~~~~~~~~(略)~~~~~~~~~~
           Retrieved_Gtid_Set: 65e0efe3-0561-11e4-ab46-000c298f7114:1-18590
            Executed_Gtid_Set: 65e0efe3-0561-11e4-ab46-000c298f7114:1-18590
~~~~~~~~~~(略)~~~~~~~~~~

Executed_Gtid_SetがちゃんとRetrieved_Gtid_Setに追いついていますね。
Slave_IO_Running、Slave_SQL_RunningがYesになっていることからも、
レプリケーションは復旧できた、と考えて良さそうです。

以上でレプリケーションの復旧手順は完了です。

何らかの理由で、あるトランザクションを飛ばしてレプリケーションを復旧させたい場合はご参考になるかと思います。

では。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です