ちょっと硬派なコンピュータフリークのBlogです。

カスタム検索

2023-09-18

MySQL 8.1シリーズにおけるInnoDB Clusterとリードレプリカの融合(ただしMySQL Serverは8.0でOK)

MySQL本体の新機能ではないのだが、MySQL ShellとMySQL Routerのイノベーションリリース(バージョン8.1)によりInnoDB Clusterに対してリードレプリカを追加することができるようになったので、今回はそのことについて解説をしていこうと思う。

InnoDB Clusterとは

このブログではInnoDB Clusterとは何かということをそもそもまだ解説していなかったように思う。詳しいことはおいおい別の投稿で触れたいと思うが、InnoDB ClusterというのはMySQL Serverのグループレプリケーションを核にしたクラスタリング機能のことだ。MySQL Shellを用いてかんたんに構築でき、なおかつMySQL Routerを介して接続することにより、インスタンス障害が生じたときに自動的に接続先を振り替えることができる。イメージ的にはこんな感じ。 

 

グループレプリケーション

グループレプリケーションは高可用性を備えたクラスタリング機能であり、最大9ノードまで構成することができる。シングルプライマリモードとマルチプライマリモードがあり、シングルプライマリモードでは一つのインスタンスをRead-Write用、その他はRead-Only用としてアクセスすることが可能だ。マルチプライマリモードではすべてのインスタンスに対してR/W可能となる。競合検出機能を備えており、複数のノードで更新がかぶってしまった場合には検出され、トランザクションはロールバックされるので、非同期レプリケーションを用いたマルチマスター構成のようなデータの不整合が起きる心配がない。

一見すると「なんて素晴らしいんだ!!」と思ってしまうが、何事もトレードオフがあるのが世の常なので手放しで喜んではいけない。マルチプライマリモードには制限事項としてSERIALIZABLE分離レベルがサポートされない、ギャップロックがノード間で共有されない(そのためファントムが起きる)などがあり、そういった制限を意識して開発する必要があるので、アプリケーションの開発の敷居が一段上がってしまう。なので個人的には従来どおりの使い方ができるシングルプライマリモードがオススメだ。シングルプライマリモードではR/WとR/Oで処理するインスタンスが別れてしまうのが難点だが、一度インスタンスにアクセスしてしまえば従来と使い方に違いはない。

そもそもの話として、マルチプライマリモードを用いても更新性能が大きく向上することは期待できないので、あまりマルチプライマーモードに固執する必要もないだろう。なぜ更新性能の向上が期待できないかというと、グループレプリケーションでは参加しているすべてのインスタンス上で同量の書き込みが起きるからだ。ひとつのインスタンス上で生じた更新はすべてのインスタンス上で複製されなければならず、どのインスタンス上で更新を行おうともディスク上での更新量はトータルでは変わらないのである。そんなわけで特別な要件が無い限りは、シングルプライマリモードを選択してほしい。ただし、従来からの非同期のマルチマスターレプリケーションをHA用途で使っているのなら、マルチプライマリモードのグループレプリケーションの方が(データの不整合が起きないので)ずっとマシなので、移行を検討してもいいだろう。

MySQL Router

MySQL Routerはその名前の通り、MySQL Serverへの接続を振り分ける機能を持ったソフトウェアだ。InnoDB Clusterとして構築することでグループレプリケーションと連携することが可能で、R/WとR/Oでそれぞれ別ポートで待ち受けしており、クライアントアプリケーションはこれから行う処理の内容に従って接続するポートを選択し、接続先をプライマリとセカンダリへ振り分けることができる。複数のセカンダリインスタンスに対してラウンドロビンによる負荷分散をすることも可能だ。

MySQL Routerは定期的にMySQL Serverのステータスをポーリングしており、障害を検知するとアプリケーションの接続先を自動的に切り替える。なのでアプリケーションはMySQL Routerを接続先にするだけで良いので、ノード障害発生時のネットワーク切り替えなどを自前で実装する必要がなくなる。ちなみにデフォルトのポーリング間隔は0.5秒だ。ひとつのMySQL Routerに対して複数のアプリケーションから接続することも可能(上の図の左側)だが、その場合はMySQL Routerに接続の負荷が集中することになるので、アプリケーションのホストごとにMySQL Routerをインストールするという形態(上の図の右側)も可能だ。

