Optimizing polymorphic pluck and repeated inflections#650
Optimizing polymorphic pluck and repeated inflections#650lgebhardt merged 3 commits intoJSONAPI-Resources:masterfrom
Conversation
1719c77 to
9f325fb
Compare
|
@lgebhardt This look okay to you? |
|
+1 for no n+1 |
c21b2ff to
9f325fb
Compare
|
I noticed a lot of these same things when doing a performance review on our code and opened #674. One thing that would be nice is to have the cache only cache based on the environment settings. So that as your changing your files locally, nothing is cached when you're in development. Possibly move the cache to rails cache so it could be cleared also. |
|
@aaronbhansen Agreed on having it be env-controlled. However, I don't think Rails.cache would be good to use here, though; I suspect the overhead of the lookup would be too high. Another problem that occurs to me looking back over it: my cache is not thread-safe. However, I experimented with using a thread-safe cache (e.g. Hash in a ThreadLocalVar) but overhead was pretty high. :-( I think I need to look into a different way of caching these values that can be forced to run pre-fork. |
|
Rails cache would be great (for clearing / management), but I agree for such time sensitive operations, the lookup would probably be to big. What would also be nice, is if the caching classes could inherit from the base classes, so that in the configuration you could even pick which class to use. Like the operations processor today It's just a thought, that leaves the base classes alone and allows a caching layer on top. I don't know if that causes more work than its worth, but would allow easy testing between the two. |
|
@DavidMikeSimon we are starting to need this more in production, I don't want to replicate this tickets efforts, but would love to help to get this out |
|
@aaronbhansen I will look into cleaning up my PR tomorrow, and after that it would be very awesome if you could review it |
82b6e08 to
20b25c6
Compare
|
@aaronbhansen @lgebhardt Okay, I believe this is now thread-safe. I also made the formatter caching configurable (though I don't think it'll cause any problems even in dev), and added a simple benchmark which you can run with 'rake test:benchmark'. On my system, here are the results with NaiveCache completely disabled: With caching added to LinkBuilder and ResourceSerializer: With caching fully enabled: |
|
@dgeb Seem alright to you? |
|
Just did a quick test run locally, it did seem to reduce call time by about 50%. Going to push to our staging server and see how it does there. I've dive into the code more, but initial results look promising. Nice work @DavidMikeSimon 👍 |
|
We've been testing this branch out on staging the last 5 days. Two things I think need to be taken into consideration on this pull request.
|
|
@aaronbhansen Good catch on point 1, I forgot to mention that. Regarding point 2, you can disable formatter caching in the config, or set the However, a formatter that's not deterministic on its input is probably a bad idea in general, because JSONAPI needs to be able to reversibly call |
|
@DavidMikeSimon Sorry for the late review of this PR. I like the performance gains. I'm probably missing something completely obvious, but what was the rational for the changes to the formatters regarding moving the class level methods to instance methods? I'm worried it will break a lot of people as @aaronbhansen has proven. Could we simply cache the class (klass) instead of instances? Even if instances are needed (and I'm not sure they are) it seems leaving the existing class level methods would prevent a lot of upgrade issues. |
|
@lgebhardt Well, the main reason is that a formatter cache is so small and so frequently accessed that it's better to have a separate cache for each thread than to try and synchronize access to a single cache across threads. I changed the nature of the Formatter classes to represent this idea. I can easily change it back, though; the |
|
@DavidMikeSimon I think you could keep the new cache stuff the same and just call the class level methods for |
|
@lgebhardt Ok, sounds good. |
1a44fd3 to
6a60e7a
Compare
|
@lgebhardt Alright, formatters are back to using class methods. |
| class << self | ||
| def format(key) | ||
| super.camelize(:lower) | ||
| key.to_s.camelize(:lower) |
There was a problem hiding this comment.
Should we still call super here incase someone wants to add an override in the base?
There was a problem hiding this comment.
Okies, I'll put that back as super. I think that change was left over from earlier experimentation on making the format methods themselves faster.
6a60e7a to
2d8e59b
Compare
|
|
||
| alias_method :polymorphic?, :polymorphic | ||
|
|
||
| def primary_key |
There was a problem hiding this comment.
Did these functions just change in order?
There was a problem hiding this comment.
No, there's one other difference: @resource_klass = become @resource_klass ||= to reduce repeated calls. I'll flip it back around so the diff shows this more clearly.
2d8e59b to
8448a49
Compare
| end | ||
|
|
||
| class << self | ||
| @@model_types_to_resource_types = {} |
There was a problem hiding this comment.
This line is vestigal, I'll remove it.
8448a49 to
53778da
Compare
|
@lgebhardt I adjusted the diff in |
|
@DavidMikeSimon Thanks! |
|
@lgebhardt No problem. :-) I also have another optimization idea that I'm working on in a different branch: caching entire serialized resources. I'll submit a PR when I have something working. |
Two optimizations are implemented in this PR.
On my app, this halves the response times on some requests. For example, a common index with lots of includes used to take between 1.505 and 1.546 seconds to complete, but with this PR that is reduced to between 0.700 and 0.795 (not including the first request, to allow for cache to warm up).