RailsでMysqlスレーブ群をロードバランサ経由で使用できる、FreshConnectionを作り始めました
追記)
FreshConnectionの最新情報はこちら。
2016/3現在、Rails4.0以降に対応しています。
以前、RailsでMysqlのスレーブサーバーたちを、LVS+keepalivedのようなロードバランサー経由で使用したいという記事を書きました( ActiveRecordのコネクションプーリングをやめてみる)。その続きになります。
前回はアクションごとにコネクションを切断するだけでしたが、今回は実際にスレーブ群を使用する、FreshConnectionというライブラリを作り始めました。
現在のところ、Rails2.3.14でのみテストしています。今日は時間がなかったので実際にロードバランサーを使った環境でテストしていませんが、次はそれと合わせてRails3対応も考えていこうと思います。
コードは以下で公開しています。
https://github.com/tsukasaoishi/fresh_connection
以下のような環境を前提にして話をすすめていきます。
RailsApp --------- Mysql(Master) 192.168.11.1
|
| +------ Mysql(Slave1)
| |
+---- LVS ------------+
192.168.11.10 |
+------ Mysql(Slave2)
1.Railsでのセットアップ
まず、GemfileにFreshConnectionを指定します。
gem "fresh_connection", "=0.0.1"
次にconfig/database.ymlでスレーブの設定をします。
production:
adapter: mysql
encoding: utf8
reconnect: true
database: kaeru
pool: 5
username: master
password: master
host: 192.168.11.1
socket: /var/run/mysqld/mysqld.sock
slave:
username: slave
password: slave
host: 192.168.11.10
max_connection: 5
production環境のスレーブは上のように書きます。基本的には通常の設定と同じ項目が指定できます。設定していないものは、マスターの設定が使われます。
ひとつだけ新しい項目があって、それがmax_connectionになります。これは、スレーブへのコネクションの最大数となります。
さらに、config/environment.rbでrackの入れ替えを行います。
...
Rails::Initializer.run do |config|
...
end
require 'fresh_connection'
ActionController::Dispatcher.middleware.swap ActiveRecord::ConnectionAdapters::ConnectionManagement, FreshConnection::Rack::ConnectionManagement
これで準備を完了です。
2.使い方
Selectなど、読み込み系のSQLはスレーブに投げられます。
Article.find(1)
マスターを使いたいときは、 :readonly => false を指定します。
Article.find(1, :readonly => false)
また、トランザクション内のクエリはすべてマスターに投げられます。
Article.transaction do
Article.find(1)
end
3.使ってみる
以下のようなアクションでテストしてみます。
class ArticlesController < ApplicationController
def test
Article.find(105)
Article.find(106, :readonly => false)
Article.transaction do
Article.find(107)
end
10.times do |num|
Article.find(100 + num)
end
render :text => "ok"
end
end
アクセスしたときのRailsログを見てみます。
[MASTER] SQL (0.1ms) SET NAMES 'utf8'
[MASTER] SQL (0.1ms) SET SQL_AUTO_IS_NULL=0
Processing ArticlesController#test (for 127.0.0.1 at 2011-10-16 21:23:42) [GET]
[MASTER] Article Columns (0.6ms) SHOW FIELDS FROM `articles`
SQL (0.1ms) SET NAMES 'utf8'
SQL (0.0ms) SET SQL_AUTO_IS_NULL=0
Article Load (0.1ms) SELECT * FROM `articles` WHERE (`articles`.`id` = 105)
[MASTER] Article Load (0.1ms) SELECT * FROM `articles` WHERE (`articles`.`id` = 106)
[MASTER] SQL (0.0ms) BEGIN
[MASTER] Article Load (0.1ms) SELECT * FROM `articles` WHERE (`articles`.`id` = 107)
[MASTER] SQL (0.0ms) COMMIT
SQL (0.0ms) SET NAMES 'utf8'
SQL (0.0ms) SET SQL_AUTO_IS_NULL=0
Article Load (0.1ms) SELECT * FROM `articles` WHERE (`articles`.`id` = 100)
SQL (0.0ms) SET NAMES 'utf8'
SQL (0.0ms) SET SQL_AUTO_IS_NULL=0
Article Load (0.1ms) SELECT * FROM `articles` WHERE (`articles`.`id` = 101)
SQL (0.0ms) SET NAMES 'utf8'
SQL (0.0ms) SET SQL_AUTO_IS_NULL=0
Article Load (0.1ms) SELECT * FROM `articles` WHERE (`articles`.`id` = 102)
SQL (0.0ms) SET NAMES 'utf8'
SQL (0.0ms) SET SQL_AUTO_IS_NULL=0
Article Load (0.1ms) SELECT * FROM `articles` WHERE (`articles`.`id` = 103)
Article Load (0.0ms) SELECT * FROM `articles` WHERE (`articles`.`id` = 104)
CACHE (0.0ms) SELECT * FROM `articles` WHERE (`articles`.`id` = 105)
CACHE (0.0ms) SELECT * FROM `articles` WHERE (`articles`.`id` = 106)
CACHE (0.0ms) SELECT * FROM `articles` WHERE (`articles`.`id` = 107)
Article Load (0.0ms) SELECT * FROM `articles` WHERE (`articles`.`id` = 108)
Article Load (0.0ms) SELECT * FROM `articles` WHERE (`articles`.`id` = 109)
Completed in 13ms (View: 0, DB: 1) | 200 OK [http://localhost/articles/test]
FreshConnectionは、マスターへのアクセスのときはSQLログに [MASTER] を付与するので、どのクエリがマスターに、どれがスレーブに投げられたのかわかるようになっています。
期待したように処理しているようです。
MySqlのクエリログは以下のようになっています。
111016 21:28:20 60 Connect master@localhost on kaeru
60 Query SET NAMES 'utf8'
60 Query SET SQL_AUTO_IS_NULL=0
60 Statistics
60 Query SHOW FIELDS FROM `articles`
61 Connect slave@localhost on kaeru
61 Query SET NAMES 'utf8'
61 Query SET SQL_AUTO_IS_NULL=0
61 Query SELECT * FROM `articles` WHERE (`articles`.`id` = 105)
60 Query SELECT * FROM `articles` WHERE (`articles`.`id` = 106)
60 Query BEGIN
60 Query SELECT * FROM `articles` WHERE (`articles`.`id` = 107)
60 Query COMMIT
62 Connect slave@localhost on kaeru
62 Query SET NAMES 'utf8'
62 Query SET SQL_AUTO_IS_NULL=0
62 Query SELECT * FROM `articles` WHERE (`articles`.`id` = 100)
63 Connect slave@localhost on kaeru
63 Query SET NAMES 'utf8'
63 Query SET SQL_AUTO_IS_NULL=0
63 Query SELECT * FROM `articles` WHERE (`articles`.`id` = 101)
64 Connect slave@localhost on kaeru
64 Query SET NAMES 'utf8'
64 Query SET SQL_AUTO_IS_NULL=0
64 Query SELECT * FROM `articles` WHERE (`articles`.`id` = 102)
65 Connect slave@localhost on kaeru
65 Query SET NAMES 'utf8'
65 Query SET SQL_AUTO_IS_NULL=0
65 Query SELECT * FROM `articles` WHERE (`articles`.`id` = 103)
61 Query SELECT * FROM `articles` WHERE (`articles`.`id` = 104)
65 Query SELECT * FROM `articles` WHERE (`articles`.`id` = 108)
61 Query SELECT * FROM `articles` WHERE (`articles`.`id` = 109)
60 Quit
61 Quit
64 Quit
62 Quit
65 Quit
63 Quit
テストではロードバランサー環境を用意する時間がなかったので、ひとつのMySqlサーバ上で実行しています。
しかし。コネクションがマスターを含めて6本存在していることがわかります。また、期待したコネクションに対してクエリが投げられていることがわかります。
4.今後の予定
・実際にロードバランサー環境でのテスト
・Rails3対応
・エラーが発生したときのフェイルセーフ機能
・スレーブへアクセスしたくないモデルの指定機能