Skip to content

Commit 0255331

Browse files
authored
760 dataloader performance with lists (graphql-java#764)
* Added shop / department / product test and tweaked ES for reworking * Made data loader dispatching more efficient and aware of completing lists * setup in lists * Added one more test and moved the code around * Fix ups from merge * Moar groovy test fix ups * Added more tests from users out there. Its not yet optimal! * Now using a barrier CF pattern to hold up any value completion until we are ready * Removed the barrier pattern - because I realize its not needed * Added a mode where the other execution strategies work but not as efficiently
1 parent ecc3b26 commit 0255331

26 files changed

Lines changed: 1180 additions & 98 deletions

‎build.gradle‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ dependencies {
4444
testCompile 'com.fasterxml.jackson.core:jackson-databind:2.8.8.1'
4545
testCompile 'org.slf4j:slf4j-simple:' + slf4jVersion
4646
testCompile 'org.awaitility:awaitility-groovy:3.0.0'
47+
testCompile 'com.github.javafaker:javafaker:0.13'
4748

4849
}
4950

‎src/main/java/graphql/execution/AsyncExecutionStrategy.java‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package graphql.execution;
22

33
import graphql.ExecutionResult;
4+
import graphql.execution.instrumentation.Instrumentation;
45
import graphql.execution.instrumentation.InstrumentationContext;
56
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
67
import graphql.language.Field;
@@ -34,7 +35,12 @@ public AsyncExecutionStrategy(DataFetcherExceptionHandler exceptionHandler) {
3435
@Override
3536
public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
3637

37-
InstrumentationContext<CompletableFuture<ExecutionResult>> executionStrategyCtx = executionContext.getInstrumentation().beginExecutionStrategy(new InstrumentationExecutionStrategyParameters(executionContext));
38+
Instrumentation instrumentation = executionContext.getInstrumentation();
39+
InstrumentationExecutionStrategyParameters instrumentationParameters = new InstrumentationExecutionStrategyParameters(executionContext);
40+
41+
InstrumentationContext<CompletableFuture<ExecutionResult>> executionStrategyCtx = instrumentation.beginExecutionStrategy(instrumentationParameters);
42+
43+
InstrumentationContext<Map<String, List<Field>>> beginFieldsCtx = instrumentation.beginFields(instrumentationParameters);
3844

3945
Map<String, List<Field>> fields = parameters.fields();
4046
List<String> fieldNames = new ArrayList<>(fields.keySet());
@@ -49,6 +55,7 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont
4955
CompletableFuture<ExecutionResult> future = resolveField(executionContext, newParameters);
5056
futures.add(future);
5157
}
58+
beginFieldsCtx.onEnd(fields,null);
5259

5360
CompletableFuture<ExecutionResult> overallResult = new CompletableFuture<>();
5461
Async.each(futures).whenComplete(handleResults(executionContext, fieldNames, overallResult));

‎src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package graphql.execution;
22

33
import graphql.ExecutionResult;
4+
import graphql.execution.instrumentation.Instrumentation;
45
import graphql.execution.instrumentation.InstrumentationContext;
56
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
67
import graphql.language.Field;
@@ -28,10 +29,14 @@ public AsyncSerialExecutionStrategy(DataFetcherExceptionHandler exceptionHandler
2829
@Override
2930
public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
3031

31-
InstrumentationContext<CompletableFuture<ExecutionResult>> executionStrategyCtx = executionContext.getInstrumentation().beginExecutionStrategy(new InstrumentationExecutionStrategyParameters(executionContext));
32+
Instrumentation instrumentation = executionContext.getInstrumentation();
33+
InstrumentationExecutionStrategyParameters instrumentationParameters = new InstrumentationExecutionStrategyParameters(executionContext);
34+
InstrumentationContext<CompletableFuture<ExecutionResult>> executionStrategyCtx = instrumentation.beginExecutionStrategy(instrumentationParameters);
3235
Map<String, List<Field>> fields = parameters.fields();
3336
List<String> fieldNames = new ArrayList<>(fields.keySet());
3437

38+
InstrumentationContext<Map<String, List<Field>>> beginFieldsCtx = instrumentation.beginFields(instrumentationParameters);
39+
3540
CompletableFuture<List<ExecutionResult>> resultsFuture = Async.eachSequentially(fieldNames, (fieldName, index, prevResults) -> {
3641
List<Field> currentField = fields.get(fieldName);
3742
ExecutionPath fieldPath = parameters.path().segment(fieldName);
@@ -40,6 +45,8 @@ public CompletableFuture<ExecutionResult> execute(ExecutionContext executionCont
4045
return resolveField(executionContext, newParameters);
4146
});
4247

48+
beginFieldsCtx.onEnd(fields, null);
49+
4350
CompletableFuture<ExecutionResult> overallResult = new CompletableFuture<>();
4451
BiConsumer<List<ExecutionResult>, Throwable> listThrowableBiConsumer = handleResults(executionContext, fieldNames, overallResult);
4552
resultsFuture.whenComplete(listThrowableBiConsumer);

‎src/main/java/graphql/execution/Execution.java‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,15 @@ public CompletableFuture<ExecutionResult> execute(Document document, GraphQLSche
9595
executionInput, graphQLSchema, instrumentationState
9696
);
9797
executionContext = instrumentation.instrumentExecutionContext(executionContext, parameters);
98-
return executeOperation(executionContext, executionInput.getRoot(), executionContext.getOperationDefinition());
98+
return executeOperation(executionContext, parameters, executionInput.getRoot(), executionContext.getOperationDefinition());
9999
}
100100

101101

102-
private CompletableFuture<ExecutionResult> executeOperation(ExecutionContext executionContext, Object root, OperationDefinition operationDefinition) {
102+
private CompletableFuture<ExecutionResult> executeOperation(ExecutionContext executionContext, InstrumentationExecutionParameters instrumentationExecutionParameters, Object root, OperationDefinition operationDefinition) {
103103

104-
InstrumentationContext<ExecutionResult> dataFetchCtx = instrumentation.beginDataFetch(new InstrumentationDataFetchParameters(executionContext));
104+
InstrumentationDataFetchParameters dataFetchParameters = new InstrumentationDataFetchParameters(executionContext);
105+
InstrumentationContext<CompletableFuture<ExecutionResult>> executionDispatchCtx = instrumentation.beginDataFetchDispatch(dataFetchParameters);
106+
InstrumentationContext<ExecutionResult> dataFetchCtx = instrumentation.beginDataFetch(dataFetchParameters);
105107

106108
OperationDefinition.Operation operation = operationDefinition.getOperation();
107109
GraphQLObjectType operationRootType = getOperationRootType(executionContext.getGraphQLSchema(), operation);
@@ -112,7 +114,9 @@ private CompletableFuture<ExecutionResult> executeOperation(ExecutionContext exe
112114
// for the record earlier code has asserted that we have a query type in the schema since the spec says this is
113115
// ALWAYS required
114116
if (operation == MUTATION && operationRootType == null) {
115-
return completedFuture(new ExecutionResultImpl(Collections.singletonList(new MutationNotSupportedError())));
117+
CompletableFuture<ExecutionResult> resultCompletableFuture = completedFuture(new ExecutionResultImpl(Collections.singletonList(new MutationNotSupportedError())));
118+
executionDispatchCtx.onEnd(resultCompletableFuture, null);
119+
return resultCompletableFuture;
116120
}
117121

118122
FieldCollectorParameters collectorParameters = FieldCollectorParameters.newParameters()
@@ -161,6 +165,9 @@ private CompletableFuture<ExecutionResult> executeOperation(ExecutionContext exe
161165

162166
result = result.whenComplete(dataFetchCtx::onEnd);
163167

168+
// note this happens NOW - not when the result completes
169+
executionDispatchCtx.onEnd(result, null);
170+
164171
return result;
165172
}
166173

‎src/main/java/graphql/execution/ExecutionStrategy.java‎

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -342,29 +342,7 @@ protected CompletableFuture<ExecutionResult> completeValue(ExecutionContext exec
342342
// when we are here, we have a complex type: Interface, Union or Object
343343
// and we must go deeper
344344

345-
GraphQLObjectType resolvedType;
346-
if (fieldType instanceof GraphQLInterfaceType) {
347-
TypeResolutionParameters resolutionParams = TypeResolutionParameters.newParameters()
348-
.graphQLInterfaceType((GraphQLInterfaceType) fieldType)
349-
.field(parameters.field().get(0))
350-
.value(parameters.source())
351-
.argumentValues(parameters.arguments())
352-
.context(executionContext.getContext())
353-
.schema(executionContext.getGraphQLSchema()).build();
354-
resolvedType = resolveTypeForInterface(resolutionParams);
355-
356-
} else if (fieldType instanceof GraphQLUnionType) {
357-
TypeResolutionParameters resolutionParams = TypeResolutionParameters.newParameters()
358-
.graphQLUnionType((GraphQLUnionType) fieldType)
359-
.field(parameters.field().get(0))
360-
.value(parameters.source())
361-
.argumentValues(parameters.arguments())
362-
.context(executionContext.getContext())
363-
.schema(executionContext.getGraphQLSchema()).build();
364-
resolvedType = resolveTypeForUnion(resolutionParams);
365-
} else {
366-
resolvedType = (GraphQLObjectType) fieldType;
367-
}
345+
GraphQLObjectType resolvedType = resolveType(executionContext, parameters, fieldType);
368346

369347
FieldCollectorParameters collectorParameters = newParameters()
370348
.schema(executionContext.getGraphQLSchema())
@@ -429,6 +407,33 @@ protected Iterable<Object> toIterable(Object result) {
429407
return (Iterable<Object>) result;
430408
}
431409

410+
protected GraphQLObjectType resolveType(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLType fieldType) {
411+
GraphQLObjectType resolvedType;
412+
if (fieldType instanceof GraphQLInterfaceType) {
413+
TypeResolutionParameters resolutionParams = TypeResolutionParameters.newParameters()
414+
.graphQLInterfaceType((GraphQLInterfaceType) fieldType)
415+
.field(parameters.field().get(0))
416+
.value(parameters.source())
417+
.argumentValues(parameters.arguments())
418+
.context(executionContext.getContext())
419+
.schema(executionContext.getGraphQLSchema()).build();
420+
resolvedType = resolveTypeForInterface(resolutionParams);
421+
422+
} else if (fieldType instanceof GraphQLUnionType) {
423+
TypeResolutionParameters resolutionParams = TypeResolutionParameters.newParameters()
424+
.graphQLUnionType((GraphQLUnionType) fieldType)
425+
.field(parameters.field().get(0))
426+
.value(parameters.source())
427+
.argumentValues(parameters.arguments())
428+
.context(executionContext.getContext())
429+
.schema(executionContext.getGraphQLSchema()).build();
430+
resolvedType = resolveTypeForUnion(resolutionParams);
431+
} else {
432+
resolvedType = (GraphQLObjectType) fieldType;
433+
}
434+
return resolvedType;
435+
}
436+
432437
/**
433438
* Called to resolve a {@link GraphQLInterfaceType} into a specific {@link GraphQLObjectType} so the object can be executed in terms of that type
434439
*

‎src/main/java/graphql/execution/ExecutorServiceExecutionStrategy.java‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import graphql.ExecutionResultImpl;
55
import graphql.GraphQLException;
66
import graphql.PublicApi;
7+
import graphql.execution.instrumentation.Instrumentation;
78
import graphql.execution.instrumentation.InstrumentationContext;
89
import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters;
910
import graphql.language.Field;
@@ -49,11 +50,15 @@ public ExecutorServiceExecutionStrategy(ExecutorService executorService, DataFet
4950

5051
@Override
5152
public CompletableFuture<ExecutionResult> execute(final ExecutionContext executionContext, final ExecutionStrategyParameters parameters) {
52-
if (executorService == null)
53+
if (executorService == null) {
5354
return new AsyncExecutionStrategy().execute(executionContext, parameters);
55+
}
5456

57+
Instrumentation instrumentation = executionContext.getInstrumentation();
58+
InstrumentationExecutionStrategyParameters instrumentationParameters = new InstrumentationExecutionStrategyParameters(executionContext);
59+
InstrumentationContext<CompletableFuture<ExecutionResult>> executionStrategyCtx = instrumentation.beginExecutionStrategy(instrumentationParameters);
5560

56-
InstrumentationContext<CompletableFuture<ExecutionResult>> executionStrategyCtx = executionContext.getInstrumentation().beginExecutionStrategy(new InstrumentationExecutionStrategyParameters(executionContext));
61+
InstrumentationContext<Map<String, List<Field>>> beginFieldsCtx = instrumentation.beginFields(instrumentationParameters);
5762

5863
Map<String, List<Field>> fields = parameters.fields();
5964
Map<String, Future<CompletableFuture<ExecutionResult>>> futures = new LinkedHashMap<>();
@@ -67,6 +72,8 @@ public CompletableFuture<ExecutionResult> execute(final ExecutionContext executi
6772
Callable<CompletableFuture<ExecutionResult>> resolveField = () -> resolveField(executionContext, newParameters);
6873
futures.put(fieldName, executorService.submit(resolveField));
6974
}
75+
beginFieldsCtx.onEnd(fields, null);
76+
7077
try {
7178
Map<String, Object> results = new LinkedHashMap<>();
7279
for (String fieldName : futures.keySet()) {
@@ -84,6 +91,7 @@ public CompletableFuture<ExecutionResult> execute(final ExecutionContext executi
8491
}
8592
results.put(fieldName, executionResult != null ? executionResult.getData() : null);
8693
}
94+
8795
CompletableFuture<ExecutionResult> result = CompletableFuture.completedFuture(new ExecutionResultImpl(results, executionContext.getErrors()));
8896
executionStrategyCtx.onEnd(result, null);
8997
return result;

‎src/main/java/graphql/execution/instrumentation/ChainedInstrumentation.java‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters;
1313
import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters;
1414
import graphql.language.Document;
15+
import graphql.language.Field;
1516
import graphql.schema.DataFetcher;
1617
import graphql.schema.GraphQLSchema;
1718
import graphql.validation.ValidationError;
@@ -104,6 +105,26 @@ public InstrumentationContext<CompletableFuture<ExecutionResult>> beginExecution
104105
.collect(toList()));
105106
}
106107

108+
@Override
109+
public InstrumentationContext<CompletableFuture<ExecutionResult>> beginDataFetchDispatch(InstrumentationDataFetchParameters parameters) {
110+
return new ChainedInstrumentationContext<>(instrumentations.stream()
111+
.map(instrumentation -> {
112+
InstrumentationState state = getState(instrumentation, parameters.getInstrumentationState());
113+
return instrumentation.beginDataFetchDispatch(parameters.withNewState(state));
114+
})
115+
.collect(toList()));
116+
}
117+
118+
@Override
119+
public InstrumentationContext<Map<String, List<Field>>> beginFields(InstrumentationExecutionStrategyParameters parameters) {
120+
return new ChainedInstrumentationContext<>(instrumentations.stream()
121+
.map(instrumentation -> {
122+
InstrumentationState state = getState(instrumentation, parameters.getInstrumentationState());
123+
return instrumentation.beginFields(parameters.withNewState(state));
124+
})
125+
.collect(toList()));
126+
}
127+
107128
@Override
108129
public InstrumentationContext<ExecutionResult> beginField(InstrumentationFieldParameters parameters) {
109130
return new ChainedInstrumentationContext<>(instrumentations.stream()

‎src/main/java/graphql/execution/instrumentation/Instrumentation.java‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters;
1111
import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters;
1212
import graphql.language.Document;
13+
import graphql.language.Field;
1314
import graphql.schema.DataFetcher;
1415
import graphql.schema.GraphQLSchema;
1516
import graphql.validation.ValidationError;
1617

1718
import java.util.List;
19+
import java.util.Map;
1820
import java.util.concurrent.CompletableFuture;
1921

2022
/**
@@ -69,7 +71,21 @@ default InstrumentationState createState() {
6971
InstrumentationContext<List<ValidationError>> beginValidation(InstrumentationValidationParameters parameters);
7072

7173
/**
72-
* This is called just before the data fetch is started and when this step finishes the {@link InstrumentationContext#onEnd(Object, Throwable)}
74+
* This is called just before the data fetching stage is started and finishes as soon as the query is dispatched ready for completion. This
75+
* is different to {@link #beginDataFetch(graphql.execution.instrumentation.parameters.InstrumentationDataFetchParameters)}
76+
* in that this step does not wait for the values to be completed, only dispatched for completion.
77+
*
78+
* @param parameters the parameters to this step
79+
*
80+
* @return a non null {@link InstrumentationContext} object that will be called back when the step ends
81+
*/
82+
default InstrumentationContext<CompletableFuture<ExecutionResult>> beginDataFetchDispatch(InstrumentationDataFetchParameters parameters) {
83+
return (result, t) -> {
84+
};
85+
}
86+
87+
/**
88+
* This is called just before the data fetching stage is started, waits for all data to be completed and when this step finishes the {@link InstrumentationContext#onEnd(Object, Throwable)}
7389
* will be called indicating that the step has finished.
7490
*
7591
* @param parameters the parameters to this step
@@ -95,6 +111,19 @@ default InstrumentationState createState() {
95111
*/
96112
InstrumentationContext<CompletableFuture<ExecutionResult>> beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters);
97113

114+
/**
115+
* This is called just before a selection set of fields is resolved and when this step finishes the {@link InstrumentationContext#onEnd(Object, Throwable)}
116+
* will be called indicating that the step has finished.
117+
*
118+
* @param parameters the parameters to this step
119+
*
120+
* @return a non null {@link InstrumentationContext} object that will be called back when the step ends
121+
*/
122+
default InstrumentationContext<Map<String, List<Field>>> beginFields(InstrumentationExecutionStrategyParameters parameters) {
123+
return (result, t) -> {
124+
};
125+
}
126+
98127
/**
99128
* This is called just before a field is resolved and when this step finishes the {@link InstrumentationContext#onEnd(Object, Throwable)}
100129
* will be called indicating that the step has finished.

‎src/main/java/graphql/execution/instrumentation/NoOpInstrumentation.java‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters;
1212
import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters;
1313
import graphql.language.Document;
14+
import graphql.language.Field;
1415
import graphql.schema.DataFetcher;
1516
import graphql.schema.GraphQLSchema;
1617
import graphql.validation.ValidationError;
1718

1819
import java.util.List;
20+
import java.util.Map;
1921
import java.util.concurrent.CompletableFuture;
2022

2123
/**
@@ -48,6 +50,11 @@ public InstrumentationContext<List<ValidationError>> beginValidation(Instrumenta
4850
return new NoOpInstrumentationContext<>();
4951
}
5052

53+
@Override
54+
public InstrumentationContext<CompletableFuture<ExecutionResult>> beginDataFetchDispatch(InstrumentationDataFetchParameters parameters) {
55+
return new NoOpInstrumentationContext<>();
56+
}
57+
5158
@Override
5259
public InstrumentationContext<CompletableFuture<ExecutionResult>> beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) {
5360
return new NoOpInstrumentationContext<>();
@@ -78,6 +85,11 @@ public InstrumentationContext<CompletableFuture<ExecutionResult>> beginCompleteF
7885
return new NoOpInstrumentationContext<>();
7986
}
8087

88+
@Override
89+
public InstrumentationContext<Map<String, List<Field>>> beginFields(InstrumentationExecutionStrategyParameters parameters) {
90+
return new NoOpInstrumentationContext<>();
91+
}
92+
8193
@Override
8294
public InstrumentationContext<CompletableFuture<ExecutionResult>> beginCompleteFieldList(InstrumentationFieldCompleteParameters parameters) {
8395
return new NoOpInstrumentationContext<>();

0 commit comments

Comments
 (0)