Backend/Spring Boot

[Spring Boot] Redis๋กœ ๋žญํ‚น ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ

giraffe_ 2024. 2. 9. 21:53

 ์šด๋™ ๋ชจ์ž„ ํ”„๋กœ์ ํŠธ์—์„œ ํŒ€ ๋žญํ‚น์„ ๋งก์•„์„œ ๊ตฌํ˜„ํ–ˆ๋‹ค. ํŒ€ ๋žญํ‚น ๊ธฐ๋Šฅ์€ ์Šคํฌ์ธ  ์ข…๋ฅ˜๋งˆ๋‹ค ๊ฒฝ๊ธฐ, ์Šน, ๋ฌด, ํŒจ, ๋ ˆ์ดํŒ… ์ˆ˜๋กœ ์ •๋ ฌ๋œ ์ƒ์œ„ 10๊ฐœ์˜ ํŒ€์˜ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

 

 

 

 

 

 

์™œ Redis์ธ๊ฐ€?


 

 ๋žญํ‚น ๊ธฐ๋Šฅ์€ ์„œ๋น„์Šค์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๋กœ ์กฐํšŒ๊ฐ€ ๋นˆ๋ฒˆํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์œผ๋ฉด ๋А๋ ค์ง„๋‹ค๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค. ์ด์ „์—๋Š” Redis๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , Spring Data JPA๋กœ ์กฐ๊ฑด(์šด๋™ ์ข…๋ฅ˜, ๊ฒŒ์ž„ ํƒ€์ž…, ์ •๋ ฌ ์กฐ๊ฑด)์— ํ•ด๋‹นํ•˜๋Š” RDB(MySQL)๋ฅผ ์กฐํšŒํ•˜์—ฌ ํŒ€ ๋žญํ‚น ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ์—ˆ๋‹ค.

 

`getTop10BySportsAndGameTypeOrderByPointDesc(sports, gameType)`

(SQL๋กœ ORDER BY๋ฅผ ์“ด ๊ฒƒ์ด๋‚˜ ๋งˆ์ฐฌ๊ฐ€์ง€์ด๋‹ค.)

 

 

 ํ•˜์ง€๋งŒ ๋ฌธ์ œ๊ฐ€ ๋žญํ‚น ์กฐํšŒ์‹œ๋งˆ๋‹ค ORDER BY ์—ฐ์‚ฐ์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•ด ์„ฑ๋Šฅ์„ ๋†’์ผ ์ˆ˜๋„ ์žˆ๊ฒ ์ง€๋งŒ, ๋งค๋ฒˆ ๋””์Šคํฌ์— ์ ‘๊ทผํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋Š” ๊ฒƒ์€ ๋ถ€๋‹ด์ด ๋œ๋‹ค.

 

 

์กฐํšŒํ•  ๋•Œ๋งˆ๋‹ค ๋ฐ์ดํ„ฐ๋ฅผ ๋งค๋ฒˆ ์ •๋ ฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ •๋ ฌ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋”ฐ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ฐ€ ์กฐํšŒํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ์ด๋‹ค.

 

Redis๋Š” NoSQL๋กœ์„œ ๋‹ค์–‘ํ•œ ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ์ง€์›ํ•˜๋Š”๋ฐ, Sorted Set์„ ์ง€์›ํ•œ๋‹ค. Sorted Set์€ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ญ์ƒ ์ •๋ ฌ๋œ ์ƒํƒœ๋กœ ์œ ์ง€ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Redis๋Š” ์ธ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ๋ฉ”๋ชจ๋ฆฌ์— ์ ‘๊ทผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์†๋„๋Š” ๋น ๋ฅด๋‹ค.

 

 

 

 

 

