ํ์ : PM(1) / Design(1) / Frontend(2) / Backend(3)
๊ธฐ๊ฐ : 2024.03 ~ 2025.03
๋งํฌ : https://github.com/M-ung/MoodBuddy_Server
์๋น์ค ๋ด์ฉ : ์ฌ์ฉ์๊ฐ ์์ฑํ ์ผ๊ธฐ๋ฅผ ๋ฐํ์ผ๋ก ๊ฐ์ ๋ถ์ํ๋ ์น ์๋น์ค
์ํต : GitHub, Slack, Notion, Discord
์ฒ์ 1์ฐจ ๊ฐ๋ฐ ๋จ๊ณ์์ ์๋น์ค์ ์์ด์ ์กฐํ ๊ธฐ๋ฅ๋ค์ ๋ชจ๋ Spring Data JPA๋ฅผ ํ์ฉํด DB์ ์ ๊ทผํ์ฌ ์กฐํํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ๋ค.
๐ ์ผ๊ธฐ ๋ชฉ๋ก ์กฐํ ๊ธฐ๋ฅ (์บ์ฑ X)
@Override
public PageCustom<DiaryResQueryDTO> getDiaries(final Long userId, Pageable pageable) {
return diaryQueryRepository.findDiariesWithPageable(userId, pageable);
}
ํ์ง๋ง 1์ฐจ ๊ฐ๋ฐ ์ดํ, ๋จ์ํ ์กฐํ ์๋๋ฅผ ๋์ด๊ณ ์ถ์ ๋ง์์ Redis ์บ์ฑ์ ๋ชจ๋ ์กฐํ ๊ธฐ๋ฅ์ ์ ์ฉํด์ ๋ฆฌํฉํ ๋งํ๋ค.
๐ ์ผ๊ธฐ ๋ชฉ๋ก ์กฐํ ๊ธฐ๋ฅ (์บ์ฑ O)
@Override
@Cacheable(
cacheNames = "getDiaries",
key = "'userId:'+#userId+'_'+'pageable.offset:'+#pageable.offset+'_'+'pageable.pageSize:'+#pageable.pageSize",
unless = "#result == null"
)
public PageCustom<DiaryResQueryDTO> getDiaries(final Long userId, Pageable pageable) {
return diaryQueryRepository.findDiariesWithPageable(userId, pageable);
}
Redis ์บ์ฑ์ ์ ์ฉํ ๋๋ถ์ ์กฐํ ์๋๋ฅผ ์ ๋ณด๋ค ํจ์ฌ ํฅ์์ํฌ ์ ์์๋ค. ํ์ง๋ง ๊ตฌํ์ ํ ํ ์๊ฐํด ๋ณด๋ ์๋์ ๊ฐ์ ์๋ฌธ์ ๋ค์ด ์๊ฒจ๋ฌ๋ค.
1. ๊ธฐํ ํน์ฑ์ ๋ฌด๋๋ฒ๋๋ ๊ฐ์ธ์ ์ธ ๊ณต๊ฐ์ด๋ค. ๊ทธ๋ฐ๋ฐ ๋ชจ๋ ์กฐํ ๊ธฐ๋ฅ์ ์บ์ฑํ ํ์๊ฐ ์์๊น?
2. ํํฐ๋ง ์กฐํ๋ ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ์กฐ๊ฑด์ด ๋งค๋ฒ ๋ค๋ฅด๋ค. ๊ทธ๋ผ ์บ์ฑ์ด ๋ถํ์ํ์ง ์์๊น?
3. ํ์ฌ Redis์ TTL๋ฅผ ์ฌ์ฉ์๋ง๋ค ๊ฐ๊ฒ ํด๋ ์ํ์ด๋ค. ๊ทธ๋ ๋ค๋ฉด ์บ์ ์คํฌํผ๋ ํ์์ด ๋ฐ์ํ์ง ์์๊น?
1. ๊ธฐํ ํน์ฑ์ ๋ฌด๋๋ฒ๋๋ ๊ฐ์ธ์ ์ธ ๊ณต๊ฐ์ด๋ค. ๊ทธ๋ฐ๋ฐ ๋ชจ๋ ์กฐํ ๊ธฐ๋ฅ์ ์บ์ฑํ ํ์๊ฐ ์์๊น?
์ ์ฒด ์ผ๊ธฐ ์กฐํ ๊ธฐ๋ฅ์ ์บ์ฑ์ ์ ์ฉํ์ง๋ง, ๋๋จธ์ง ๊ฐ์ ์ผ๊ธฐ ์กฐํ, ํํฐ๋ง ์ผ๊ธฐ ์กฐํ์ ๊ฐ์ด ๋งค๋ฒ ๋ค๋ฅด๊ฒ ์กฐํ๋๋ ๊ฒฝ์ฐ๋ ์บ์ฑ์ ์ ๊ฑฐํ ์์ ์ด๋ค.
2. ํํฐ๋ง ์กฐํ๋ ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ์กฐ๊ฑด์ด ๋งค๋ฒ ๋ค๋ฅด๋ค. ๊ทธ๋ผ ์บ์ฑ์ด ๋ถํ์ํ์ง ์์๊น?
ํํฐ๋ง ๊ฐ์ ๊ฒฝ์ฐ๋ ์ฌ์ฉ์๊ฐ ๋งค๋ฒ ๋ค๋ฅด๊ฒ ์กฐํํ ๊ฐ๋ฅ์ฑ์ด ๋งค์ฐ ํฌ๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์บ์ฑ์ ๋ถํ์ํ๋ค๊ณ ํ๋จ๋๋ค.
3. ํ์ฌ Redis์ TTL๋ฅผ ์ฌ์ฉ์๋ง๋ค ๊ฐ๊ฒ ํด๋ ์ํ์ด๋ค. ๊ทธ๋ ๋ค๋ฉด ์บ์ ์คํฌํผ๋ ํ์์ด ๋ฐ์ํ์ง ์์๊น?
TTL์ ์ฌ์ฉ์๋ง๋ค ๊ฐ์ ์ค์ ํ์ฌ ์บ์ ์คํฌํผ๋ ํ์์ด ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ํฌ๋ค.
์บ์ ์คํฌํผ๋ ํ์์ด๋?
์บ์ ์คํฌํผ๋๋ ์บ์๊ฐ ๋ง๋ฃ๋ ๋, ์ฌ๋ฌ ์์ฒญ์ด ๋์์ DB๋ก ๋ชฐ๋ฆฌ๋ฉด์ ๋ถํ๊ฐ ๋ฐ์ํ๋ ํ์์ ๋งํ๋ค.
์ฆ ์ฝ๊ฒ ๋งํ๋ฉด, ์บ์ ๋ง๋ฃ ํ ์๋ง์ ์์ฒญ์ด ๋์์ DB๋ก ๋ชฐ๋ ค ์๋ฒ๊ฐ ๊ณผ๋ถํ๋๋ ๋ฌธ์ ์ด๋ค.์บ์ ์คํฌํผ๋ ํ์์ ์๋๋ฆฌ์ค๋ ์๋์ ๊ฐ๋ค.
1. ์บ์๊ฐ ๋ง๋ฃ๋๋ค.
2. ๋ชจ๋ ์์ฒญ์ด ์บ์๋ฅผ ์ฐพ์ง๋ง, ๋ฐ์ดํฐ๊ฐ ์๋ค.
3. ๋์์ ๋ชจ๋ ์์ฒญ์ด DB์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ค ํ๋ค.
4. DB ๋ถํ ๊ธ์ฆํ๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์๋์ ๊ฐ์ ๋ฐฉ๋ฒ๋ค์ ๊ณ ๋ คํ๋ค.
1. ์ค์ผ์ค๋ฌ๋ฅผ ํตํด ์บ์๋ ํ
์ดํฐ๊ฐ ์ ๊ฑฐ๋ ๋์ฏค์ ๊ฐฑ์ ํ๋ ๋ฐฉ๋ฒ
2. ์บ์ ๋ง๋ฃ ์๊ฐ์ ์ฌ์ฉ์๋ง๋ค ๋๋ค์ผ๋ก ๋ฐฐ์ ํ๋ ๋ฐฉ๋ฒ
1. ์ค์ผ์ค๋ฌ๋ฅผ ํตํด ์บ์๋ ํ
์ดํฐ๊ฐ ์ ๊ฑฐ๋ ๋์ฏค์ ๊ฐฑ์ ํ๋ ๋ฐฉ๋ฒ
์ค์ผ์ค๋ฌ๋ฅผ ํตํด ๋งค๋ฒ ์บ์๋ ๋ฐ์ดํฐ๊ฐ ์ ๊ฑฐ๋ ๋์ฏค์ ๊ฐฑ์ ํ๋ ๋ฐฉ์์ ๊ณผํ ๋ฐฉ์์ด๋ผ๊ณ ํ๋จํ๋ค.
๊ทธ ์ด์ ๋ ๋ง์ฝ ์ฌ์ฉ์๊ฐ ์ฅ๊ธฐ๊ฐ ์ ์ํ์ง ์์ ์ฌ์ฉ์์์๋ ๋ถ๊ตฌํ๊ณ ๊ณ์ ์บ์ฑ์ ๊ฐฑ์ ํด ์ฃผ๋๊ฑด ๋ถํ์ํ๋ค๊ณ ์๊ฐํ๋ค.
2. ์บ์ ๋ง๋ฃ ์๊ฐ์ ์ฌ์ฉ์๋ง๋ค ๋๋ค์ผ๋ก ๋ฐฐ์ ํ๋ ๋ฐฉ๋ฒ
์บ์ ๋ง๋ฃ ์๊ฐ์ ์ฌ์ฉ์๋ง๋ค ๋๋ค์ผ๋ก ๋ฐฐ์ ํด ์ฃผ๋๊ฑด ์ข์ ๋ฐฉ์์ด๋ผ๊ณ ์๊ฐํ๋ค. ์ด๋ ๊ฒ ์ ์ฉํ๋ค๋ฉด ๋์์ ์บ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐฑ์ฑํ ์ผ์ ๋ฐฉ์งํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
โจ ์๋ก์ด ๋ฐฉ์ โจ
๋ฌด๋๋ฒ๋๋ ์ฌ์ฉ์๋ง์ ๊ณต๊ฐ์ด๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ์๊ฐ ์ผ๊ธฐ๋ฅผ ์์ฑ, ์์ , ์ญ์ ํ๋ ์ผ์ด ์๋ค๋ฉด ์ผ๊ธฐ ๋ชฉ๋ก๋ค์ ํญ์ ๋๊ฐ๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์ค์ผ์ค๋ฌ๋ก ์บ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐฑ์ ํ์ง ์๊ณ TTL์ ๊ธธ๊ฒ ์ ์งํ ํ ์ผ๊ธฐ ์ ์ฅ, ์์ , ์ญ์ ํ ๋๋ง๋ค ์บ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐฑ์ ํ๋ ๋ฐฉ๋ฒ์ด๋ค.
๋ ํน์๋ผ๋ ๋ฐ์ํ ์ ์๋ ์บ์ ์คํฌํผ๋ ํ์์ ๋ฐฉ์งํ๊ณ ์ TTL ์๊ฐ์ ๋๋ค์ผ๋ก ๋ฐฐ์ ํ๋ ๊ฒ์ด๋ค.
๋จผ์ ๋งค๋ฒ ์กฐ๊ฑด์ด ๋ฌ๋ผ์ง๋ ์ผ๊ธฐ ์กฐํ ๊ธฐ๋ฅ์๋ ์บ์ฑ ์ ์ฉ์ ์ ๊ฑฐํ๋ค.
@Override
public PageCustom<DiaryResQueryDTO> getDiariesByEmotion(final Long userId, DiaryEmotion diaryEmotion, Pageable pageable) {
return diaryQueryRepository.findDiariesByEmotionWithPageable(userId, diaryEmotion, pageable);
}
@Override
public PageCustom<DiaryResQueryDTO> getDiariesByFilter(final Long userId, DiaryReqFilterDTO requestDTO, Pageable pageable) {
return diaryQueryRepository.findDiariesByFilterWithPageable(userId, requestDTO, pageable);
}
๊ทธ๋ฆฌ๊ณ ๋ ์ง ์์ผ๋ก ์ค๋๋ ์, ์ต์ ์์ผ๋ก ์ผ๊ธฐ๊ฐ ์บ์ฑ ์กฐํ๋ ์ ์๊ฒ ์๋์ ๊ฐ์ด ๊ตฌํํ๋ค.
@Override
@Cacheable(
cacheNames = "diaries",
key = "'userId:' + #userId + '_sort:' + #isAscending + '_page:' + #pageable.pageNumber + '_size:' + #pageable.pageSize",
unless = "#result == null"
)
public PageCustom<DiaryResQueryDTO> getDiaries(final Long userId, boolean isAscending, Pageable pageable) {
return diaryQueryRepository.findDiariesWithPageable(userId, isAscending, pageable);
}
๊ทธ๋ฆฌ๊ณ ์ผ๊ธฐ ์ ์ฅ, ์์ , ์ญ์ ์ ์บ์ ๋ฐ์ดํฐ๋ฅผ ์ด๊ธฐํํ๊ณ ์ฒซ ํ์ด์ง๋ง ์บ์์ ๊ฐฑ์ ๋ ์ ์๋๋ก ๊ตฌํํ๋ค.
@Service
@RequiredArgsConstructor
public class RedisServiceImpl implements RedisService {
private final RedisTemplate<String, String> redisTemplate;
private final DiaryQueryService diaryQueryService;
private static final String DIARIES_CACHE_PREFIX = "diaries::userId:";
private static final String DIARY_COUNT_CACHE_PREFIX = "diary_count:userId:";
private static final int PAGE_SIZE = 20;
@Override
public void deleteDiaryCaches(Long userId) {
deleteCacheByUserIdAndCacheName(userId);
deleteCountCacheByUserId(userId);
cacheFirstPage(userId);
}
private void deleteCacheByUserIdAndCacheName(Long userId) {
var pattern = DIARIES_CACHE_PREFIX + userId + "*";
var keys = redisTemplate.keys(pattern);
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
}
}
private void deleteCountCacheByUserId(Long userId) {
String pattern = DIARY_COUNT_CACHE_PREFIX + userId;
var keys = redisTemplate.keys(pattern);
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
}
}
private void cacheFirstPage(Long userId) {
diaryQueryService.refreshDiariesCache(userId, false, PageRequest.of(0, PAGE_SIZE));
}
}
๋ ๋ชจ๋ ์ฌ์ฉ์๊ฐ TTL์ด ๊ฐ์ผ๋ฉด ์บ์ ์คํฌํผ๋ ํ์์ด ์ผ์ด๋ ์ ์๊ธฐ ๋๋ฌธ์, ์บ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ๋ TTL์ 24์๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋๋ค ์ ์ฅํ ์ ์๊ฒ ํ๋ค.
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory cf) {
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
int randomTTL = 24 + (int) (Math.random() * 5);
cacheConfigurations.put("diaries", defaultCacheConfiguration().entryTtl(Duration.ofHours(randomTTL)));
return RedisCacheManager.builder(cf)
.cacheDefaults(defaultCacheConfiguration())
.withInitialCacheConfigurations(cacheConfigurations)
.build();
}
์ ์์ ์ผ๋ก ์กฐํ ๋ฐ ์บ์์ ์ ์ฅ๋๋ ๊ฑธ ํ์ธํ ์ ์๋ค.
์บ์ฑ ์กฐํ๋ง ํ๋ฉด ์๋๊ฐ ๋นจ๋ผ์ง๊ณ ์๋๋ฅผ ๊ฐ์ ๋ง ํ๋ค๋ฉด ์ด๊ฒ์ด ์ ๋ต์ด๋ผ๊ณ ์๊ฐํ๋ค.
ํ์ง๋ง ๋จ์ํ ์บ์์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ์กฐํํ๋ ๊ฑด "์บ์ ์คํฌํผ๋ ํ์"๊ณผ ๊ฐ์ ๋ฌธ์ ๋ฅผ ์ด๋กํ ์ ์๊ธฐ ๋๋ฌธ์ ์บ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ๋ ์ ์คํด์ผ ํ๋ค๋ ๊ฑธ ๋ฐฐ์ธ ์ ์์๋ค.
๋ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์บ์์ ์ ์ฅํ๊ธฐ๋ณด๋จ ์ํฉ์ ๋ฐ๋ผ ์บ์ฑ ์ ์ฉ์ด ํ์ํ ์ง ์ ํ ์ง ํ๋จํ๋ ๊ฒ์ด ์ค์ํ๋ค๋ ๊ฑธ ๋ฐฐ์ ๋ค.
์ฑ๋ฅ๋ง ์ค์์ํ๋ ๊ฐ๋ฐ์๊ฐ ์๋, ์ํฉ์ ๋ฐ๋ผ ์ค๊ณ๋ฅผ ํ ์ ์๋ ๊ฐ๋ฐ์๋ก ์ฑ์ฅํด์ผ๊ฒ ๋ค๊ณ ๋ค์งํ๋ค.