<GraphQL> 6. DataLoader with Spring Boot
by BFine가. DataLoader 란?
a. Docs
- 지난 포스팅[2022.04.06 - [공부(2021)/GraphQL] - 5. 비동기 처리 / N+1 문제 with Spring Boot]에서 DataLoader를 사용해보았다.
- 좀 더 자세히 살펴보면 위의 클래스 설명을 보면 요청한 key값들에 대한 배치 로딩을 지원하는 유틸 클래스이다.
=> 일반적으로 각각의 key값에 대한 실행은 비동기로 처리 된다. (return은 CompletableFuture)
- 아래에서 DataLoader의 각각의 필드가 어떤 역할인지 확인해보자.
b. DataLoaderHelper
- DataLoaderHelper : DataLoader 클래스의 기능을 분리한 클래스
- StatisticsCollector : DataLoader 실행에 관련된 상태 정보들을 저장해두는 클래스
c. CacheMap & ValueCache
- DataLoader의 cache 옵션이 true일때 사용되는 저장소들로 2단계 전략을 가지고 있다.
- CacheMap은 일반적인 Map으로 구성되어있고 Value 값이 CompletableFuture<V> 형태이다.
- ValueCache는 수명이 길거나 외부 cache 처리가 필요할때 사용한다.
=> 디폴트는 NoOpValueCache를 사용하기 때문에 값을 이중 처리 하지는 않는다.
=> NoOpValueCache 외에 다른 구현체가 없어서 추가로 필요한 경우에 직접 커스텀 클래스로 만들어야 하는 것으로 보인다.
나. DataLoader 만들기
private DataLoaderRegistry dataLoaderRegistry(){
BatchLoader<String,String> locationBatchLoader = new BatchLoader<String, String>() {
@Override
public CompletionStage<List<String>> load(List<String> kindList) {
return CompletableFuture.supplyAsync(()-> placeService.getLocationList(kindList));
}
};
DataLoader<String, String> locationDataLoader = DataLoaderFactory
.newDataLoader(locationBatchLoader);
return DataLoaderRegistry.newRegistry()
.register("locationDataLoader",locationDataLoader)
.build();
}
a. BatchLoader
- 지난번에 동물들의 위치를 확인하는 DataLoader를 등록하는 부분을 만들었었는데 람다를 풀어서 다시 써보았다.
=> 위의 .load 의 kindList에는 동물의 종류가 들어온다. (ex. 개, 호랑이, 기린 등)
- 일반적으로 DataLoaderFactory를 통해 DataLoader 생성해야하며 이를 위한 파라미터 값으로 BatchLoader를 받는다.
- BatchLoader는 FunctionalInterface이며 각각의 key 값에 대해서 값을 load하는 역할을 하는 DataLoader의 핵심 부분이다.
- DataLoaderFactory의 DataLoader 생성 메서드는 다양한 BatchLoader 클래스들을 이용하여 생성한다. 아래에서 살펴보자.
b. MappedBatchLoader
- MappedBatchLoader는 클래스명에서 느껴지듯 return은 Map형태, 파라미터로는 Set을 가지는 것을 볼 수 있다.
- 로직도 맞게 변경해보았는데 List로 변환 작업을 줄여서 좀 더 간결하게 만들 수 있었다.
- 내부로직을 비교해보면 key값에 대해 한번 Set으로 변환하여 중복을 제거한 후 .load 메서드에 파라미터로 전달해서 실행한다.
=> 여기서 .load 메서드는 위의 BatchLoader를 익명함수로 구현하여 service 로직을 등록한 그 부분이 실행된다.
- 그리고 아래 List와는 다르게 Map으로 결과를 받아서 List 형태로 다시 변환해주는 작업이 추가되어 있는 것을 볼 수 있다.
- 여기서 주의할 부분은 BatchLodaer를 쓰는 경우에 전체 중복포함된 keys값이 들어올거라 생각 할 수도 있는데 cache 설정에 따라 다르다.
=> cache 설정이 true인 경우에는 앞에서 CacheMap으로 처리하기 때문에 BatchLoader도 마찬가지로 중복이 제거 되어 keys 값에 추가가 된다.
c. Try
- DataLoaderFactory 메서드를 살펴보면 BatchLoader 의 Value 값이 Try<V> 로 되어있는 것을 볼 수 있다. 어떤건지 살펴보자
- 내용을 보면 실행결과나 Exception을 가지고 있는 상태를 담는 wrapper 클래스로 보인다.
- 배치 처리에서 중요한 부분은 100개를 처리하다 1개라도 오류가 났을때 어떻게 처리할 것 인지가 중요하다.
=> 이부분을 위해 Try 클래스로 결과를 wrap 해서 성공과 예외에 대한 개별적인 파악이 가능하다.
private DataLoaderRegistry dataLoaderRegistry(){
BatchLoader<String, Try<String>> locationBatchLoaderTry = new BatchLoader<String, Try<String>>() {
@Override
public CompletionStage<List<Try<String>>> load(List<String> keys) {
return CompletableFuture.supplyAsync(()->placeService.getLocationListWithTry(keys));
}
};
DataLoader<String, String> locationDataLoaderTry = DataLoaderFactory.newDataLoaderWithTry(locationBatchLoaderTry);
return DataLoaderRegistry.newRegistry()
.register("locationDataLoaderTry",locationDataLoaderTry)
.build();
}
}
@Slf4j
@RequiredArgsConstructor
@Service
public class PlaceService {
private final PlaceRepository placeRepository;
public List<Try<String>> getLocationListWithTry(List<String> kindList){
log.info("Service 실행");
List<Place> placeList = placeRepository.findAll();
List<Try<String>> locationListTry = new ArrayList<>();
for (String kind : kindList){
Try<String> location = Try.tryCall(() -> getLocation(placeList, kind));
locationListTry.add(location);
}
return locationListTry;
}
private String getLocation(List<Place> placeList, String kind) {
log.info("Try 실행");
if("개".equals(kind)){
throw new IllegalArgumentException("[개]일 경우 IllegalArgumentException");
}
Map<String, String> placeMap = placeList.stream()
.collect(Collectors.toMap(Place::getKind, Place::getPlaceName));
String location = placeMap.get(kind);
return location;
}
}
- Try 를 테스트 해보기위해 살짝 이상하지만 private 메서드로 분리해 실제 값을 가져오는 부분은 람다로 추가해주었다.
- 그리고 예외 상황에서 어떤 결과 값과 로그가 나오는지 확인하기 위해 throw를 추가하고 테스트 해보았다.
- 결과를 보면 error 메세지가 발생을 했지만 결과로는 kind가 개 인 경우를 제외하고 정상적인 데이터를 받아오는 것을 볼 수 있다.
- 로그도 살펴보면 어떤 Element에 어느 필드가 오류났는지에 대한 내용이 출력되는 것을 볼 수 있다.
- 처음엔 Try 가 무슨 클래스 인지 했는데 해보니 상당히 중요한 클래스로 느껴졌고 최대한 활용해야 할 것 같다.
d. DataLoaderOptions
- DataLoaderOptions 를 통해 DataLoader의 동작을 제어할 수 있다. 위의 이미지에서 보이는 것 처럼 여러 설정을 지원하고 있다.
- 여기서 .setMaxBatchSize 는 keys 값의 개수를 제어해서 service 로직 호출 횟수가 달라질 수 있다. (디폴트는 -1)
다. DataLoaderHelper
a. 핵심기능
- 위에서 살펴보았던 DataLoader의 가장 중요한 핵심 기능을 처리하는 DataLoaderHelper의 내부를 살펴봐야겠다.
- 주의할 부분은 DataLoader 를 필드값으로 가지고 있는 것을 볼 수가 있다.
b. loaderQueue
- loaderQueue는 배치로딩을 하기 전 key 값을 저장해두고 이를 이용해 실행결과(CompletableFuture)를 저장해두는 역할을 한다.
=> dataLoader를 synchroized하는 것으로 보아 Thread-safe하게 처리하고 있는 것으로 보인다.
c. dispatch
- loaderQueue에서 저장했던 key 값과 CompletableFuture(빈껍데기) 및 Context를 꺼내서 하위로 전달한다.
- 위에서 받은 값들 통해 .invokeLoader 를 실행하여 CompletableFuture로 결과들을 받아온다.
=> 추가 처리로 실행처리한 값으로 loaderQueue에 있던 CompletableFuture에 실행결과를 complete 처리를 한다.
- 이 결과(CompletableFuture)는 DispatchResult 형태로 wrap 되어 DataLoaderRegistry로 전달된다.
'공부 > GraphQL' 카테고리의 다른 글
<GraphQL> 5. 비동기 처리 / N+1 문제 with Spring Boot (0) | 2022.04.06 |
---|---|
<GraphQL> 4. 동물원 예제 만들기 with Spring Boot (0) | 2022.04.02 |
<GraphQL> 3. Exception Handling with Spring Boot (0) | 2022.03.27 |
<GraphQL> 2. Query와 Type with Spring Boot (2) | 2022.03.26 |
<GraphQL> 1. 설정하기 with Spring Boot (2) | 2022.03.14 |
블로그의 정보
57개월 BackEnd
BFine