Java

[Redis Cache] Cache miss 전략

ride-dev 2024. 2. 27. 17:49

OilNow 프로젝트에선,

데이터가 존재하지 않으면 db에 있는 데이터를 조회,

redis에 데이터를 저장하는 cache miss 전략을 사용했습니다.

Batch Job이 실행중일 때, DB로 직접 조회하게 될 경우 부하가 커질 수 있으며,

데이터 처리 속도가 낮아질 수도 있기 때문에,

데이터를 cache storage에 저장하여 조회 성능을 향상시켰습니다.

package com.pj.oil.cache;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisKeyValueAdapter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;

@Configuration
@EnableRedisRepositories(
        basePackages = "com.pj.oil.gasStation.repository.redis",
        redisTemplateRef = "gasStationRedisTemplate",
        enableKeyspaceEvents = RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP
)
public class GasStationRedisConfig {

    @Value("${spring.gas-station.redis.host}")
    private String redisHost;
    @Value("${spring.gas-station.redis.port}")
    private int redisPort;

    @Bean(name = "gasStationConnectionFactory")
    public LettuceConnectionFactory gasStationConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisHost, redisPort);
        return new LettuceConnectionFactory(config);
    }

    @Bean
    public RedisTemplate<String, Object> gasStationRedisTemplate(@Qualifier("gasStationConnectionFactory") LettuceConnectionFactory lettuceConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(lettuceConnectionFactory);
        return template;
    }
}

application.properties에 선언한 내용을 기반으로 의존성을 주입합니다.

Redis를 하나만 사용한다면, RedisRepository만으로도 충분하겠지만,

다중 데이터 소스를 구현하기 때문에 RedisTemplate을 구현합니다.

 

아래는 cache miss 전략을 활용한 메서드 중 하나입니다.

    public List<LowTop20PriceRedis> findLowTop20PriceByAreaCodeAndProductCode(String areaCode, String productCode) {
        LOGGER.info("[findLowTop20PriceByAreaCodeAndProductCode] request area: {} product: {}", areaCode, productCode);
        List<LowTop20PriceRedis> redisPrice = lowTop20PriceRedisRepository.findByAreaCodeAndProductCode(areaCode, productCode);
        List<LowTop20PriceRedis> result = processRedisData(redisPrice);
        if (result != null) return result;

        List<LowTop20Price> dbPrice = lowTop20PriceRepository.findLowTop20PriceByAreaCodeAndProductCode(areaCode, productCode);
        if (!dbPrice.isEmpty()) {
            LOGGER.info("[findLowTop20PriceByAreaCodeAndProductCode] db data does exist");
            result = dbPrice.stream()
                    .peek(station -> {
                        station.setPollDivCode(getPollDivNameByCode(station.getPollDivCode()));
                        transformStationCoordinates(station);
                    })
                    .map(LowTop20PriceRedis::transferDataToRedis)
                    .toList();
            LOGGER.info("data size: {}", result.size());

            LOGGER.info("data -> redis");
            lowTop20PriceRedisRepository.saveAll(result);

            return result;
        }
        LOGGER.info("[findLowTop20PriceByAreaCodeAndProductCode] db data does not exist");

        LOGGER.info("data does not exist");
        return null;
    }

먼저 redis에 있는 cache data를 조회하고, 데이터가 존재하면 반환합니다.

데이터가 존재하지 않으면 db 데이터를 조회하고, redis에 저장합니다.

그리고 데이터를 반환합니다.

 

시간을 측정해본 결과 평균적으로

db를 사용한 조회에는 약 100ms가 소요 되었고,

cache를 활용한 조회에는 약 20ms가 소요되었는 것을 확인할 수 있었습니다.

728x90