-
Notifications
You must be signed in to change notification settings - Fork 110
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
Dy/pm 15994 query optimization #294
Conversation
…iable to take ld flag instead
…ready found an association to load for the current field
…ecks for nil directly instead
object_to_hash(obj, | ||
view_name: view_name, | ||
local_options: local_options) | ||
if object.respond_to?(:reflect_on_all_associations) && preload_associations? |
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.
Would you flip this comparison so that it is
if preload_associations? && object.respond_to?(:reflect_on_all_associations)
While overall impact is small, we should put the less performant method after the &&
as it is not always needed to execute.
# Example: object_relation is an Account that has_one :user and has_many :subscriptions. | ||
# The hash returned would be {:user => :user, :subscriptions => :subscriptions, | ||
# :subscription_ids => :subscriptions}. Then the calling method would preload | ||
# :subscriptions and :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.
We've been using yardoc meta tags to generate our documentation on https://rubydoc.info/gems/blueprinter/0.25.3/Blueprinter/Base. To keep it consistent, would you change these docs to use yardoc tags. And same where you have the other method docs as well.
collection_associated_hash | ||
end | ||
end | ||
end |
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.
missing newline
# The hash returned would be {:user => :user, :subscriptions => :subscriptions, | ||
# :subscription_ids => :subscriptions}. Then the calling method would preload | ||
# :subscriptions and :user | ||
def get_field_to_association_hash(object_relation) |
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.
instead of get_field_to_association_hash
it should be field_to_association_hash
or field_to_association
. Ruby methods typically are nouns representing the return value.
@@ -6,6 +6,8 @@ def self.included(base) | |||
|
|||
module SingletonMethods | |||
include TypeHelpers | |||
include AssociationHelpers | |||
@@should_preload_associations = 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.
Two things:
- Instead of
should_preload_associations
, it should just bepreload_associations
. Otherwise it seems unsure and somewhat like a test file. - This should be in configurations.rb https://github.com/procore/blueprinter/blob/60b8f94616e18aa0346fa3c697b9e30c66c4ec8c/lib/blueprinter/configuration.rb#L3-L18 so that users can configure their options like everything else.
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 what you are saying here @philipqnguyen . One thing to note is that the preload_associations
variable you are referencing is not meant to be a permanent field. It would only exist until we are done testing this feature. After that time, blueprinter will try to eager load associations that would otherwise not be eager loaded. My main concerns are:
- I will need to introduce more code into the client.
- Developers in the client may start using this option which will could result in blueprinter continuing to user eager loading even if we turn the flag off. This would be a problem if a bug arises and we want to roll back the eager loading changes in blueprinter. We will not be able to do so without a redeploy
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.
Blueprinter is a public gem and changes to Blueprinter should be ready for use by the greater Ruby community.
If the goal in this PR is just to test this feature, I suggest 2 things:
a) Either have the monolith's Gemfile reference this branch
b) Or directly override the monolith's Blueprinter with these changes (basically a monkey patch).
When you have completed the test in the monolith, then we can introduce the change into Blueprinter itself.
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 like the idea of pointing the Gemfile to this branch. I would also like to be able to toggle this feature with a feature flag, as opposed to a config that would allow developers to use the feature in other places as we test. The idea being that we would only scope the change to a narrow part of the code (extended view of tasks controller).
I would propose:
- Pointing the client repo to this local branch via the Gemfile as you suggested.
- Using the feature flag as is currently implemented on this branch
- Remove the feature flag once we're done testing then merge into master
This approach would decrease the likelihood of needing a redeploy in case something goes wrong because we would be able to toggle the feature flag off.
Please let me know what you think. Thank you
object_to_hash(obj, | ||
view_name: view_name, | ||
local_options: local_options) | ||
if object.respond_to?(:reflect_on_all_associations) && preload_associations? |
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.
What happens if the object given is already eager loaded?
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.
Good call. I'll add a check for this
collection_associated_hash[association_name_as_symbol] = association_name_as_symbol | ||
end | ||
# do our best to determine the autogenerated method name. Does not account for custom names | ||
collection_ids = association_name_as_symbol.to_s[0..-2] + '_ids' |
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 took me some time to understand why you were doing [0..-2]
. You can instead use association_name_as_symbol.to_s.singularize
, which is part of ActiveSupport in Rails. This section of the code should only be executing inside Rails, so you should have access to that method.
collection_associated_hash = Hash.new | ||
if object_relation[0].respond_to?(association_name_as_symbol) | ||
collection_associated_hash[association_name_as_symbol] = association_name_as_symbol | ||
end |
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 might be able to DRY up line 56 - 59 because these are identical to line 39 - 43 in the singular_associated_fields_hash
method above
list_of_associations_to_preload << field_to_association_hash[field.name] | ||
end | ||
end | ||
object_relation_with_associations_loaded = object.preload(list_of_associations_to_preload) |
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.
Does this preload nested associations?
For example, User.preload(posts: :comments)
?
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.
No, it does not
Thanks for opening this PR @davidyeeprocore1, and apologies for the extended delay here. Looking through the proposed changes, while there's certainly benefit here, my fear is that it over-extends the responsibilities of Blueprinter, which is primarily focused on the serialization of data and less about how the data is aggregated beforehand. Moving forward, ensuring that Blueprinter isn't directly tied to any persistence layer will help improve maintainability and enable more use cases for applications that do not use From my perspective, to ensure clear, enforceable boundaries in application code, any required preloading should be handled prior to invoking |
@njbbaer are you considering taking this proposal forward? I agree with @lessthanjacob's take that this definitely extends the responsibilities for a serializer. Also, when you consider supporting things like Arrays, hashes and other objects like Mongo docs, this could mean adding additional overhead just for handling one type of data. ActiveRecord's internal API can change with versions and we have internally burnt our fingers trying to conditional eager_load outside of core AR. It gets really tricky to manage this across rails versions etc. So I would definitely recommend to let end-users optimise for stuff like eager-loading instead of the serializer trying to do it magically. |
@ritikesh I agree with you and @lessthanjacob, this expands Blueprinter's scope probably more than we want. I reached out to @davidyeeprocore1 directly to see if he wants to make a case for it, but if not I'll close this PR shortly. |
I can understand why this isn't being merged, but Blueprinter should at least expose the introspection capabilities necessary for other tools do this. Otherwise it's just an "N+1 query generator". Would you all be open to accepting a PR like that? |
@jhollinger - would something like Bullet be of help? |
@ritikesh No, never found Bullet very useful. Dev/staging data is never complete enough to find all N+1's outside of production. Blueprint already has the info necessary to do eager loading (with a little help from AR reflections maybe). If Blueprint just exposed its definitions for other tools to hook into, it would be so much more powerful (e.g. a |
Hello! Just catching up on this. I like the approach proposed by @jhollinger regarding a Perhaps we can use this PR as inspiration/reference for building the |
Currently, blueprinter does not attempt to eager load associations for collections of activerecords (ActiveRecordRelation). This results in n+1 queries being performed. The way this is resolved at the moment is the client (application that ingests blueprinter) must perform preloading. This PR aims to solve the n+1 query issue from within blueprinter rather than placing the burden on the client.