티스토리 뷰

728x90

캐러셀이란

캐러셀 사례. 쿠팡(좌) 오늘의집(우)

캐러셀은 슬라이드쇼와 같은 방식으로 콘텐츠를 표시하는 UX 구성 요소입니다.

회사에서 운영하는 서비스에서도 테마 별 캐러셀을 사용하고 있습니다.

 

 

문제 상황

staging QA에서 홈화면에 접근 시 메인배너만 보이고 새로고침 전 까진 캐러셀들이 보이지 않는다는 리포트가 들어왔습니다.

브라우저 네트워크 탭에서 확인해보니 캐러셀 목록 데이터를 조회하는 API의 응답속도가 17초 이상 나왔습니다.

 

 

분석

일단 어디가 병목지점인지를 확인하기 위해 코드 중간중간 타이머를 심어 시간을 측정했습니다.

확인 결과, 상품정보목록를 불러오는 부분에서 10초 이상을 소요되어 이 부분을 더 자세히 살펴보았습니다.

 

서비스는 PHP Laravel로 개발되어있습니다.

(기존 로직을 간결하게 재현한 코드로, 실 운영 중인 코드와는 다소 차이가 있습니다)

public function getCarouselList()
{
    // Redis 캐시 데이터 조회
    $carouselList = redis->get('main:carouselList');
    if(!empty($carouselList)){
        return json_decode($carouselList);
    }
    
    // RDBMS에서 캐러셀 데이터 조회
    $carouselList = $this->carouselDao->getCarouselList();
    
    // 홈화면에 필요한 상품 데이터 추가
    foreach ($carouselList as $carousel){
        // 이 부분에서 지연 발생
        $productList = $this->getProductListWithBuyingPrice($carousel->productIdList);
        // ... 생략
    }
    
    // 조회한 내용 캐싱
    redis->set('main:carouselList', json_encode($carouselList));
    redis->expire('main:carouselList', 60*2);
    
    return $carouselList;
}


// 지연이 발생했던 메소드
private function getProductListWithBuyingPrice($productIdList)
{
    // 상품의 기본정보 목록 조회
    $productList = $this->productBo->getProductList($productIdList);

    // 2중 for문 발생
    for($i = 0; $i < count($productList); $i++){
        $product = $productList[$i];
        // 상품별 최저가 계산
        $price = $this->productBo->getMinBuyingPriceByProductId($product->id);
        $product->buyingPrice = empty($price) ? '-' : $price;
                
        // 상품별 썸네일 이미지 정보 조회
        $product->image = $this->productBo->getProductThumbnailImage($product->id);

        $productList[$i] = $product;
    }

    return $productList;
}

문제 포인트

- for문을 사용하는 코드를 for문 안에서 실행하여 시간복잡도 증가

- 상품 상세 API에서 쓰던 코드를 다량의 상품조회 로직에서 재사용

- private 메소드의 반복문 안에서 상품ID만큼의 DB조회가 추가로 발생

 

즉, 등록된 캐러셀이 많아지면서 DB를 조회하는 횟수가 기하급수적으로 늘어난 것입니다.

 

이슈 리포팅 당시 등록된 캐러셀은 7개

각 캐러셀 당 상품 수는 33~96개, 평균 48개

이를 기준으로 DB조회 횟수를 개산해보면

 

캐러셀 목록 조회 : 1회

캐러셀별 상품 정보 조회 : 7회

상품 기본정보 조회 : 7 x 48 = 336회

최저가 정보 조회 : 7 x 48 = 336회

썸네일 이미지 정보 조회 : 7 x 48 = 336회

캐러셀 목록 조회 API 한번 호출할 때마다 약 1,016번의 DB 조회가 발생하는 것이었습니다. 🤯

 

당시 MVP 개발 중이라 처음 테스트 할 땐 캐러셀도 상품도 많이 등록하지 않았기 때문에 미처 모르고 지나다가

운영팀에서 서비스 오픈을 앞두고 본격적으로 홈화면에 상품을 등록하기 시작하면서 발견하게 되었습니다. 😢

 

DB 조회 횟수가 성능에 영향을 미치는 이유

DB서버는 디스크에 데이터를 저장하는 IO Bound입니다.

데이터 읽기/쓰기 시 입출력(I/O) 과정에서 물리적인 이동이 수반되어 메모리 보다 느립니다.

메모리는 디스크보다 10^5 ~ 10^6배 이상 빠릅니다.

즉, IO부하 줄이기의 핵심은 디스크 입출력 횟수를 줄이고 최대한 메모리 내에서 처리하도록 하는 것입니다.

 

자세한 내용은 CPU Bound, IO Bound에 관해 정리했던 이전 포스팅을 참고해주세요

https://jibsakim.tistory.com/27

 

CPU bound, IO bound

CPU bound 부하 프로그램 처리 속도가 CPU에 의해 좌우되어 디스크의 입출력은 없지만 CPU의 계산 속도에 의존하고 있기 때문에 CPU에서 발생하는 부하입니다. 과거 제가 API 서버에 엑셀 다운로드 기

jibsakim.tistory.com

 

 

해결

그래서 저는 DB 조회 횟수를 줄이기 위해 각 캐러셀 별 상품정보 조회를 캐러셀 당 1번씩만 조회하도록 상품테이블, 이미지 테이블, 가격정보 테이블을 JOIN하는 방식으로 코드를 개선했습니다.

 

DB 조회 횟수를 1016 -> 7회(=등록된 캐러셀 수)로 감소시켰고

그에 따라 API의 응답시간 또한 18s -> 0.8s로 단축되었습니다.

 

캐러셀과 상품정보도 모두 JOIN해서 쿼리 한번으로 끝내는 방법도 있었겠지만,

홈화면에 등록하여 사용하는 캐러셀 수는 일정 수준 이상 증가하지 않고

캐러셀은 전시도메인에 해당하고 상품 정보는 상품도메인에 해당하므로

두 도메인 간 의존성을 조금이나마 낮추는 게 맞다 판단하여 두 테이블은 JOIN하지 않았습니다.

 

 

 

정리

2022년 12월의 일로 벌써 반 년 넘게 지났습니다. 지금 와서 복기해보면 너무 바보같았던 것 같습니다. 😓

이 이슈를 계기로 이후 개발할 땐 앞으로 데이터 규모가 얼마나 커질지 생각하면서 개발하게 되었습니다.

또한 다른 사람이 개발한 모듈을 호출할 때는 그냥 막 가져다 쓰는 게 아니라 내부 구조가 어떻게 되어있는지, 내가 개발하고자 하는 로직에부합하는지를 한번 더 생각해보게 되었습니다.

 

728x90
댓글
300x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
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
글 보관함