Mercari DBRE(DataBase Reliability Engineering Team)のtaka-hです。
本エントリでは、メルカリの開発環境のデータベース移行の事例を紹介します。なお記事は、MySQL Advent Calendar 2024、およびTiDB Advent Calendar 2024とのクロスポストになります。
この記事の手法を用いて、開発環境では、MySQLからTiDBへ移行すると同時に、スキーマ統合するということを達成しています。本記事により、読者の方々がProxySQLや、TiDBのエコシステムに興味を持っていただけることを期待しています。
TL;DR
- TiDBのData Migration(以後、TiDB DMと呼びます)のTable Routingを用いると、TiDBにスキーマ統合したインスタンスを作成できる
- 2段のProxySQLを構成し、1段目でルーティングを、2段目でinit-connectにスキーマ変更を設定することによって、アプリケーションの滑らかな移行が達成できる
次の構成図を、説明しきることが本エントリのゴールです。
背景
本節では、今回、スキーマ統合に至った経緯を簡単に説明します。
メルカリの本番環境では、サービス提供開始時から必要なデータ保存量やトラフィックの増加に伴い、垂直シャーディングを繰り返してきました。すなわち、データの性質が異なり、結合クエリの発行が必要ない範囲に関しては、ソースおよびレプリカのセット(以後、クラスタと呼びます)を分け、複数のクラスタで運用をしています。
一方で、開発環境ではデータ規模が商用環境ほど大きくないため、運用をシンプルにするためにも単一のクラスタで運用をしています。
ただ、この垂直分割は一定回数繰り返すことで、分割の難易度は次第に高くなります。やがて、大きなクラスタをこれ以上分割するのが難しくなり、データベースのスケーラビリティを解消するために、非常に複雑で骨の折れる、データおよび機能実装の分離が、アプリケーションに求められるようになります。反対に分割を行わないと、より高性能なハードウェアが求められ、マネージドサービスで所望のインスタンスクラスがない、また、ハードウェアの調達時間が長期化しやすい、といった悩みもありました。そこでメルカリでは、MySQLの互換性が非常に高く、スケーラビリティーの優れたTiDBへ移行することを決断しました。
次に、 垂直シャーディングとスキーマの関係について説明します。
クラスタ構成が開発環境と商用環境で異なるため、アプリケーション開発はこれを前提に行う必要があり、開発時にあらかじめテーブルの結合などが必要ない範囲を定義し、開発環境では、そのまとまりごとにスキーマを作成していました。
開発環境でスキーマをまたがなければ、商用環境でクラスタをまたぐ心配はなく、そのような観点から、アプリケーションはスキーマをまたぐようなクエリは発行しないような実装としていました。
他方で、開発環境と商用環境のスキーマの環境差分は、ユーザー定義、他システム連携のシステム構成など様々な環境差分につながり、開発環境の構成が過度に複雑化したり、開発環境での様々なテストの実効性を低下させることにつながっていました。そこで、TiDBの移行の決定とともに、元々抑止しようとしたクラスタまたぎのクエリを事前に抑制することが一時的に達成されなくなる点を加味しても、スキーマ統合を行いこの環境差分を解消することにしました。
切替の作戦
本節では、MySQLで複数のスキーマに分かれていたデータへのクエリを、TiDBにどのように安全かつスムーズに移行していくか、についての考慮事項について記載します。
対象としているデータベースは、利用しているアプリケーションがとても多く、複数箇所のデータベースの接続の設定変更に、時間がかかることが予想されました。各チームは異なる開発サイクル(Sprint)を持ち、DBREで事前に十分にテストを行っているとはいえ、切戻しも考えられました。
そこで、以下の2点を考慮の上、最終的にはProxySQLというプロキシを下図のように多段に構成し移行をすすめました。
- 切替を各アプリケーションで個別に行うのではなく、DBREで一元的にコントロールする
- アプリケーションの必要なコンフィグ修正のタイミングに柔軟性をもたせる
なお、本移行においては、ProxySQLの機能としては非常に重要であり、これを目的に利用する人が多いであろう、Multiplexingと呼ばれる接続を多重化する機能を無効化して利用しています。
移行後のデータの準備
本節では、移行後のデータの準備について説明します。
MySQLからTiDBへの移行には、TiDB DM を利用します。
TiDB DMはMySQLのbinary logの情報を元に、DDLを含む変更/差分ログをダウンストリームのTiDBに反映することができます。
TiDB DMでは、初期のデータ同期および、差分の同期がどちらも可能で、大量のデータがある場合は、TiDBに対して、論理データを非常に高速に物理インポートするTiDB lightningと合わせて利用することで、高速にデータが準備できます。
スキーマ統合については、TiDB DMの標準機能であるTable Routingを利用することにより、非常に簡易に解決されます。アップストリームのMySQLの、どのスキーマのどのテーブルを、ダウンストリームのどこに移動するか、を routes という設定に RE2 syntaxの正規表現で記載するだけで、統合したデータが準備されます。
下記に、TiDB DMのコンフィグ例 (一部抜粋)を記します。
mysql-instances:
- source-id: "dev-database"
route-rules: ["route-mercari", "route-mercari-admin"]
target-database:
host: tidb.internal.mercari.jp.
routes:
route-mercari-admin:
schema-pattern: "mercari_dev_jp_admin"
target-schema: "mercari_admin"
route-mercari:
schema-pattern: "mercari_dev_jp_master|mercari_dev_jp_ads|..."
target-schema: "mercari"
2段ProxySQL
本節では、2段ProxySQLにより先程の要件をどのように達成するかを順に説明します。
1段目のProxySQLは、トラフィックシフトを行う役割を持ちます。
ProxySQLでは、mysql_query_rulesという設定で、様々なルーティングなどが可能で、移行割合を weight というパラメータにより、コントロールできます。
これにより、アプリケーションからのレプリカに対する10%のトラフィックをTiDBに、残りの90%をMySQLにルーティングする、といったことなどが実現できます。
また、TiDBへのトラフィックに関しては、1段目のProxySQLが接続するスキーマに応じて適切な2段階目のProxySQLにルーティングします。
これによりアプリケーション開発者が設定を変更することなく、MySQLからTiDBへ切り替わっていく状況が達成されました。
mysql_servers=
(
{
address = "mercari-admin-proxysql-router-ilb.internal.mercari.jp." # ProxySQL(mercari_admin)
hostgroup = 2
weight = 100
},
{
address = "mysql.internal.mercari.jp." # 移行元MySQL
hostgroup = 2
weight = 0
},
{
address = "mercari-proxysql-router-ilb.internal.mercari.jp." # ProxySQL(mercari)
hostgroup = 3
weight = 100
},
{
address = "mysql.internal.mercari.jp." # 移行元MySQL
hostgroup = 3
weight = 0
},
{
address = "tidb.internal.mercari.jp." # 移行先TiDB
hostgroup = 6
weight = 100
}
)
mysql_query_rules=
(
{
schemaname = "mercari_dev_jp_admin" # 旧スキーマ
flagOUT = 1
},
{
schemaname = "mercari_admin" # 新スキーマ
flagOUT = 2
},
{
schemaname = "mercari" # 新スキーマ
flagOUT = 2
},
{
flagIN = 1
destination_hostgroup = 2 # ProxySQL(mercari_admin) or MySQL
},
{
destination_hostgroup = 3 # ProxySQL(mercari) or MySQL
},
{
flagIN = 2
destination_hostgroup = 6 # TiDB
}
)
2段目のProxySQLは、アプリケーションが古いスキーマに接続する設定で動作する際に、スキーマ統合後のTiDBに透過的にアプリケーションを実行させる役割を持ちます。
現在、移行元のMySQLと、移行先のTiDBでスキーマ構成が異なりますから、何も考慮せずにMySQLのトラフィックをTiDBへシフトさせると当然失敗する訳です。スキーマが異なったとしても、同じアプリケーションの設定で、重み付きで少しずつトラフィックをシフトできたら、事前のテスト可能性が向上し、大変望ましいと思います。
この問題を、MySQLサーバーのグローバルスコープのパラメータである、init_connectに関連したProxySQLのinit_connectパラメータでスキーマ変更をさせることにより、解決させます。
mysql_variables=
{
…
init_connect = "use mercari_admin"
…
}
mysql_servers=
(
{
address = "tidb.internal.mercari.jp."
hostgroup = 1
weight = 100
)
実例にて、動作を確認しましょう。
mercari_dev_jp_adminというスキーマがmercari_adminのスキーマに、スキーマ統合(移行)されるケースを考えます。アプリケーションのスキーマの設定は、古いスキーマ mercari_dev_jp_admin だったとしましょう。
このとき、次の処理の順番により、アプリケーションから見て、スキーマ統合された新しいスキーマのテーブルに対して透過的にクエリが発行されます。
- 接続フェーズでアプリケーションは mercari_dev_jp_admin (旧スキーマ)に接続を試行
- ProxySQLのinit_connectにより接続スキーマが mercari_admin (新スキーマ)に変更される
- クエリはmercari_admin (新スキーマ)上で実行される
1点付け加える必要があるのは、移行後のダウンストリームのデータとして、スキーマ統合をした実データとともに、移行前の空のスキーマ(テーブルなどのデータは存在しなくて良い)が必要であることです。
また、本構成が利用できる前提条件として、そもそもクエリにスキーマを変更したり、スキーマを指定してクエリを発行するといった実装がない、という前提があります。メルカリのケースでは移行の背景からも、スキーマを指定してスキーマをまたぐクエリが発行される心配は、元からありませんでした。
この設定の追加によりもたらされるメリットは2つあります。
- 読取りトラフィックを、新・旧スキーマに重み付きで流す、といった段階的な移行が可能になることで、より安全にトラフィックが移行できる
- アプリケーションのスキーマ設定は、新・旧どちらでも動作し、コンフィグの並行運用期間が取れるため、各開発チームのタイミングで設定の移行を柔軟に進められる
実際には、トラフィックを全て切り替えた後、動作が問題ないことが確認されてから、各アプリケーションにクライアントの接続先スキーマの設定変更依頼を行い、これによりメルカリの開発環境のスムーズな移行が達成されました。(※空のスキーマを誤って不要だと錯誤し、削除してしまうというミスは発生しました)
なお、本切替のTiDBへのトラフィックシフトの前には、事前に
- 主要な利用シナリオ用に作成したベンチマークシナリオによるTiDBの性能評価
- 想定される遅延増加を、擬似的にデータベースレスポンスに付与し、アプリケーションへの影響を評価、アプリケーションの修正が必要な箇所を特定
- 現行のクエリをキャプチャし、TiDBへリプレイし問題の有無を確認
ということを終えておりました。
終わりに
この記事では、メルカリの開発環境で、MySQLからTiDBへ移行すると同時にスキーマ統合する際に用いた手法について説明しました。
多数のアプリケーション接続をかかえるデータベースを、アプリケーション設定変更は実際に移行が成功した後に、順次行える構成を考え、移行を成功させました。ポイントは次の2点です。
- TiDBのData MigrationのTable Routingを用いて、TiDBにスキーマ統合したインスタンスを作成
- 2段のProxySQLを構成し、1段目でルーティングを、2段目でinit_connectにスキーマ変更を設定
メルカリでは、絶え間なく変化するアプリケーションやトラフィックに柔軟に対応するため、クラウドサービスや、ProxySQLなどのOSSソフトウェアを活用したり、必要な機能を独自に実装を行い、問題の解決にチャレンジしています。
様々な課題に意欲的にチャレンジする仲間を募集しています。