Sorted Set ์‚ฌ์šฉ


 

 

 Sorted Set์€ key ํ•˜๋‚˜์— ์—ฌ๋Ÿฌ๊ฐœ์˜ {value , score}๊ฐ€ ๋“ค์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค.

 

  • key : ์ •๋ ฌ ์กฐ๊ฑด
  • member : teamId
  • score : ๊ฒฝ๊ธฐ ์ˆ˜ or ์Šน ํšŸ์ˆ˜ or ๋ฌด ํšŸ์ˆ˜ or ํŒจ ํšŸ์ˆ˜ or ํฌ์ธํŠธ

 

 

 ํ•˜์ง€๋งŒ ์šด๋™ ์ข…๋ฅ˜์™€ ํƒ€์ž…๋ณ„, ์ •๋ ฌ ์กฐ๊ฑด๋ณ„๋กœ ๊ฐ๊ฐ Redis Sorted Set์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ๋งž๋Š”์ง€ ์˜๋ฌธ์ด ๋“ค์—ˆ๋‹ค. ๊ฒฝ๊ธฐ ์ข…๋ฅ˜๊ฐ€ ๋†๊ตฌ, ์ถ•๊ตฌ, ๋ฐฐ๋“œ๋ฏผํ„ด์œผ๋กœ 3๊ฐœ์ด๊ณ , ํƒ€์ž…์ด ๊ฐ ์šด๋™๋ณ„๋กœ 2๊ฐœ์”ฉ์ด๊ณ , ์ •๋ ฌ ์กฐ๊ฑด์€ ๊ฒฝ๊ธฐ, ์Šน, ๋ฌด, ํŒจ, ๋ ˆ์ดํŒ…์œผ๋กœ 5๊ฐœ๊ฐ€ ๋œ๋‹ค. ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋Š” ์กฐํ•ฉ์˜ ์ˆ˜๊ฐ€ 3 * 2 * 5๋กœ 30๊ฐ€์ง€๊ฐ€ ๋œ๋‹ค.

 

 ๊ทธ๋ž˜์„œ key๋ฅผ ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑํ•ด์•ผํ• ์ง€ ๊ณ ๋ฏผ์ด ๋˜์—ˆ๋‹ค. ๋ชจ๋“  ๊ฒฝ์šฐ์˜ ์ˆ˜๋งˆ๋‹ค key๋ฅผ ๋”ฐ๋กœ ๋„ค์ด๋ฐ์„ ํ•˜๊ณ , ๋„ค์ด๋ฐํ•œ ๊ฒƒ์„ ๊ฐ€์ง€๊ณ  ์กฐ๊ฑด๋ฌธ์„ ๊ฑธ์–ด ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฝ์ž… ๋ฐ ์กฐํšŒํ•ด์•ผํ•˜๋Š” ๊ฒƒ์„ ์ƒ๊ฐํ•˜๋‹ˆ ์•„๋“ํ–ˆ๋‹ค.

 

๊ทธ๋Ÿฐ๋ฐ ์ƒ๊ฐ๋ณด๋‹ค ๊ฐ„๋‹จํ•˜๊ฒŒ ํ•ด๊ฒฐ๋˜์—ˆ๋‹ค. key๋ฅผ`sports+gameType+sortType`์™€ ๊ฐ™์ด ์ด์–ด๋ถ™์ด๋ฉด, ๊ฒฝ์šฐ์˜ ์ˆ˜๋ฅผ ์ƒ๊ฐํ•  ํ•„์š”๊ฐ€ ์—†์–ด์ง„๋‹ค.

 

 

 

 

 

๊ตฌํ˜„ํ•˜๊ธฐ


์‹œํ€€์Šค๋‹ค์ด์–ด๊ทธ๋žจ (๋žญํ‚น ์กฐํšŒ)

 

  • 1. ํด๋ผ์ด์–ธํŠธ์—์„œ ์Šคํฌ์ธ  ์ข…๋ฅ˜, ๊ฒŒ์ž„ ํƒ€์ž…, ์ •๋ ฌ ์กฐ๊ฑด์— ๋งž๋Š” ๋žญํ‚น ์ •๋ณด๋ฅผ ์š”์ฒญํ•œ๋‹ค.
  • 2. Redis์—์„œ ์š”์ฒญ์„ key(sports+gameType+sortType)๋กœ ํ•˜๋Š” ์ƒ์œ„ 10๊ฐœ ํŒ€์„ ๊ฒ€์ƒ‰ํ•œ๋‹ค.
  • 3. Redis์—์„œ ์ƒ์œ„ 10๊ฐœ ํŒ€์˜ id๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  • 4. RDB์—์„œ ์ƒ์œ„ 10๊ฐœ ํŒ€์„ ๊ฒ€์ƒ‰ํ•œ๋‹ค.
  • 5. RDB์—์„œ ์ƒ์œ„ 10๊ฐœ ํŒ€์˜ ์ •๋ณด๋ฅผ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

 

 

 

 

RedisRepository

RedisTemplate๋ฅผ ์‚ฌ์šฉํ•ด Redis์˜ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ–ˆ๋‹ค.

 

@Repository
public class RankingRedisRepository {
    @Autowired
    private RedisTemplate redisTemplate;
    private ZSetOperations<String, String> zSetOperations ;

    private final int START = 0;
    private final int END = 9;

    @PostConstruct
    private void init() {
        zSetOperations = redisTemplate.opsForZSet();
    }

    public void save(String sports, String gameType, String sortType, int teamId, int score) {
        String key = sports + gameType + sortType;
        zSetOperations.add(key, Integer.toString(teamId), score);
    }

    public List<Integer> getTeamRanking(String sports, String gameType, String sortType) {
        String key = sports + gameType + sortType;
        Set<String> idSet = zSetOperations.reverseRange(key, START, END);

        return idSet.stream().map(Integer::parseInt).collect(Collectors.toList());
    }
}

 

 

