Skip to content

Commit 182ac0a

Browse files
authored
Merge pull request #6 from felipe-gdr/performance-checks
Add performance checks example
2 parents e3d2bfb + 72f081a commit 182ac0a

File tree

26 files changed

+1269
-2
lines changed

26 files changed

+1269
-2
lines changed

performance-checks/.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.gradle
2+
/build/
3+
!gradle/wrapper/gradle-wrapper.jar
4+
5+
### STS ###
6+
.apt_generated
7+
.classpath
8+
.factorypath
9+
.project
10+
.settings
11+
.springBeans
12+
.sts4-cache
13+
14+
### IntelliJ IDEA ###
15+
.idea
16+
*.iws
17+
*.iml
18+
*.ipr
19+
/out/
20+
21+
### NetBeans ###
22+
/nbproject/private/
23+
/nbbuild/
24+
/dist/
25+
/nbdist/
26+
/.nb-gradle/

performance-checks/README.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Performance checks
2+
3+
This example has a few mechanisms to prevent your GraphQL server from dealing with expensive queries sent by abusive clients
4+
(or maybe legitimate clients that running expensive queries unaware of the negative impacts they might cause).
5+
Also, it has a couple of timeout strategies that, although won't help ease the burden on the server (since the expensive
6+
operations are already under way), will provide a better experience to the consumer that won't have to wait forever for
7+
their requests to return.
8+
9+
Here we introduce 4 mechanisms to help with that task. 3 of them are based on GraphQL Java instrumentation capabilities,
10+
and the forth one is a bit out GraphQL Java jurisdiction and more related to web servers.
11+
12+
1. MaxQueryDepthInstrumentation: limit the depth of queries to 5
13+
2. MaxQueryComplexityInstrumentation: set complexity values to fields and limit query complexity to 5
14+
3. A custom Instrumentation that sets a timeout period of 3 seconds for DataFetchers
15+
4. A hard request timeout of 10 seconds, specified in the web server level (Spring)
16+
17+
The first 2 items will actually prevent server overload, since they act before the request reach the DataFetchers, which
18+
perform the expensive operations. Number 3 and 4 are timeouts that force long running executions to return early to customers.
19+
20+
# The schema
21+
The schema we're using is quite simple:
22+
23+
```graphql
24+
type Query {
25+
instrumentedField(sleep: Int): String
26+
characters: [Character]
27+
}
28+
29+
type Character {
30+
name: String!
31+
friends: [Character]
32+
energy: Float
33+
}
34+
```
35+
36+
There are a few interesting facts related to this example.
37+
38+
* instrumentedField: returns a fixed string ("value"). It receives a parameter "sleep" that forces the DataFetcher to
39+
take that amount of seconds to return. Set that value to anything above 3 seconds and a timeout error will be thrown.
40+
This simulates a long running DataFetcher that would be forcefully stopped.
41+
42+
```graphql
43+
{
44+
instrumentedField(sleep: 4) # will result in an error
45+
}
46+
```
47+
48+
* friends: will return a list of characters, that can themselves have friends, and so on... It's quite clear that queries
49+
might overuse this field and end up having a large number of nested friends and characters. Add 5 or more levels of
50+
friends and an error will be thrown.
51+
52+
```graphql
53+
{
54+
characters {
55+
name
56+
friends {
57+
name
58+
friends {
59+
name
60+
friends {
61+
name
62+
friends {
63+
name # an error will be thrown, since the depth is higher than the limit
64+
}
65+
}
66+
}
67+
}
68+
}
69+
}
70+
```
71+
72+
* energy: getting this field involves some expensive calculations, so we've established that it has a complexity value
73+
of 3 (all the other fields have complexity 0). We've also defined that a query can have a maximum complexity of 5. So,
74+
if "energy" is present 2 times or more in a given query, an error will be thrown.
75+
76+
```graphql
77+
{
78+
characters {
79+
name
80+
energy
81+
friends {
82+
name
83+
energy # an error will be thrown, since we've asked for "energy" 2 times
84+
}
85+
}
86+
}
87+
```
88+
89+
# Request timeout
90+
Although this is not really GraphQL Java business, it might be useful to set a hard request timeout on the web server
91+
level.
92+
To achieve this using Spring, the following property can be used:
93+
94+
```
95+
spring.mvc.async.request-timeout=10000
96+
```
97+
98+
99+
# Running the code
100+
101+
To build the example code in this repository type:
102+
103+
```
104+
./gradlew build
105+
```
106+
107+
To run the example code type:
108+
109+
```
110+
./gradlew bootRun
111+
```
112+
113+
To access the example application, point your browser at:
114+
http://localhost:8080/
115+
116+
## Note about introspection and max query depth
117+
A bad side effect of specifying a maximum depth for queries is that this will prevent introspection queries to properly
118+
execute. This affects GraphiQL's documentation and autocomplete features, that will simply not work.
119+
This is a tricky problem to fix and [has been discussed in the past](https://github.com/graphql-java/graphql-java/issues/1055).
120+
121+
You can still use GraphiQL to execute queries and inspect results. If you want documentation and autocomplete back in
122+
GraphiQL, just temporarily disable the max depth instrumentation.

performance-checks/build.gradle

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
buildscript {
2+
ext {
3+
springBootVersion = '2.0.5.RELEASE'
4+
}
5+
repositories {
6+
mavenCentral()
7+
}
8+
dependencies {
9+
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
10+
}
11+
}
12+
13+
apply plugin: 'java'
14+
apply plugin: 'eclipse'
15+
apply plugin: 'org.springframework.boot'
16+
apply plugin: 'io.spring.dependency-management'
17+
18+
group = 'com.graphql-java.examples'
19+
version = '0.0.1-SNAPSHOT'
20+
sourceCompatibility = 1.8
21+
22+
repositories {
23+
mavenCentral()
24+
}
25+
26+
27+
dependencies {
28+
compile "io.reactivex.rxjava2:rxjava:2.1.5"
29+
implementation('org.springframework.boot:spring-boot-starter-web')
30+
implementation('com.graphql-java:graphql-java:10.0')
31+
implementation('com.google.guava:guava:26.0-jre')
32+
testImplementation('org.springframework.boot:spring-boot-starter-test')
33+
}
53.4 KB
Binary file not shown.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#Sun Oct 07 10:24:43 AEDT 2018
2+
distributionBase=GRADLE_USER_HOME
3+
distributionPath=wrapper/dists
4+
zipStoreBase=GRADLE_USER_HOME
5+
zipStorePath=wrapper/dists
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-all.zip

performance-checks/gradlew

Lines changed: 172 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)