パフォーマンスチューニング on Rails Author: hmori Date:2008/01/07
パフォーマンスチューニングとは 「ハードウェアを最大限に活用する」ということ. 効率の悪い部分を突き止め、 最適なパフォーマンスを示すように、 システムのハードウェアやソフトウェアを分析し調整する.
チューニングの一般論 パフォーマンスは依存し合う多数の要因による チューニングは常にトレード・オフを伴う システムに影響を与えずに調査することは不可能 目標値の設定が必要(具体的な目標の設定) パフォーマンスチューニング手順 ①調査(Assess) ②測定(Measurement) ③分析(Analyze) ④特定(Identify)、調整(Tune)
前提 Railsのチューニング方法は多種にありますが、 ここではDBアクセスのチューニングを観点に 進めます。 ※DBはMySQLを前提としています
調査方法(1) Railsログ SQL、経過時間などが表示される. (※大抵これでわかる) development.log Completed in 0.98500 (1 reqs/sec) | Rendering: 0.04700 (4%) | DB: 0.62500 (63%) | 200 OK ↑ ↑ ↑ 1リクエストの時間 レンダリング DBアクセス
調査方法(2) SQLログ ボトルネックになっているSQLを探すのに適す. MySQLの場合 <my.ini> log-slow-queries #SlowLogの有効化(ログファイル名を指定可能)long query-time=1 #SlowLogに記録する処理時間(秒)の上限 log-long-format #インデックスを使用しないSQL文の記録
調査方法(3) Logファイル (mysql/data/{hostname}-slow.log) # Time: 080106 22:55:42 # User@Host: root[root] @ localhost [127.0.0.1] # Query_time: 550 Lock_time: 0 Rows_sent: 1 Rows_examined: 7024 use parformtest_development; SELECT SQL_CALC_FOUND_ROWS * FROM `t1s`;
調査方法(4) >EXPLAIN {調査したいSQL} ※SQLコマンドラインより実行 ※type, key, rowsに着目する Type: 検索時のタイプ (ALLの場合、full scanが行われている) Key: 使用されたindex-key Rows: 検索時に走査された行数
INDEXによるチューニング(1) 効果が期待されるパターン ・EXPLAINによる実行計画でkeyがNULLとなっている場合 ・WHERE句に指定される検索キーである場合 メリット ・ソースを変更することなくチューニングが行える ・検索に対して効果が大きい デメリット ・INDEX対象テーブルへのINSERTのコストが増大する ⇒大量データのINSERT処理がある場合、要注意 ・INDEX領域を使うため容量が必要となる ・OPTIMIZEを実行しないとパフォーマンス劣化となる場合がある ⇒定期的な最適化バッチ等を考慮する必要がある
INDEXによるチューニング(2) How to --Migration-- def self.up create_table :t1s do |t| t.column :trans_no, :integer end add_index :t1s, :trans_no 注意) ※必ずEXPLAINでkeyにINDEXが使用されているか確認する ※JOINされるテーブルはPrimaryIndexが指定されている
include句によるチューニング(1) 効果が期待されるパターン ・has_many throughで結合、参照されている場合 メリット ・比較的に簡単なソース修正でチューニングが行える デメリット ・参照されないデータに対しても必ずJOINされる ・状況によっては不必要なデータを転送する可能性がある
include句によるチューニング(2) How to --Controller-- @t1s = T1.find( :all, :include => [:m1,:m2,:m3,:m4,:m5,:m6,:m7,:m8,:m9], :conditions => ["t1s.trans_no<?",10] )
抽出カラム絞込みによるチューニング(1) 効果が期待されるパターン ・必要なデータが全カラムに対して少ない場合 メリット ・DBからのデータ転送量を減らすことでコストが削減できる デメリット ・JOINしている場合、直接SQLを記述する必要がある(find_by_sql) ⇒Objectの階層が変わるため注意が必要 ・参照される可能性があるカラムの洗い出しが必要となる ・ソースコードの可読性が劣化する
抽出カラム絞込みによるチューニング(2) How to --Controller-- @t1s = T1.find_by_sql( ["SELECT `m01` FROM t1s LEFT OUTER JOIN m1s ON m1s.id=t1s.m1_id WHERE t1s.trans_no<?", 10] )
検証(1) 名称マスタを持つ大量トランザクションデータの検索を想定した. あえてデータ転送量を増やすため名称マスタの列数を大きくした. トランザクション T1 10カラム 65535件 マスタ M1 99カラム 9件 T1の各idカラムに紐付くマスタの値を参照した. T1.id1 ⇒ M1.m01 T1.id2 ⇒ M1.m02 T1.id3 ⇒ M1.m03 : T1.id9 ⇒ M1.m09
検証(2) ①INDEXによるチューニングによる検証 検索キー trans_noにindexを追加 チューニング前 >EXPLAIN SELECT * FROM t1s WHERE (t1s.trans_no<10) チューニング後
検証(3) ①INDEXによるチューニングによる検証 log結果(平均) ・チューニング前 Total 0.83ms Rendering:0.37ms DB:0.35ms ・チューニング後 Total 0.60ms Rendering:0.38ms DB:0.19ms
検証(4) ②include句チューニングによる検証 コントローラ修正 @t1s = T1.find( :all, :conditions => ["t1s.trans_no<?",10] ) ↓ :include => [:m1,:m2,:m3,:m4,:m5,:m6,:m7,:m8,:m9], チューニング前 Total 0.60ms Rendering:0.38ms DB:0.19ms チューニング後 Total 0.48ms Rendering:0.05ms DB:0.07ms
検証(5) ③抽出カラムの絞込みによるチューニング コントローラ修正 @t1s = T1.find( :all, :include => [:m1,:m2,:m3,:m4,:m5,:m6,:m7,:m8,:m9], :conditions => ["t1s.trans_no<?",10] ) ↓ @t1s = T1.find_by_sql( [“SELECT `m01`・・・ FROM t1s LEFT OUTER JOIN m1s ON m1s.id=t1s.m1_id WHERE t1s.trans_no<?", 10] ※一部略記 チューニング前 Total 0.48ms Rendering:0.05ms DB:0.07ms チューニング後 Total 0.094ms Rendering:0.042ms DB:0.015ms
結果まとめ INDEXによるチューニングはDBアクセス部の効果が高い include句のチューニングはレンダリング部の効果が高い
チューニングの注意点 経験上、必要以上のチューニングはソースコードの可読性を落とします。 必要な場合のみに実施するようにしてください。 チューニング計画を立てる際は目標値を設定し、 様々な角度から調査を行いつつチューニングを行うようにしてください。
参考文献 パフォーマンスチューニングとは Ruby on Railsのパフォーマンス向上に関する10のtips http://www.atmarkit.co.jp/fjava/rensai3/devedge01/devedge01_1.html Ruby on Railsのパフォーマンス向上に関する10のtips http://blog.tkmr.org/tatsuya/show/268-ruby-on-rails-10-tips 【MySQLウォッチ】第8回 MySQLチューニングのテクニック http://itpro.nikkeibp.co.jp/members/ITPro/oss/20040624/146333/ mysqlチューニング Self-referential has_many :through associations http://blog.hasmanythrough.com/2006/4/21/self-referential-through
パフォーマンスチューニング on Rails おわり