-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Can't avoid under/overfetching #1697
Comments
Good catch! I think that what you describe should have been caught by As for |
Issue 1: BelongsTo in course collection@pablobm based on your hint, the following workaround in def collection_includes
super + [program: :translations]
end (The above is necessary because OK so now the course index works. Issue 2: HasMany/BelongsTo circular reference in course showNow I try to view a course. I have a data over-fetching of Course because Course has an associative attribute of type HasMany: Bullet::Notification::UnoptimizedQueryError at /admin/courses/my-course
user: sdubois
GET /admin/courses/my-course
AVOID eager loading detected
CourseModule => [:course]
Remove from your query: .includes([:course]) As a workaround and because I know no other way to solve the issue, I can live with removing Issue 3: BelongsTo in course formNow I try to edit a course. I get an N+1 in the form because it has a drop-down program select with the different program titles. The Issue 4: HasMany in formNow I continue to try to edit a course. I get another N+1 problem: Bullet::Notification::UnoptimizedQueryError at /admin/courses/my-course/edit
user: sdubois
GET /admin/courses/my-course/edit
USE eager loading detected
CourseModule => [:translations]
Add to your query: .includes([:translations])
def find_resource(param)
scoped_resource.eager_load(course_modules: :translations).find_by(slug: param)
end However this has no effect, the error about missing eager loading remains. Now I am stuck and don't know how to make the edit form work without having N+1 queries. My ultimate goal is to be able to allow bullet to raise errors whenever it detects an issue in development and test. @pablobm would you know how to unblock the issues above? I find the different ways that are required to apply scoping in the different actions very confusing. For example it feels odd that I have to write the same kind of information in the dashboard BelongsTo attribute's It feels to me the best would be to have a single way to define scoping for each dashboard's associative attributes, which by default would apply to all actions (index/show/edit), with an option to narrow the scope to some actions or to exclude some actions. |
Thank you for that comprehensive information @sedubois. I agree that this part of the code is a bit of a mess at the moment, and it's difficult to work with it. I'm looking into ways to refactor/redesign Administrate to overcome these difficulties. Regarding each specific issue you have, a main problem is that you have an association,
|
I just spent a while trying to figure out how to avoid N + 1 on a resource's index page, what finally solved it for me was overriding
This also worked for the show page. I didn't check to see if it solves all of OP's issues, but this appears to be the best way to quickly fix index & show query problems. I similarly thought that in the dashboard adding the below would help, after finding a related issue & PR, but it did not appear to do anything.
I then found this issue searching issues for "scoped_resource" to see if anything had been metioned about this, since I couldn't find anything in relation to eager loading issues anywhere except what was said related to |
Thank you for your feedback, @AFlowOfCode. I've been looking into this a bit today, using the Bullet gem and the Administrate demo app. On first approach, I'm not sure that Administrate can reliably infer what should be loaded eagerly and what shouldn't. For example, in the demo app, on the Customer index page, Bullet advises that we eager-load administrate/spec/example_app/app/models/order.rb Lines 15 to 17 in e71ebd8
Also on Customer index, Bullet advises that we remove an Other setups will have other use cases, and I don't think it's possible to predict them. What we may have to do is to document this, and provide hooks (perhaps in For example, I just gave it a quick go and refactored
An integrator could then override I'm not sure this is necessarily the solution we need. For one it only works for |
I would like to voice my need for this fix. Specifically in my use case I have a lot of associated records (~19000 associated with just one object). Loading all of these just for a Thanks for the excellent project, and I'd be happy to help build up a PR on this. |
@cguess - Which PRs are welcome. I must admit that there's always the problem that they may inadvertently introduce design decisions that depart from the general direction of the project, or cause other issues that are not obvious. For this reason, sometimes (more often than I'd like) they can end up unmerged and abandoned. However they are useful, as experiments necessary for figuring out solutions. |
@pablobm when there's a has_many association on an object the backend will display a column with the count of the number of associations with each line item. However, instead of doing a I'll look into putting together a PR for this. |
@cguess did you get anywhere with a PR for this? I'm running into the same problem on an app (Rails 6.1, Administrate 0.16.0):
Problem also occurs on nested pages, eg. User has many Accounts, and I'm rendering a few inline Account entries there, same problem with over-eager loading that final association. |
Perhaps this should be solved outside Administrate? I'm thinking of using the |
Having dug into it a little bit, there seems to be two possible improvements:
|
Hello all. I've been looking into this in the last few days. I think I may have a solution, but let's see what you think first. 1. Administrate's implementation of eager loadsI think that the first problem is that Administrate eager-loads all associative fields, and this is not useful. The default behaviour in collections (ie: index page and has_many tables) is the following:
Of these behaviours, it only makes sense to eager-load by default in the first case ( So a first fix would be to change the following: administrate/lib/administrate/field/base.rb Lines 15 to 17 in 108c819
To the following: def self.associative?
self <= BelongsTo || self <= HasOne
end (The name of the method should change too, and also be moved elsewhere, but this is the general idea). That should fix @jamie's issue at #2088 2. Counter cachesThe above should improve things, but they could be further improved by using counter caches in your models: https://guides.rubyonrails.org/association_basics.html#options-for-belongs-to-counter-cache However the over-eager load has to be removed first as per above. 3. An
|
Finally got some time to look at this again - @pablobm I think you're on the nose. I can confirm that on my reproduction app if I make that change to Administrate::Field::Base.associative? to I'd argue that a proper fix for this use case should run counts without needing N queries for it, maybe leverage https://github.com/k0kubun/activerecord-precounter (or just borrow the technique)? Relevant code flow that might need tweaking:
|
As a workaround for anyone following this thread, you can spot fix this in the current administrate version by tweaking the impacted dashboards in your app: def collection_includes
super.without(:messages, :logs)
# arguments are the COLLECTION_ATTRIBUTES entries you want to suppress eager loading
end |
That precounter gem looks neat. On one hand, I think that Administrate should use Rails's own default, conventional choices which in this case would mean using counter caches, but it would be neat if we could offer some sort of hook for users to plug this as an alternative. I haven't looked into it, but I suspect it won't be trivial 🙁 Having said that, if someone wants to experiment I'd be happy to review. |
Closing this as I think that #2211 solves some of the problems, while others are out of scope. Happy to continue discussing this if you think there's more to it. |
What were you trying to do?
I added bullet to my project, which shows that I should add eager loading for my dashboard relationships to avoid N+1 queries when showing the index. For instance I have a
Course
withprogram: Field::BelongsTo
, and thisprogram
is visible on the course index.Bullet throws:
The issue is gone if I remove
program
from the Course'sCOLLECTION_ATTRIBUTES
.So following the documentation, I changed
program:
toField::BelongsTo.with_options(scope: -> { Program.eager_load(:translations) })
.What did you end up with (logs, or, even better, example apps are great!)?
The option has no effect. In fact even if I change to
Field::BelongsTo.with_options(scope: -> { raise "I got called!" })
, it does not raise. It seems that thisscope
option is not applied.What versions are you running?
The text was updated successfully, but these errors were encountered: