woniper

CompletableFuture 비동기 처리로 성능 개선하기 본문

Spring

CompletableFuture 비동기 처리로 성능 개선하기

woniper1 2018. 9. 8. 15:20

이 글은 CompletableFuture API를 설명하는 글은 아니다. CompletableFuture로 어떻게 성능을 개선했는지에 대한 경험 글이다.

개인 프로젝트로 bookup 이란 웹 애플리케이션을 개발하고 있다. 원하는 도서가 오프라인 서점에 재고가 있는지 검색하는 서비스다. 오프라인 서점마다 Open API가 없어, html 크롤링으로 개발했다. 그런데 개발하며 문제가 생겼다. 바로 오프라인 서점의 수가 많아질 수록 성능이 느려진다는 것이다. 재고 조회 순서는 아래와 같다.

  1. ISBN(도서 고유값) 값을 구하기 위해 네이버 Open API를 통해 ISBN 값을 얻는다. (API 요청)
  2. ISBN으로 오프라인 서점에 해당 도서의 재고를 순서대로 크롤링한다.

왜 성능이 느릴까? 이유는 여러 http 요청을 동기(Synchronous)로 처리 하기 때문이다. 지원되는 오프라인 서점은 총 3개 (교보문고, 알라딘, 반디앤루니스)다. ISBN으로 각 서점에 요청을 할 때 하나씩 요청한다.

동기 처리

public Book getBook(String isbn) {
    Book aladinBook = aladinBookCrawler.findByIsbn(isbn);
    List<BookStore> bookStores = mapToBookStore(isbn, kyoboBookRestTemplate.findByIsbn(isbn));
    aladinBook.merge(bookStores);

    return aladinBook;
}

private List<BookStore> mapToBookStore(String isbn, Optional<KyoboBookStore> kyoboBookStore) {
    if (StringUtils.isEmpty(isbn) || !kyoboBookStore.isPresent())
        return null;

    return kyoboBookStore.get().getItems().stream()
            .filter(x -> x.getAmount() > 0)
            .map(x -> new BookStore(x.getStoreName(), kyoboProperties.createUrl(x.getStoreId(), isbn)))
            .collect(Collectors.toList());

}

동기로 처리하면 3개의 서점을 순서대로 크롤링한다. 예를 들어 각 서점 조회 응답(response) 시간이 1초라면, 총 3초가 걸린다. API가 3초면 꽤 긴 시간이다.

비동기 처리

public Book getBook(String isbn) {
    CompletableFuture<Book> bookFuture =
            CompletableFuture.supplyAsync(() -> mapBook(naverBookRestTemplate.findByIsbn(isbn)))
            .thenApplyAsync(x -> x.merge(getBookStores(isbn)));

    return FutureUtils.getFutureItem(bookFuture)
            .orElseThrow(() -> new NotFoundBookException(isbn));
}

private List<BookStore> getBookStores(String isbn) {
    List<BookStore> bookStores = new ArrayList<>();

    bookStoreFinders.stream()
            .map(x -> CompletableFuture.supplyAsync(() -> x.findByIsbn(isbn)))
            .collect(Collectors.toList())
            .forEach(x -> bookStores.addAll(FutureUtils.getFutureItem(x).orElse(Collections.emptyList())));

    return bookStores;

}

private Book mapBook(NaverBook.Item item) {
    return new Book(item.getTitle(), item.getDescription());
}

네이버 API로 ISBN을 얻은 후 해당 도서를 보유한 오프라인 서점을 찾기 위해 CompletableFuture로 서점 조회를 비동기로 처리한다. 예를 들어 각 서점 조회 응답(response) 시간이 1초라면, 동기는 3초가 걸렸지만, 비동기는 약 1초가 걸린다. 1초도 꽤 긴시간이지만, 3초보단 훨씬 좋은 응답 시간이다. 하지만 마음에 드는 성능은 아니다. 크롤링으로 처리하다보니 성능상 많은 제약이 있다.

해당 소스는 여기서 확인

Comments