Redis 세션 저장소와 캐시 저장소의 분리하기

허진혁·2023년 6월 7일
0

🤔 고민 사항

현재 레디스는 세션과 캐시 구분없이 하나의 Redis 서버로 관리 하고 있어요. 그러나 앞으로 프로젝트 규모가 커진다고 하면, 캐싱이 많이 쌓일 것이고, 레디스 서버 한 대에 많은 요청이 몰릴거라고 생각했어요. 이에 따라 응답 속도가 저하될 것으로 예상할 수 있어요. 그래서 레디스 서버도 분산할 필요가 있다고 판단했어요.

🙇 기존의 Redis

현재 레디스는 위 그림같이 하나의 서버에서 모든 요청을 받고 처리하고 있어요.

레디스 공식문서에 따르면 레디스는 일반적인 상황에서는 싱글 스레드로 운영되고 있어요.
그래서 Redis 서버의 모든 자료구조는 atomic 해요. 이로 인해 데이터의 정합성이 보장되요.

👀 그러나 CPU를 하나 밖에 쓰지 못한다는 단점은 존재해요. Redis가 메모리에서 운영되기 때문에 CPU에서 생기는 병목현상이 일어날 가능성이 존재해요. 하지만 atomic한 자료구조를 읽는 것은 cpu-intensive하지 않기에 위와 같은 상황은 드물거에요.

It only performs a single read() system call every time there is something new to read from the client socket.

😃 분리가 가능한 이유

'지금까지 잘 동작하던 서버를 분리한다면 문제가 생기지는 않을까?' 라는 의문점을 자연스럽게 가져 보았어요. Redis에 저장된 데이터들은 RDBMS에 저장되는 데이터들처럼 서로 연관되어 있지 않아요. 이것은 NoSQL을 사용하는 큰 장점중에 하나에요.

RDBMS에 생성된 테이블들은 RDBMS의 이름 Relational처럼 서로 다양한 관계로 있어요. 그래서 테이블 별로 서버를 분리했다가는 데이터를 가져올 때마다 과도한 네트워크 트래픽으로 발생할 것이고, 성능이 더 저하될 거에요. 하지만 Redis에 저장된 세션 데이터와 캐시 데이터 간에는 연관성이 없기 때문에 데이터를 분산시켜도 문제가 없어요.

➗ Redis 서버 분리

두 개의 서버를 운영하니 두 개의 포트가 있을거에요. 레디스 서버를 만들 때 사용하던 포트를 각각 넣어주면 되요.

RedisCacheConfig

Redis 서버와 연결을 하려면 RedisConnectionFacory 빈이 필요한데 현재는 기존 Redis 서버와 연결하는 RedisConnectionFacory 빈 밖에 없었어요. 따라서, 분리한 캐시 서버로 연결하는 RedisConnectionFactory 빈을 별도로 생성했어요.

다만, RedisConnetionFactory가 Bean으로 2개 생성될 거에요. 메서드 명으로 스프링이 자동으로 매핑해줄 것이지만, 보기 편하게 명시적으로 Bean 이름도 '@Bean(name = "redisCacheConnectionFactory")'를 통해 지정해 두었어요

@Configuration
public class RedisCacheConfig {

    private final String host;
    private final int port;

    public RedisCacheConfig(
            @Value("${spring.data.redis.cache.host}") String host,
            @Value("${spring.data.redis.cache.port}") int port
    )
    {
        this.host = host;
        this.port = port;
    }
    
    @Bean(name = "redisCacheConnectionFactory")
    public RedisConnectionFactory redisCacheConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setHostName(host);
        return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }


    @Bean
    public CacheManager cacheManager(@Qualifier("redisCacheConnectionFactory") RedisConnectionFactory connectionFactory) {
        return RedisCacheManager.RedisCacheManagerBuilder
                .fromConnectionFactory(connectionFactory)
                .cacheDefaults(defaultCacheConfiguration())
                .build();
    }
	...
}

RedisAuthConfig

여기에도 @Bean(name = "redisTokenConnectionFactory")을 통해 빈 이름을 명시적으로 설정해 두었어요.

@Configuration
public class RedisAuthConfig {

    private final String host;

    private final int port;

    public RedisAuthConfig(@Value("${spring.data.redis.auth.host}") final String host,
                           @Value("${spring.data.redis.auth.port}")final int port) {
        this.host = host;
        this.port = port;
    }

    // lettuce
    @Bean(name = "redisTokenConnectionFactory")
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }

    ...

✅ 결과

위와 같은 과정을 거쳐 세션 저장소와 캐시 저장소를 분리할 수 있었어요. 그래서 부하를 분산하고 성능을 향상한 어플리케이션 운영환경을 구축해보았어요.

profile
Don't ever say it's over if I'm breathing

0개의 댓글