[ReSeller Project] Redis, 세션 저장소와 캐시 저장소를 분리해 보자 ❗

홍정완·2022년 10월 24일
0

ReSeller Project

목록 보기
14/15
post-thumbnail

ReSeller 프로젝트사용자의 로그인 정보를 담은 session자주 조회되는 캐시 데이터들을 하나의 Redis 서버로 관리하고 있다. 그러나 프로젝트 규모가 커지고 캐싱 되는 데이터가 많아질수록 응답 속도가 저하될 것으로 예상이 된다.


이번 포스팅에서는 아래와 같은 주제로 진행된다.

  1. 세션 저장소와 캐시 저장소를 분리하여 부하를 분산시켰을 때 얻을 수 있는 이점을 살펴보자.

  2. Docker를 사용하여 세션 저장소와 캐시 저장소를 분리해 보자.



세션 저장소와 캐시 저장소를 분리 시 - 이점



메모리 관리

메모리 관리는 Redis를 사용하는 데 있어서 가장 중요하다.
메모리 관리 여부에 따라 Redis 사용이 오히려 마이너스 요소로 작용할 수 있다.


현재 진행하고 있는 ReSeller 프로젝트는 저장소를 아래와 같은 목적으로 운영하고 있다.

세션 저장소

  • 로그인 세션 정보
  • 인증번호 (회원가입)

캐시 저장소

  • (상품, 브랜드) 조회

그러나 프로젝트 규모가 커지고 사용자가 증가함에 따라 캐시 저장소에 등록되는 데이터, 로그인 정보를 담은 세션, 인증번호 또한 증가하게 될 것으로 예상한다.

레디스는 In-memory Data Stroe이기 때문에 Physical Memory 이상을 사용하게 되면 swap이 발생한다. swap이 발생하게 되면 메모리 page 접근 시마다 읽고 쓰기 때문에 성능이 크게 저하된다. 보통 레디스가 갑자기 느려졌다 하면 해당 이슈일 확률이 높다. 자세한 내용은 해당 [우아한테크세미나] 191121 우아한레디스 by 강대명님영상을 참고하길 바란다. 세션 저장소와 캐시 저장소를 분리하여 사용하면 효율적인 메모리 관리가 가능하다.



처리 속도 향상


위 그림처럼 세션 저장소와 캐시 저장소를 분리하면 서버 한 대에 집중되어 있던 부하가 두 대의 서버로 나누어지기 때문에 성능이 좋아지게 된다.


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

그러나 CPU를 하나 밖에 쓰지 못한다는 단점이 있다. Redis가 메모리에서 운영되기 때문에 CPU에서 생기는 병목현상은 드문 편이지만 어떤 명령어에 대한 작업이 끝나기 전까지 다른 명령어들은 대기해야 한다.

또한 Redis는 싱글 스레드로 동작하기 때문에 o(n) 이상 명령어 사용 시 더 많은 대기시간이 발생하게 되는데 이러한 경우에도 서버를 나누어서 사용하고 있다면 대기시간을 줄이는 데 도움이 될 수 있다.


따라서 레디스 서버를 목적과 기능별로 분리해서 사용한다면 분리된 서버의 수만큼 CPU를 더 많이 사용할 수 있으므로 처리 속도를 향상시킬 수 있다.



Docker에 Redis 설치



현재 Redis 서버에 이미 6379 포트로 레디스 서버를 사용하고 있다. 따라서 추가적인 Redis 서버는 docker를 이용해 설치해 보자.


docker pull redis

위 명령어를 입력하여 redis로 등록된 image를 가져온다.


docker run --name 이름 -d -p 포트 번호:6379 redis

위 명령어를 입력하면 redis 컨테이너가 실행된다. 이름은 굳이 안 넣어줘도 된다.

이름 : ex) ho
포트 번호 : ex) 6388


docker ps

위 명령어를 입력해서 redis 컨테이너가 정상적으로 실행되었는지 확인할 수 있다.



Redis 서버 분리




먼저 위와 같이 application.yml 또는 application.properties에서 사용할 port 번호와 host를 설정하자. 아직 local 개발 중이므로 cache - host를 도커 머신 IP로 지정해 줬다.


public class CacheConfig {

	...
    // Cache
    @Value("${spring.redis.cache.host}")
    private String redisHost;

    @Value("${spring.redis.cache.port}")
    private int redisPort;

    @Bean(name = "redisCacheConnectionFactory")
    public RedisConnectionFactory redisCacheConnectionFactory() {
        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisHost,
            redisPort);
        return lettuceConnectionFactory;
    }
	...
    
    // Session
    @Value("${spring.redis.session.host}")
    private String redisHost;

    @Value("${spring.redis.session.port}")
    private int redisPort;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisHost,
            redisPort);
        return lettuceConnectionFactory;
    }
    ...

세션 저장소로 사용할 RedisConnectionFactory와 캐시 데이터 저장소로 사용할 RedisConnectionFactory를 분리해 주자.

주의할 점은 RedisConnectionFactory 타입의 Bean이 2개가 존재하기 때문에 반드시 @Qualifier@Bean 이름을 명시적으로 지정해서 빈 주입 시 충돌이 일어나지 않도록 하자.


@Autowired : 명시한 타입과 일치하는 빈을 먼저 검색, 동일한 타입의 빈이 여러 개가 존재하는 경우 문제가 발생
@Primary : 동일한 타입의 빈이 여러 개가 존재하는 경우, 해당 어노테이션이 붙은 빈을 우선적으로 주입
@Qualifier : 동일한 타입의 빈이 여러 개가 존재하는 경우, 지정된 조건과 일치하는 빈을 주입
@Resource : @Autowired와 달리 명시한 빈의 아이디와 일치하는 빈을 먼저 검색하여 주입


public class CacheConfig {    

    //.. 생략

    @Bean
    public CacheManager redisCacheManager(
        @Qualifier("redisCacheConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
        RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder
            .fromConnectionFactory(redisConnectionFactory)
            .cacheDefaults(redisCacheDefaultConfiguration())
            .withInitialCacheConfigurations(redisCacheConfigurationMap()).build();
        return redisCacheManager;
    }
}

@Qualifier("redisCacheConnectionFactory") 이와 같이 @Qualifier를 통해 주입할 빈을 명시적으로 처리해야 한다.


docker exec -i -t 이름 redis-cli

마지막으로 위 명령어를 입력해서 redis-cli를 실행해 캐시 데이터가 정상적으로 저장되었는지 확인해 보자.

profile
습관이 전부다.

0개의 댓글