์ด๋ ๋ชจ์ ํ๋ก์ ํธ์์ ํ ๋ญํน์ ๋งก์์ ๊ตฌํํ๋ค. ํ ๋ญํน ๊ธฐ๋ฅ์ ์คํฌ์ธ ์ข ๋ฅ๋ง๋ค ๊ฒฝ๊ธฐ, ์น, ๋ฌด, ํจ, ๋ ์ดํ ์๋ก ์ ๋ ฌ๋ ์์ 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 ์ ๋ ๊ฐ์ ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
'Backend > Spring Boot' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring Boot] Spring Boot์ Swagger ์ถ๊ฐํ๊ธฐ (0) | 2024.04.29 |
---|---|
[Spring Boot] Spring Boot 3๊ณผ Spring Boot 2 ๋น๊ต (0) | 2024.03.19 |
[Redis] Spring Boot์ Redis ์ฐ๋ํ๊ธฐ + RedisTemplate, RedisRepository (0) | 2024.02.03 |
[Spring Cloud] Eureka Registered instance ์ ๊ฑฐํ๊ธฐ (0) | 2023.05.12 |
[Spring] Spring Boot์ QueryDSL ๋์ ๊ธฐ (0) | 2023.04.13 |