기록하며 성장한다 - 개발, 회고

개발 회고록

[ 개발 회고록 ] Redis Session, Cache 저장소 분리하기

전대홍 2024. 1. 24. 15:30

 


서론

현재 배달과 관련 된 토이프로젝트를 진행하고 있다.

토이 프로젝트 링크 <<<

필자는 해당 프로젝트에서 Session 과, Cache로 인한 처리 속도 향상, Lock 사용 이렇게 3가지를 이유로 Redis 를 사용하고 있다.

그러나 이번 프로젝트에서 Redis 를 처음 사용하기에 여러 레퍼런스들을 참고하여 개발을 하고 있다. 그렇게 개발을 하는 도중, Session 과 Cache 를 분할 하여 개발하는 내용을 확인하였다.

그래서 레디스 서버를 분리하면 얻을 수 있는 장점이 무엇인지, 그리고 어떻게 분리할 수 있는지를 알아보고, 프로젝트에 추가하였다.

 

 


본론

1. Session 과 Cache 서버를 분리하는 이유

사실 꼭 Session 과 Cache 만을 분리한다고 생각할 필요는 없다.

사용자가 너무 많다면 필요에 의해, Cache 안에서도 장바구니, 배달 가능 매장 목록, 매장 검색 등을 기준으로도 분리할 수 있다.

우선 분리를 하는 이유로는 메모리 관리와, 처리 속도 향상, 그리고 목적에 따른 분리 이렇게 3가지 이유가 있다.

먼저 메모리 관리면에서 보면,

사용자가 증가함에따라, 캐시 저장소에 등록되는 데이터가 많아진다. 레디스는 인스턴스의 메모리 사용량이 물리적 메모리 한계를 초과하게 되면, 추가적인 데이터를 저장하기 위해 스왑(Swap) 공간을 사용하게 된다. 스왑(Swap)은 디스크 공간을 가상 메모리로 사용하는 메커니즘이기 때문에, 메모리에서 빠르게 읽어온다는 레디스의 장점이 사라지게 된다.

이러한 이유로, 하나의 레디스에서 많은 메모리 사용량이 생기지 않게하기 위해 분리를 하는 것이다.

다음은 처리 속도 향상 면에서이다.

Redis 는 싱글 스레드로 동작을 한다. 싱글 스레드로 동작하는 만큼 데이터의 atomic 함을 보장해주기 때문에, 동시성 문제가 발생하지 않는다는 장점이 있다.

그러나 하나의 CPU 로 동작하기 때문에 한 번에 하나의 커맨드만 실행할 수 있다.

그러므로, 레디스 서버를 분산시켜 한 번에 여러가지 일을 처리 할 수 있게 하는 것이 처리속도면에서 좋아질 수 있다. ( 서버 분리한 수만큼 CPU 를 더 많이 사용할 수 있다. )

마지막으로 목적에 따라 분리할 수 있다.

Session 은 사실상 인증 인가 문제를 해결한다는 목적을 가지고 사용하며, Cache 는 DB에서 직접 조회하지 않게 함으로써 성능을 향상 시키는 목적으로 사용한다.

이러한 목적에 맞게 분리한다면 좀 더 쉽게 레디스를 관리할 수 있다.

 

2. Session 과 Cache 서버를 분리하는 방법

일단 Redis 를 설치하고, 기본적으로 RedisConfig 까지 다 만들어서 사용을 하고 있는 상태에서, Redis 를 분리한다고 가정하겠다. ( 필자의 상황이 그러했다. )

먼저, properties 에는 port 번호를 2개로 나눈 Redis 의 정보를 입력해준다.아래 properties 는 필자의 DNS 서버 기준으로 개발을 한 것이다. 그러나 만약 local 환경에서 테스트를 진행하고 싶다면, host 부분을 localhost 로 수정해주면 된다.

또한 local 환경에서 redis 서버를 2대 띄우기 위해서는