InnoDB Clusterに対するリードレプリカ

さて、グループレプリケーションは最大9ノードまでの構成であり、大規模なシステムでのスケールアウトに対応できるとは言い難い。グループレプリケーションはPAXOSベースの合意形成プロトコルを用いているので、高可用性を担保するには最低3ノードが必要になるのだが、トランザクションのCOMMIT実行時のオーバーヘッドを考えると、あまりノード数を増やしたくもない。個人的には予備も含めた4ノードか5ノードが良いと思う。そんなわけで、データベースの参照負荷をスケールアウトをさせたい場合には、互いに同期を取る必要があるグループレプリケーションではなく、スケールアウトの実績のある非同期レプリケーションを利用したいところだ。そんなワケで、MySQL 8.1のイノベーションリリースでは、待望のリードレプリカが登場したというわけだ。リードレプリカとは非同期レプリケーションにおけるレプリカ(従来の呼び方ではスレーブ)のことだ。何となくカッコいい呼び方になった気がするので、皆さんもぜひそう呼んで欲しい。InnoDB Clusterにリードレプリカをつけたときの基本的なイメージはこんな感じ。


InnoDB Clusterのリードレプリカは、接続先のインスタンスがダウンした場合に他のインスタンスへ接続し直す、いわゆる自動フェイルオーバーに対応している。MySQL Server自身の話では、実は8.0.22の時点ですでにレプリケーションの自動フェイルオーバーが初期実装されていた。8.0.22の時点では、現在のソースへの接続が切れた場合に、管理者が指定したソースへの切り替えが行われるというだけの機能だったのだが、その次のバージョンである8.0.23においてグループレプリケーションの構成を認識して、適切なインスタンスへ自動的にフェイルオーバーする機能が実装されたた。InnoDB Clusterのリードレプリカが対応しているのもこのバージョンからととなっている。なお、8.0.27以降のバージョンとそれより古いバージョンでは、グループレプリケーションに対する自動フェイルオーバーの操作方法が異なっているのだが、MySQL Shellを介して操作した場合にはその違いは吸収されているので、自前でやらずMySQL Shellを使っていれば構築・管理は万事OKだ。

余談だが、MySQL Routerを介するパターンでは、システムの規模が巨大になりMySQL Routerのインスタンス数も膨大にになってしまうと、MySQL Routerによるステータスのポーリングがボトルネックになってスケールしなくなってくるかも知れないので注意して欲しい。(例えば数千のMySQL Routerが0.5秒ごとにポーリングを行ってしまうのを想像して欲しい。)そんな場合には、リードレプリカに対する負荷分散は従来のようにLVSなどを使ってロードバランスするというような構成のほうが良い結果が得られる可能性が高い。上の図は、中規模までのお手軽構成だと考えてもらったほうが良いだろう。MySQL Routerのインスタンス数には気を配って欲しい。

MySQLのレプリケーションと言えば、マスター(最近はソースと呼ぶんだよ!)がダウンしたときにスレーブ(最近はレプリカと呼ぶんだよ!)のひとつを昇格させるというのがスケールアウト構成での高可用性のための戦略だったが、自動でそれを実現するためのツールはなかなか良いものは無かった。公式からはmysqlfailoverぐらいしかなくて、これは出来の問題であまり使い物にならなかったので松信さん作のMHAを使っていた人も多かったと思うのだが、そのMHAももはやメンテされなくなって久しい。また、MHA自身はマスターの昇格(マスター障害時に新しいマスターの選定とスレーブからの接続先切り替え)を行うのみであり、クライアントアプリケーションからの接続の切り替えについてはLVSなどを用いて別途行う必要があった。

