Skip to content
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

How to use with dataloader? #8

Open
ichaly opened this issue Apr 12, 2022 · 7 comments
Open

How to use with dataloader? #8

ichaly opened this issue Apr 12, 2022 · 7 comments

Comments

@ichaly
Copy link

ichaly commented Apr 12, 2022

Can use dataloader to avoid N+1?

@mjarkk
Copy link
Owner

mjarkk commented Apr 12, 2022

Can you give a bit more context?
The library can handle n+1 requests as far as I know.

@ichaly
Copy link
Author

ichaly commented Apr 18, 2022

var users = map[int]User{
	1: {ID: 1, Name: "Elon Musk"},
	2: {ID: 2, Name: "Steve jobs"},
	3: {ID: 3, Name: "Bill Gates"},
}

var friends = map[int]int{
	1: 2,
	2: 3,
	3: 1,
}

type User struct {
	ID   int
	Name string
}

func (my User) ResolveFriends(ctx *graphql.Ctx) []User {
	fmt.Println("ResolveFriends")
	return []User{users[friends[my.ID]]}
}

type QueryRoot struct{}

type MethodRoot struct{}

func (my QueryRoot) ResolveUsers() ([]User, error) {
	s := make([]User, 0, len(users))
	for _, v := range users {
		s = append(s, v)
	}
	return s, nil
}

func (my MethodRoot) ResolveSignUp(ctx *graphql.Ctx, args struct {
	ID   int
	Name string
}) (User, error) {
	user := User{ID: args.ID, Name: args.Name}
	users[args.ID] = user
	return user, nil
}

In this case when get friends ‘ResolveFriends’ will execute 3 times,if it load from database is not good for performance。
So can i use some lazy dataloader to optimize it like https://github.com/yckao/go-dataloader

@mjarkk
Copy link
Owner

mjarkk commented Apr 18, 2022

Ah now I get it.
I think you can use libraries like that :)

Tough for the library you provided as example you'll need the request's context.Context but that's currently not exposed.
I'll try to also expose the request's context.Context so you can use use that library you provided as example

@mjarkk
Copy link
Owner

mjarkk commented Apr 18, 2022

I've added a GetContext and SetContext method to the request, for more info about them see: README.md#golang-context

@ichaly
Copy link
Author

ichaly commented Apr 19, 2022

Thanks for your reply!

The problem is the ResolveMethods are synchronous,it must return after time window,So the loader doesn't work for avoid N+1
Maybe I didn't use YARQL correctly and help me correct it,thanks

func (my User) ResolveFriends(ctx *yarql.Ctx) []User {
	fmt.Println("ResolveFriends")
	thunk := userLoader.Load(ctx.GetContext(), my.ID)
	//The loader is asynchronous but the fetch is synchronous
	//get will wait until thunk value or error is set
	//So the loader doesn't work for avoid N+1
	val, _ := thunk.Get(ctx.GetContext())
	return []User{val}
}

Here is a demo https://github.com/ichaly/yarql-example

@mjarkk
Copy link
Owner

mjarkk commented Apr 19, 2022

I've filled a bit around with your example and now I get the idea of the library.
It's indeed true that this library is extremely synchronous and I think that will not change, tough that doesn't mean we can't have other solutions.

I'm not yet sure what I'm going to do with this but I'm thinking of a few things:
1. Make it possible to have a PreRresolve.. method that is called before Resolve.. where you can call the data loader's Load method.

func (Schema) PreResolveUser(ctx *yarql.Ctx, args struct{Id int}) {
  dataLoader.Load("User", args.Id, ctx.GetContext())
}
func (Schema) ResolveUser(ctx *yarql.Ctx, struct{Id int}) User {
    // This one is called after all PreResolve.. methods are executed
  return dataLoader.Get("User", args.Id, ctx.GetContext())
}

2. Make it possible for a Resolve.. method to return a method where the contents of the Resolve.. should call the data loader Load method and then the returned method is only called after all Resolve.. methods are called

func (Schema) ResolveUser(ctx *yarql.Ctx, args struct{Id int}) func() User {
  dataLoader.Load("User", args.Id, ctx.GetContext())
  return func() {
    // This one is called after all of the above are executed
    return dataLoader.Get("User", args.Id, ctx.GetContext())
  }
}

3. Have some sort of method to identify in the args of a query to preload data

type UserID int
func (id UserID) Preload(ctx *yarql.Ctx) {
  // This method will be called whenever you use the UserID type as arg
  dataLoader.Load("User", args.Id, ctx.GetContext())
}

type ResolveUserArgs struct {
  Id UserID
}

func (Schema) ResolveUser(ctx *yarql.Ctx, args ResolveUserArgs) func() User {
  return dataLoader.Get("User", args.Id, ctx.GetContext())
}

Have our own data loader inside this library as the data loader you provided as example is not build for performance (this is not really a solution as this one still needs one of the above to work)

@ichaly
Copy link
Author

ichaly commented Apr 20, 2022

support plan 2, simple and understandable,no extra concept

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants