[GraphQL] 비동기로 요청 처리하기

본 포스트는 공식 레퍼런스를 참고해 GraphQL을 공부하며 직접 작성한 가이드 입니다.
본 포스트는 2021년 7월 최신 버전인 v16.2를 기준으로 작성되어 있습니다.

비동기로 요청 처리하기

기본적으로 GraphQLResolver는 동기적으로 작업을 처리하도록 설계되었다. 만약 내려줘야하는 데이터에서 시간이 오래걸리는 작업이 있다고 하자. 이런 작업은 비동기로 처리하도록 설계하는게 어플리케이션 성능에 긍정적인 영향을 미칠 확률이 높다. 이번에는 요청을 비동기로 처리하는 방법을 알아보자.

먼저 가져오는데 시간이 오래걸리는 데이터인 BgmWebToon에 추가한다.

1
2
3
4
5
6
7
8
9
10
11
type WebToon {
id: ID!
webToonType: WebToonType
title: String
image: Image
bgm: Bgm
}

type Bgm {
url: String!
}

그리고 이에따라 BgmResolver도 추가해준다. 데이터가 가져오는데 시간을 오래걸림을 표현하기위해 3초간 지연을 시킨다. Image또한 3초간 지연을 시키도록 추가한다.

1
2
3
4
5
6
7
8
9
10
11
@Slf4j
@Component
public class BgmResolver implements GraphQLResolver<WebToon> {
public Bgm bgm(WebToon webToon, DataFetchingEnvironment env) {
log.info("requesting bgm data for webtoon id: {}", webToon.getId());

ThreadUtils.ThreadStop(3);

return Bgm.builder().url("Bgm Url Resource").build();
}
}

그리고 요청을 보낸 후 로그를 살펴보자.

1
2
3
4
2021-07-25 19:00:16.185  INFO 15392 --- [nio-8080-exec-2] c.s.g.resolver.webtoon.ImageResolver     : get image data from local context. thumbnail : thumbnail URL
2021-07-25 19:00:16.185 INFO 15392 --- [nio-8080-exec-2] com.sumfi.graphql.utils.ThreadUtils : Stop http-nio-8080-exec-2 Thread 5 seconds
2021-07-25 19:00:21.194 INFO 15392 --- [nio-8080-exec-2] c.s.g.resolver.webtoon.BgmResolver : requesting bgm data for webtoon id: 3568e088-ec83-11eb-9a03-0242ac130003
2021-07-25 19:00:21.194 INFO 15392 --- [nio-8080-exec-2] com.sumfi.graphql.utils.ThreadUtils : Stop http-nio-8080-exec-2 Thread 5 seconds

하위 리졸버들은 모두 같은 스레드인 nio-8080-exec-2에서 돌고있는걸 볼 수 있다. 이제 이 요청을 비동기로 돌려보자. 방법은 간단한데 CompletableFuture를 반환하도록 하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Slf4j
@Component
@RequiredArgsConstructor
public class BgmResolver implements GraphQLResolver<WebToon> {
private final Executor webtoonTaskExecutor;

public CompletableFuture<Bgm> bgm(WebToon webToon, DataFetchingEnvironment env) {
log.info("requesting bgm data for webtoon id: {}", webToon.getId());

return CompletableFuture.supplyAsync(
() -> {
log.info("getting bgms for webtoon id: {}", webToon.getId());
ThreadUtils.ThreadStop(3);
return Bgm.builder().url("Bgm Url Resource").build();
}, webtoonTaskExecutor);
}
}

@Slf4j
@Component
@RequiredArgsConstructor
public class ImageResolver implements GraphQLResolver<WebToon> {
private final Executor webtoonTaskExecutor;

public CompletableFuture<DataFetcherResult<Image>> image(WebToon webToon, DataFetchingEnvironment env) {
log.info("requesting image data for webtoon id: {}", webToon.getId());

return CompletableFuture.supplyAsync(() -> {
Image image = env.getLocalContext();

log.info("get image data from local context. thumbnail : {}", image.getThumbnail());

ThreadUtils.ThreadStop(3);

return DataFetcherResult.<Image>newResult()
.data(image)
.build();
}, webtoonTaskExecutor);
}
}

그리고 요청을 비동기로 돌릴 executor bean을 등록해주자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class AsyncConfiguration {
@Bean(name = "webtoonTaskExecutor")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setQueueCapacity(100);
taskExecutor.setThreadNamePrefix("webtoonTaskExecutor-");
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
}

이제 요청을 보내보고 디버깅을 해보자.


1
2
3
4
2021-07-25 19:30:38.514  INFO 15588 --- [nTaskExecutor-1] c.s.g.resolver.webtoon.ImageResolver     : get image data from local context. thumbnail : thumbnail URL
2021-07-25 19:30:38.514 INFO 15588 --- [nTaskExecutor-1] com.sumfi.graphql.utils.ThreadUtils : Stop webtoonTaskExecutor-1 Thread 3 seconds
2021-07-25 19:30:38.515 INFO 15588 --- [nTaskExecutor-2] c.s.g.resolver.webtoon.BgmResolver : getting bgms for webtoon id: 3568e088-ec83-11eb-9a03-0242ac130003
2021-07-25 19:30:38.515 INFO 15588 --- [nTaskExecutor-2] com.sumfi.graphql.utils.ThreadUtils : Stop webtoonTaskExecutor-2 Thread 3 seconds

ImageResolverBgmResolver가 각각 1번, 2번 스레드에서 돌고 있음을 볼 수 있다.

Repository

모든 가이드의 예제 코드는 SongHayoung/springboot-graphql-tutorial에서 확인할 수 있습니다.

Author: Song Hayoung
Link: https://songhayoung.github.io/2021/07/25/GraphQL/graphql-7/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.