InnoDB Clusterとリードレプリカの組み合わせなら、マスターの昇格に相当する部分も、アプリケーションからの接続の切り替えも自動で行ってくれるという、大変便利な代物となっている。非同期レプリケーションをスケールアウト目的で使用していて、マスターのインスタンス障害時に自動昇格したいという場合には、ぜひInnoDB Cluster+リードレプリカの構成を検討してみて欲しい。

バージョン要件

InnoDB Clusterに対してリードレプリカを追加するという機能はMySQL Shell 8.1.0から追加された機能であり、リードレプリカに対してルーティングするという機能はMySQL Router 8.1.0で追加された機能だ。そのため、MySQL ShellとMySQL Routerはバージョン8.1が必須となっている。ただしMySQL Server自体は8.0.23以上のバージョンであれば対応が可能である。前述の通り、MySQL Serverにおいて必要な機能はレプリケーションの自動フェイルオーバーで、それはバージョン8.0.23で実装されているからだ。

クエリの挙動が変わったりするのが心配でMySQL Serverは8.0からバージョンを上げたくないというような場合には、MySQL Serverだけ8.0シリーズのまま据え置きで、MySQL ShellとMySQL Routerだけ8.1シリーズを使うという構成もアリだ。むしろイノベーションリリースの位置づけを考えると、次のLTSが出るまではむしろバージョンが混在する構成こそが主流になるだろう。

InnoDB Clusterの構築

ここからはInnoDB Cluster+リードレプリカの使い方について説明しようと思う。

InnoDB Clusterにリードレプリカを追加するには、肝心のInnoDB Clusterがなければ話が始まらない。というわけで自動フェイルオーバーつきのリードレプリカを試したい人は、まずInnoDB Clusterを構築して欲しい。InnoDB Clusterそのものについてはおいおい別の機会に書こうと思うので、今回はさらりと大まかな手順を解説しようと思う。

InnoDB Cluster構築手順の大まかな流れを説明すると次のような感じ。

  • MySQL Server用のホストを3つ以上準備する
  • MySQL Server 8.0.23以降のバージョンをインストールし、それぞれ適切な設定を施す
  • MySQL Shell用のホストを準備する(オプション)
    • MySQL Serverと同じホストを使っても構わない
  • MySQL Shellをインストールする
  • MySQL Shellからアクセスするためのユーザーを各インスタンス上で作成する(オプション)
    • ユーザーのホスト部にはワイルドカード(%)を用い、同じアカウントでアクセスできるようにする
  • MySQL Shell上で各種操作を行う
    • 各インスタンスに対してdba.configureInstance()を実行する
    • ひとつのインスタンスに接続し、dba.createCluster()を実行する
    • クローンを利用し、Cluster.addInstance()でその他のインスタンスを追加する
    • Cluster.status()でステータスを確認する

まず、MySQL ShellでMySQL Serverに接続するには、次のようにコマンドを実行する。mysqlコマンドと同じようにオプションを利用することができるが、コマンド名はmysqlshだ。

shell$ mysqlsh -uUSERNAME -pPASSWORD -hHOSTNAME

ユーザー名やパスワード、ホスト名は適宜置き換えて欲しい。パスワードは引数上で省略すると、プロンプトで入力を求められる。(これはmysqlコマンドなど従来のクライアントコマンドと同じ挙動だ。)接続するとデフォルトではJavaScriptモードになっており、各種管理タスクを実行することができる。どんなコマンドがあるかということについては、"\h"を実行してヘルプを表示して欲しい。JavaScriptの変数が定義されている場合には、dba.help()あるいはdba.help('configureInstance')というふうにコマンドを実行することにより、オブジェクト自身のヘルプや、定義されているコマンドのヘルプを参照することができる。どのユーザーで接続するときも同じ書式なのだが、初回の設定をする場合には対象のMySQL Serverと同じホスト上にインストールされたMySQL Shellからrootユーザーでアクセスするのがオススメだ。

shell$ mysqlsh -uroot -p -h127.0.0.1

dba.configureInstance()というのはInnoDB Clusterを実行するために必要なオプションを設定するためのコマンドだ。引数として接続先のインスタンスのURIを指定することもできるが、通常は対象のインスタンスにログインしてからdba.configureInstance()を使用する。パスワードを保存していない場合、パスワードが求められるので適宜入力して欲しい。