기존의 6379 와,

C:\레디스 경로 \redis-server.exe --port 6380 를 통해 6380 포트로 신규 서버를 열어주면 된다.

cli 프로그램을 구동할때는
C:\레디스경로\redis-cli.exe -h 127.0.0.1 -p 6380 을 해주면 된다.

# session redis
spring.cache.type = redis
spring.data.redis.host = DNS 주소 1
spring.data.redis.port = 6379
spring.data.session.store-type = redis
spring.data.seesion.redis.flush-mode = on_save

# cache redis
spring.second-redis.cache.type = redis
spring.second-redis.data.redis.host = DNS 주소 2
spring.second-redis.data.redis.port = 6380
spring.second-redis.data.session.store-type = redis
spring.second-redis.data.session.redis.flush-mode = on_save

 

그리고 RedisConfig 를 아래와 같이 수정해준다.

Redis에서 @Cacheable 를 사용하고싶으면 따로 설정해주어야 하는게 있는데, 그건 다음에 Redis 를 활용한 캐싱에 대해서 따로 포스팅을 하겠다.

아래처럼 각각 다른 DNS 주소와, Port 번호를 담는 ConnectionFactory 를 만들어주고, 그에 맞는 @Bean 설정을 해주면 된다. 이 때 @Primary 를 통해 기본 Connection을 지정해주어야 하며,

캐시에 이용되는 템플릿이나, 캐시매니저에는 반드시 캐시관련 Connection 을 연결해주어야 한다.

@Configuration
@Slf4j
@EnableRedisHttpSession
public class RedisConfig {

    @Value("${spring.data.redis.host}")
    private String sessionHost;

    @Value("${spring.data.redis.port}")
    private int sessionPort;

    @Value("${spring.second-redis.data.redis.host}")
    private String cacheHost;

    @Value("${spring.second-redis.data.redis.port}")
    private int cachePort;

    @Bean({"redisConnectionFactory", "redisSessionConnectionFactory"})
    @Primary
    public RedisConnectionFactory redisSessionConnectionFactory() {
        RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration(sessionHost, sessionPort);
        return new LettuceConnectionFactory(redisConfiguration);
    }

    @Bean("redisCacheConnectionFactory")
    public RedisConnectionFactory redisCacheConnectionFactory() {
        RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration(cacheHost, cachePort);
        return new LettuceConnectionFactory(redisConfiguration);
    }


    @Bean
    public RedisTemplate<?, ?> redisTemplate() {
        RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(redisCacheConnectionFactory());
        return redisTemplate;
    }


    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }

 
    @Bean
    public RedisCacheManager redisCacheManager() {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
                .defaultCacheConfig()
                .disableCachingNullValues()
                .serializeKeysWith(
                        RedisSerializationContext.SerializationPair
                                .fromSerializer(new StringRedisSerializer())
                )
                .serializeValuesWith(
                        RedisSerializationContext.SerializationPair
                                .fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class))
                )
                .entryTtl(Duration.ofMinutes(10L)); // 캐시 유지시간 10분

        return RedisCacheManagerBuilder
                .fromConnectionFactory(redisCacheConnectionFactory())
                .cacheDefaults(redisCacheConfiguration)
                .build();
    }


}

 

이렇게 하고, Local 에서 테스트를 진행해보았다.

6379 Session 저장 정보
6380 캐시 저장 정보

 

세션 정보는 6379에, 캐시 정보는 6380 에 저장된 것을 확인 할 수 있다.

 

 


결론

대용량 트래픽와, 대용량 데이터를 마주하게 되면

메모리와 성능을 무시할 수 없다.

항상 많은 트래픽을 받게되면, 그만큼 데이터가 많이 쌓이게되고, 그로 인해 성능이 떨어질 수 있다.

그러한 문제를 해결하기 위한 방법을 항상 고민해야하며,

그 중 좋은 방법은 서버를 증설하는 방법인 것 같다.