バックエンドにRDBMSを使うアプリケーションにとって、SQLのチューニングは重要だけど放置されることが多いものだったりもします。その理由としては、
- そもそも、SQLを使いこなせるレベルの知識・能力がない
- (特に、フレームワークの中から使う場合)ORMオブジェクト⇔SQL⇔DBアクセスと変換が入るので、やりたいことを制御しづらい
などがあります。とはいえ、画像処理などを除けばWebアプリの大きな時間を占めるのはデータベースアクセスですし、ときに僅かなチューニングが、数十倍の速度差になるなど劇的な効果をもたらすこともありますので、やはり重要となります。
遅い箇所を洗い出す
データベースの速度を調べるにはいくつか方法があります。
まずは、「実際にSQLを投げてみる」というのが簡潔な手段です。何も仕掛けていない本番サーバで速度を知りたい場合、このような原始的な手段が必要となることがあります。
そして、1秒以上かかるような、絶対的に長時間のクエリに対しては、スロークエリログを仕掛ける、という方法があります。バッチで長時間のクエリが必ず発生する、という条件だとそれがじゃまになることもありますが、どこで発生しても取れる、というのはいいところです。
あと、本番環境では難しいのですが、フレームワーク内で使えるプロファイラが存在する例も多いです。Railsでも、rack-mini-profiler
のような、各SQLの時間まで取れるプロファイラがあります(参考)。
実際に高速化した例
いま進行中のプロジェクトから抜き出した例ですが、やはり検索画面で一覧生成となると、書き方を間違えればあっという間に0.5秒を突破するような長時間クエリとなります。
- スロークエリを見ていたところ、検索画面に使われているクエリに数十秒、なんていう凄まじい時間がかかっていてまずい状況となっていまいた。調べてみたところ、Eager Loadのために行っていたJOINで、STIのためにJOINする側のテーブルに条件がかかっていたのに引きずられて、必要以上の列を一時テーブルで生成してしまっていることが判明しました。このテーブルをJOINせず、eager loadを
preload
で行うようにした結果、クエリ時間が0.2秒まで改善しました。 - これまた同じ画面で、ページのリクエストタイムが1秒弱になっていたので追いかけてみたところ、
DISTINCT
のための結果生成に時間がかかって、クエリの処理が0.4秒以上かかっていることに気づきました。調べれば、DISTINCT
をかける前から1件しかありえなかったので外したところ、それだけでクエリ実行は0.1秒を切るようになりました。
クエリ自体を削る
FOREIGN KEY
などの関係上で、マスターもDBに持たせることが多いですが、このあたりはかんたんに変わらないので、(更新時にキャッシュクリアするフックをつけて)長期キャッシュにしておけば、そもそも最初以外はDBを叩く必要もなくなります。