mysql-js> dba.configureInstance()
Configuring local MySQL instance listening at port 3306 for use in an InnoDB cluster...

This instance reports its own address as host1:3306

ERROR: User 'root' can only connect from 'localhost'. New account(s) with proper source address specification to allow remote connection from all instances must be created to manage the cluster.

1) Create remotely usable account for 'root' with same grants and password
2) Create a new admin account for InnoDB cluster with minimal required grants
3) Ignore and continue                                                                                                                                                                                                                       
4) Cancel

Please select an option [1]: 2
Please provide an account name (e.g: icroot@%) to have it created with the necessary
privileges or leave empty and press Enter to cancel.
Account Name: icadmin
Password for new account: ************
Confirm password: ************

applierWorkerThreads will be set to the default value of 4.

NOTE: Some configuration options need to be fixed:
+----------------------------------------+---------------+----------------+----------------------------+
| Variable                               | Current Value | Required Value | Note                       |
+----------------------------------------+---------------+----------------+----------------------------+
| binlog_transaction_dependency_tracking | COMMIT_ORDER  | WRITESET       | Update the server variable |
+----------------------------------------+---------------+----------------+----------------------------+

Do you want to perform the required configuration changes? [y/n]: y

Creating user icadmin@%.
Account icadmin@% was successfully created.

Configuring instance...
The instance 'host1:3306' was configured to be used in an InnoDB cluster.

この例のように、実行するとInnoDB Cluster管理用のアカウントの作成と、足りないオプションの設定が行われる。InnoDB Cluster管理用アカウントはすべてのインスタンスで同じ名前にする必要があるので気をつけて欲しい。管理用アカウントについては、アカウント名とパスワードだけがプロンプトで入力を求められているが、ホスト名の部分はワイルドカード(%)としてアカウントが作成される。余談だが、MySQL Shellのプロンプトはもっと派手なのだが、ここでは簡単のために「mysql-js>」というもので表現している。jsというのはJavaScriptモードの意味だ。

すべてのインスタンスの設定が完了したら、dba.createCluster()コマンドでクラスターを作成するのだが、InnoDB Clusterの構築は、ここで作成したアカウントで行うので一旦つなぎ直そう。再接続はコマンドを抜けなくても可能だ。

mysql-js> \c icadmin@localhost

つなぎ直したらdba.createCluster()を実行する。必須の引数はクラスター名だ。他にも色々オプションはあるのだが今回は割愛する。

mysql-js> var cluster = dba.createCluster('My_InnoDB_Cluster')

dba.createCluster()は、Clusterクラスのオブジェクトを返す。クラスターに対する各種操作はこのオブジェクトを通じて行うことになるので、この例のように変数に格納しておくと良い。変数はセッションをクローズするまで利用可能だ。クラスターに対してどのような操作ができるかについては、cluster.help()で確認して欲しい。

クラスターを作成した時点では、接続先のインスタンスがクラスターに参加した状態になっている。なので他のインスタンスをクラスターに追加する必要がある。

mysql-js> cluster.addInstance('icadmin@host2')
mysql-js> cluster.addInstance('icadmin@host3')

Cluster.addInstance()を実行すると、どのリカバリーメソッドを利用するかということを聞かれることになる。持っているGTIDセットがクラスターと追加するインスタンスで互換性が無い状態だと、選択肢はCloneかAbortになる。フルバックアップをリストアするなどして、GTIDセットが現時点のクラスターの部分集合になっている場合には、Incrementalという選択肢も出てくる。リカバリーメソッドはどちらが良いかというのは運用次第である。Cloneは文字通りクローンプラグインを利用して、インスタンスのデータをまるごとコピーしてくる方法だ。データの不整合なども起きない確実で簡単な方法であるが、運用中のインスタンスに対してデータ読み取りのための負荷がかかる。どのインスタンスからデータをコピーするかについては指定はできないのだが、プライマリのみの状態であればプライマリをドナーにする一方、セカンダリがあればそちらが優先される。ただしどのセカンダリーがドナーになるかは指定できない。セカンダリにはそれなりに負荷がかかっても良いという場合には、クローンを使うのが非常にお手軽だ。データがまだ少ない構築時などは迷わずクローンで構わないだろう。Incrementalは非同期レプリケーションを用いてデータを最新までキャッチアップする手法だが、GTIDを確実に取り扱う必要がある。既存のフルバックアップを活用できるので、運用中のインスタンスへの負荷がかからないという点ではとても魅力的な選択肢となる。この辺の話はおいおいInnoDB Clusterの話をするときに詳しい話を書こうと思う。

