graphql-ruby
graphql-ruby copied to clipboard
Cache Gathered Selections
When a fragment is spread on a list of items, we re-call gather_selections(...) for each item on the list, and that can get really slow.
I'm trying to find a reusable data structure which will reduce the work in that case without slowing down the common case.
I tried this before and it caused an infinite loop -- repeatedly running the same fields -- so I'm going to take it slower and write unit tests for it.
TODO:
- [x] Make the new system opt-in
- [x] Pass CI with it turned on for some builds
- [ ] Ensure that the benchmark improves runtime and memory
The main challenge here is handling the fact that there's a split in what can be done when:
- Ahead-of-time:
- can gather up selections by name
- can enter into untyped inline fragments (
... { ... }) - can't enter into typed inline fragments or into fragment spreads, because you can't check the current (runtime) type
- TODO: if the fragment's type matches the type which owns the selection, can you continue without performing any checks?
- can't resolve directives because you don't have the object
- Running the query:
- can check the runtime type of the object against the static type condition of a fragment, optionally continuing
- can evaluate directives, since the runtime object is present
- TODO handle
@skipand@includespecially, since they don't need the object?
- TODO handle
So you end up needing a creating of bookkeeping objects ahead-of-time so that, at runtime, you can check the various conditions (types, directives) and optionally continue exection.
TODO maybe a hybrid approach would work, where it tries to use a a cached selection, and it does, if there aren't any conditions that require runtime work, but if there's a type condition or directive, it fails back to runtime gathering (the current implementation).
This will be a big lift for an unquantified gain. I'm definitely open to pursuing it more in the future ... but not now, so I'll close this.