현재 만들고 있는 프로젝트에서 내 정보 보기(GET) 기능은 정보가 단순하며 name을 제외한 나머지 항목들은 반복적으로 동일하게 제공되어진다. 또한, 항상 최신 데이터로 유지되지 않아도 핵심 비즈니스 로직에 영향을 주지 않는다고 판단되었고 해당 기능은 user 마이크로서비스에서 사용자의 정보를 가져와야하기 때문에, 조금이라도 blocking되는 상황을 막고자 적용을 고려하게 되었다.
캐시를 구현할 때 방법으로 로컬과 글로벌 방식이 있다.
사용자의 정보 보기 기능은 데이터의 일관성이 조금 어긋나더라도 서비스 운영에 큰 타격을 주지 않기 때문에 local cache로 구현하게 되었다.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
spring:
redis:
host: localhost
port: 6379
@EnableCaching
@Configuration
public class RedisConfig {
...
}
@Repository
public interface InfoRepository extends CrudRepository<Info, String> {
}
@Cacheable
적용하기 @Cacheable(key = "#id", cacheNames = "user")
@Override
public UserDto getUserInfo(String token, Long id) {
ResponseEntity<UserDto> userInfo = userServiceClient.getUserInfo(token);
return userInfo.getBody();
}
@CacheEvict
로 캐시 삭제 적용 -> 데이터의 변경이 일어나는 메서드에 추가 @CacheEvict(key = "#id", cacheNames = "user")
@Override
public UserInfoDto updateUserName(String token, UpdateNameDto updateNameDto, Long id) {
ResponseEntity<String> res = userServiceClient.changeName(token, updateNameDto);
return UserInfoDto.builder()
.success(true)
.message(res.getBody())
.build();
}
Redis에서는 Byte code로 데이터를 저장하므로 Serializable을 implements 해야한다.
public class Entity implements Serializable {}
redis cli로 확인해본 결과, 사용자 4번의 정보가 레디스에 저장되었다가 닉네임 수정후 정상적으로 삭제되었다.
사용자의 정보는 이름 변경시 삭제되도록 했기 때문에 데이터의 불일치 문제는 해결했지만 사용자가 탈퇴했을 때에는 계속 캐시 저장소에 남아있게 되는 문제점이 있다. 따라서 캐시 데이터에 TTL을 두어 시간이 지나면 삭제되도록 추가해보았다.
@Slf4j
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Bean
@Override
public CacheManager cacheManager() {
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory());
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.prefixKeysWith("user:")
.entryTtl(Duration.ofHours(1L));
builder.cacheDefaults(config);
return builder.build();
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPort(port);
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
}
@Cacheable(key = "#id", cacheNames = "user", cacheManager = "cacheManager")
@CacheEvict(key = "#id", cacheNames = "user", cacheManager = "cacheManager")
운영체제에서 LRU 캐시, 캐시의 특징 이론적으로만 배웠었는데 실제로 적용해보고 캐시의 장점을 느낄 수 있어서 좋았다. 여러 기능에 캐시를 남발하는 것보다 어떠한 상황에서 캐시를 적용하고 내부적으론 어떻게 구현되어야 베스트일지 더 연구해 봐야겠지만 말이다.