본문 바로가기

Database/Redis

Redis Session 병목 해결 및 최적화하기

https://liasn.tistory.com/7

 

nGrinder로 성능 테스트 과정에서 발견한 Redis Session 병목

애플리케이션 서버를 개발하고 '나의 서버는 어느 정도의 트래픽을 처리할 수 있을까?'라는 고민을 할 수 있습니다. 하지만 여러 개의 브라우저를 띄워 동시에 요청을 보내는 방법은 한계가 있

liasn.tistory.com

이전 글에서 'TPS 수치가 0이 되는 시점에 CPU 사용량의 대부분을 차지하는 스레드가 모두 위의 스택 정보를 가진다면 병목 현상은 WAS에서 Redis Session을 만료 처리하는 과정에서 발생한다.'라는 가설을 세웠는데 이번 글에서는 가설 입증을 위해 병목이 왜 발생했고, 해결 방법은 무엇이고, 해결하면 병목이 사라지는지를 확인해 보도록 하겠습니다.


발생 원인

Campool 서버는 Session 관리를 위한 Session Storage의 구현체Redis를 사용합니다. 그래서 Spring Session Redis를 사용하기 위해 Spring 공식 문서를 참고하여 적용했고 문서에 의하면 Spring Session Redis를 사용하기 위해서는 아래 사항을 만족해야 한다고 명시되어 있었습니다.

 

  • SessionRepositoryFilter 구현체를 빈으로 등록하고 서블릿 컨텍스트에 등록 
  • RedisConnectionFactory 구현체를 빈으로 등록

저는 Spring Boot의 Auto-configurationspring-boot-starter-data-redis를 이용하면 위 사항들을 자동으로 등록해줄 수 있다고 생각하여 이 방법으로 Spring Session Redis를 구현했습니다. 근데 여기서 문제가 발생합니다.

 

Auto-configuration을 사용하면 RedisHttpSessionConfiguration설정 클래스로 사용되고 Session Repository로 스택 정보에서 볼 수 있었던 RedisIndexedSessionRepository빈으로 정의되어 있었습니다.

 

...
@Bean
public RedisIndexedSessionRepository sessionRepository() {
    RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
    RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisTemplate);
    ...
    return sessionRepository;
}
...

 

원본

 

병목을 발생시키는 원인이라 짐작한 클래스가 정의되어 있으니 분명 문제와 연관이 깊을 것이라 생각해 RedisIndexedSessionRepository의 코드를 살펴봤고 세션 만료 정책과 관련 있는 RedisSessionExpirationPolicy필드로 가지고 있는 것을 확인했습니다.

 

즉, 핵심적인 세션 저장, 읽기 이외의 세션 만료 정책과 관련된 내용을 갱신하기 위한 많은 수의 Redis Operation을 수행하는 코드가 있는데, 이것은 저장뿐만 아니라 조회에도 성능 저하를 일으키며 실제로 CPU 사용량이 높은 스레드의 스택에서도 세션 만료 정책 클래스의 onExpirationUpdated 메소드를 호출하는 것을 고려하면 충분히 병목의 원인이 될 수 있다고 판단했습니다.

 

 

추가적으로 하이퍼커넥트의 기술 블로그에서도 관련 글이 있어 참고하였습니다.

 

 

해결 방법

저는 문제가 되는 Repository가 정의된 RedisHttpSessionConfiguration를 사용하지 않고 Repository를 간단하게 직접 구현해 빈으로 등록하는 방법을 선택했습니다. 또 Campool 서버는 Session Event가 필요하지 않기 때문에 spring.session.redis.configure-action 옵션을 none으로 설정하고 Redis Keyspace Notification비활성화하여 최적화를 진행했습니다.

 

Redis Keyspace Notification 참고

스프링 2.4.x 기준으로 정적 이너 클래스인 EnableRedisKeyspaceNotificationsInitializer로 활성화를 시키는데  RedisHttpSessionConfiguration 자체를 사용하지 않으면 활성화되지 않습니다.

 

@EnableSpringHttpSession
@Configuration
public class RedisSessionConfig {

    ...

    @Bean
    public RedisOperations<String, Object> sessionRedisOperations(
            RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        return template;
    }

    @Bean
    public RedisSessionRepository sessionRepository(
            RedisOperations<String, Object> sessionRedisOperations) {
        RedisSessionRepository redisSessionRepository = new RedisSessionRepository(
                sessionRedisOperations);
        redisSessionRepository.setDefaultMaxInactiveInterval(Duration.ofSeconds(300));
        return redisSessionRepository;
    }
    
    ...

}

 

application.properties

spring.session.redis.configure-action=none

 

 

문제의 현상이 사라지는지 확인

수정한 소스 코드를 빌드 후 jar를 교체하였고 이전과 동일한 환경 및 스크립트로 성능 테스트를 진행했습니다.

 

이전 테스트 결과

이전 테스트 결과

 

개선 후 테스트 결과

개선 후 테스트 결과

문제가 되었던 TPS 그래프의 출렁임 현상이 해결하고 피크 TPS는 약 9200, 평균 TPS는 약 8500의 안정적인 그래프를 볼 수 있었습니다. 

 

 

결론

Spring Boot의 Auto-configuration은 사용자에게 편리함이라는 강력한 이점을 제공해주지만 그 이면에는 예상치 못한 문제를 발생시킬 수 있다는 것을 항상 고려해야 한다는 것을 깨달았습니다. nGrinder를 이용해 실제 성능 테스트를 진행하면서 병목을 발견할 수 있었고, 서버 상태 모니터링스레드 덤프 분석을 통해 병목의 원인이 무엇인지, 어떻게 해결할 것인지를 알 수 있었습니다.