GTID ( 3 )

August 07, 2018

MySQL 5.7 における GTID

MySQL 5.7 では GTID に関していくつか新しい機能が追加されている。

GTID のオンライン有効化

MySQL 5.7 では MySQL サーバを停止することなく、オンラインで GTID の有効化ができるようになった。
手順は以下のようになる。

1. すべての SQL 文を GTID 互換にする
2. マスターとスレーブで GTID が生成されるようにする
3. フルバックアップを取得する
4. GTID レプリケーションに切り替える

また、GTID をオンラインで無効化するには以下の手順をふむ必要がある。(有効化のときとほぼ逆のことをする)

1. バイナリログのファイル名とポジションを使ったレプリケーションに切り替える
2. マスターとスレーブで GTID が生成されないようにする
3. フルバックアップを取得する
4. enforce_gtid_consistency をオフにする

具体的な設定方法については以下の記事が参考になる。

昇格しないスレーブの GTID 管理の効率化

MySQL 5.7 では昇格しないスレーブ上での GTID の管理が効率化している。
GTID は基本的にバイナリログの中に格納されるが、GTID を記録するためだけに全てのスレーブにてバイナリログを有効化するのはディスクスペースの無駄となる。
MySQL 5.7 では、バイナリログを有効化しなくても GTID を記録できるように mysql.gtid_executed というテーブルが追加された。バイナリログが有効になっていないスレーブでは GTID はこのテーブルに記録される。

mysql> SHOW CREATE TABLE mysql.gtid_executed \G;  
*************************** 1. row ***************************  
       Table: gtid_executed  
Create Table: CREATE TABLE `gtid_executed` ( 
  `source_uuid` char(36) NOT NULL COMMENT 'uuid of the source where the transaction was originally executed.',  
  `interval_start` bigint(20) NOT NULL COMMENT 'First number of interval.',  
  `interval_end` bigint(20) NOT NULL COMMENT 'Last number of interval.',  
  PRIMARY KEY (`source_uuid`,`interval_start`)  
) ENGINE=InnoDB DEFAULT CHARSET=latin1  
1 row in set (0.00 sec)  

発信源となったサーバーの UUID 及びトランザクション ID の範囲を示す 2 つの整数値がカラムとして定義されている。
なお、トランザクションを実行するたびに gtid_executed テーブルに行が追加されると、テーブルが巨大なサイズになってしまうため、gtid_executed テーブルは定期的にスキャンされ、1つの範囲で表現できるトランザクション ID はマージされるようになっている。
このマージ操作を行うタイミングは gtid_executed_compression_period で決まっており、デフォルトは 1000 となる。つまり 1000 トランザクションに 1 回マージ操作が行われる。

OK パケットに GTID

MySQL のレプリケーションは非同期であり、同期レプリケーションではない。また準同期レプリケーションモードもあるが、その場合も同期するのは IO スレッドまでであり、SQL スレッドがバイナリログを再生して、トランザクションが完了するのを待つわけでは無い。つまり、マスター上でトランザクションを実行した直後にスレーブへ問い合わせをすると、まだそのトランザクションが実行される前のデータしか参照できない可能性がある。

そこで、マスター上でコミットしたトランザクションがスレーブに伝搬されるのを待ってからスレーブに問い合わせる方法として GTID を用いるものが考案された。
そのためにまず必要なのは、マスター上でコミットしたトランザクションの GTID を取得することであり、それを実現するために MySQL 5.7 ではプロトコルに改良が加えられ、OK パケット (MySQL のプロトコルにおいて、クエリ実行後に送信されるパケット) に GTID を含める方法がとられている。

デフォルトでは OK パケットに GTID は含まれない。GTID を含めるには gtid_mode が ON になっており、session_track_gtids オプションを有効化する必要がある。OK パケットから GTID を取得するには OWN_GTID (自分自信が保有する RW トランザクションのみ GTID を返す) か ALL_GTID (Read-Only なトランザクションであっても最新の GTID Set を返す) を指定する必要がある。

$ cat /etc/my.cnf | grep -v ^#  
[mysqld]  
datadir=/var/lib/mysql  
socket=/var/lib/mysql/mysql.sock  
symbolic-links=0  
  
server_id=106  
gtid_mode=ON  
enforce_gtid_consistency  
log_bin=mysql-bin  
expire_logs_days = 7  
log_slave_updates  
session_track_gtids=OWN_GTID  
  
[mysqld_safe]  
log-error=/var/log/mysqld.log  
pid-file=/var/run/mysqld/mysqld.pid  

実際に SHOW VARIABLES で確認してみると、sessiontrackgtids が OWN_GTID になっていることが確認できる。

mysql> SHOW VARIABLES LIKE '%gtid%';  
+----------------------------------+-----------+  
| Variable_name                    | Value     |  
+----------------------------------+-----------+  
| binlog_gtid_simple_recovery      | ON        |  
| enforce_gtid_consistency         | ON        |  
| gtid_executed_compression_period | 1000      |  
| gtid_mode                        | ON        |  
| gtid_next                        | AUTOMATIC |  
| gtid_owned                       |           |  
| gtid_purged                      |           |  
| session_track_gtids              | OWN_GTID  |  
+----------------------------------+-----------+  
8 rows in set (0.01 sec)  

WAIT_FOR_EXECUTED_GTID_SET

OK パケットや gtid_executed システム変数から GTID を取得した場合、それをスレーブが実行するまで待つ方法について、MySQL 5.6 では WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS という関数が実装された。
これは SQL スレッドがこれらの GTID を実行するまで待ち、待っている間に SQL スレッドが実行したトランザクション数を返す。タイムアウトした場合は -1 が返却され、SQL スレッドが動作していなかったり GTID モードが有効になっていない場合、NULL が返却される。
GTID が実行されたのを待つというだけであれば問題ないが、SQL スレッドが停止しているときに NULL が返却されるのはよろしくなくて、SQL_THREAD_WAIT_AFTER_GTIDS の呼び出しをリトライするなどの処理が必要になってくる。

そこで、MySQL 5.7 では、SQL スレッドの動作に無関係な WAIT_FOR_EXECUTED_GTID_SET という関数が追加された。これは成功なら 0 、タイムアウトなら 1 を返す。スレーブ上でこの関数を実行し、0 が返却されれば、マスターの更新が反映されたあとの状態であることが保証される。


 © 2023, Dealing with Ambiguity