Selective Resolvers #399
jbellenger
started this conversation in
RFCs
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
About Selective Resolvers
In Viaduct, resolvers for fields and nodes may be declared as "selective". This allows these resolvers to constrain their return values to just the fields selected in the requested selection set. This is a useful way to optimize the performance of resolvers that are responsible for expensive-to-resolve fields.
Selective resolvers are difficult for the Viaduct engine to handle: the engine is optimized for running a resolver once, storing the resolved value, and reusing that value in all the places that that value is requested. This presents a challenge for selective resolvers, which can be configured in ways that make it difficult to guarantee they will be executed only once.
Problems with The Current State
I've done part of the work in massaging the engine to accommodate selective resolvers. The plan was to store the requested selection set as part of the OER key that field values are stored under. This would allow selective resolvers to be re-executed if they were requested with different selection sets.
For example, in this system if a field was requested with
{a}and again with{a b}, the resolver for that field would be executed twice: once to resolve{a}and a second time to resolve{a b}This design has a couple of issues that I've come to realize make it not worth continuing:
The plan of "just re-execute the resolver" if there are any differences is inefficient. Selective resolvers are selective because they are inherently expensive to execute. The ideal engine would re-execute a field for only the selections that are missing. In the example above, the second execution should resolve only
{b}, not{a b}.In this design, it is difficult to re-execute parent fields. For example, a selection set like
{ foo { bar { x } }, whereBar.xreadsBar.ywhich can only be produced byQuery.foo, is difficult to handle because the FieldResolver has already traversed into a "dead end" OER. The current model requires unwinding the stack to theQuery.foofield, re-executing it, potentially merging the new OER with the existing OER, and then resuming traversal.This design direction of storing selection sets in the field keys doesn't cover node resolvers, which don't use these keys in the same way.
Selective OER keys have exact-match semantics, which introduces many new ways for a caller to attempt to read data using a key for which there is no writer. This usually manifests as the engine "hanging", which is a failure mode that I think we can improve on.
Future State
I'm declaring an intent to pivot selective resolver support into a different direction. This direction is oriented around "materialization" which will be supported by a family of "Mat"-themed classes.
The core ideas are:
KeyTreeis introduced as a mergeable, diffable, and traversable tree of OER keys. This is used to model the shape of a selection set.Relevant to selective resolvers, a KeyTree will be used to describe a resolvers Output Selection Set as well as the selection set requested by a caller or another resolver.
A
MatLedgeris a stateful observer of a resolver. It is used to record theKeyTrees that a resolver has been called with, and can be used to answer questions like "does the already-resolved data cover these requested selections".A
Matis a function that is responsible for invoking a field or node resolver to materialize aKeyTree. It does this by receiving a requested KeyTree, consulting the MatLedger to create a KeyTree that describes uncovered keys, and then turning that KeyTree into a QueryPlan that can be executed.Finally, all of this is coordinated through OERs. Each OER may contain a pointer back to the Mat that produced it, which may be executed by FieldResolver.
I believe that this architecture will have these properties:
if a selective resolver is re-executed, it will be for only the missing selections.
coordinating through OERs addresses a core problem with re-executing parent resolvers that have already been traversed by FieldResolver.
MatLedger can be used to significantly improve our ability to detect if a read key has a writer, eliminating the number one source of engine hangs.
All reactions