Cache를 이용하면서 조회수 업데이트하기

2024. 7. 5. 22:17TIL

✔오늘 배운 중요한 🔑 point

  • 캐시 데이터를 참조할경우 db를 조회하지 않기 때문에 조회수 기능을 업데이트 하기 위한 방법 중 하나는 스케줄러를 이용하는 방법이 있다.

🎯 오늘 배운 내용

 

현재의 프로젝트에서 캐시서버를 적용하는 이유는 자주 참조하는 게시글에 대해서 DB를 직접 조회하는 것보다는 캐시 서버에 있는 데이터를 조회하도록 하는것이 성능 향상에 도움이 되기 때문이다.

하지만 사용자가 DB에 있는 게시글을 조회하는 것이 아니고 캐시 데이터를 조회하기 때문에 사용자가 몇번이고 해당 게시글을 조회한다고 해도 조회수가 올라가지는 않는 문제점이 발생한다.

 

@Cacheable 어노테이션을 이용해서 캐시 데이터를 참조하기 때문에 post.views +=1 의 코드는 맨 처음 한번만 수행되고 그 다음부터는 해당 캐시 데이터가 삭제될때까지 실행이 되지 않는다.

 

물론 따로 조회수를 늘리는 함수를 작성하는것도 하나의 방법이 될수 있지만, 최종적으로 SQL쿼리문을 발생시킨다면 캐시서버를 활용하는 의미가 없을 것이다.

 

그렇다면 DB에 직접 조회수를 업데이트하지 않고 캐시 데이터를 참조할때 DB에 저장되있는 게시글의 조회수가 업데이트 되도록 하는 방법을 고민해봐야할 것이다.

 

많은 고민 끝에 내가 선택한 방법은 스케줄러를 이용하여 1분간격으로 조회수를 업데이트 하는 방법이다.

 

package com.teamsparta.withdog.infra.redis

import org.slf4j.LoggerFactory

import org.springframework.stereotype.Service
import java.util.concurrent.ConcurrentHashMap

@Service
class ViewCount {
    private val logger = LoggerFactory.getLogger(javaClass)
    private val viewCounts = ConcurrentHashMap<Long, Long>()

    fun incrementViewCount(postId: Long) {
        viewCounts.merge(postId, 1) { oldValue, _ ->
            val newValue = oldValue + 1
            logger.info("해당 postId: $postId. 조회수 카운트: $newValue")
            newValue
        }
    }

    fun getViewCountsAndClear(): Map<Long, Long> {
        val copy = HashMap(viewCounts)
        viewCounts.clear()
        logger.info("조회수 초기화. 현재 카운트: $copy")
        return copy
    }
}

조회수를 1증가 시키는 로직과

 

import com.teamsparta.withdog.domain.post.repository.PostRepository
import org.springframework.data.repository.findByIdOrNull
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
class ViewCountUpdater(
    private val postRepository: PostRepository,
    private val viewCount: ViewCount
) {

    @Scheduled(fixedRate = 60000) // 1분 간격
    @Transactional
    fun updateViewCounts() {
        val viewCounts = viewCount.getViewCountsAndClear()
        for ((postId, count) in viewCounts) {
            val post = postRepository.findByIdOrNull(postId)
            if (post != null &&!post.isDeleted) {
                post.views += count
                postRepository.save(post)
            }
        }
    }
}

1분동안의 조회수를postRepository.save(post) 를 DB에 저장하는 스케줄러를 이용하면  캐시 데이터를 참조할때도 1분 간격으로 조회수가 업데이트가 된다.

 

 

 

 

1분동안의 조회수를 기억해두고 있다가 1분뒤에 총 조회수를 DB에 기존 조회수에 더해서 저장한다

 

 

 

🤔 어떻게 활용할까?

캐시 메모리를 참조하더라도 스케줄러를 이용하여  일정한 간격으로 db에 업데이트하여 데이터의 정합성을 해결할 수 있다.

📓 오늘의 한줄

"The power of intuitive understanding will protect you from harm until the end of your days."

- Lao Tzu -