ProxySQLによるMySQLのスケーリング:マスターの悲鳴から本番環境の安定稼働まで

Database tutorial - IT technology blog
Database tutorial - IT technology blog

スケーリングの壁:マスターデータベースのCPU使用率が100%に達するとき

管理していたプラットフォームの同時実行ユーザー数が100人から10,000人に急増した瞬間のことをよく覚えています。ダッシュボードは1時間は緑色のままでしたが、その後、大混乱に陥りました。レイテンシは50msから2秒以上に跳ね上がりました。標準的なMaster-Slave構成でしたが、アプリケーションは単純なSELECTクエリでさえ、すべてのクエリをマスターに投げ続けていました。スレーブの利用率は2%にとどまる一方で、マスターのCPUは98%に張り付いていました。

直感的な反応は、通常、ハードウェアのアップグレードです。RAMを増設したり、より高速なNVMeドライブに移行したりすることを考えがちです。しかし、それは一時的な解決策にすぎません。本当のボトルネックはアーキテクチャにあることが多いのです。アプリケーションに、クラスター全体でワークロードを分散させるためのインテリジェンスが欠けているのです。

アプリケーションレベルでのルーティングに潜むコスト

まだ手動でコネクションプールを管理していた頃、PHPのコード内で直接、参照/更新分離(Read/Write splitting)を処理しようとしたことがありました。書き込み用に db_master、読み取り用に db_slave を定義しました。それは、2台目のスレーブを追加するまでは機能していました。しかし、マスターが故障してスレーブが昇格した際、24個のマイクロサービスの構成ファイルを手動で更新しなければなりませんでした。IPアドレスの1文字のタイポが原因で、20分間オフラインになりました。

データベースのトポロジーをハードコーディングすると、主に3つの頭の痛い問題が発生します。

  • 構成の負債: ノードを変更するたびに、アプリケーションの再デプロイが必要になります。
  • コネクションの枯渇: 100個のアプリケーションインスタンスがそれぞれ3つのノードに対して20個のコネクションを維持すると、DBはアイドル状態のまま6,000個のコネクションを浪費することになります。
  • ヘルスチェックの欠如: アプリケーションが重要なクエリを送信する前に、スレーブが30秒遅延していないかを確認することは稀です。

なぜProxySQLが他の選択肢よりも優れているのか

ProxySQLに落ち着く前に、いくつかの候補を検討しました。HAProxyは素晴らしいTCPバランサーですが、「SQLを解釈」しません。DELETESELECT を区別できないため、クエリタイプによる分離には役に立ちません。MaxScaleは強力ですが、ライセンス形態の変更により、予算を重視するステークホルダーに納得させるのが困難でした。

ProxySQLが異なるのは、高パフォーマンスでプロトコルを認識するプロキシである点です。MySQLプロトコルを理解しています。繰り返されるクエリをキャッシュし、煩雑なSQLを動的に書き換え、詳細なルールに基づいてトラフィックをルーティングできます。アプリケーション側は、データベースのトポロジーが変更されたことを知る必要すらありません。

より良いアプローチ:ProxySQLの導入

私の本番環境では、アプリケーションとデータベースの間にProxySQLを配置しています。アプリケーションから見れば、ProxySQLは単一の、極めて堅牢なMySQLサーバーのように見えます。データベースから見れば、ProxySQLは非常に効率的で行儀の良いクライアントのように見えます。

1. サーバーのグループ化

ProxySQLは ホストグループ(Hostgroups) を使用してノードを整理します。通常、マスター(書き込み用)にはホストグループ0、スレーブ(読み取り用)にはホストグループ1を使用します。

-- ProxySQL管理インターフェースにアクセス
mysql -u admin -padmin -h 127.0.0.1 -P 6032

-- クラスタノードを定義
INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (0, '10.0.0.10', 3306); -- マスター
INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (1, '10.0.0.11', 3306); -- スレーブ 1
INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (1, '10.0.0.12', 3306); -- スレーブ 2

LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;

2. ユーザーを安全に管理する

ProxySQLはゲートキーパーとして機能するため、バックエンドのユーザー情報をミラーリングする必要があります。大量のユーザーリストを移行したり、CSVデータをこれらの設定用のSQLインサートに変換したりする場合、私は toolcraft.app/ja/tools/data/csv-to-json を使用しています。ブラウザ内ですべてを処理するため、データベースの認証情報がサードパーティのサーバーに漏洩する心配がありません。

INSERT INTO mysql_users(username, password, default_hostgroup) VALUES ('app_user', 'strong_pass', 0);
LOAD MYSQL USERS TO RUNTIME;
SAVE MYSQL USERS TO DISK;

3. トラフィックを精密にルーティングする

ここからがロジックの本番です。ProxySQLに対し、SELECTをスレーブに送信するように指示しますが、一つだけ重要な例外があります。それは SELECT ... FOR UPDATE です。行ロックを正しく機能させるために、これらはマスターに留める必要があります。

-- ロックを伴う読み取りはマスターに維持
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply) 
VALUES (1, 1, '^SELECT.*FOR UPDATE$', 0, 1);

-- それ以外のすべてのSELECTをスレーブプールにルーティング
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply) 
VALUES (2, 1, '^SELECT', 1, 1);

LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;

本番環境で得た教訓

ルールを設定するのは簡単な部分です。負荷がかかった状態でシステムを安定させるには、さらにいくつかの調整が必要です。

「新規レコード」のレースコンディションを解決する

ユーザーは、プロフィールを作成して保存ボタンを押した直後に、リダイレクト先のスレーブがマスターから500ms遅れているために「404 Not Found」が表示されるのを嫌います。ProxySQLは Seconds_Behind_Master を監視できます。私は厳格な閾値(通常は10秒)を設定し、遅延しているスレーブを読み取りローテーションから自動的に外すようにしています。

UPDATE mysql_servers SET max_replication_lag = 10 WHERE hostgroup_id = 1;
LOAD MYSQL SERVERS TO RUNTIME;

コネクションプーリングによる勝利

私が目にした最も劇的な改善の一つは、コネクションの多重化(multiplexing)です。あるプロジェクトでは、2,000個のアプリケーションスレッドがコネクションを奪い合っていました。ProxySQLを導入することで、これらをわずか150個の永続的なバックエンドコネクションに集約しました。この変更だけで、アプリケーションのコードを1行も書き換えることなく、マスターのCPU使用率が18%低下しました。

リアルタイムのクエリ監査

stats データベースはトラブルシューティングの宝庫です。データベースノードで負荷の高いスロークエリログを有効にする必要はありません。代わりに、プロキシに直接問い合わせて、最も負荷の高いクエリを見つけることができます:

SELECT count_star, sum_time, hostgroup, digest_text 
FROM stats_mysql_query_digest 
ORDER BY sum_time DESC LIMIT 5;

最後に

ロードバランシングのロジックをアプリケーションからProxySQLに移動することは、スケーラビリティにおいて大きな転換点となります。これにより、クリーンな抽象化レイヤーが提供されます。メンテナンスの実行、読み取り容量の拡張、あるいは深夜3時のフェイルオーバー対応などを、アプリケーション側でパケットを1つも落とすことなく行うことができます。まずは基本的なルーティングから始め、パフォーマンスの向上を実感したら、クエリミラーリングやキャッシュなどの高度な機能を試してみてください。

Share: