はじめに
先月にリリースされた Innovation Release となる MySQL 9.2.0 では、MySQL Shell に Routing Guideline なる機能が実装されました。
リリースノートの情報とリファレンスは以下になります。
- Changes in MySQL Shell 9.2.0 (2025-01-21, Innovation Release)
- MySQL Shell 9.2 Reference / 7.8 Routing Guidelines
今回は、新しく実装された Routing Guideline を使用してみた内容を紹介したいと思います。
Routing Guideline とは
従来の MySQL Router では設定ファイルをベースに Port ごとに振り分け先を設定するのが一般的な使用方法でした。
設定ファイル以外にも、振り分け先に関する一部の調整は、MySQL Shell の API を通じて行うことが可能となっています。
また、MySQL Shell の API は JavaScript版とPython版が用意されており、使用者のお好みに応じて選択することができます。
Routing Guideline は、振り分け先の設定を上記の API を通して、従来の設定ファイルでの設定方法よりも、より細かくルーティングの設定ができるようになります。
例えば、 こちら に定義されているような変数を使用してマッチングパターンが定義できるので、以下のようなルーティングも可能となります。
- 接続ユーザに応じて、振り分け先を変える。
- 接続元のIPアドレスに応じて、振り分け先を変える。
- アクセスするスキーマごとに、振り分け先を変える。
- 複数の振り分け先に対するアクセスの割合を調整をする。
検証
構成
検証するにあたり、以下のような環境を用意しました。
ホスト名 | 備考 |
---|---|
router | MySQL Router |
node01 | Group Replication (Primary) |
node02 | Group Replication (Secondary) |
node03 | Group Replication (Secondary) |
replica01 | Read Replica |
replica02 | Read Replica |
構成は InnoDB Cluster Read Replicas となります。
InnoDB Cluster Read Replicas
については、弊社の以下のブログでも紹介しておりますので、合わせて読んで頂ければと思います。
- MySQL Shell 8.1 新機能 : MySQL InnoDB Cluster Read Replicas について (1)
- MySQL Shell 8.1 新機能 : MySQL InnoDB Cluster Read Replicas について (2)
初期状態確認
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
MySQL localhost JS > cluster.status() { "clusterName": "myCluster", "defaultReplicaSet": { "name": "default", "primary": "node01:3306", "ssl": "REQUIRED", "status": "OK", "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.", "topology": { "node01:3306": { "address": "node01:3306", "memberRole": "PRIMARY", "mode": "R/W", "readReplicas": { "replica01:3306": { "address": "replica01:3306", "role": "READ_REPLICA", "status": "ONLINE", "version": "9.2.0" }, "replica02:3306": { "address": "replica02:3306", "role": "READ_REPLICA", "status": "ONLINE", "version": "9.2.0" } }, "replicationLag": "applier_queue_applied", "role": "HA", "status": "ONLINE", "version": "9.2.0" }, "node02:3306": { "address": "node02:3306", "memberRole": "SECONDARY", "mode": "R/O", "readReplicas": {}, "replicationLag": "applier_queue_applied", "role": "HA", "status": "ONLINE", "version": "9.2.0" }, "node03:3306": { "address": "node03:3306", "memberRole": "SECONDARY", "mode": "R/O", "readReplicas": {}, "replicationLag": "applier_queue_applied", "role": "HA", "status": "ONLINE", "version": "9.2.0" } }, "topologyMode": "Single-Primary" }, "groupInformationSourceMember": "node01:3306" } |
node01 が Group Replication の Primary ノードで、このノードに対して replica01, replica02 の Read Replica が存在します。
また、node02, node03 は Group Replication の Secondary ノードとなっていることが読み取れます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
MySQL localhost JS > cluster.listRouters() { "clusterName": "myCluster", "routers": { "router.verificationsub.fukvcn1.oraclevcn.com::system": { "currentRoutingGuideline": null, "hostname": "router.verificationsub.fukvcn1.oraclevcn.com", "lastCheckIn": "2025-02-14 15:51:36", "localCluster": "myCluster", "roPort": "6447", "roXPort": "6449", "rwPort": "6446", "rwSplitPort": "6450", "rwXPort": "6448", "supportedRoutingGuidelinesVersion": "1.0", "version": "9.2.0" } } } |
{Cluster}.listRouters()
では、 "currentRoutingGuideline": null
と出力されており、これは Routing Guideline が適用されていないことを示します。
また、MySQL Router の Port は以下のように設定されています。
- Read / Writer 用ポート
- rwPort:6446
- rwXPort:6448(X Protocol用)
- Read 用ポート
- roPort:6447
- roXPort:6449(X Protocol用)
- Read / Writer Splitting 用ポート
- rwSplitPort:6450
※ Read / Writer Splitting 用ポート については以下のブログでも紹介しております。
試しに Read 用ポート(6447)
にアクセスすると、Group Replication の Secondary ノードのみにアクセスしていることが分かります。
1 2 3 4 5 6 |
$ for i in {1..5};do mysql --login-path=admin -P 6447 -BNe "SELECT @@hostname";done node03 node02 node03 node02 node03 |
Routing Guideline の作成
それでは、Routing Guideline を作成してみます。
リファレンスに記載されている例が Python の API を使用している為、同様に MySQL Shell で Primary ノードにアクセスし、Python モードで以下のように実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# mysqlsh icadmin@localhost --py MySQL Shell 9.2.0 Copyright (c) 2016, 2025, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type '\help' or '\?' for help; '\quit' to exit. Creating a Classic session to 'icadmin@localhost' Fetching schema names for auto-completion... Press ^C to stop. Your MySQL connection id is 583 Server version: 9.2.0 MySQL Community Server - GPL No default schema selected; type \use <schema> to set one. MySQL localhost Py > MySQL localhost Py > cluster = dba.get_cluster() MySQL localhost Py > rg = cluster.create_routing_guideline("blog") Creating Default Routing Guideline... * Adding default destinations... ** Destination 'Primary' successfully added. ** Destination 'Secondary' successfully added. ** Destination 'ReadReplica' successfully added. * Adding default routes... ** Route 'rw' successfully added. ** Route 'ro' successfully added. NOTE: Routing guideline 'blog' won't be made active by default. To activate this guideline, please use .set_routing_option() with the option 'guideline'. Routing Guideline 'blog' successfully created. MySQL localhost Py > |
{Cluster}.create_routing_guideline() を使用して Routing Guideline を作成することで、デフォルトの Routing Guideline が作成されます。
詳細については、以下のリファレンスもご参照下さい。
作成された Destinations は {Routing Guideline}.destinations() で確認することができます。
1 2 3 4 5 6 7 8 |
MySQL localhost Py > rg.destinations() +-------------+------------------------------------+ | destination | match | +-------------+------------------------------------+ | Primary | $.server.memberRole = PRIMARY | | Secondary | $.server.memberRole = SECONDARY | | ReadReplica | $.server.memberRole = READ_REPLICA | +-------------+------------------------------------+ |
初期状態では、以下3つの Destinations が作成されました。
- Primary:Group Replication の Primary ノード
- Secondary:Group Replication の Secondary ノード
- ReadReplica:Read Replica ノード
これをどのようにルーティングしているかを確認するには、 {Routing Guideline}.show() を使用するのがよいでしょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
MySQL localhost Py > rg.show() Routing Guideline: 'blog' Cluster: 'myCluster' Routes ------ - rw + Match: "$.session.targetPort = $.router.port.rw" + Destinations: * node01:3306 (Primary) - ro + Match: "$.session.targetPort = $.router.port.ro" + Destinations: * node02:3306, node03:3306 (Secondary) * node01:3306 (Primary) Destination Classes ------------------- - Primary: + Match: "$.server.memberRole = PRIMARY" + Instances: * node01:3306 - Secondary: + Match: "$.server.memberRole = SECONDARY" + Instances: * node02:3306 * node03:3306 - ReadReplica: + Match: "$.server.memberRole = READ_REPLICA" + Instances: * replica01:3306 * replica02:3306 Unreferenced servers -------------------- - None |
視覚的に分かりやすく、以下の情報が読み取れます。
- rw の設定
Read / Writer 用ポート(6446)
へのアクセスは、Group Replication の Primary ノードに振り分ける。
- ro の設定
Read 用ポート(6447)
へのアクセスは、Group Replication の Secondary ノードに振り分けられ、Secondary ノード全てにアクセスできない場合は、Primary ノードに振り分ける。
(Destinations:
に定義されているものは、上から順に評価されるようです。)
より詳細に確認したい場合は、 {Routing Guideline}.as_json() が役に立ちそうです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
MySQL localhost Py > rg.as_json() { "destinations": [ { "match": "$.server.memberRole = PRIMARY", "name": "Primary" }, { "match": "$.server.memberRole = SECONDARY", "name": "Secondary" }, { "match": "$.server.memberRole = READ_REPLICA", "name": "ReadReplica" } ], "name": "blog", "routes": [ { "connectionSharingAllowed": true, "destinations": [ { "classes": [ "Primary" ], "priority": 0, "strategy": "round-robin" } ], "enabled": true, "match": "$.session.targetPort = $.router.port.rw", "name": "rw" }, { "connectionSharingAllowed": true, "destinations": [ { "classes": [ "Secondary" ], "priority": 0, "strategy": "round-robin" }, { "classes": [ "Primary" ], "priority": 1, "strategy": "round-robin" } ], "enabled": true, "match": "$.session.targetPort = $.router.port.ro", "name": "ro" } ], "version": "1.0" } |
{Routing Guideline}.show() では、設定されている routing_strategy までは確認できませんでしたが、設定されている全ての情報が確認できそうです。
ルート の設定
デフォルトで作成されたルート情報を削除してから、ルート情報を設定していきます。
ルートの削除は {Routing Guideline}.remove_route() で行います。
1 2 3 4 5 6 7 |
MySQL localhost Py > rg.remove_route("rw"); Route successfully removed. MySQL localhost Py > rg.remove_route("ro"); ERROR: Route 'ro' is the only route in the Guideline and cannot be removed. A Routing Guideline must contain at least one route. Traceback (most recent call last): File "<string>", line 1, in <module> mysqlsh.Error: Shell Error (51811): Cannot remove the last route of a Routing Guideline |
ルート情報は、最低限1つの設定が存在する必要があるようで、ro の設定削除はエラーとなりました。
ro の設定は一旦、残しておいて、ルート情報を新たに作成します。
ルートの作成には、 {Routing Guideline}.add_route() を使用します。
1 2 3 4 5 6 7 |
MySQL localhost Py > rg.add_route( -> "rw_and_rwsplit", -> "$.session.targetPort IN ($.router.port.rw, $.router.port.rw_split)", -> ["first-available(Primary)"] -> ); -> Route 'rw_and_rwsplit' successfully added. |
第1引数には作成するルートの名前、第2引数にはマッチングパターン、第3引数には routing_strategy と Destinations を指定しています。
要約すると、 Read / Writer 用ポート(6446)
と Read / Writer Splitting 用ポート (6450)
へのアクセスを Group Replication の Primary ノード に割り振るルートを rw_and_rwsplit
という名前で作成しています。
なお、現時点では、routing_strategy には、 first-available
もしくは round-robin
しか設定することができません。
デフォルトで作成された ro の設定を削除して確認すると、作成した rw_and_rwsplit
のみが存在することが分かります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
MySQL localhost Py > rg.remove_route("ro"); Route successfully removed. MySQL localhost Py > rg.show() Routing Guideline: 'blog' Cluster: 'myCluster' Routes ------ - rw_and_rwsplit + Match: "$.session.targetPort IN ($.router.port.rw, $.router.port.rw_split)" + Destinations: * node01:3306 (Primary) Destination Classes ------------------- - Primary: + Match: "$.server.memberRole = PRIMARY" + Instances: * node01:3306 - Secondary: + Match: "$.server.memberRole = SECONDARY" + Instances: * node02:3306 * node03:3306 - ReadReplica: + Match: "$.server.memberRole = READ_REPLICA" + Instances: * replica01:3306 * replica02:3306 Unreferenced servers -------------------- - None |
1 2 3 4 5 6 |
MySQL localhost Py > rg.routes() +----------------+---------+-----------+--------------------------------------------------------------------+--------------------------+-------+ | name | enabled | shareable | match | destinations | order | +----------------+---------+-----------+--------------------------------------------------------------------+--------------------------+-------+ | rw_and_rwsplit | 1 | 1 | $.session.targetPort IN ($.router.port.rw, $.router.port.rw_split) | first-available(Primary) | 0 | +----------------+---------+-----------+--------------------------------------------------------------------+--------------------------+-------+ |
さらに、 Read 用ポート(6447)
へのアクセスは、以下のようなものを作成したいと思います。
① Group Replication の Secondary ノード と Read Replica にアクセスする。
② 但し、 batch_user
というユーザがアクセスした場合は、Read Replica にアクセスする。
①については、以下のように設定しました。
Group Replication の Secondary ノード と Read Replica にアクセスできない場合は、Group Replication の Primary ノード にアクセスするという条件も付与しています。
1 2 3 4 5 6 7 8 9 10 11 |
MySQL localhost Py > rg.add_route( -> "ro", -> "$.session.targetPort = $.router.port.ro", -> [ -> "round-robin(Secondary,ReadReplica)", -> "first-available(Primary)" -> ], -> {"order": 10} -> ); -> Route 'ro' successfully added. |
②については、以下のように設定しています。
Read Replica にアクセスできない場合は、Group Replication の Secondary ノードへ、それもアクセスできない場合は、Group Replication の Primary ノード にアクセスするという条件になっています。
1 2 3 4 5 6 7 8 9 10 11 12 |
MySQL localhost Py > rg.add_route( -> "ro_batch_access", -> "$.session.targetPort = $.router.port.ro AND $.session.user = 'batch_user'", -> [ -> "round-robin(ReadReplica)", -> "round-robin(Secondary)", -> "first-available(Primary)" -> ], -> {"order": 1} -> ); -> Route 'ro_batch_access' successfully added. |
order
指定を追加していますが、これは評価の優先順位を指定するもので、値が小さい程、優先して評価されます。
ここまでの設定で以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
MySQL localhost Py > rg.show() Routing Guideline: 'blog' Cluster: 'myCluster' Routes ------ - rw_and_rwsplit + Match: "$.session.targetPort IN ($.router.port.rw, $.router.port.rw_split)" + Destinations: * node01:3306 (Primary) - ro_batch_access + Match: "$.session.targetPort = $.router.port.ro AND $.session.user = 'batch_user'" + Destinations: * replica01:3306, replica02:3306 (ReadReplica) * node02:3306, node03:3306 (Secondary) * node01:3306 (Primary) - ro + Match: "$.session.targetPort = $.router.port.ro" + Destinations: * node02:3306, node03:3306 (Secondary) * replica01:3306, replica02:3306 (ReadReplica) * node01:3306 (Primary) Destination Classes ------------------- - Primary: + Match: "$.server.memberRole = PRIMARY" + Instances: * node01:3306 - Secondary: + Match: "$.server.memberRole = SECONDARY" + Instances: * node02:3306 * node03:3306 - ReadReplica: + Match: "$.server.memberRole = READ_REPLICA" + Instances: * replica01:3306 * replica02:3306 Unreferenced servers -------------------- - None |
1 2 3 4 5 6 7 8 |
MySQL localhost Py > rg.routes() +-----------------+---------+-----------+---------------------------------------------------------------------------+----------------------------------------------------------------------------+-------+ | name | enabled | shareable | match | destinations | order | +-----------------+---------+-----------+---------------------------------------------------------------------------+----------------------------------------------------------------------------+-------+ | rw_and_rwsplit | 1 | 1 | $.session.targetPort IN ($.router.port.rw, $.router.port.rw_split) | first-available(Primary) | 0 | | ro_batch_access | 1 | 1 | $.session.targetPort = $.router.port.ro AND $.session.user = 'batch_user' | round-robin(ReadReplica), round-robin(Secondary), first-available(Primary) | 1 | | ro | 1 | 1 | $.session.targetPort = $.router.port.ro | round-robin(Secondary, ReadReplica), first-available(Primary) | 2 | +-----------------+---------+-----------+---------------------------------------------------------------------------+----------------------------------------------------------------------------+-------+ |
Routing Guideline の適用
作成した Routing Guideline を適用して、アクセスしてみたいと思います。
まずは、Routing Guideline を適用するには {Cluster}.set_routing_option() を使用します。
1 2 3 |
MySQL localhost Py > cluster.set_routing_option("guideline","blog") Routing option 'guideline' successfully updated. Routing Guideline 'blog' has been enabled and is now the active guideline for the topology. |
検証する際は、 mysqlrouter.conf
の [metadata_cache:bootstrap]
セクションにある ttl
の設定を短めに設定しておくと、設定変更内容が早く反映される為、お勧めです。
適用後は Read / Writer 用ポート(6446)
と Read / Writer Splitting 用ポート (6450)
へのアクセスが Group Replication の Primary ノード に割り振られています。
1 2 3 4 5 6 7 8 9 10 11 12 |
# for i in {1..5};do mysql --login-path=admin -P 6446 -BNe "SELECT @@hostname";done node01 node01 node01 node01 node01 # for i in {1..5};do mysql --login-path=admin -P 6450 -BNe "SELECT @@hostname";done node01 node01 node01 node01 node01 |
Read 用ポート(6447)
へのアクセスを batch_user
以外のユーザでアクセスすると想定通り Group Replication の Secondary ノード と Read Replica にラウンドロビンでアクセスされています。
1 2 3 4 5 6 |
# for i in {1..5};do mysql --login-path=admin -P 6447 -BNe "SELECT @@hostname";done replica02 node02 node03 replica01 replica02 |
Group Replication の Secondary ノード と Read Replica のMySQLサービスを全て停止すると、 Group Replication の Primary ノード にアクセスします。
1 2 3 4 5 6 |
# for i in {1..5};do mysql --login-path=admin -P 6447 -BNe "SELECT @@hostname";done node01 node01 node01 node01 node01 |
また、 Read 用ポート(6447)
へのアクセスを batch_user
ユーザでアクセスした場合は、Read Replica にラウンドロビンでアクセスされています。
1 2 3 4 5 6 |
# for i in {1..5};do mysql --login-path=batch_user -P 6447 -BNe "SELECT @@hostname";done replica01 replica02 replica01 replica02 replica01 |
Read Replica のMySQLサービスを全て停止すると、 Group Replication の Secondary ノード にアクセスします。
1 2 3 4 5 6 |
# for i in {1..5};do mysql --login-path=batch_user -P 6447 -BNe "SELECT @@hostname";done node02 node03 node02 node03 node02 |
Group Replication の Secondary ノード の MySQLサービスも全て停止すると、 Group Replication の Primary ノード にアクセスします。
1 2 3 4 5 6 |
# for i in {1..5};do mysql --login-path=batch_user -P 6447 -BNe "SELECT @@hostname";done node01 node01 node01 node01 node01 |
より細かく設定してみる
先程のケースではデフォルトの Destinations 以外を使用していなかった為、以下の Read Replica へのアクセスをラウンドロビンではなく特定のサーバを優先して使用するようにしてみます。
② 但し、
batch_user
というユーザがアクセスした場合は、Read Replica にアクセスする。
まずは、現状の ro_batch_access
のルートを削除して、Destinations に Read Replica のサーバを追加します。
Destinations の追加には {Routing Guideline}.add_destination() を使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
MySQL localhost Py > rg=cluster.get_routing_guideline("blog") MySQL localhost Py > rg.remove_route("ro_batch_access"); Route successfully removed. MySQL localhost Py > MySQL localhost Py > rg.add_destination("Replica01","$.server.address = 'replica01'"); Destination 'Replica01' successfully added. MySQL localhost Py > rg.add_destination("Replica02","$.server.address = 'replica02'"); Destination 'Replica02' successfully added. MySQL localhost Py > MySQL localhost Py > rg.destinations() +-------------+------------------------------------+ | destination | match | +-------------+------------------------------------+ | Primary | $.server.memberRole = PRIMARY | | Secondary | $.server.memberRole = SECONDARY | | ReadReplica | $.server.memberRole = READ_REPLICA | | Replica01 | $.server.address = 'replica01' | | Replica02 | $.server.address = 'replica02' | +-------------+------------------------------------+ |
ルートを追加する際には、 Replica02, Replica01 の順に指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
MySQL localhost Py > rg.add_route( -> "ro_batch_access", -> "$.session.targetPort = $.router.port.ro AND $.session.user = 'batch_user'", -> [ -> "first-available(Replica02)", -> "first-available(Replica01)", -> "round-robin(Secondary)", -> "first-available(Primary)" -> ], -> {"order": 1} -> ); -> Route 'ro_batch_access' successfully added. |
このように指定することで、Read Replica の中でも特定のサーバを優先してアクセスするように設定することも可能となります。
1 2 3 4 5 6 |
# for i in {1..5};do mysql --login-path=batch_user -P 6447 -BNe "SELECT @@hostname";done replica02 replica02 replica02 replica02 replica02 |
まとめ
Routing Guideline は MySQL Router のルーティングがより柔軟に設定できるようになり、今後、使用するユーザが増えてきそうな予感がします。
現時点では、Innovation Release でしか使用できませんが、今後リリースされる Long-Term Support (LTS) のバージョンでも使用できるようになるのを期待して待ちたいと思います。