スマートスタイル TECH BLOG

データベース&クラウド技術情報

MySQL ReplicaSetで簡易的なフェイルオーバ機能を実現する

MySQLの非同期レプリケーション環境におけるHAの実現方法としては一例として以下のようなソフトウェアがあります。

この内MHA、mysqlfailoverはすでに開発が停止しており、MaxScaleはBSLライセンスであるため気軽に利用するには敷居が高いように思います。
Orchestratorは現在も開発が活発であり、ユーザも多いかと思います。
一方で多機能な分設定が複雑であったり、バックエンドのデータベースを必要とするなど、小規模なMySQLクラスタで気軽に利用するにはToo muchな印象を受けます。

そこで今回は、MySQL ReplicaSetと、MySQL Shellの機能を利用して小規模な環境でのHA機能の実装を試してみたいと思います。
なお、記事中に紹介した内容はあくまで実験的なものです。

MySQL ReplicaSet

MySQL ReplicaSet は、これまでの一般的な非同期レプリケーションの環境を、MySQL InnoDB ClusterのようにMySQL Routerと統合し、MySQL Shell から管理可能とした複合的なソリューションです。
データベース部分はこれまでの非同期レプリケーションと何ら変わり無いものです。

MySQL ReplicaSetはMySQL Routerと連携することにより、プライマリ・セカンダリ間の負荷分散やプライマリへのルーティングが可能ですが、MySQL Routerには自動フェイルオーバ機能は提供されていません。

https://dev.mysql.com/doc/refman/8.0/ja/mysql-innodb-replicaset-introduction.html

MySQL レプリケーションに基づいている場合、InnoDB ReplicaSet には単一のプライマリがあり、複数のセカンダリインスタンスにレプリケートされます。
InnoDB ReplicaSet では、自動フェイルオーバーやマルチプライマリモードなど、InnoDB クラスタ が提供するすべての機能が提供されるわけではありません。
ただし、インスタンスの構成、追加、削除などの機能も同様にサポートされています。
たとえば、障害が発生した場合は、セカンダリインスタンスに手動でスイッチオーバーまたはフェイルオーバーできます。

そのため、もし自動的にMySQL Routerの接続をセカンダリサーバに切り替えたいとなった場合、何らかの仕組みをユーザ側で実装する必要が発生します。

MySQL Shellの setPrimaryInstance(), forcePrimaryInstance() について

MySQL Shell APIには、 setPrimaryInstance() , forcePrimaryInstance() という、MySQL ReplicaSetのプライマリを他のセカンダリに切り替えるための関数が存在します。

setPrimaryInstance

このコマンドは、レプリカセットのPRIMARYの安全な切り替えを実行します。
現在のPRIMARYはSECONDARYに降格されて読み取り専用になり、プロモートされたインスタンスは読み取り/書き込みマスターになります。
他のすべてのSECONDARYインスタンスは、新しいPRIMARYから複製するように更新されます。

スイッチオーバー中に、プロモートされたインスタンスは古いPRIMARYと同期され、トポロジの変更がコミットされる前に、PRIMARYに存在するすべてのトランザクションが確実に適用されます。
その同期ステップに時間がかかりすぎるか、SECONDARYインスタンスのいずれかで不可能な場合、スイッチは中止されます。
これらの問題のあるSECONDARYインスタンスは、フェイルオーバーを可能にするために、修復するか、レプリカセットから削除する必要があります。

安全なスイッチオーバーを可能にするには、すべてのレプリカセットインスタンスがシェルから到達可能であり、一貫したトランザクションセットを持っている必要があります。
PRIMARYが使用できない場合は、代わりにforce_primary_instance()を使用して強制フェイルオーバーを実行する必要があります。

forcePrimaryInstance

このコマンドは、現在のPRIMARYが使用できず、復元できない災害シナリオで、レプリカセットのPRIMARYの強制フェイルオーバーを実行します。
指定された場合、ターゲットインスタンスはPRIMARYにプロモートされ、他の到達可能なSECONDARYインスタンスは新しいPRIMARYに切り替えられます。
ターゲットインスタンスには、到達可能なインスタンスの中で最新のGTID_EXECUTEDが設定されている必要があります。
そうでない場合、操作は失敗します。
ターゲットインスタンスが指定されていない(またはnullである)場合、最新のインスタンスが自動的に選択されてプロモートされます。

強制フェイルオーバー後、古いPRIMARYは新しいPRIMARYによって無効と見なされ、レプリカセットの一部にすることはできなくなります。
インスタンスがまだ使用可能な場合は、レプリカセットから削除して、新しいインスタンスとして再度追加する必要があります。
フェイルオーバー中に新しいPRIMARYに切り替えることができなかったSECONDARYインスタンスがあった場合、それらも無効と見なされます。

