기존 로컬 개발환경에서 docker로 redis를 사용했을때는 큰 문제가 되지 않았다.
하지만...
Aws에 서비스를 올리고나선 문제가 발생되었다.
AWS ElastiCache를 사용하면서 겪은 문제를 정리해본다.
Spring-session 관련한 redis 사용법을 검색하면, 구글에 많은 경험들이 검색결과로 보여진다.
딱히 사용법을 따라해보고, 할정도의 팁만 있다는게 문제지만, 레퍼런스에서 Example를 따라하는 수준이다.
stackoverflow에서 여러 고민이 보여진 질문이 보이는것을 추적하면서 따라가게 되었는대, 도움이되었던 사이트는 HyperConnect 블로그와 일부 git-issue들이다.
1. 발견된 문제점
- Key TTL이 만료되었어도 일부 데이터(spring:session:index:FindByIndexNameSessionRepository
.PRINCIPAL_NAME_INDEX_NAME:$username)의 쓰레기 값이 계속 쌓인다.
- 결국 Redis 공간이 full이되어 OOM Exception이 발생
- 서비스 장애로 발생되어 결국 서비스가 정지되며, 레디스 key를 비워주지 않으면 어플리케이션은 정상동작 안됨(반복)
2. 문제의 시작점
@EnableRedisHttpSession
public class RedisConfig{
....
}
@EnableRedisHttpSession 이것은 AutoConfiguration으로 RedisIndexedSessionRepository 을 생성하여 사용한다.
RedisIndexedSessionRepository은 spring:session:index:FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX
_NAME:$username라는 set을 만드는대, 해당 Set에서 member를 삭제하려면 SessionDestroyedEvent를 수신받아야한다.
@Bean
ConfigureRedisAction configureRedisAction() {
return ConfigureRedisAction.NO_OP;
}
ConfigureRedisAction.NO_OP 사용은 Redis config의 notify-keyspace-events에 SessionDestroyedEvent를 설정하지 않는다.
AWS ElastiCache는 ConfigureRedisAction.NO_OP를 활성화해야 Spring이 Redis에 접속할 수 있게 한다.
따라서 어플리케이션이 Redis Config를 제어하지 못하게 되어있다.
ElastiCache는 SessionDestroyedEvent를 활성화시키기 위해 수동으로 작업을 해야한다.
aws.amazon.com/ko/premiumsupport/knowledge-center/elasticache-redis-keyspace-notifications/
문제 발생후 알게 된것이지만, ConfigureRedisAction.NO_OP를 좀더 알아봤어야 했다. : (
아래는 해당 SessionDestroyedEvent를 설정해야하는 것을 설명하고 있다.
docs.spring.io/spring-session/docs/current/reference/html5/#api-redisindexedsessionrepository-sessiondestroyedevent
3. 문제의 해결
우리 서비스에는 굳이 필요 없는 Scheduler와 index:FindByIndexNameSessionRepository set이 필요 없었다.
우리 서비스는 로그인 후 인증된 session을 클러스터링을 위해 Spring-session을 도입한것이기에 다른 방법으로 적용했다.
@Configuration
@EnableSpringHttpSession
public class RedisConfig {
...
@Bean
public RedisOperations<String, Object> sessionRedisOperations(){
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(oauthRedisConnectionFactory());
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(maxInactiveInterval));
return redisSessionRepository;
}
@Bean
public static ConfigureRedisAction configureRedisAction() {
return ConfigureRedisAction.NO_OP;
}
}
위의 코드로 RedisIndexedSessionRepository를 사용않하는 방법으로 변경하였다.
반영 결과 EnableRedisHttpSession을 사용했던 생성된 Key4개 중 3개 Key가 삭제되었다.
삭제된 Key
Key(spring:session:expirations, spring:session:expires:$sessionId)
Key(spring:session:index:FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX
_NAME:$username)
유지된 Key
Key(spring:session:sessions:$sessionId)
위의코드 적용 결과를 보면, expireEvent를 RedisIndexedSessionRepository.OnMessage()를 받지 못하는 환경에서는
RedisIndexedSessionRepository를 SessionRepository로 사용하지 않는게 낫다.
그리고 굳이 해당 RedisIndexedSessionRepository를 Customize하는것도 불필요 해보인다.
위의 해결방법은 근본적인 해결방법이고, 귀찮다고 생각된다면 ElastiCache의 notify-keyspace-events 설정을 통해 쓰레기 값이 발생되지 않을 것이다.
Reference
- hyperconnect.github.io/2018/10/21/spring-session-migration.html
- stackoverflow.com/questions/55818188/custom-spring-session-repository-redis-with-dynomite
- github.com/spring-projects/spring-session/issues/1406
'Dev & Tip > Spring' 카테고리의 다른 글
Spring-boot-webflux netty로 실행 안되는 증상 (0) | 2020.11.24 |
---|---|
SpringBoot AOP Java 코드로 설정 (0) | 2020.11.11 |