-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use cleverer patterns to fetch/iterate AR objects when checking presence #5833
Use cleverer patterns to fetch/iterate AR objects when checking presence #5833
Conversation
@@ -3,7 +3,7 @@ module DeleteArticles | |||
module_function | |||
|
|||
def call(user, cache_buster = CacheBuster) | |||
return unless user.articles.any? | |||
return if user.articles.blank? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as we iterate with .map
right after (which issues a select * anyway), this is going to issue one less query.
@@ -6,7 +6,8 @@ | |||
follow_cue: @boosted_article.organization&.tag_line || @boosted_article.organization&.tag_line %> | |||
<% end %> | |||
|
|||
<% if @suggested_articles.any? %> | |||
<%# the pattern .present?/.each has the advantage of issuing only a single SQL query to load objects in memory %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you'll notice the pattern .present?/.each
many times in this PR
@@ -1,14 +1,15 @@ | |||
<div id="sidebar-wrapper-right" class="sidebar-wrapper sidebar-wrapper-right"> | |||
<div class="sidebar-bg" id="sidebar-bg-right"></div> | |||
<div class="side-bar sidebar-additional showing" id="sidebar-additional"> | |||
<% if Article.active_threads([@tag, "discuss"], Timeframer.new(params[:timeframe]).datetime).any? %> | |||
<% active_threads = Article.active_threads([@tag, "discuss"], Timeframer.new(params[:timeframe]).datetime) %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this just made sense, we were calling .active_threads
twice in the same partial
@@ -1,4 +1,4 @@ | |||
<% if !organizations.any? %> | |||
<% if organizations.none? %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if not any
made less sense that if none
;)
@@ -8,11 +8,11 @@ | |||
<% end %> | |||
<% @stories.each_with_index do |story, i| %> | |||
<%= render "articles/single_story", story: story %> | |||
<% if i == 0 && @comments.any? %> | |||
<% if i == 0 && @comments.present? %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
users/comment_section
will definitely load comments, so we can preload them here, this, combined with the changes inside the partial, saves one or two queries
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice one @rhymes 👏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really think we should keep organization.users.find_each
<% if @organization.users.present? %> | ||
<div class="widget"> | ||
<div class="widget-suggested-follows-container"> | ||
<header><h4>meet the team</h4></header> | ||
<div class="widget-body"> | ||
<% @organization.users.find_each do |user| %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this may happen sooner than you think. You get one large org added right now and this will break. I think I would prefer to stick with the safety of find_each
rather than risking it for a small speed up from using .present?/each
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering about this line yesterday, but then I decided that I like @rhymes change, since find_each
's default batch size is 1000, and it seems very unlikely that we'd have an org with more users than that. And I guess it wouldn't break, "just" consume a lot of memory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given we have talked about setting up communities for large organizations, our first Github, Microsoft etc will have well over 1k users. As for breaking modes, this will either timeout in which case the page will fail to load OR if we do OOM there is a chance it takes the whole box down with it as I have not seen great memory slice control from Heroku
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok let's revert it, there's a larger issue at stake here which is the fact that we shouldn't do these kind of queries in the views (actually we shouldn't do queries in the views as much as possible :D). I'm going to switch back to .find_each
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw that you re-requested my review but still seeing the each change here. Did you forget to push it up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mstruve oh shoot! yes :( done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Been to that rodeo before! 😝
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mstruve Thanks for the context, I wasn't aware of any plans to onboard organizations of this size.
One question regarding the failure modes: in order to render the page, we'll still need to finish all iterations of find_each
, so if that takes too long we will still run into timeouts, right? This suggests that we should probably keep a mental note of this particular line and move the SQL queries out of the view as @rhymes suggested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For sure but I think thats a project for another day
<% if @user.organizations.present? %> | ||
<div id="sidebar-organizations" class="widget"> | ||
<div class="widget-suggested-follows-container"> | ||
<header><h4>organizations</h4></header> | ||
<div class="widget-body"> | ||
<% @user.organizations.find_each do |organization| %> | ||
<% @user.organizations.each do |organization| %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going this way I am fine with bc I dont think a user will likely every belong to thousands of orgs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are nice optimisations @rhymes!
Sorry @lightalloy and @Ridhwana for invalidating your reviews :( I left out a change, see aca30c7 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🚀
What type of PR is this? (check all applicable)
Description
As you know AR has many "presence" methods attached to "has many" relations:
.present?
,.blank?
,.any?
,.empty?
,.none?
,.exists?
Apart from the obvious result we expect due to their names, these are not all intercheangeable, sometimes resulting in unnecessary queries.
Armed with the section "any?, exists? and present?" (I've already submit a PR to optimize counts #5478) in the article 3 ActiveRecord Mistakes That Slow Down Rails Apps: Count, Where and Present by Nate Berkopec I went around the code, replaced those that could be cleverer in how they fetch data, documented the why of some of them and made sure they made sense for our cases.
Please let me know if some do not make sense for you.
Related Tickets & Documents
A bit of a background, I'm going to quote Nate directly:
Confusing eh? :D Fortunately there's a nice table in the article that summarizes what happens in Rails 5.1+:
from https://www.speedshop.co/2019/01/10/three-activerecord-mistakes.html