さて、インスタンスの追加が完了すればクラスターのステータスを確認しよう。こんな感じでCluster.status()が表示されればオッケーだ。

mysql-js> cluster.status()
{
    "clusterName": "hoge", 
    "defaultReplicaSet": {
        "name": "default", 
        "primary": "host1:3306", 
        "ssl": "REQUIRED", 
        "status": "OK", 
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.", 
        "topology": {
            "host1:3306": {
                "address": "host1:3306", 
                "memberRole": "PRIMARY", 
                "mode": "R/W", 
                "readReplicas": {}, 
                "replicationLag": "applier_queue_applied", 
                "role": "HA", 
                "status": "ONLINE", 
                "version": "8.0.34"
            }, 
            "host2:3306": {
                "address": "host2:3306", 
                "memberRole": "SECONDARY", 
                "mode": "R/O", 
                "readReplicas": {}, 
                "replicationLag": "applier_queue_applied", 
                "role": "HA", 
                "status": "ONLINE", 
                "version": "8.0.34"
            }, 
            "host3:3306": {
                "address": "host3:3306", 
                "memberRole": "SECONDARY", 
                "mode": "R/O", 
                "readReplicas": {}, 
                "replicationLag": "applier_queue_applied", 
                "role": "HA", 
                "status": "ONLINE", 
                "version": "8.0.34"
            }
        }, 
        "topologyMode": "Single-Primary"
    }, 
    "groupInformationSourceMember": "host1:3306"
}

MySQQL Routerの追加

InnoDB Clusterの素晴らしいところは、どのインスタンスに接続すれば良いかということを、ユーザーが考えなくても良い点だ。MySQL Routerを介して接続することで、Read-WriteあるいはRead-Onlyの処理を実行するのに適したインスタンスへ自動的に案内してくれる。インスタンスに障害が生じた際にも、素早く接続先のインスタンスを切り替えてくれるのである。(インスタンス障害時には当然一時的な切断が発生するのだが、再接続により適切なインスタンスに接続できる。)

MySQL RouterをInnoDB Clusterに接続するのはとても簡単である。まず、MySQL Routerを動かしたいホストにMySQL Routerをインストールし、その上でブートストラップという処理を行う。ブートストラップとはMySQL Routerの初期化処理のことだ。mysqlrouterコマンドをブートストラップモードで実行すると、一度InnoDB Clusterへ接続して種々のデータを取得し、自動設定を行うという仕組みだ。ブートストラップが終わるとMySQL Routerを起動するだけでルーティングが開始される。デフォルトの設定を変更したい場合には適宜mysqlrouter.confを編集すると良い。

ひとつ気をつけて欲しいのは、初期化には2つのタイプがあるということだ。指定したディレクトリ内に関連するすべてのファイルを作成する自己完結型のものと、デフォルトのインストールレイアウトを使用するものだ。tar.gz版のバイナリを用いる場合などには必然的に自己完結型のみとなる。RPMなどのパッケージを用いた場合には、デフォルトのインストールレイアウトを利用するのが面倒臭くなくて良い。Windowsについては今回は割愛。

自己完結型のものには、自動的に生成された設定ファイルやステートファイルなどの他に、起動スクリプトなどが含まれる。自己完結型のブートストラップの場合、--bootstrapオプションの他に--directoryオプションを指定する。例えば/opt/mysqlrouterというディレクトリ内に作成する場合には以下のようにコマンドを実行する。--userオプションで指定しているOSユーザーは事前に作成しておこう。

