<aside> 💡 사용자가 많을 수록 생성과 업데이트가 빈번하고 조회 또한 매우 많이 일어나기 때문에 빠른 속도가 필요
</aside>
RankingService
@Service
@RequiredArgsConstructor
public class RankingService {
final StringRedisTemplate redisTemplate;
private static final String LEADERBOARD_KEY = "leaderBoard";
public boolean setUserScore(String userId, int score){
ZSetOperations zSetOps = redisTemplate.opsForZSet();
zSetOps.add(LEADERBOARD_KEY, userId, score);
return true;
}
public Long getUserScore(String userId){
ZSetOperations zSetOps = redisTemplate.opsForZSet();
return zSetOps.reverseRank(LEADERBOARD_KEY, userId);
}
public List<String> getTopRank(int limit){
ZSetOperations zSetOps = redisTemplate.opsForZSet();
Set<String> rangeSet = zSetOps.reverseRange(LEADERBOARD_KEY, 0, limit - 1);
assert rangeSet != null;
return new ArrayList<>(rangeSet);
}
}
Redis의 SortedSet을 사용하여 랭킹 시스템을 구현하였다. 각 사용자별 랭크를 확인할 수 있는 getUserScore과 n번째 랭킹까지 확인할 수 있는 getTopRank로 구성하였다.
다음은, 100만개의 사용자 정보를 이용한 테스트 코드이다. Sort를 이용한 정렬 시간과 Redis의 SortedSet을 이용한 랭킹 요청 시간을 비교한다. (물론, 완벽히 같은 조건은 아니므로 참고만 하자)
@SpringBootTest
public class RankingTest {
@Autowired
private RankingService rankingService;
@Test
void getUserRank(){
//1) 사용자 별 랭크
Instant before = Instant.now();
String userId = "user_100";
Long userRank = rankingService.getUserScore(userId);
Duration elapsed = Duration.between(before, Instant.now());
System.out.printf("%s RANK (%d), TOOK %d ms%n", userId, userRank, elapsed.getNano() / 1000000);
//2) 상위 랭킹
before = Instant.now();
int limit = 10;
List<String> topRank = rankingService.getTopRank(limit);
elapsed = Duration.between(before, Instant.now());
System.out.printf("Top %d ranking, TOOK %d ms%n ", limit, elapsed.getNano() / 1000000);
}
// 100만개의 사용자 정보 Insert
@Test
void insertScore(){
for (int i=0; i<100*10000; i++){
int score = (int)(Math.random() * 100 * 10000);
String userId = "user_" + i;
rankingService.setUserScore(userId, score);
}
}
// sort를 이용한 정렬 시간 비교
@Test
void inMemorySortPerformance(){
ArrayList<Integer> list = new ArrayList<>();
for (int i=0; i<100*10000; i++){
int score = (int)(Math.random() * 100*10000);
list.add(score);
};
Instant before = Instant.now();
Collections.sort(list);
Duration elapsed = Duration.between(before, Instant.now());
System.out.println((elapsed.getNano() / 1000000) + "ms");
}
}