HwangHub

[이슈] 레디스 캐시 업데이트 이슈 트러블슈팅 본문

PROJECT-LOG/likelion.university

[이슈] 레디스 캐시 업데이트 이슈 트러블슈팅

HwangJerry 2024. 1. 5. 17:14

문제 인지

현재 프로젝트에 다음과 같은 문제가 있다.

  • 우리 프로젝트에서는 레디스를 사용하여 commentCount, likeCount를 관리하고 있다.
  • 이를 기준으로 한 sort(랭킹)을 위해서 사용한다.
  • 하지만 정상적으로 redis가 업데이트 되고 있지 않는 것으로 확인되었다.

상황 파악

우리 프로젝트에서 레디스가 어떻게 사용되고 있는지 먼저 파악하였다.

  • 기본적으로 직렬화할 때 TIMESTAMPS 형식을 disable 함
  • 직렬화할 때 자바의 LocalDateTime으로 적용시킴
  • 타입 검사할 때 non-final이 기본값으로 되도록 설정
  • Lettuce(레디스 자바 클라이언트) 사용
  • 적용 자료구조 : String
    • key-value 형태로 저장중 (우리는 <String, Object>로 저장)
      • string의 prefix 가 "postCount : {postId}"로 구성됨
      • value는 PostCountInfo {Long commentCount, Long likeCount} 로 구성됨
      • ttl은 86400L로 설정 (second 단위, 1 day ; 즉, 업데이트 주기는 하루)
        public static final Long POST_COUNT_INFO_EXPIRE_SEC = 86400L;
    • get, set 명령어 사용 with opsForX()
    • hash table로 이용

문제 원인 확인

like를 생성하거나 지울 때, 그리고 comment가 생성되거나 지워질 때 redis에 processor를 통해 업데이트를 해줘야 하는데, 이게 로직에 반영되어 있지 않아서 발생하는 문제였다. 즉, 현재는 레디스 데이터 업데이트를 expiredate에 의존하고 있는 것이다. (말이 안되는 상황)

단순히 정리하면, 레디스를 처음 써보다 보니 발생한 초보적인 실책이다. 다행히 현재 레디스 사용 로직이 단순했고, 차근차근 로직을 살펴보며 이해해보니 문제 파악이 어렵지 않았다. 바로 한번 고쳐보자.

문제 해결

DB에 count 쿼리 날려서 업데이트 하는건 비용이 크니까,
사실 DB 수정 api가 동작할 때 마다 해당 데이터값을 +1, -1만 해주면 되는 것이므로 redis에서 해당 데이터를 가져온 뒤에 +1 , -1 을 해주면 될 듯 하다.

AS-IS

수정해야 하는 곳은 전부 동일한 로직으로 전개되므로, 대표적으로 하나의 로직만 확인해보자.

public class CreateOrDeletePostLikeUseCase {
    // ...생략...
    public boolean execute(PostLikeRequest request) {  
        return postLikeDomainService.createOrDeletePostLike(getServiceDto(request));  
    }
}

기존에는 usecase에서 위와 같이 DB 수정 로직만 다루고 있었다.
redis와 security 등의 util에 대하여는 usecase에 관심사를 할당하고 있으므로, 여기서 해당 로직을 처리해줘야 하지만 관련 로직이 없어서 업데이트가 되지 않았던 것이다.

TO-BE

이 메서드는 좋아요를 등록/취소 로직을 한번에 갖고 있는 api다. (여담이지만, 이렇게 구현한 이유는 프론트에서도 boolean 변수를 활용하여 관리하겠지만, 잘못 호출하여 like 엔티티에 의도치 않은 연산이 발생하는 걸 막기 위해서 서버 로직 상에서도 멱등하게 동작하도록 하나로 합쳤다.)

여기서 redis 캐시를 업데이트 하기 위해서는 좋아요가 수행된 경우와 취소된 경우를 구분하여 캐시를 업데이트 했어야 했다. 따라서 아래와 같이 수정되었다.

public class CreateOrDeletePostLikeUseCase {
    // ...생략...
    public boolean execute(PostLikeRequest request) {  
        boolean hasCreated = postLikeDomainService.createOrDeletePostLike(getServiceDto(request));  
        Long postId = request.postId();  
        PostCountInfo countInfo = getOrCreatePostCountInfoProcessor.execute(postId);  
        Long commentCount = countInfo.getCommentCount();  
        Long likeCount = countInfo.getLikeCount();  
        if (hasCreated) {  
            updatePostCountInfoProcessor.execute(postId, commentCount, ++likeCount);  
        } else {  
            updatePostCountInfoProcessor.execute(postId, commentCount, --likeCount);  
        }  
    return hasCreated;  
    }
}
Comments