REDIS 캐싱을 활용하여 DB호출 수 줄이기

hanana·2024년 2월 4일
0
post-custom-banner

도입계기

토이프로젝트에서 JWT토큰인증과정을 도입하면서
인증이 필요한 요청마다 AUTHENTICATION 헤더를 불러와서
요청한 유저가 DB에 존재하는 유저인지 확인하는 과정이 필요했다.
매 요청마다 불필요한 IO가 발생하는 문제가 있어서 이를 캐싱을 통해서 해결해보았다.


개발환경

windows11
Intellij IDEA Ultimate
Spring Boot 3.2.1
Kotlin
JDK17

build.gradle
redis 의존성을 추가해준다.

dependencies {
	//생략
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    //생략
}    

RedisConfig

@Configuration
@EnableRedisRepositories
@RequiredArgsConstructor
class RedisConfig(
    private val redisProperties: RedisProperties
) {

	// 커넥션 정보 설정
    @Bean
    fun redisConnectionFactory(): RedisConnectionFactory {
        val redisURI = RedisURI.Builder.redis(redisProperties.host, redisProperties.port).build()
        val configuration = LettuceConnectionFactory.createRedisConfiguration(redisURI)
        val factory = LettuceConnectionFactory(configuration)
        factory.afterPropertiesSet()
        return factory
    }

	// RedisTemplate 설정
    @Bean
    fun userRedisTemplate(redisConnectionFactory: RedisConnectionFactory): RedisTemplate<String, User> {
        val redisTemplate: RedisTemplate<String, User> = RedisTemplate()
        redisTemplate.connectionFactory = redisConnectionFactory
        redisTemplate.keySerializer = StringRedisSerializer()
        redisTemplate.valueSerializer = Jackson2JsonRedisSerializer(User::class.java)

        return redisTemplate

    }

}

UserCacheRepository

@Repository
@RequiredArgsConstructor
class UserCacheRepository(
    private val redisTemplate: RedisTemplate<String, User>

) {
    private val log = LoggerFactory.getLogger(javaClass)

    fun setUser(user: User) {
        val key = "USER:${user.userName}"
        // USER_CACHE_TTL 은 최상위 함수로 Duration.ofDays(2) 을 의미한다.
        // (캐시를 이틀동안 저장하겠다는 의미)
        log.info("Set User to Redis {}, {}", key, user);
        redisTemplate.opsForValue().set(key, user, USER_CACHE_TTL)
    }
    fun getUser(userName: String): User? {
        val key = "USER:$userName"
        val user = redisTemplate.opsForValue().get(key)
        log.info("Get data from Redis {} , {}", key, user);
        return user
    }
}

준비가 되었다면 기존 JWT필터에서 유저정보를 가지고오는 부분을 수정한다.

기존 userService.loadUserByUserName(userName)의 구현을

override fun loadUserByUserName(userName: String): User {
    return userCacheRepository.getUser(userName // 캐시에서 데이터 조회후
        ?:getUserByUserNameOrException(userName) // Null이면 DB에서 조회 수행
}


userService.login()

override fun login(userName: String, password: String): String {
    // 회원가입 여부 체크
    val user: User =  userCacheRepository.getUser(userName)
        ?:getUserByUserNameOrException(userName)
    userCacheRepository.setUser(user); // 유저정보가 있다면 캐싱

    // 비밀번호 체크
    if(!passwordEncoder.matches(password, user.password)) {
        throw SnsApplicationException(ErrorCode.INVALID_PASSWORD)
    }
    // 토큰 생성
    return JwtUtils.generateToken(userName, secretKey, expiredMs)
}

이후 실제 API를 통해서 정상적으로 동작하는지 확인해본다.
로그인

로그인이 성공적으로 진행되었고,
redis에도 정상적으로 저장이 된 모습을 확인할 수 있다.

이후 인증이 필요한 API호출을 해본다.

미리 남긴 로그를 통해서
유저정보를 Redis에서 성공적으로 가지고 왔음을 확인할 수 있었고,
또한 캐싱에 성공한 데이터는 더이상 유저정보를 조회하는 DB IO가 사라졌다.

profile
성숙해지려고 노력하지 않으면 성숙하기까지 매우 많은 시간이 걸린다.
post-custom-banner

0개의 댓글