クエリの最適化とは?
クエリの最適化とは、データベースに対するクエリ(検索や操作の命令)の実行速度やリソース効率を向上させるための手法や戦略です。アプリケーションのパフォーマンスを向上させるために非常に重要な要素であり、大規模なデータセットや頻繁に実行されるクエリで特に効果を発揮します。
なぜクエリの最適化が重要か
•高速な応答性: ユーザーが待たされる時間を減らし、UX(ユーザーエクスペリエンス)を向上させます。
•サーバー負荷の軽減: サーバーリソース(CPU、メモリ、ディスクI/O)を効率的に使用します。
•スケーラビリティの向上: アプリケーションが多くのユーザーやデータ量の増加に対応できるようにします。
•コスト削減: クラウドサービスでは、効率的なクエリによってリソース利用コストを抑えることができます。
Railsのgem bulletを使って不要なクエリを見つける
Railsアプリでクエリの最適化を学習するにあたって、どこの処理に無駄があるのか、どこの処理を最適化できるのかを判断するには、経験が必要です。
プログラミング学習3ヶ月目の私には、経験が圧倒的に不十分だったので何かいい方法はないかと調べていたら、無駄なクエリを判断してくれるbulletというgemがあったので導入して、学習していきます。
bullet導入方法
bulletは開発環境で使用することを想定していますので、
group :development do
gem 'bullet'
end
developmentグループの中に記述していきます。
記述できたらbundle install
をターミナルで実行。
そうすると、development.rbに下記の内容が追記されます。
Rails.application.configure do
#ここから下が追記されます
config.after_initialize do
Bullet.enable = true # Bulletを有効化
Bullet.alert = true # ブラウザで通知
Bullet.bullet_logger = true # Bullet用ログファイルに記録
Bullet.rails_logger = true # Railsのログに出力
Bullet.add_footer = true # ページのフッターに通知を表示
end
#ここまで
#他の記述は省略
end
Bullet.alert
ブラウザでポップアップ通知を表示します(開発中に確認が簡単)。
Bullet.bullet_logger
log/bullet.logに問題の詳細を記録します。
Bullet.rails_logger
Railsの開発ログ(log/development.log)に問題を記録します。
Bullet.add_footer
ページの下部に問題の通知を表示します。
使い方
まずはrails s
でサーバーを立ち上げてページにアクセスします。
私の場合(例)
users_controller/indexアクション
def index
@users = User.all
end
usersページへアクセスします。
そうすると画面上にこのような表示が出ます。
ここに無駄なクエリが発生していますと教えてくれます。
またフッター部分にも
このように表示されます。
railsアプリのlog/bullet.logファイルやターミナルのログにも残ります。
ここの内容を読み取って、クエリを最適化する方法を考えて実装していけばいいわけです。
たとえば私の場合だと、
user: ec2-user
GET /users
USE eager loading detected
User => [:profile_image_attachment]
Add to your query: .includes([:profile_image_attachment])
Call stack
/home/ec2-user/environment/RealShare/app/models/user.rb:31:in `get_profile_image'
/home/ec2-user/environment/RealShare/app/views/common/_users_index.html.erb:34:in `block in _app_views_common__users_index_html_erb___2423191927727510205_22300'
/home/ec2-user/environment/RealShare/app/views/common/_users_index.html.erb:32:in `_app_views_common__users_index_html_erb___2423191927727510205_22300'
/home/ec2-user/environment/RealShare/app/views/public/users/index.html.erb:1:in `_app_views_public_users_index_html_erb___3543943466301762287_22280'
細かい内容は
user: ec2-user
:実行中のユーザー名
GET /users
:getリクエストでusers_pathへアクセスしようとしている
USE eager loading detected
# Bulletが「Eager Loading(事前ロード)」が不足していることを検出したメッセージです。
User => [:profile_image_attachment]
# N+1問題が発生しているモデルと関連データを示しています。
Add to your query: .includes([:profile_image_attachment])
# Bulletが提案している改善方法です
Call stack
# N+1問題が発生しているコードの実行経路(スタックトレース)を示します。
/home/ec2-user/environment/RealShare/app/models/user.rb:31:in`get_profile_image'
# app/models/user.rbの31行目にあるget_profile_imageメソッド。
# profile_image_attachmentのデータ取得が、このメソッド内で行われていることを示します。
/home/ec2-user/environment/RealShare/app/views/common/_users_index.html.erb:34:in `block in
_app_views_common__users_index_html_erb___2423191927727510205_22300'
# app/views/common/_users_index.html.erbの34行目。
# ビュー内でユーザーごとにget_profile_imageを呼び出しており、これが原因で個別のクエリが発行されています。
/home/ec2-user/environment/RealShare/app/views/common/_users_index.html.erb:32:in `_app_views_common__users_index_html_erb___2423191927727510205_22300'
# 部分テンプレート(_users_index.html.erb)がレンダリングされる際、個別クエリが発行されていることを示しています。
/home/ec2-user/environment/RealShare/app/views/public/users/index.html.erb:1:in `_app_views_public_users_index_html_erb___3543943466301762287_22280'
# users/indexビューが最初に呼び出され、その中で部分テンプレート_users_index.html.erbが含まれています。
とこのように細かく教えてくれます。
処理を修正します。
def index
@users = User.includes(profile_image_attachment: :blob)
end
#今回は画像データまで取得したいので:blobを追記します
再度、rails s
を実行します。
先ほどまでuser一つ一つに対して画像取得クエリが、事前にロードされることで
最適化できました。
まとめ
1, 無駄なクエリの検出:Bulletを使用することで、どの箇所に無駄なクエリが発生しているかを簡単に把握できます。
2, 具体的な改善方法の提案:Eager Loadingを活用して関連データを一括で取得し、N+1問題を解決する方法がわかります。
3, ログの活用:ログや通知機能を利用することで、問題点を詳細に分析し、コードをより効率的に修正できます。
bulletを活用してクエリの最適化の学習を進めて開発に取り入れていきたいと思います。