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