Rails

【Rails】関連付けによる遅延読み込みを検知できるstrict_loading機能を使ってみる

Rails

 

今回は関連付けによる遅延読み込み(Lazy Loading)を防ぐことができる機能「strict_loading」を使ってみたのでまとめたいと思います。遅延読み込みを防ぐことでパフォーマンスに大きく影響するN+1問題を解決できるため、非常に便利な機能が導入されたと言えますね。

N+1問題の検知にはgemのbulletが有名です。
Rails
【Rails】N+1問題をアラート表示してくれるgem「bullet」を初心者向けにまとめてみたN+1問題をアラート表示してくれるgem「bullet」について解説しています。個人開発や練習用のアプリケーションでは導入の必要はありませんが、大規模開発になるとパフォーマンスは非常に重要になってくるので、ぜひ「bullet」というgemがあるということを頭に入れておきましょう。...

strict_loadingとは

strict_loadingとは、Rails6.1からActiveRecordに追加された機能で、関連付け(アソシエーション)による遅延読み込み(Lazy Loading)を防ぐことができる機能です。これにより、アプリケーションのパフォーマンスに影響を与えるN+1問題を回避することができます。

To prevent lazy loading of associations, strict_loading will cascade down from the parent record to all the associations to help you catch any places where you may want to preload instead of lazy loading.

参考: Strict loading in Active Record and more

 

N+1問題を検知する手段として他にはgem「bullet」があります。

Rails
【Rails】N+1問題をアラート表示してくれるgem「bullet」を初心者向けにまとめてみたN+1問題をアラート表示してくれるgem「bullet」について解説しています。個人開発や練習用のアプリケーションでは導入の必要はありませんが、大規模開発になるとパフォーマンスは非常に重要になってくるので、ぜひ「bullet」というgemがあるということを頭に入れておきましょう。...

strict_loadingを使ってみる

では実際にアプリケーションにstrict_loadingを導入し、どのように遅延読み込み(Lazy Loading)を検知してくれるのか確かめてみます。

新規アプリケーションの作成

% rails new strict-loading-app -d mysql
% cd strict-loading-app
% rails db:create
  • アプリケーション名: strict-loadnig-app
  • Ruby: 3.1.2
  • Rails: 7.0.3
  • DB: MySQL

User/Tweetモデルの作成

UserとTweetモデルをScaffoldの機能を用いて作成します。

% rails g scaffold User name:string
% rails g scaffold Tweet title:string user:references
% rails db:migrate
Rails
【Rails】秒速でアプリケーション開発できるscaffoldの使い方を簡単にまとめてみた手っ取り早くアプリケーションを開発することができるscaffold(スキャフォールド)の使い方について簡単に解説しています。新しい機能実装をテストしてみたい、すぐにでもアプリケーションを立ち上げたいといった場合に便利な機能なので、ぜひ覚えておきましょう。...

関連付け(アソシエーション)の定義

UserとTweetは1対多の関係になるため、以下のように関連付けを定義します。

class User < ApplicationRecord
  has_many :tweets
end
class Tweet < ApplicationRecord
  belongs_to :user
end

初期データの投入

遅延読み込み(Lazy Loading)を発生させるため初期データを投入します。

% rails c
> user = User.create!(name: '田中太朗')
> user.tweets.create!(title: '今日の朝ご飯')
> user.tweets.create!(title: '今日の昼ご飯')
> user.tweets.create!(title: '今日の夜ご飯')

strict_loadingの定義

関連付け(アソシエーション)にstrict_loadingを定義します。これにより、レコードを取得する際に遅延読み込みが発生した場合はエラーが表示されるようになります。

class User < ApplicationRecord
  has_many :tweets, strict_loading: true
end

それぞれのアソシエーションに定義せずとも、以下のようにモデル単位でまとめてstrict_loadingを適用することも可能です。

class User < ApplicationRecord
  self.strict_loading_by_default = true

  has_many :tweets
end

 

アプリケーション全体に適用する場合はapplication_record.rbに定義します。

class ApplicationRecord < ActiveRecord::Base
  self.strict_loading_by_default = true

  primary_abstract_class
end

N+1の検知

では実際にN+1が検知されるのか検証してみます。

User.last.tweetsとして関連先を取得すると、以下のように「#<Tweet::ActiveRecord_Associations_CollectionProxy:0x3660>」が出力されます。

% rails c
> User.last.tweets
User Load (0.2ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1
=> #<Tweet::ActiveRecord_Associations_CollectionProxy:0x3660>

 

一方で遅延読み込み(Lazy Loading)を回避するincludesメソッドを定義すると、エラーは吐かれずに関連先を取得することができました。

% rails c
> User.includes(:tweets).last.tweets
  User Load (0.4ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1
  Tweet Load (0.5ms)  SELECT `tweets`.* FROM `tweets` WHERE `tweets`.`user_id` = 2
=> [#<Tweet:0x000000010a98abb0
  id: 1,
  title: "今日の朝ご飯",
  user_id: 2,
  created_at: Mon, 20 Jun 2022 14:35:46.728524000 UTC +00:00,
  updated_at: Mon, 20 Jun 2022 14:35:46.728524000 UTC +00:00>,
 #<Tweet:0x000000010a98aa48
  id: 2,
  title: "今日の昼ご飯",
  user_id: 2,
  created_at: Mon, 20 Jun 2022 14:35:54.225074000 UTC +00:00,
  updated_at: Mon, 20 Jun 2022 14:35:54.225074000 UTC +00:00>,
 #<Tweet:0x000000010a98a8e0
  id: 3,
  title: "今日の夜ご飯",
  user_id: 2,
  created_at: Mon, 20 Jun 2022 14:35:58.030737000 UTC +00:00,
  updated_at: Mon, 20 Jun 2022 14:35:58.030737000 UTC +00:00>]

 

このようにstrict_loadingの機能を用いることで、簡単に関連付け(アソシエーション)による遅延読み込み(Lazy Loading)を防ぐことができます。

エラーではなくログに表示する

先ほどの例では、遅延読み込み(Lazy Loading)が発生した場合はエラーになりましたが、エラーにせずログに表示することも可能です。

config/environments/development.rbに以下の1行を追加します。

config.active_record.action_on_strict_loading_violation = :log

参考

 

今回は関連付けによる遅延読み込み(Lazy Loading)を防ぐことができる機能「strict_loading」についてまとめました。導入も簡単なので、gem「bullet」からの乗り換えを検討してみても良さそうです。