Add support for eager loading Active Storage variants#40842
Add support for eager loading Active Storage variants#40842pixeltrix merged 2 commits intorails:mainfrom
Conversation
with_all_variant_records method to eager load all variant records on an attachment at once|
Looks interesting @ghiculescu! This will allow to do |
cb81070 to
cdc626f
Compare
f0e15ab to
d788f15
Compare
|
does this work if the storage is aws s3? |
|
@rickyPanzer this should work with any storage backend. It doesn't actually interact with it the storage service at all. If you're using tracked variants, that will allow you to store in your DB a pointer to a variant in the storage backend - this PR enables you to eagerly load those pointers. But both this and variant tracking itself should be backend agnostic. |
|
Just coded against this PR last night, works great! I hope it gets merged in |
|
@georgeclaghorn can I please ask for a review here? |
d788f15 to
e92cdcd
Compare
|
Some news about it? I think that's a great feature and could help a lot for pages with lots of images on AS |
|
@VinyLimaZ @rickyPanzer @vizcay can you share what use cases (/sample code) you have in mind for this feature? It would help me ensure that the current implementation will do what you need. |
|
Is there any news about this? I was trying to do something like this: I want to include the thumbnail variant so I don't have to request one by one in the serializer Thanks! |
e4a8e10 to
3b14b91
Compare
|
Would love to keep this PR alive. @ghiculescu I've got a page that shows a few hundred thumbnails (50px variant of original) and there doesn't seem to be a way to avoid the N+1 unless I store the thumbnails as a separate attachment. Hope this can get in. |
|
I'd love to keep it alive too! I think it's ready to go and the build is green, I'm just not sure how to get it merged in 🤷♂️ |
@rickyPanzer would you mind sharing how you monkeypatched the |
@ghiculescu looks like there is one failing test on the current build, looks like an unrelated test may need to be updated? |
3b14b91 to
430fd82
Compare
|
@tosbourn looks like it was unrelated. I've rebased against main which should fix it 🤞 Update: all green. |
|
I posted a blog this morning for those who want to monkeypatch an existing Rails install to gain this functionality. See the "Extra credit" section |
|
@wbharding you might like the Any chance you can share the context around your calling code? I think the Active Storage Way (tm) would be to do I suspect one of the reasons this PR is being held up is because you only get N+1s if you do |
|
Hey @ghiculescu sure thing! Really appreciate the work that you've been putting into figuring out the solution to this and getting it accepted. The use case I describe in the blog post is for my site noteapps.info, where there are a few reasons that we are using We are running a site on Heroku (high variance in response time, high variance in transit between database and query) that is trying to render up to hundreds of images per page load, like this page, where any checkbox icon you hover on pops a screenshot. Since our screenshot URLs use a small watermark, and are rendered within a presenter (no access to typical view methods, all data returned as JSON), making a call with the deep Another example is the app-specific page, where we need to look up about 150 images for a particular note taking app. If it weren't for Finally, we cache all of our pages as aggressively as possible with Cloudflare. Calculating the processed URLs before rendering the page ensures that these cached pages can be rendered and have all of their images loaded with no calls being made directly to our Heroku server. ✨ |
|
@pixeltrix Is there any chance to have a backport? Rails 7 seems very far and this would be a life-safer for us. |
|
@collimarco I’d suggest running Rails against the main branch, it’s much less scary than you’d think and means you’ll basically get all the stuff that’s gonna be in Rails 7. |
|
Ouch... I'm testing this PR in our application and it does not fix the N+1 issue for us :( This is the code:
This is the log output for each iteration if you call Basically |
As noted at rails#40842 (comment), the current implementation works great with the `processed` method, but doesn't work with other methods such as `key`. This PR fixes that. Why is this fix necessary? Currently when you call `VariantWithRecord#key`, `key` gets called on `VariantWithRecord` -> `Attached::One` -> `Attachment` -> `Blob`. This results in n+1 calls to load the `Attachment`. But for this purpose we shouldn't need to load it, as the `VariantWithRecord` already knows about it relevant `Blob`. So this PR changes the method to call directly to that blob.
As noted at rails#40842 (comment), the current implementation works great with the `processed` method, but doesn't work with other methods such as `key`. This PR fixes that. Why is this fix necessary? Currently when you call `VariantWithRecord#key`, `key` gets called on `VariantWithRecord` -> `Attached::One` -> `Attachment` -> `Blob`. This results in n+1 calls to load the `Attachment`. But for this purpose we shouldn't need to load it, as the `VariantWithRecord` already knows about it relevant `Blob`. So this PR changes the method to call directly to that blob.
|
#42981 fixes that ^ |
As noted at rails#40842 (comment), the current implementation works great with the `processed` method, but doesn't work with other methods such as `key`. This PR fixes that. Why is this fix necessary? Currently when you call `VariantWithRecord#key`, `key` gets called on `VariantWithRecord` -> `Attached::One` -> `Attachment` -> `Blob`. This results in n+1 calls to load the `Attachment`. But for this purpose we shouldn't need to load it, as the `VariantWithRecord` already knows about it relevant `Blob`. So this PR changes the method to call directly to that blob.
Docs for rails/rails#40842 + rails/rails#39397 [ci skip]
As noted at rails/rails#40842 (comment), the current implementation works great with the `processed` method, but doesn't work with other methods such as `key`. This PR fixes that. Why is this fix necessary? Currently when you call `VariantWithRecord#key`, `key` gets called on `VariantWithRecord` -> `Attached::One` -> `Attachment` -> `Blob`. This results in n+1 calls to load the `Attachment`. But for this purpose we shouldn't need to load it, as the `VariantWithRecord` already knows about it relevant `Blob`. So this PR changes the method to call directly to that blob.
I observe se exact same behavior on my end. This code does not seems to fix this N+1 issue. I'm running Rails '7.0.4' on my project. Is there anything I can do to help testing it ? |
Currently Active Storage variant tracking runs a query for each attachment to check for a variant. Regular Rails n+1 prevention techniques (
includes) cannot prevent this.This PR adds
with_all_variant_records, as well as allowingincludesto work as expected on Active Storage attachments.It also updates the builtin has_many scope to also load variant records. So code like this which currently has n+1s will not have them after this PR:
Fixes several of the comments in #37901.
In implementing this, I borrowed heavily from the style of #39397.