FSharp.Data.GraphQL icon indicating copy to clipboard operation
FSharp.Data.GraphQL copied to clipboard

[Discussion] Implement Per-Request Caching + Add an option for batching

Open TOTBWF opened this issue 8 years ago • 5 comments

Description

One of the biggest issues with GraphQL is that queries are very difficult to optimize, and may result in

  1. Fetching the same data repeatedly

  2. Inefficient fetches for lists

The query -> linq module provides some solutions for this, but it relies on all the data coming from the same source, and also requires a linq provider to be available for that data source. On top of that, there seems to be a lot of ✨magic✨ going on under the hood that makes it difficult for the user to optimize themselves.

Proposal

The proposal to fix this is three fold:

  1. Implement a Haxl-like solution for batching + caching. I've implemented a port here.

  2. Implement a default per-request caching strategy for all resolvers. I can't quite see any downsides to doing this, but this is open for discussion

  3. Add a mechanism for the user to supply their own data sources, which allows them to define their own batching behaviour. This gives us the best of both worlds: Allowing some mechanism to make query execution more efficient, as well as providing flexibility for users to define the best way to do so.

I'm working on a Proof-Of-Concept right now, and all progress can be seen here. Let me know if anyone has any thoughts/concerns

TOTBWF avatar Jun 30 '17 17:06 TOTBWF

Following up from a discussion on gitter, I'll see if I can take a stab at this over the next few days.

My plan is to create a Define.BatchField. Where the resolver's signature would look something like:

ResolveFieldContext -> seq<'Val> -> seq<'Val * 'Res>. The resolver would take as input the list of the parent values (instead of a single parent as with normal resolvers) and return a list of results grouped by parent.

e.g. from gitter the getHotelCreators' signature

getHotelCreators : ResolveFieldContext -> seq<HotelLocation> -> seq<HotelLocation * User>

    Define.Object<HotelLocation>
        (name = "Hotel", isTypeOf = (fun o -> o :? HotelLocation),
         fieldsFn = fun () ->
             [ Define.Field("id", String, "Identifier of the location.", fun _ (r: HotelLocation) -> r.Id)
               Define.Field("name", String, "Name of the location.", fun _ (r: HotelLocation) -> r.Name)
               Define.AsyncBatchField("creator", UserType, "Creator of the location.", fun ctx hotels -> getHotelCreator hotels)
        ) ])```

johnberzy-bazinga avatar Jul 22 '19 18:07 johnberzy-bazinga

@johnberzy-bazinga This would make life great. Is there any help I can give for adding a Define.BatchField? I would use it everywhere.

Kurren123 avatar Mar 26 '20 23:03 Kurren123

@TOTBWF do you want to update your prototype to the lates code?

xperiandri avatar Feb 15 '24 09:02 xperiandri

There is an example showing how to do batching here: https://github.com/fsprojects/FSharp.Data.GraphQL/issues/255#issuecomment-1640617865

I'm unsure if batching belongs in this library, since async provides a powerful customization point already.

njlr avatar Feb 15 '24 09:02 njlr

I no longer work with F# nor GraphQL, so this prototype should be considered a historical artifact instead of live work. If anyone else wants to run with it, they are more than welcome to though!

TOTBWF avatar Feb 15 '24 16:02 TOTBWF