この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。
railsで開発しているとN+1 Queryというエラーに遭遇しました。このN+1 Queryについて調べてみると、別名N+1問題と呼ばれており、サービスのパフォーマンスを落とす問題の一つであることがわかりました。今回は、この問題を解決する為に調べたN+1問題の原因と解決方法について記事にしました。
N+1問題とは
N+1問題を簡単に説明すると、クエリがデータ数よりも多くなります。データ数よりも多くのクエリが発行されることで、サービスのパフォーマンスを低下させます。少量のデータの場合は、問題を感じることは少ないと思いますが、実際にサービス運用では膨大なデータ量を扱うことがほとんどなので放っておくことはできません。
問題発生例
実際に、どのような場合にこの問題が発生するのか例題を使って説明します。
以下のようなハードウェアモデルとソフトウェアモデルが存在するとします。ハードウェア1つに対して複数のソフトウェアが結びつく関係です。
class Hardware < ActiveRecode::Base
has_many :softwares
end
class Software < ActiveRecode::Base
belongs_to :hardware
end
この2つのモデルを使用したコードが以下にありますが、このコードがN+1問題を発生させてしまいます。このコードの処理は次のことを行っています。
1. ジャンルがアクションのソフトウェアのデータを取得
2. 取得したソフトウェアのデータ1つ1つに対してハードウェアのデータを取得
softwares = Software.where(genre: "action")
softwares.each do |software|
software.hardware
end
1.の処理では、ジャンルがアクションのソフトウェアをまとめて取得している為、クエリの数は1つになります。
2.の処理では、取得したソフトウェアの数だけバードウェアを1つずつ取得しているので、クエリの数は取得したソフトウェアの数になります。
つまりこの2つの処理では、取得したソフトウェアの数をNと置き換えると、N+1のクエリを発行していることになります。
解決方法
N+1問題の原因は、データ一覧取得後、取得していないデータを参照していることです。(上の例だと、ソフトウェアを取得している時にハードウェアを取得せずに、後から取得している。)この原因を解消する為に、データ一覧取得時にincludesで関連するデータをまとめて取得する方法があります。下のコードは、先ほどの例題にincludesを使用して改善したものです。
softwares = Software.where(genre: "action").includes(:hardware)
このようにincludesを使用することで後からデータを参照した場合でもクエリが発生することはありません。includesを使用した場合のクエリ数は、ジャンルがアクションのソフトウェア取得の1回と取得したソフトウェアに結び付くハードウェアの取得の1回の計2回となります。
まとめ
RailsエラーでN+1という文字を発見したら、クエリが余分に発行されていないか確認してincludesで対応しましょう。