【Rails】 N + 1 문제 (eager_load 및 preload) 앱에서 배우기
소개
최근 SQL의 지식이 붙어 왔기 때문에, N+1 문제를 재차 공부.
그렇다면 아직 아직 모르는 부분이 있었으므로, 아웃풋 해 간다.
출력 방법은 교재 앱으로 실시 (아래 URL 참조)
실제로 손을 움직여 지식의 정착을 도모한다
우리가 학교에서 취급하고 있던 데이터 수는 털 같은 것이므로, 이번은 700 가까운 데이터 수로 실시.
데이터 수가 많기 때문에 표시 속도에 영향을 미치는 것을 알 수 있습니다.
대책 방법
자세한 것은 가득 기사가 있으므로, 할애합니다.
아래의 2개의 기사가 알기 쉬웠으므로 봐 주세요.
그러면 실제로 손을 움직여 가려고 생각합니다.
첫 번째 preload를 사용합니다.
app/views/profiles/_articles.html.erb
<% @articles.each do |article| %>
<div class="box">
<article class="media">
<div class="media-content">
<div class="content">
<h3>
<span class="icon has-text-success">
<i class="fas fa-file-alt"></i>
</span>
<%= article.title %>
</h3>
<p><%= article.description %></p>
<% article.tags.each do |tag| %>
<span class="tag is-light"><%= tag.name %></span>
<% end %>
</div>
</div>
</article>
</div>
<% end %>
@articles 의 내용을 확인.
컨트롤러를 보러갑니다.
app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
def index
@user = User.find(1)
raise Forbidden unless user_safe?
@skill_categories = user_reccomend_skill_categories
- @articles = @user.articles ←このコードが原因
+ @articles = @user.articles.preload(:tags)
end
# 略
메소드 목록에서 이번 사례를 찾으면 "루프 내에서 관련 테이블의 값을 사용하는 경우"가 일치합니다.
이 일로부터 eager_load인가, preload를 사용해, 기사 데이터의 취득시에 태그 데이터를 일괄 취득하는 대응이 효과가 있을 것 같습니다.
또 이번 케이스에서는 관련 테이블에서의 좁혀가 없고, LEFT OUTER JOIN구를 사용하는 메리트가 없기 때문에 preload를 사용합니다.
Completed 200 OK in 6792ms ➩ Completed 200 OK in 5137ms
速度改善することができた。
두 번째 eager_load를 사용합니다.
로그
Skill Load (0.1ms) SELECT "skills".* FROM "skills" WHERE "skills"."skill_category_id" = ? [["skill_category_id", 1265]]
↳ app/views/profiles/_user.html.erb:42
게시 SQL 아래에 ↳ app/views/profiles/_user.html.erb:42라는 로그가 출력됩니다.
app/views/profiles/_user.html.erb
<% @skill_categories.each do |category| %>
<div class="box is-medium">
<h4 class="title is-6">
<span class="icon has-text-success">
<i class="fas fa-toolbox"></i>
</span>
<%= category.name %>
</h4>
<% category.skills.each do |skill| %> ←42行目
<span class="icon has-text-success">
<i class="fas fa-check"></i>
</span>
<span class="tag is-light"><%= skill.name %></span>
<% end %>
</div>
<% end %>
category는 @skill_categories.each의 블록 변수임을 확인할 수있었습니다.
그럼 @skill_categories 란 무엇입니까?
@skill_categories 을 컨트롤러에 기재되어 있다고 생각하기 때문에 보겠습니다.
app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
def index
@user = User.find(1)
raise Forbidden unless user_safe?
@skill_categories = user_reccomend_skill_categories ←このコード
@articles = @user.articles.preload(:tags)
end
private
def user_safe?
@user.user_cautions.all? do |user_caution|
Time.zone.now > user_caution.caution_freeze.end_time
end
end
def user_reccomend_skill_categories
@user.skills.map(&:skill_category).
filter { |skill_category| skill_category.reccomend }.uniq
end
end
@skill_categories은 private 메소드의 user_reccomend_skill_categories 메소드의 반환 값과 같습니다.
마찬가지로 app/controllers/profiles_controller.rb에 정의되어 있습니다.
이것입니다.
def user_reccomend_skill_categories
@user.skills.map(&:skill_category).
filter { |skill_category| skill_category.reccomend }.uniq
end
이 메소드는 아래와 같은 내용이 걸려 있습니다.
`
① @user 에 묶는 skills를 모두 취득
② map 메소드로 skill마다 묶는 skill_category를 취득
③filter 메소드로 skill_category 인스턴스의 reccomend 속성이 true인 인스턴스만을 취득
④ ③까지의 실행 결과인 복수의 skill_category를 요소에 가지는 Array내의 skill_category 인스턴스를 일의로 한 Array를 돌려준다
※ 참고로 filter 메소드는 조건에 일치하는 요소만 추출하여 새로운 배열을 만들 수 있는 메소드입니다.
그럼. 메소드를 수정해 갑니다.
def user_reccomend_skill_categories
SkillCategory.eager_load(:skills).
where(reccomend: true).
where(skills: { user_id: @user.id })
end
메소드 목록에서 이번 사례를 찾으면 "루프 내에서 관련 테이블의 값을 사용하는 경우"가 일치합니다.
이 일로부터 eager_load인가, preload를 사용해, 스킬 카테고리 데이터의 취득시에 스킬 데이터를 일괄 취득하는 대응이 효과가 있을 것 같습니다.
또 데이터 취득시에 WHERE구를 사용해 좁히는 일로 1번의 SQL로 데이터 취득을 실시할 수 있습니다.
이 경우 where 메소드를 사용하기 때문에 eager_load를 사용합니다.
보기를보고 로그를 확인하면 · ·
Completed 200 OK in 6792ms ➩ Completed 200 OK in 2609ms
初回からここまで速度改善することができた。
이런 식으로 이번에는 eager_load와 preload를 사용해 보았습니다.
사이고에게
알고있는 것만으로 크게 성능을 향상시킬 수 있으므로 앞으로도 배워 나갈 것입니다.
데이터베이스 지식을 구축하고 싶습니다.
Reference
이 문제에 관하여(【Rails】 N + 1 문제 (eager_load 및 preload) 앱에서 배우기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/skyvader0524/items/244fcbec0668c7e872f6텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)