`getTeamRanking`

  • sports, gameType, sortType์— ํ•ด๋‹นํ•˜๋Š” ์ƒ์œ„ 10๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•œ๋‹ค.
  • ์กฐํšŒํ•˜๋ฉด teamId์˜ `Set<String>`์ด ๋ฐ˜ํ™˜๋œ๋‹ค. ํŒ€ ๋ฆฌ์ŠคํŠธ๋ฅผ ์กฐํšŒํ•˜๊ธฐ ์œ„ํ•ด `findAllByTeamIdIn`์„ ์“ฐ๊ฒŒ ๋  ๊ฒƒ์ธ๋ฐ, `List<Integer>`๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Integer์˜ List๋กœ ๋ณ€ํ™˜ํ–ˆ๋‹ค.

 

 

 

 

RankingService

@Service
@Transactional
public class RankingServiceImpl implements RankingService {
    @Autowired
    private RankingRedisRepository rankingRedisRepository;
    @Autowired
    private TeamJPARepository teamJPARepository;

    @Override
    public List<Team> viewRanking(String sports, String gameType, String sort) {
        List<Integer> teamIdList = rankingRedisRepository.getTeamRanking(sports, gameType, sort);
        List<Team> teamList = teamJPARepository.findAllByTeamIdIn(teamIdList);
        sortBySortType(sort, teamList);
        return teamList;
    }

    private void sortBySortType(String sort, List<Team> teamList) {
        switch (sort) {
            case "times" : teamList.sort(Comparator.comparing(Team::getMatchTimes).reversed()); break;
            case "win" : teamList.sort(Comparator.comparing(Team::getWin).reversed()); break;
            case "draw" : teamList.sort(Comparator.comparing(Team::getDraw).reversed()); break;
            case "lose" : teamList.sort(Comparator.comparing(Team::getLose).reversed()); break;
            case "rating" : teamList.sort(Comparator.comparing(Team::getPoint).reversed()); break;
        }
    }
}

 

 

`viewRanking`

  • ๊ฒ€์ƒ‰ ์กฐ๊ฑด sports, gameType, sortType์— ํ•ด๋‹นํ•˜๋Š” ๋ฐ์ดํ„ฐ 10๊ฐœ๋ฅผ Redis์—์„œ ์กฐํšŒ
  • `findAllByTeamIdIn`๋กœ RDB์—์„œ ๋ฐ์ดํ„ฐ ์กฐํšŒ
  • sortType์— ๋”ฐ๋ผ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋‹ค์‹œ ์ •๋ ฌ

 

`findAllByTeamIdIn`์„ ํ†ตํ•ด teamList๋ฅผ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋˜๋ฉด, ํ•ด๋‹น ๋ฉ”์†Œ๋“œ ์‹คํ–‰ ์ „ sorted set์— ์˜ํ•ด ์ •๋ ฌ๋˜์—ˆ๋˜ ์ˆœ์„œ๊ฐ€ ์•„๋‹ˆ๋ผ teamId ์ˆœ์„œ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ๋‹ค์‹œ ์ •๋ ฌ ์กฐ๊ฑด์— ๋”ฐ๋ผ teamList๋ฅผ ์ •๋ ฌ์‹œ์ผœ์•ผ ํ–ˆ๋‹ค.

 

 

 

 

 

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ

@Test
@DisplayName("viewRanking")
void test1() {
	String sports = "๋†๊ตฌ";
    String gameType = "3vs3";
    String sortType = "rating";

    List<Team> teamList = rankingService.viewRanking(sports, gameType, sortType);
    Assertions.assertThat(teamList.size()).isEqualTo(10);
}

 

 

 

 

 

 

์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ


 ์–ผ๋งˆ๋‚˜ ๋žญํ‚น ์กฐํšŒ ๊ธฐ๋Šฅ์˜ ์„ฑ๋Šฅ์ด ๊ฐœ์„ ๋˜์—ˆ๋Š”์ง€๋ฅผ ์•Œ๊ณ  ์‹ถ์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์„ฑ๋Šฅ์„ ํ…Œ์ŠคํŠธ ํ•  ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ํ•˜๋‚˜ ์ •ํ–ˆ๋‹ค.

 

"10๋งŒ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋™์‹œ์— 100๋ช…์ด ์กฐํšŒํ–ˆ์„ ๋•Œ ๊ฑธ๋ฆฌ๋Š” API ์‘๋‹ต ์‹œ๊ฐ„ ์ธก์ •ํ•˜๊ธฐ"

 

 

 

 

ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด RDB์— 10๋งŒ๊ฐœ์˜ ํŒ€์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•ด ๋„ฃ์—ˆ๊ณ , ์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ Redis์—์„œ๋„ 10๋งŒ ๊ฐœ ํŒ€์˜ ๋žญํ‚น ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋™์‹œ์— 100๋ช…์ด API๋ฅผ ์กฐํšŒํ•˜๋Š” ์ƒํ™ฉ์—์„œ์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด JMeter๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ถ€ํ•˜ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ๋‹ค.

 

 

 

ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ

 

์•ฝ 1/10 ์ •๋„ ๊ฐœ์„ ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.