개인 프로젝트로 진행하는 쇼핑몰 프로젝트에서 상품 랭킹 서비스 구현을 Redis의 sorted set을 이용해서 구현한 내용을 정리 해보자 📒
현재 프로젝트에서 사용자가 상품조회를 요청하게 되면 Product-Service 에서 Rank-Service로 조회요청에 대한 이벤트를 발행 하게 되고 이를 Rank-Service 쪽에선 Consume 하여 상품 ID을 통해 조회수를 증가시키고 있습니다!
랭킹 기능은 현재 사용중인 MySql DB에 저장된 데이터의 count 값을 증가 시켜 구현할 수 있겠으나, 만약 이 데이터 값이 많아 진다면 order by하여 값들을 계산하여 return 해주는 시간이 데이터가 늘어날수록 늘어날 것으로 판단 하였기 때문에 Redis를 선택하여 사용하였습니다.
일단 저의 프로젝트 구성은 MSA구성으로 분산환경에 맞춰 개발을 진행했기 때문에 이를 공통적으로 관리 할 수 있는 환경이 필요 했고 Redis로 선택하게 되었습니다.
상품을 지속적으로 조회수가 많아지수록 Rank DB에 대한 부하가 심해질것 이며, 이를 해결 하기 위하여 도입하기로 하였으나 이번 주제랑은 다른 관계로 다른 포스팅에서 작성 하도록 하겠습니다!
로직 구현이 필요없다
sorted set은 score를 하나하나 비교하여 정렬해줄 필요가 없다 -> score 값을 비교하여 정렬하기 때문이다.
(1) rank 추가
$ ZADD <key> <score> <member>
(2) rank의 score 값 조회
$ ZSCORE <key> <member>
만약 없는 key값을 조회하려고 하면 (nil)으로 값을 반환한다.
일단 간단한 등록과 조회를 할 수 있는 명령어를 살펴보았습니다.
@Query("select r from RankingEntity r order by r.clickNum desc")
List<RankingEntity> findWithPagingOrderByClickNum(Pageable pageable);
이 직접접인 DB call을 통하여 순위를 확인한 경우
541S의 성능을 보여 줬습니다. 하지만 만약 데이터가 계속적으로 많아질 경우 성능이 더 늦어질 것입니다.
ZSetOperations zSetOps = redisTemplate.opsForZSet();
Set<String> rangeRakingSet = zSetOps.reverseRange("CLICK_RANK",0,limit-1);
위와 같이 redis sortedSet을 활용하여 순위를 집계할 경우 11s의 성능을 보여주며 RDB에 직접 접근하는것 보다 빠르다는 것을 확인할 수 있었습니다!
Redis를 사용함에 있어 트래픽이 적을 경우는 오버헤드같은 경우를 고려하지 않아도 되겠지만 대용량 데이터를 사용할 시에는 이러한 오버헤드를 잘 고려해야한다고 합니다.
또한 Redis가 만약 죽어버린다면 데이터를 소실할 경우가 있습니다 이때에는 Redis의 백업 정책 혹은 cluster를 활용하여 이를 방지하여야 할것입니다!
이러한 것들은 추후 포스팅에서 다뤄볼 예정입니다!
일단 redis 모든 노드등의 문제가 생겨 순위 서비스가 만약 데이터를 잃는다고 하여도, 일단 순위에 대한 서비스 자체가 현재 진행하는 프로젝트의 서비스에서 크리티컬한 문제를 일으키지 않을 거라 판단 하였습니다. 이러할 경우 Look Aside 패턴을 사용하여 이전 데이터를 RDB에서 불러오는 전략을 세워볼 수 있을거라 판단했습니다. 이러한 읽기 전략이나 쓰기 전략은 추후에 모아서 글을 작성해 해보도록 하겠습니다!