shell$ sudo mysqlrouter --bootstrap icadmin@host1 --directory=/opt/mysqlrouter --user=mysqlrouter

デフォルトのインストールレイアウトを利用する場合には次のようにコマンドを実行しよう。違いは--directoryオプションがないという点だ。各種ファイルはOSの流儀に従って各所に配置される。(設定ファイルは/etc/mysqlrouter、ログは/var/log、起動スクリプトは/etc/init.dなど)

shell$ sudo mysqlrouter --bootstrap icadmin@host1 --user=mysqlrouter

MySQL Routerはブートストラップ時にMySQL上に接続用のユーザーを作成するので注意して欲しい。知らないと「あれ、なんだこのユーザー!?作った覚えないぞ!!」と驚いてしまうことになるだろう。自分でアカウントを事前に作成して、それを使用させるようにすることもできるが、自動で作成させたほうがお手軽だ。

起動は自己完結型の場合にはブートストラップ時に作成されたstart.shというスクリプトで行い、RPMでインストールした場合はsystemd経由で行う。systemd用の設定ファイルはパッケージインストール時に作成されている。

(自己完結型)
shell$ sudo /opt/mysqlrouter/start.sh
(RPM)
shell$ systemctl start mysqlrouter.service

MySQL Routerのデフォルトの待ち受けポートは6446(R/W用)と6447(R/O用)だ。起動したらまず接続してみて欲しい。

shell$ mysqlsh -h127.0.0.1 -P6446 -uicadmin -p --sql

InnoDB Cluster本体の構築はこれで終了だ。あとは必要に応じてMySQL Routerを追加したり、あるいはMySQL Serverのインスタンス数を増やしたりして欲しい。

リードレプリカの追加

いよいよリードレプリカの追加手順について説明しよう。追加は極めて簡単だ。まずは新しくホストを準備してMySQL Serverをインストールする。インストールするものは同じバージョンであることが望ましい。なぜなら、クローンは同じバージョンでなければ使用できないからだ。クローンを利用せず、既存のインスタンスから取得したバックアップをリストアしても別に構わないのだが、クローンのほうがお手軽であり、以下ではクローンを利用する場合に手順を説明する。

まずはdba.configureInstance()の実行だ。これはInnoDB Clusterのときと同じなので詳細は割愛する。管理者ユーザー名はInnoDB Clusterと同じものにしよう。同じでない場合、InnoDB Cluster側でも新たに希望する名前のユーザーを作成する必要がある。なぜなら、クローン実行後にはInnoDB Cluster側のデータになるので、レプリカにだけ別名のユーザーを作成してもクローンにより消えてしまうからだ。

準備が整ったらいよいよリードレプリカの追加だ。リードレプリカを追加するには次のようにコマンドを実行しよう。以下はレプリカ用のホスト名がreplica-host1の例である。

mysql-js> cluster.addReplicaInstance('icadmin@replica-host1')

例によってリカバリーメソッドを聞いてくるので、クローン(C)を選択しよう。リードレプリカの追加手順自体はこれで完了だ。非常に簡単だということがおわかり頂けただろう。

ちなみに、リードレプリカはデフォルトではプライマリインスタンスをソースにしてレプリケーションを行うが、セカンダリをソースにすることも可能だ。その場合はCluster.addReplicaInstance()を次のようにオプション付きで実行すると良い。

mysql-js> cluster.addReplicaInstance('icadmin@replica-host1', {replicationSources: 'secondary'})

'secondary'という単体の文字列ではなく、複数のインスタンスを配列で指定することで、特定のインスタンスをソースに指定することも可能だ。(例:['host2:3306', 'host3:3306'])

リードレプリカを追加したらCluster.status()で見てみよう。追加したリードレプリカもステータスの表示の中に含まれていることがわかるだろう。以下の例は、リードレプリカを2台追加した例だ。ひとつをプライマリの下に、もうひとつをセカンダリの下につけている。

