-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
More eager loading in admin and api #3398
More eager loading in admin and api #3398
Conversation
backend/app/controllers/spree/admin/product_properties_controller.rb
Outdated
Show resolved
Hide resolved
cb7fd19
to
99dbbfc
Compare
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.
Awesome, thanks.
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.
@softr8 thank you for this PR!
I have some minor concerns about some changes, please let me know what you think... thanks 🙏
@@ -3,7 +3,7 @@ | |||
json.cache! [I18n.locale, order] do | |||
json.(order, *order_attributes) | |||
json.display_item_total(order.display_item_total.to_s) | |||
json.total_quantity(order.line_items.sum(:quantity)) | |||
json.total_quantity(order.line_items.to_a.sum(&:quantity)) |
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.
Not sure about this change... I think the previous code was simpler and more performant, as the sum
was done directly in the DB, without needing for ruby to instantiate all the line items, convert them to_a
and sum their quantity
fields... or is the change for another reason?
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.
The order should already have preloaded the line items. If we do a sum
in the database than we always load it from the database. And even if the line items have not been preloaded yet, they would be loaded by this call. It is always preferable to use array methods instead of where
statements as the latter will always - no matter what - load from the database, while the former would only load if necessary.
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.
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.
Oh, I see:
order = create :order_with_line_items, line_items_count: 10
Benchmark.bm do |bm|
bm.report { order.line_items.sum :quantity }
bm.report { order.line_items.to_a.sum &:quantity }
bm.report { order.line_items.reload.to_a.sum &:quantity }
end
user system total real
0.000908 0.000217 0.001125 ( 0.001124)
0.000036 0.000006 0.000042 ( 0.000041)
0.000956 0.000162 0.001118 ( 0.001122)
so, as long as the association has already been loaded from the DB, using ruby is so much faster... and when not, the numbers seem to be in the same ballpark. Then this makes definitely sense 👍
def available_store_credit_total(currency:, force_reload: true) | ||
# This reload makes eager loading not to work when hitting read only endpoints | ||
store_credits.reload if force_reload | ||
store_credits.to_a. |
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.
while we are at it, I think it may be worth, in a dedicated commit, to slightly optimize this method... I don't see any reason to not use ActiveRecord for selecting the store credits with the right currency:
store_credits.where(currency: currency).sum(&:amount_remaining)
what do you think?
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.
Not sure about it, the thing is that we're trying to re-use as much as possible all data pre-loaded with eager loading, and if we do it, it'd execute extra queries to the db.
core/app/models/spree/order.rb
Outdated
@@ -671,13 +672,13 @@ def add_store_credit_payments | |||
|
|||
def covered_by_store_credit? | |||
return false unless user | |||
user.available_store_credit_total(currency: currency) >= total | |||
user.available_store_credit_total(currency: currency, force_reload: false) >= total |
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 we're changing the behavior here, as it was reload
ing previously... can you explain the reason for this?
core/app/models/spree/order.rb
Outdated
end | ||
alias_method :covered_by_store_credit, :covered_by_store_credit? | ||
|
||
def total_available_store_credit | ||
return 0.0 unless user | ||
user.available_store_credit_total(currency: currency) | ||
user.available_store_credit_total(currency: currency, force_reload: false) |
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.
same as above, I think the behavior is changed now
@@ -686,9 +687,9 @@ def order_total_after_store_credit | |||
|
|||
def total_applicable_store_credit | |||
if can_complete? || complete? | |||
payments.store_credits.valid.sum(:amount) | |||
valid_store_credit_payments.to_a.sum(&:amount) |
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.
Like above, I think the previous version was more performant, as it did all calculations in the DB without instantiating and cycling ruby objects
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.
It is not more performant this way. Quite contrary this only does the math in the database if the payments have not been loaded yet. If they already were loaded the sum in database approach is actually slower, as it unnecessarily loads the payments again.
core/app/models/spree/order.rb
Outdated
else | ||
[total, (user.try(:available_store_credit_total, currency: currency) || 0.0)].min | ||
[total, (user.try(:available_store_credit_total, currency: currency, force_reload: false) || 0.0)].min |
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 see another change of behavior... by the way, this may be totally legit, I did not investigate, can you share your motivation for this change? 🙏
With the concept of galleries, the relationship changed and it now uses variant images
This changes allows to specify eager loading in the belongs_to definition at the conrtoller level class Controller < Spree::Admin::ResourceController belongs_to :product, find_by: :slug, includes: {variant: :prices} end
Calling product property name was causing query n+1, eager loading properties solved the problem
We can eager load only the first level
It requires a lot of information to be rendered, this change tries to eager load as much data as possible, perfomance gains are notable in orders with multiple line items
99dbbfc
to
c1f89ef
Compare
@softr8 I reviewed the changes, now I understand using I see specs are red, can you look if the issue is related to these changes? |
Updated eager loading relationships, it no renders big partial so no need to eager load extra information, created new relationship to fetch valid store credits payments so rails can load them automatically and also used previous relationship with .to_a instead of active record executing extra queries
c1f89ef
to
234a36d
Compare
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.
looks good to me, thank you @softr8 👍
Description
With recent changes, some previous eager loading attempts are no longer applicable, I'm trying to update as much as possible.
I've added the option to specify
includes
in the resource controller when the resource depends of another one and is specified viabelongs_to
. We can now use it everywhereChecklist: