GraphQL Java - Batching

Use DataLoader

Use GraphQL process, you may need to do multiple queries on a single map data. Using the original data loading, it is prone to performance issues.

By using a java-dataloader, you can bind cache (Cache) and batch (Batching) manner, initiate a request on the bulk pattern data. If dataloader relevant data have been acquired before, then it will cache data value, and then returns directly to the caller (no need to repeat initiate a request).

Suppose we have a StarWars execution statement is as follows: It allows us to find a hero, a friend of his friend's name and a friend's name. Obviously there will be a part of a friend data, it will be repeated this request to the query.

        {
            hero {
                name
                friends {
                    name
                    friends {
                       name
                    }
                }
            }
        }

Results thereof are as follows:

        {
          "hero": {
            "name": "R2-D2",
            "friends": [
              {
                "name": "Luke Skywalker",
                "friends": [
                  {"name": "Han Solo"},
                  {"name": "Leia Organa"},
                  {"name": "C-3PO"},
                  {"name": "R2-D2"}
                ]
              },
              {
                "name": "Han Solo",
                "friends": [
                  {"name": "Luke Skywalker"},
                  {"name": "Leia Organa"},
                  {"name": "R2-D2"}
                ]
              },
              {
                "name": "Leia Organa",
                "friends": [
                  {"name": "Luke Skywalker"},
                  {"name": "Han Solo"},
                  {"name": "C-3PO"},
                  {"name": "R2-D2"}
                ]
              }
            ]
          }
        }

Compared to the original implementation is, every query when they are called once DataFetcher to get a person object.

In this scenario, calls will be launched 15 times, and there are a lot of data which is many times repeated requests. Binding dataLoader, a request can be made more efficient data.

Query statement for the hierarchy, GraphQL level will gradually decrease and then click Query. (For example: first treated hero field, then treated Friends, and then processing each of friends friend). data loader is a contract, it can be obtained using an object query, but it will delay initiate the request object data. On each level, dataloader.dispatch () method will trigger batch all requests on this level. In the open cache of conditions before any request to have the data will be returned directly, rather than initiate a request to call again.

The above example, the only person objects only five. By using cache + batch acquisition mode, in fact, launched only three network calls on the realization of the requested data.

15 times compared to the original request methods, efficiency is greatly improved.

If the java.util.concurrent.CompletableFuture.supplyAsync (), can also be turned by asynchronous execution, to further improve the efficiency, reduce the response time.

Sample code is as follows:

        //
        // a batch loader function that will be called with N or more keys for batch loading
        // This can be a singleton object since it's stateless
        //
        BatchLoader<String, Object> characterBatchLoader = new BatchLoader<String, Object>() {
            @Override
            public CompletionStage<List<Object>> load(List<String> keys) {
                //
                // we use supplyAsync() of values here for maximum parellisation
                //
                return CompletableFuture.supplyAsync(() -> getCharacterDataViaBatchHTTPApi(keys));
            }
        };


        //
        // use this data loader in the data fetchers associated with characters and put them into
        // the graphql schema (not shown)
        //
        DataFetcher heroDataFetcher = new DataFetcher() {
            @Override
            public Object get(DataFetchingEnvironment environment) {
                DataLoader<String, Object> dataLoader = environment.getDataLoader("character");
                return dataLoader.load("2001"); // R2D2
            }
        };

        DataFetcher friendsDataFetcher = new DataFetcher() {
            @Override
            public Object get(DataFetchingEnvironment environment) {
                StarWarsCharacter starWarsCharacter = environment.getSource();
                List<String> friendIds = starWarsCharacter.getFriendIds();
                DataLoader<String, Object> dataLoader = environment.getDataLoader("character");
                return dataLoader.loadMany(friendIds);
            }
        };


        //
        // this instrumentation implementation will dispatch all the data loaders
        // as each level of the graphql query is executed and hence make batched objects
        // available to the query and the associated DataFetchers
        //
        // In this case we use options to make it keep statistics on the batching efficiency
        //
        DataLoaderDispatcherInstrumentationOptions options = DataLoaderDispatcherInstrumentationOptions
                .newOptions().includeStatistics(true);

        DataLoaderDispatcherInstrumentation dispatcherInstrumentation
                = new DataLoaderDispatcherInstrumentation(options);

        //
        // now build your graphql object and execute queries on it.
        // the data loader will be invoked via the data fetchers on the
        // schema fields
        //
        GraphQL graphQL = GraphQL.newGraphQL(buildSchema())
                .instrumentation(dispatcherInstrumentation)
                .build();

        //
        // a data loader for characters that points to the character batch loader
        //
        // Since data loaders are stateful, they are created per execution request.
        //
        DataLoader<String, Object> characterDataLoader = DataLoader.newDataLoader(characterBatchLoader);

        //
        // DataLoaderRegistry is a place to register all data loaders in that needs to be dispatched together
        // in this case there is 1 but you can have many.
        //
        // Also note that the data loaders are created per execution request
        //
        DataLoaderRegistry registry = new DataLoaderRegistry();
        registry.register("character", characterDataLoader);

        ExecutionInput executionInput = newExecutionInput()
                .query(getQuery())
                .dataLoaderRegistry(registry)
                .build();

        ExecutionResult executionResult = graphQL.execute(executionInput);

As above, we added DataLoaderDispatcherInstrument instance. Because we want to adjust its initialization options (Options). If you do not explicitly specified, it defaults will automatically be added.

Use AsyncExecutionStrategy policy Data Loader

graphql.execution.AsyncExecutionStrategy is the only implementation of the strategy dataLoader. This strategy can be executed to determine the best time to dispatch its own, and it is not completed by tracking how many fields, and whether they are a list of values, etc. for this purpose.

Other implementation strategies, such as: ExecutorServiceExecutionStrategy policy can not implement this feature. When the detected data loader does not use AsyncExecutionStrategy strategy, it calls the dispatch method data loader when it encounters each field. Although it is possible to reduce the number of requests by way of the cache value, but can not use the batch request policy.

request specific Data Loader

If you are initiating a Web request, the data may be specific to the user who requested it. If there is user-specific data, it is not intended to cache data for the user A, and then in subsequent requests provided to the user B.

Scope DataLoader example is very important. Creating dataLoader instance, and to ensure that data is only cached in the web request for every web request, but is not valid for other web requests. It also ensures that the call only affect the execution of this graphql without affecting other graphql request execution.

By default, DataLoaders acts as a cache. If the value of key request had access to before, then they will automatically return to it in order to improve efficiency.

If the data needs to be shared among multiple web requests, then the need to modify the cache implementation data loader, so that different requests, data loader which data can be shared by a number of intermediate layers (e.g., cache or redis memcached).

In the use of the process, still created every time a request for data loaders, open sharing data between different data loader by caching layer.

        CacheMap<String, Object> crossRequestCacheMap = new CacheMap<String, Object>() {
            @Override
            public boolean containsKey(String key) {
                return redisIntegration.containsKey(key);
            }

            @Override
            public Object get(String key) {
                return redisIntegration.getValue(key);
            }

            @Override
            public CacheMap<String, Object> set(String key, Object value) {
                redisIntegration.setValue(key, value);
                return this;
            }

            @Override
            public CacheMap<String, Object> delete(String key) {
                redisIntegration.clearKey(key);
                return this;
            }

            @Override
            public CacheMap<String, Object> clear() {
                redisIntegration.clearAll();
                return this;
            }
        };

        DataLoaderOptions options = DataLoaderOptions.newOptions().setCacheMap(crossRequestCacheMap);

        DataLoader<String, Object> dataLoader = DataLoader.newDataLoader(batchLoader, options);

Asynchronous call batch loader function

Encoding mode using the data loader, by all outstanding data loader request to request a combined bulk loading, improve the efficiency of the request.

GraphQL - Java keeps track of those data loader request has not been completed and calls dispatch method at the right time, triggering the bulk of the requested data.

ALL

Guess you like

Origin www.cnblogs.com/pku-liuqiang/p/11528338.html