Skip to content

[RFC] Concurrent Resolvers #389

@chris-ramon

Description

@chris-ramon

Concurrent Resolvers

  • At the moment we don't have built-in functionality to run resolvers concurrently.

  • We want to integrate built-in, opt-in, performant and simple solution to run resolvers concurrently.

Current Implementation

  • Resolvers are executed synchronously. Therefore not possible to run Goroutines within resolvers.

Current alternative solutions

  • Run Goroutines before graphql.Do:

Proposed solutions

Note: Thunk is a function that is returned from a function.

1) Resolver returns a Thunk

✔️ PR: #388
✔️ Opt-in — Via thunks
✔️ Performant — No additional goroutines
✔️ Simple — Uses existing public API
✔️ Up-to-date
Example:

"pullRequests": &graphql.Field{
	Type: graphql.NewList(PullRequestType),
	Resolve: func(p graphql.ResolveParams) (interface{}, error) {
		ch := make(chan []PullRequest)
		// Concurrent work via Goroutines.
		go func() {
			// Async work to obtain pullRequests.
			ch <- pullRequests
		}()
		return func() interface{} {
			return <-ch
		}, nil
	},
},

2) Resolver returns a Goroutined Thunk

✔️ PR: #213
✔️ Opt-in — Via thunks
❗ Performant — Goroutine per resolver
✔️ Simple — Uses existing public API
✔️ Up-to-date
Example:

"pullRequests": &graphql.Field{
	Type: graphql.NewList(PullRequestType),
	Resolve: func(p graphql.ResolveParams) (interface{}, error) {
		ch := make(chan []PullRequest)
		// Concurrent work via Goroutines.
		go func() {
			// Async work to obtain pullRequests.
			ch <- pullRequests
		}()
		return func() (interface{}, error) {
			return <-ch, nil
		}, nil
	},
},

3) Resolver returns a Channel

✔️ PR: #357
✔️ Opt-in — via new public APIs
✔️ Performant — No additional goroutines
❗ Simple — New set of JavaScript promises-like public APIs
✔️ Up-to-date
Example:

ch := make(chan []PullRequest)
// Concurrent work via Goroutines.
// Async work to obtain pullRequests.
graphql.Do(graphql.Params{
	Schema:        schema,
	RequestString: tc.RequestString,
	IdleHandler: func() {
		ch <- &graphql.ResolveResult{
			Value: pullRequests,
		}
	},
})

// ...
"pullRequests": &graphql.Field{
	Type: graphql.NewList(PullRequestType),
	Resolve: func(p graphql.ResolveParams) (interface{}, error) {
		return graphql.ResolvePromise(ch), nil
	},
},

4) Resolver's field flag

✔️ PR: sogko#23
✔️ Opt-in — via Field.Parallel
❗ Performant — Goroutine per resolver
✔️ Simple — Uses existing public API
❗ Up-to-date
Example:

"pullRequests": &graphql.Field{
	Type: graphql.NewList(PullRequestType),
	Parallel: true,
	Resolve: func(p graphql.ResolveParams) (interface{}, error) {
		ch := make(chan []PullRequest)
		// Concurrent work via Goroutines.
		go func() {
			// Async work to obtain pullRequests.
			ch <- pullRequests
		}()
		return <-ch, nil
	},
},

5) Built-in Goroutined Resolver

✔️ PR: #132
❗ Opt-in — Built-in support
❗ Performant — Goroutine per resolver
✔️ Simple — Uses existing public API
❗ Up-to-date
Example:

"pullRequests": &graphql.Field{
	Type: graphql.NewList(PullRequestType),
	// Resolve runs async
	Resolve: func(p graphql.ResolveParams) (interface{}, error) {
		var pullRequests []PullRequest
		// Async work to obtain pullRequests
		return pullRequests, nil
	},
},

6) Goroutined Resolver via Dataloader

✔️ PR: #154
✔️ Opt-in — Via new public APIs
✔️ Performant — No additional goroutines
❗ Simple — New public APIs
❗ Up-to-date
Example:

dl := dataloader.DataLoader{
	pullRequestsLoader: dataloader.New(sch, dataloader.Parallel(func(key interface{}) dataloader.Value {
		var pullRequests []PullRequest
		// Async work to obtain pullRequests
		return dataloader.NewValue(pullRequests, nil)
	})),
}

var loaderKey = struct{}{}
ctx := context.WithValue(parent, loaderKey, dl)

graphql.Do(graphql.Params{
	Schema:        schema,
	RequestString: query,
	Context:       ctx,
	Executor:      &executor,
})

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions