はしがき
2023年度末ぐらい、「N+1の問題」がありました。その時は、なんとなく、解決しましたが、解決の内容や知識など、整理しておきたい気持ちなので、記事を作成します。
N+1の問題 ってなに?
(ORM の)OOP や RDB のパラダイムから発生する問題
ORM から loop 処理途中、loop の毎回 SQL を発生、これでパフォーマンスが低下される問題
OOP の Object は他の Object と関係を持つことができます。
メモリの random access で、ある Object の関係になっている他の Object にリファレンスできます。
Random access: https://en.wikipedia.org/wiki/Random_access
RDB にもテーブルとかテーブルは関係を持つことができます。
しかし、単純に、あるテーブルから関係になっている他のテーブルのレコードを取得することではなく、追加的なクエリーが必要です。
つまり、OOP はリファレンス(参考しているアドレス)から、他のテーブルのデータの取得できます。
RDB は追加クエリーで、他のテーブルのデータの取得できます。
問題を発生してみよう
前提のテーブル
テーブル関係
brand:machine = 1:N
関係です。
テーブルレコード
rails から問題発生
# 悪いこーど生成
Brand.all.each do |b|
if b.machines.size >= 2
b.name = "big " + b.name
b.save!
end
end
発生しました!
Brand のレコードのごとに SQL が実行されます。
これはパフォーマンスによくない影響があるはずです。
解決方法
Query の内容も確認したかったので、log/development.log
から確認しました。
preload
Brand.preload(:machines).all.each do |b|
if b.machines.size >= 2
b.name = "big " + b.name
b.save!
end
end
指定した関係のテーブルの複数のクエリーで取得して、キャッシュします。
preload の場合、絞り込み条件(where など)には元のテーブルのカラムのみ使えます。
eager_load
Brand.eager_load(:machines).all.each do |b|
if b.machines.size >= 2
b.name = "big " + b.name
b.save!
end
end
指定した関係のテーブルの LEFT OUTER JOIN で取得して、キャッシュします。
LEFT OUTER JOIN なので、絞り込み条件(where など)に対応するテーブルのカラムが使えます。
includes
preload と eager_load を柔軟に対応します。
追加的なこと
元々ロジックが悪いので、N+1の問題になっているかもしれませんね。
これのロジックを修正してみます。
ロジックを修正してみる
Brand.where(id: Machine.group(:brand_id)
.having('COUNT(*) >= 2')
.pluck(:brand_id))
.update_all("name = CONCAT('big ', name)")
ここは pluck を使いましたが、pluck は追加クエリーが生成されるので、select の方が良いかもしれません。
変更したロジックを subquery を使ってみる
UPDATE brands
SET name = CONCAT('big ', name)
WHERE id IN (
SELECT m.brand_id
FROM machines as m
GROUP BY m.brand_id
HAVING COUNT(*) >= 2
);
むしろ、クエリーをはっきりしていたら、クエリーで直接にレコードを取得する方法が良いかもしれません
参考&関連 URL
https://incheol-jung.gitbook.io/docs/q-and-a/spring/n+1
https://qiita.com/muroya2355/items/d4eecbe722a8ddb2568b
https://qiita.com/massaaaaan/items/4eb770f20e636f7a1361
https://velog.io/@xogml951/JPA-N1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EC%B4%9D%EC%A0%95%EB%A6%AC
https://gmlwjd9405.github.io/2019/02/01/orm.html
https://stackoverflow.com/questions/22881974/how-to-understand-reference-in-object-oriented-programming
https://en.wikipedia.org/wiki/Reference_(computer_science)
https://nippondanji.blogspot.com/2015/06/rdb.html
https://coding-factory.tistory.com/870
https://programmer93.tistory.com/83
https://railsguides.jp/active_record_querying.html
https://qiita.com/k-o-u/items/31e4a2f9f5d2a3c7867f