-
Notifications
You must be signed in to change notification settings - Fork 106
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
Priming the cache #168
Priming the cache #168
Conversation
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.
Nice and simple 🎉
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 how simple this is. I wonder if the simplicity could potentially hide performance bugs. What do you think?
def test_prime_will_not_replace_already_cached_value | ||
loader = EchoLoader.for | ||
|
||
assert_equal :a, loader.load(:a).sync | ||
|
||
loader.prime(:a, :not_a) | ||
|
||
assert_equal :a, loader.load(:a).sync | ||
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.
comment: This seems like it potentially is a footgun for surprising behavior. Particularly with ActiveRecord's lazy loading of associations, what if you try to prime with a model that has preloaded an association that you need (N.B. if you're using GraphQL::Batch, you probably shouldn't be loading associations like this, but stranger things have happened), but it has already been fulfilled with a version that does not have that association preloaded?
question: Should we emit a warning or something by default when attempting to prime an already-fulfilled promise? One could then opt in to allow either overriding or ignoring the warning.
This could very well be overkill but it's the first thing that came to mind when I saw the implementation.
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.
Yes, this is indeed not meant to be used to populate the loader with values that are in a certain state, for some kind of perf reasons. The values you put in should behave the same as the values the loader would have loaded on its own.
I'm not sure there's a good and simple way to protect against this, except in documenting its intended use.
Given this, I don't think we should emit a warning when priming a value for a key that already exists in the loader cache. That is meant to be a no-op - loading or priming should result in equivalent results - so it shouldn't seem to the developer that they are doing something wrong. Given the resolution of GraphQL it can be very hard to reason or know/ensure that you'd be priming keys that won't exist.
The behaviour here (priming inserts a key in the cache only if the key didn't exist) is also the behaviour of the other data loader libraries I found.
What do you think?
I updated the prime logic a bit, to deal with the cases when your prime a key that's already in the queue: def prime(key, value)
- promise = cache[cache_key(key)] ||= ::Promise.new.tap { |p| p.source = self }
- promise.fulfill(value) unless promise.fulfilled?
+ cache[cache_key(key)] ||= ::Promise.resolve(value).tap { |p| p.source = self }
end |
Sometimes data is loaded through some means, which will also later be loaded by an independent batch data loader. It can be convenient to then prime the cache of that data data loader with the already loaded data, to remove redundant calls.
The use-case I have can be summarized by this query:
The
shopsFollowed
resolver performs a query to load 10 shops that the current user follows. This is returned as theShop
type, which is used extensively in the API. TheShop.followedByMe
field returns a boolean of whether the current user follows the shop. It's backed by a custom batch data loader.For the specific
shopsFollowed
use-case, we know that all the shops returned are followed by the current user, so there's no need for the custom batch data loader to load this again.With
prime
we can have theshopsFollowed
resolver pre-populate this information on the loader.This is available in other graphql batch data loader libraries, see e.g.