cluster.status()
{
    "clusterName": "My_InnoDB_Cluster", 
    "defaultReplicaSet": {
        "name": "default", 
        "primary": "host1:3306", 
        "ssl": "REQUIRED", 
        "status": "OK", 
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.", 
        "topology": {
            "host1:3306": {
                "address": "host1:3306", 
                "memberRole": "PRIMARY", 
                "mode": "R/W", 
                "readReplicas": {
                    "replica-host1:3306": {
                        "address": "replica-host1:3306", 
                        "role": "READ_REPLICA", 
                        "status": "ONLINE", 
                        "version": "8.0.34"
                    }
                }, 
                "replicationLag": "applier_queue_applied", 
                "role": "HA", 
                "status": "ONLINE", 
                "version": "8.0.34"
            }, 
            "host2:3306": {
                "address": "host2:3306", 
                "memberRole": "SECONDARY", 
                "mode": "R/O", 
                "readReplicas": {
                    "replica-host2:3306": {
                        "address": "replica-host2:3306", 
                        "role": "READ_REPLICA", 
                        "status": "ONLINE", 
                        "version": "8.0.34"
                    }
                }, 
                "replicationLag": "applier_queue_applied", 
                "role": "HA", 
                "status": "ONLINE", 
                "version": "8.0.34"
            }, 
            "host3:3306": {
                "address": "host3:3306", 
                "memberRole": "SECONDARY", 
                "mode": "R/O", 
                "readReplicas": {}, 
                "replicationLag": "applier_queue_applied", 
                "role": "HA", 
                "status": "ONLINE", 
                "version": "8.0.34"
            }
        }, 
        "topologyMode": "Single-Primary"
    }, 
    "groupInformationSourceMember": "host1:3306"
}

MySQL Routerの接続先切り替え

R/O用のポートに接続した際に、MySQL Routerがどこへ接続先を向けるかという点については3つの選択肢がある。

  • セカンダリ(secondaries)
  • リードレプリカ(read_replicas)
  • 両方(all)

デフォルトはセカンダリだ。なので単にリードレプリカを追加しただけではMySQL Router経由でアクセスされないのである。リードレプリカへ負荷を割り振るには、MySQL Routerがどこに接続するかを設定し直す必要がある。その作業はMySQL Shell上で行う。

MySQL Shellでプライマリインスタンスに接続したら、まずdba.getCluster()でクラスターの操作ができるようにし、Cluster.listRouters()でルーターの一覧を表示しよう。

mysql-js> var cluster = dba.getCluster()
mysql-js> cluster.listRouters()
{
    "clusterName": "My_InnoDB_Cluster", 
        "host101::system": {
            "hostname": "router-host1", 
            "lastCheckIn": "2023-09-16 22:52:43", 
            "roPort": "6447", 
            "roXPort": "6449", 
            "rwPort": "6446", 
            "rwXPort": "6448", 
            "version": "8.1.0"
        }
    }
}

この例ではrouter-host1という名前のホストにMySQL Routerがインストールおよびブートストラップされており、名称は「system」というデフォルトの名前が使われている。余談だが名称はブートストラップ実行時に指定することが可能だ。(複数のMySQL Routerをひとつのホスト上で実行するにはポートやディレクトリが重複しないようにする他に、名称もかぶらないようにする必要がある。)「router-host1::system」というのがこのルーターの名称というかIDとなっている。ルーターの操作はこの名前をを使って行う。

mysql-js> cluster.setRoutingOption('router-host1::system', 'read_only_targets', 'all')

このようにコマンドを実行することで、セカンダリとリードレプリカの双方に対して参照の負荷を割り振ることになる。リードレプリカの台数が少ない場合にはセカンダリも負荷を捌くのに活用したいと考え、allを選択することが多いだろう。一方でレプリカの台数が多くなってきた場合、セカンダリを「フェイルオーバーとリードレプリカのソース用」として温存したいと考える人も多いはずだ。その場合には、allの代わりにread_replicasを指定するとMySQL Routerからの参照の負荷はすべてリードレプリカだけに割り振られることになる。

