データベースの負荷分散において、最も一般的かつ効果的な手法の一つが「読写分離(リードレプリカの活用)」です。MySQLにおける読写分離は、更新処理(INSERT/UPDATE/DELETE)をマスター(主系)サーバーに、参照処理(SELECT)をスレーブ(従系)サーバーに振り分けることで、システム全体のプロセッシング能力を向上させるアーキテクチャです。
読写分離を導入する主な目的
- スループットの向上: 読み取り専用のクエリを複数のスレーブに分散させることで、参照負荷の高いアプリケーションの応答速度を改善します。
- 書き込み負荷の軽減: マスターサーバーを書き込み処理に専念させることで、ロック競合を減らし、トランザクションの安定性を高めます。
- 高可用性の確保: マスター障害時にスレーブを昇格させる、あるいは参照処理を継続するといった冗長構成の基盤となります。
実装アプローチの比較
1. アプリケーション層での制御(コードベース)
アプリケーションコード内で、実行するクエリの内容に応じて接続先のデータソースを動的に切り替える手法です。
メリット: インフラ構成がシンプルに保て、特定のビジネスロジックに基づいた柔軟なルーティング(例:特定のユーザーだけ最新データを追うためにマスターを参照させる等)が容易です。
デメリット: 言語やフレームワークごとに実装が必要であり、データベース構成変更時のコード修正コストが発生します。
// Javaにおける抽象的なデータソースルーティングの例
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 現在のコンテキストが読み取り専用か判定
return TransactionContextHolder.isReadOnly() ? "slave" : "master";
}
}
2. プロキシ・ミドルウェアの利用
アプリケーションとデータベースの間にプロキシサーバーを配置し、SQL文を解析して自動的に振り分けを行う手法です。ProxySQL、MySQL Router、ShardingSphereなどが代表的です。
メリット: アプリケーション側は単一の接続先を意識するだけで済み、構成変更が透過的になります。
デメリット: プロキシ自体が単一障害点(SPOF)にならないよう冗長化が必要であり、ネットワークホップが増えることによる僅かなレイテンシが発生します。
基盤となるレプリケーション設定
読写分離を実現するには、まずMySQLの非同期または準同期レプリケーションを構築する必要があります。
マスター側の設定 (my.cnf)
[mysqld]
server-id = 101
log-bin = mysql-bin
binlog-do-db = target_db
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1
スレーブ側の設定 (my.cnf)
[mysqld]
server-id = 201
relay-log = relay-bin
read_only = 1
ProxySQLによるクエリルーティングの構築
ミドルウェアを利用した具体的なルーティング設定例を紹介します。ここでは、書き込み(Hostgroup 0)と読み取り(Hostgroup 1)を分離します。
-- サーバー定義の登録
INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (0, 'master-db-host', 3306);
INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (1, 'slave-db-01', 3306);
INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (1, 'slave-db-02', 3306);
-- ルーティングルールの定義
-- 1. FOR UPDATE文などはマスターへ強制
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply)
VALUES (1, 1, '^SELECT.*FOR UPDATE$', 0, 1);
-- 2. 通常のSELECT文はスレーブグループへ
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply)
VALUES (2, 1, '^SELECT.*', 1, 1);
-- 設定の反映
LOAD MYSQL SERVERS TO RUNTIME;
LOAD MYSQL QUERY RULES TO RUNTIME;
運用上の技術的課題と対策
レプリケーション遅延への対応
マスターでの更新がスレーブに反映されるまでには、ネットワークやIOの状態により僅かなタイムラグが生じます。更新直後のデータを参照する場合、古いデータを読み取ってしまう(イベント一貫性)リスクがあります。
- 解決策1: 重要な処理(注文確定後の詳細表示など)では、SELECTであってもマスターに接続するヒント句(Hint)を使用する。
- 解決策2: 準同期レプリケーション(Semisynchronous Replication)を採用し、データの整合性を高める。
トランザクションの整合性
トランザクション(BEGIN...COMMIT)内でのSELECTは、不整合を防ぐために原則としてマスターで行うべきです。多くのミドルウェアやフレームワークでは、トランザクション開始時に自動的にマスターへ接続を固定する機能が備わっています。
ヘルスチェックとフェイルオーバー
スレーブがダウンした際に、そのサーバーをルーティング対象から自動的に除外する仕組みが必要です。ProxySQLなどのミドルウェアは標準で死活監視機能を備えていますが、マスターがダウンした場合は、オーケストレーター(Orchestrator)等と連携して新しいマスターを選出し、ルーティング情報を書き換える運用設計が不可欠です。