注意
強制フェイルオーバーは潜在的に破壊的なアクションであり、最後の手段としてのみ使用する必要があります。
古いPRIMARYには、プロモートされているSECONDARYにまだ複製されていないトランザクションが含まれている可能性があるため、
フェイルオーバー後にデータが失われる可能性があります。
さらに、障害が発生したと推定されたインスタンスが引き続き更新を処理できる場合、たとえば、インスタンスが配置されているネットワークはまだ機能しているが、
シェルから到達できないため、プロモートされたクラスターから分岐し続けます。
分岐したトランザクションセットを回復または再調整するには、手動による介入が必要であり、障害が発生したMySQLサーバーを回復できたとしても、実行できない場合があります。
多くの場合、強制フェイルオーバーが必要な災害から回復するための最も速くて簡単な方法は、そのような分岐したトランザクションを破棄し、
新しく昇格したPRIMARYから新しいインスタンスを再プロビジョニングすることです。

要約すると、setPrimaryInstance はプライマリをセカンダリに降格してからread_only=1とし、プライマリ候補のセカンダリのトランザクションが適用されるのを待機してからプライマリに昇格するという動作になり、 forcePrimaryInstanceは、現在のプライマリをレプリカセットから破棄し、プライマリ候補のセカンダリのトランザクションが適用されるのを待機してからプライマリに昇格する という動作になります。
前者はプライマリに接続して設定を行う必要があるためスイッチオーバ用、後者はフェイルオーバ用の関数と言えます。

まずはこれらの関数を試してみましょう。

検証環境について

今回の検証環境は以下の構成です。

OSにはCentOS8を使用しました。

レプリカセットの構築については、以前の記事でご紹介していますので、参考にしていただければと思います。

MySQL の InnoDB ReplicaSet を構築してみる

以下が初期状態です。

プライマリダウン時にsetPrimaryInstance()を実行

node-0(PRIMARY)systemctl stop mysqld を実行すると、statusがUNREACHABLEとなり、node-1(SECONDARY) ではレプリケーションソースへ接続不可のエラーが発生していますが、instanceRoleは切り替えられていないことが確認できます。

MySQL Routerは接続先をinstanceRoleにより振り分けるため、PRIMARYへの接続(6446ポート)はエラーになります。
SECONDARYへの接続(6447ポート)は接続可能です。

この状態からMySQL Shellでセカンダリに接続し、 setPrimaryInstance() を実行してみましたが、やはりプライマリへの操作が行えないためにエラーとなりました。
エラーメッセージの内容の通り、このような障害ケースでは forcePrimaryInstance() を使いましょう。

プライマリダウン時にforcePrimaryInstance()を実行

では引き続き forcePrimaryInstance() を実行します。

成功したようです。
node-0(旧PRIMARY) のstatusがINVALIDATEDになり、node-1(旧SECONDARY)のinstanceRoleがPRIMARYになりました。

これによってMySQL Routerの6446への接続もnode-1にルーティングされるはずです。

想定通りの動作となりました。

なお、このように一度INVALIDATEDとなったインスタンスは再度接続可能な状態とした上で、rejoinInstanceで再度クラスタに参加させる必要が発生します。
一度正常な状態に戻すためにrejoinInstance()を実行します。

スクリプトによる実装

forcePrimaryInstance() を利用することで以下のbashスクリプト(myfailover.sh)のように簡単にフェイルオーバ用プログラムを作成することができました。
スクリプト内では以下のソフトウェアを使用しています。

  • jq
  • MySQL Shell

以下の仕様としました。

  1. 起動時myfailover.statusファイルにcandidates(接続候補)の内最初に接続できたノードでdba.getReplicaSet.status()の結果を出力
  2. myfailover.statusファイルから現在のプライマリノードのIPアドレスを取得
  3. “select 1” をPrimaryに実行し成功したら一定時間SLEEPし、2から繰り返し
  4. 3が失敗した場合にSECONDARYのノードに接続しforcePrimaryInstance()を実行しプログラムを終了
  5. 4が失敗したらエラー終了

実行してみましょう。

現在のプライマリはnode-0です。

それではフェイルオーバをテストします。
MySQL Routerの6446ポートに接続し続ける処理を実行します。

node-0のmysqldを停止します。

以下のように接続エラーが発生します。

myfailover.logでは、接続エラーをきっかけにフェイルオーバが実行されたログが出力されました。

接続テストの方では無事MySQL Routerが新プライマリに接続を向けてくれました。

その後のヘルスチェックも正常に行われたようです。

色々と考慮すべき点はあるものの、100行にも満たないスクリプトで簡単に自動フェイルオーバ機能を実装できました。

まとめ

フルスタックなHAソフトウェアを採用する場合、構築や技術習得に一定の時間が必要です。

しっかりとしたHA基盤を利用するのが最も良いことは言うまでもありませんが、現在何の仕組みも無く運用されている場合には、まずはシンプルなスクリプトを利用するのも良いかもしれません。

また、例えば以下のPercona社が開発したPacemaker用Resource Agentのプログラムがありますが、MySQL Routerがフェイルオーバを行い、MySQL Shell APIがレプリケーションを考慮したプライマリのプロモートを行ってくれる現在では、もっと簡単にResource Agentを書き直せるでしょう。

https://github.com/Percona-Lab/pacemaker-replication-agents/blob/master/doc/PRM-setup-guide.rst

ぜひMySQL Shell APIをお楽しみください!

Return Top