똑같은 데이터를 매번 데이터베이스에서부터 가져오면 사용자와 서버입장에서 모두 비효율적이다.
그렇기에 한번 가져온 데이터의 경우엔 캐싱처리를 해서 두 번째 요청부턴 효율적으로 가져오고자 한다.
현재 프로젝트의 경우에 RefreshToken과 로그아웃 관리를 위해 Redis를 사용하고 있다. 그렇기에 캐싱 저장소로 Redis를 사용해 보기로 했다.
❗️❗️ 주의 ❗️❗️
과정상의 코드는 별로 없다.
바로 캐싱을 적용하기 전에 간단하게 공부를 해보니 캐싱에도 여러가지 전략이 있다고 한다.
캐싱 전략을 세울 때 유의해야 할 것은 무엇일까?
- 캐싱 데이터는 주로 메모리에 저장되기 때문에 용량 제한이 있다. (주로 16 ~ 32 기가)
- 따라서 캐싱 데이터의 유효기간 설정이 중요하다.
- 따라서 어떤 데이터를 캐싱해 둘지 선택하는 것이 중요하다.
- 캐싱 데이터와 DB의 데이터의 정합성이 중요하다.
- 따라서 C U D 작업에서 캐싱 데이터를 수정할지 R 할 때 어떻게 할 것이지에 대한 전략을 세워야 한다.
- 나의 프로젝트는 정상적으로 종료된 채팅만 조회가 가능하게 되어있다. 따라서 채팅 시작과 채팅 종료에서 캐시데이터 업데이트가 필요할 것으로 보인다.
- 따라서 Look Aside와 Write Around 조합을 사용해서 캐싱을 적용하도록 하겠다.
Look Aside 란
데이터를 찾을 때 우선 캐시에 저장된 데이터가 있는지 우선적으로 확인하는 전략.
나의 프로젝트의 경우 만일 캐시에 데이터가 없다면 Mongo DB에서 조회해 온다.
Write Around 란
모든 데이터를 DB에만 저장하는 기법 (캐시를 갱신하지 않음)
Cache miss가 발생할 때만 DB와 캐시에 데이터를 저장한다.
Cache와 DB의 데이터가 일치하지 않을 수 있음.
따라서 DB에 저장된 데이터가 수정, 삭제될 때마다, Cache 또한 수정하거나 삭제해야 하며, Cache의 expire를 짧게 유지해야 한다. 우리 코드에서는 채팅 종료 및 채팅 삭제에서 CacheEvict를 적용한 게 캐시데이터를 변경하는 거다.
캐싱 전략에 대해 더 자세히 보고 싶다면 이 글이 아주 자세히 정리되어 있다.. 👍
[ 첫 번째 조회 ]
[ 새로운 채팅 추가 ]
[ 두 번째 조회 ]
여전히 데이터가 두 개이다.
캐싱처리는 됐지만 추가된 데이터가 반영되지 않아 데이터 정합성이 깨지는 걸 볼 수 있다.
@CachePut(value = "SelfIntros", key = "'selfAll'", unless = "#result == null", cacheManager = "cacheManager")
캐시 데이터 갱신을 위해 위의 코드를 채팅 종료 메서드에 적용해 주었지만 여전히 적용이 되지 않음
@CachePut 은 메서드의 반환값으로 캐시갱신이 되기 때문에 현재 반환타입이 void인 채팅종료에서 적용이 되지 않는 것은 당연하다.
어떻게 해결할까?
@CacheEvict(value = "SelfIntros", key = "'selfAll'", cacheManager = "cacheManager")
@CacheEvict를 적용해 보자.
이는 메서드가 실행될 때 캐시 데이터를 모두 삭제하게 된다. 따라서 채팅 종료 후 첫 조회 요청이 들어오면 캐시 데이터가 없기 때문에 데이터베이스에 갔다 오게 된다.
[ 첫 번째 조회 ]
"selfIntros": [
{
"chatRoomId": "665c121e3c4d4b0bc61925a1",
},
{
"chatRoomId": "665c12413c4d4b0bc61925a2",
},
{
"chatRoomId": "665d13927098ba30b99c3e25",
},
{
"chatRoomId": "665d14aa76777e49157346f1",
}
]
현재 테스트해 보느라 2개가 더 추가된 4개가 현재 조회가 된다.
[ 새로운 채팅 생성 ]
[ 두 번째 조회 ]
"selfIntros": [
{
"chatRoomId": "665c121e3c4d4b0bc61925a1",
},
{
"chatRoomId": "665c12413c4d4b0bc61925a2",
},
{
"chatRoomId": "665d13927098ba30b99c3e25",
},
{
"chatRoomId": "665d14aa76777e49157346f1",
},
{
"chatRoomId": "665d17dec584f95771a36554",
}
]
소요시간과 응답 데이터를 보니 캐싱이 잘 적용되었고 데이터 정합성 또한 잘 지켜지는 것을 볼 수 있다.
최종 코드
// 조회
@Cacheable(value = "CSChats", key = "'csAllTopic:' + #memberId", unless = "#result == null", cacheManager = "cacheManager")
public CSChatHistoryList findAllCSChat(String memberId) {
... 코드 생략
}
// 채팅 종료 -> 새로운 채팅이 추가됨
@Caching(evict = {
@CacheEvict(value = "CSChats", key = "'csAll:' + #memberId + ':' + #csChatInfo.topic()", cacheManager = "cacheManager"),
@CacheEvict(value = "CSChats", key = "'csAllTopic:' + #memberId", cacheManager = "cacheManager"),
@CacheEvict(value = "CSChat", key = "#csChatInfo.chatRoomId()", cacheManager = "cacheManager")
})
public CSChatHistory terminateCSChat(String memberId, CSChatInfo csChatInfo) {
... 코드 생략
}
// 채팅 삭제
@Caching(evict = {
@CacheEvict(value = "CSChats", key = "'csAll:' + #memberId + ':' + #csChatInfo.topic()", cacheManager = "cacheManager"),
@CacheEvict(value = "CSChats", key = "'csAllTopic:' + #memberId", cacheManager = "cacheManager"),
@CacheEvict(value = "CSChat", key = "#csChatInfo.chatRoomId()", cacheManager = "cacheManager")
})
public void deleteCSChat(String memberId, CSChatInfo csChatInfo) {
... 코드 생략
}
'스프링부트' 카테고리의 다른 글
RestTemplate를 RestClient로 ?! (0) | 2024.07.01 |
---|---|
@Qualifier와 @Autowired (1) | 2024.01.09 |
Springboot JPA BigText 설정하기 (0) | 2024.01.09 |
Springboot 3.x Cors 문제 해결하기~! (0) | 2024.01.09 |