リードレプリカへの負荷分散について、個々のルーターの設定をするのは面倒である。実は、ルーター名を空文字にするとクラスターのデフォルトのルーティングオプションを設定することができる。デフォルトのオプションを設定しておけば個々のルーターについていちいち設定をする必要はないので、一括で設定したい場合にはルーター名の指定無しでデフォルトの挙動を指定しておくと良いだろう。その上でルーターごとの設定を施すと、特定のルーターだけ振る舞いを変えることが可能だ。

移行のシナリオ

既に運用しているシステムを、InnoDB Cluster+リードレプリカの構成にするケースについて考えてみよう。

いちばん移行が容易なケースは、既にInnoDB Clusterを使用している場合だろう。まずMySQL 8.0の最新版へInnoDB Clusterをアップグレードし、MySQL ShellとMySQL Routerを8.1シリーズにアップグレードする。リードレプリカを利用するにはメタデータを格納するデータベースのバージョンをアップグレードする必要があるので、プライマリへ接続してdba.upgradeMetadata()を実行しよう。そうしたらあとはリードレプリカを追加して、ルーティングオプションを設定するだけだ。

スタンドアロンで使っている場合には、アプリケーションの処理を、どれがR/WでどれがR/Oなのかを洗い出すところから始めよう。そして新たなホストを用意して、今使っているインスタンスをInnoDB Cluster化して、アプリケーションをMySQL Router経由でアクセスさせる。InnoDB Cluster化する際、MySQL Serverは8.0にする必要があるが、MySQL ShellとMySQL Routerのバージョンは8.1を使用すること。そうしておけば、dba.upgradeMetadata()の必要がないのでひとつ手間が省ける。あとは前述のケースと同じだ。

非同期レプリケーションを使って負荷分散している場合には、GTIDモードが前提となるので、GTIDモードを利用していない場合、まずはGTIDモードへの切り替えを行う。これはオンラインで対応が可能だ。すでにGTIDモードでレプリケーションを運用している場合には、レプリカのgtid_executedが、ソースのgtid_executedのサブセットになっており、余計なGTIDが含まれていないことを確認しよう。余計なGTIDが含まれている場合にはデータの再同期などの対処が必要になるだろう。そしてどこかのタイミングで計画メンテにより、ソースをInnoDB Cluster化し、レプリカをInnoDB Clusterのリードレプリカとして再登録するというのが作業の全容だ。作業は以下の流れで行う。

  • すべてのレプリカ上でレプリケーションを停止し、設定を解除する(STOP REPLICA; RESET REPLICA ALL;)
  • すべてのインスタンスに対してdba.configureInstance()を行う
  • クラスターを作成する(dba.createCluster())
  • セカンダリを選択して、クラスターに追加する(Cluster.addInstance())
  • リードレプリカを追加する(Cluster.addReplicaInstance())
  • クラスターのデフォルトのルーティングポリシーを変更する
  • MySQL Routerをインストールおよびブートストラップし、アプリケーションの接続先をMySQL Routerに向ける

台数が多い場合、dba.configureInstance()を一台ずつ行うのは面倒に感じるだろう。だが安心して欲しい。dba.configureInstance()はオプションを指定することで、ウィザード形式の入力を必要とせずに実行することが可能だ。スクリプトで実行すれば大した手間ではないだろう。

mysql-js> dba.configureInstance('root@localhost', {clusterAdmin: 'icadmin', clusterAdminPassword: 'New-Password', applierWorkerThreads: 16, interactive: false})

まとめ

今回はMySQL 8.1シリーズで機能追加された、InnoDB Clusterに対するリードレプリカについて解説した。InnoDB Clusterは、「スレーブでスケールアウトさせつつマスターに障害が生じたときに昇格させたい」というニーズを具現化したものだ。MySQL Shellによる構築および管理は手軽であり、InnoDB Clusterは最低3ノード必要であるものの、追加のクラスタリングソフトウェアが必要ないという点もお手軽である。MHAからの移行先をどうしようか迷っている場合や、今使っているデータベースの負荷が増大してまさに参照のスケールアウトについて検討している方などには、うってつけの機能だと言えるだろう。

0 コメント:

コメントを投稿