-
Notifications
You must be signed in to change notification settings - Fork 11
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
Skip redundant queries when include_data is disabled #47
Skip redundant queries when include_data is disabled #47
Conversation
Well, found one more missing piece, please take a look ) I merged them without your approval but hope that is not an issue since they are about documentation and tests. They blocked |
941ca34
to
3fdda71
Compare
Have no ideas how to overcome that |
Hey again;) Regarding #46 - it'd be cool to have 0.10.0rc4 tests back as my we're using it in our company :D |
No problem, I'll add ) |
Here we go ) #48 |
3fdda71
to
b0ad1df
Compare
Rebased to master with testing against v0.10.0.rc4, pushed ) |
b0ad1df
to
4333617
Compare
4333617
to
a87b378
Compare
ugh, no chance to escape.. |
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.
Sorry that it took some time. I did a first round of review - let me know what you think about my comments.
Btw, good to know that include_data
option exists in newer AMS versions. I'd never notice it, because we're using 0.10.0rc4 in my company 🗡
@@ -64,7 +64,7 @@ class UserSerializer < BaseSerializer | |||
# The previous one is a shorthand for the following lines: | |||
lazy_relationship :blog_posts, loader: AmsLazyRelationships::Loaders::Association.new("User", :blog_posts) | |||
has_many :blog_posts, serializer: BlogPostSerializer do |serializer| | |||
serializer.lazy_blog_posts | |||
-> { serializer.lazy_blog_posts } |
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 can still be called without using a proc, right?
Maybe we could keep the old version for simplicity and just mention the proc form in the include_data
example you added in https://github.com/Bajena/ams_lazy_relationships/pull/47/files#diff-04c6e90faac2675aa89e2176d2eec7d8R134 ?
-> { serializer.lazy_blog_posts } | |
serializer.lazy_blog_posts |
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 can still be called without using a proc, right?
sure, Reflection monkey-patch will forward non-proc value as it is.
But I believe that in readme is better to propose the best possible sample by default.
Just imagine:
- engineer_1 adds non-proc custom finders all over the services & serializers;
- engineer_2 turns
ActiveModel::Serializer.config.include_data_default = false
globally - local naive tests will assert successfully that json response is free from
data
, but - production servers will continue to fail due to OOM and that will be quite tricky to discover why
I think we need to leave -> { serializer.lazy_blog_posts }
as a very default sample, and add a small hint that serializer.lazy_blog_posts
will work as well but can produce redundant 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.
I think we need to leave -> { serializer.lazy_blog_posts } as a very default sample, and add a small hint that serializer.lazy_blog_posts will work as well but can produce redundant queries.
Ok, I must admit that your explanation makes sense to me. Let's leave proc, but also let's mention the non-proc version as you 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.
roger that! )
README.md
Outdated
end | ||
``` | ||
|
||
Keep attention that custom finder should be returned in a form of lambda, |
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.
Keep attention that custom finder should be returned in a form of lambda, | |
If you're using `include_data` AMS setting make sure that your association blocks return a lambda in order to avoid redundant SQL queries when `include_data` AMS setting evaluates to `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.
Again, typically include_data
will be turned off a way further from the project beginning, on the very last phase of make it work, make it right, make it fast
;) I think it's good to make an accent how to do the things better right from beginning )
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
# Described problem is a very specific use case for pure AMS applications. | ||
# The bad news is that `ams_lazy_relationships` is always utilize the | ||
# association block - | ||
# https://github.com/Bajena/ams_lazy_relationships/blob/v0.2.0/lib/ams_lazy_relationships/core/relationship_wrapper_methods.rb#L32-L36 |
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'd avoid adding links to specific versions in the code. Let's get rid of lines 32-44 - they may quickly get out of date.
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.
That is exactly why I pinned v0.2.0
tag here ))
end | ||
end | ||
|
||
::ActiveModel::Serializer::Reflection.prepend AmsLazyRelationships::Extensions::Reflection |
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'd consider applying this patch only to AMS versions >= 0.10.3.
Nobody using lower versions of AMS is probably going to return lambdas from their association blocks anyways as they won't benefit from that anyhow.
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 thought a lot about that from the beginning, but this way we need to apply the same condition in default define_lazy_association
block.
Even worse, readme description will be more complex as well since for lower AMS versions the proc-like custom finder will instantly fail. I.e. we propose the snippet of code which will crush on AMS version still accepted by gemspec
file..
As an option, we can move all include_data
stuff in separate lib file. require 'ams_lazy_relationships/efficient_finders'
will engage monkey-patch and proc-like default finder, without said require
nothing will be applied.
How 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.
As an option, we can move all
include_data
stuff in separate lib file.
Notice that this way ams_lazy_relationships
will still silently produce a huge problems to consumers due to cancelled effect from include_data
optimizations in sense of DB hits and memory usage.
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, you're right, doing it the way I suggested generates more problems than it solves. Let's keep it then. But in such case let's make sure that all AMS versions are tested for proc evaluation (specs are skipped for lower AMS versions in https://github.com/Bajena/ams_lazy_relationships/pull/47/files#diff-6e72b60a2fb8b930cc9741785114a948R871).
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.
They are tested already due to the new default define_lazy_association
block, but I'll add an accented test with proc-like custom finder )
```ruby | ||
class BlogPostSerializer < BaseSerializer | ||
lazy_has_many :comments do | ||
include_data :if_sideloaded |
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.
Wasn't it supposed to be:
include_data :if_sideloaded | |
include_data 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.
:if_sideloaded
is the most complex example, this way data
will be included for explicit ?include=association
and rejected without it.
In second case we need to suppress any kind of finders but we need to keep the finder alive for the time when first case will happen.
i.e.
lazy_has_many :comments do
include_data false
nil
end
is completely ok and ams_lazy_relationship
will behave like that without this PR.
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, thanks for explanation - so this will work correctly, without N+1s, both when the resource is included and when it isn't 👍
README.md
Outdated
``` | ||
|
||
Feel free to skip custom lazy finder for association if your goal is just to | ||
define `include_data` setting nor some links/metas: |
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.
define `include_data` setting nor some links/metas: | |
define `include_data` setting to render some links/metas: |
?
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.
include_data
is not about links and metas, but mostly about skipping so-called json relationships
.
By default AMS will add to response the primary keys for each association defined in serializer. I.e.
class OrganizationSerializer
has_many :users
end
OrganizationSerializer.new(org).as_json
# {data: {relationships: { users: { [{id: 1}, {id: 2}, ..., {id: 123_567_89} ] } } } }
That happens always even without explicit ?include=users
, that produces SELECT * FROM users
query on the way and memory allocation for 123_567_89
AR objects.
include_data = false or :if_sideloaded
is a way to survive.
I never tried but believe that include_data = :if_sideloaded
can be combined with links and metas
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.
True, you're right, I missed the point :) But TBH here I mostly meant that this sentence is not a correct english sentence and tried to rework it a bit. Would you be able to rephrase it a bit?
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.
will try, thanks! )
Co-Authored-By: Jan Bajena <bajena3@gmail.com>
Co-Authored-By: Jan Bajena <bajena3@gmail.com>
@Bajena Ready for the second round of review ) |
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.
Just the specs and some minor doc changes left and we should be able to !
@@ -64,7 +64,7 @@ class UserSerializer < BaseSerializer | |||
# The previous one is a shorthand for the following lines: | |||
lazy_relationship :blog_posts, loader: AmsLazyRelationships::Loaders::Association.new("User", :blog_posts) | |||
has_many :blog_posts, serializer: BlogPostSerializer do |serializer| | |||
serializer.lazy_blog_posts | |||
-> { serializer.lazy_blog_posts } |
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 need to leave -> { serializer.lazy_blog_posts } as a very default sample, and add a small hint that serializer.lazy_blog_posts will work as well but can produce redundant queries.
Ok, I must admit that your explanation makes sense to me. Let's leave proc, but also let's mention the non-proc version as you suggested 👍
README.md
Outdated
end | ||
``` | ||
|
||
Keep attention that custom finder should be returned in a form of lambda, |
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
README.md
Outdated
``` | ||
|
||
Feel free to skip custom lazy finder for association if your goal is just to | ||
define `include_data` setting nor some links/metas: |
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.
True, you're right, I missed the point :) But TBH here I mostly meant that this sentence is not a correct english sentence and tried to rework it a bit. Would you be able to rephrase it a bit?
```ruby | ||
class BlogPostSerializer < BaseSerializer | ||
lazy_has_many :comments do | ||
include_data :if_sideloaded |
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, thanks for explanation - so this will work correctly, without N+1s, both when the resource is included and when it isn't 👍
end | ||
end | ||
|
||
::ActiveModel::Serializer::Reflection.prepend AmsLazyRelationships::Extensions::Reflection |
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, you're right, doing it the way I suggested generates more problems than it solves. Let's keep it then. But in such case let's make sure that all AMS versions are tested for proc evaluation (specs are skipped for lower AMS versions in https://github.com/Bajena/ams_lazy_relationships/pull/47/files#diff-6e72b60a2fb8b930cc9741785114a948R871).
@Bajena Everything is done I hope ) Please release a new gem version with this stuff ) |
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.
Great stuff!
There is a general problem inside AMS related to custom association finder combined with
include_data
setting:The problem is that
belongs_to
block will be fully evaluated each time for each object, and only after that AMS is able to take into accountinclude_data
mode -That causing redundant (and so huge potentially!) SQL queries and AR objects allocation when
include_data
appears to befalse
butbelongs_to
block defines instant (not a kind of AR::Relation) custom association finder.Described problem is a very specific use case for pure AMS applications. The bad news is that
ams_lazy_relationships
is always utilize the association block -This way we break
include_data
optimizations for the host application.In order to overcome that we are forced to monkey-patch
AmsLazyRelationships::Extensions::Reflection#value
method and make it to be ready for Proc returned by association block. This way we will use a kind ofas association block, then AMS will evaluate it, get the value of
include_data
setting, make a decision do we need to continue with that association, if so -
will finally evaluate the proc with lazy relationship inside it.
In additional, now we allow skipping the custom lazy finder when our goal is just to define
include_data
setting nor some links/metas: