[트러블 슈팅] redis 관련 문제 발생(2025.01.03)
IOS 개발자 분들에게 문제 상황을 들었다.
현재 발생한 문제는 좋아요 & 좋아요 취소와 회원 상세 조회 api 의 응답값이 401 에러가 온다는 것이었다.
1. 좋아요 & 좋아요 취소 문제
현재 좋아요 로직은 현재 사용자 아이디 값(= userId)과 좋아요할 회원 아이디(= likedId) 두 가지 값이 필수 값이다.
userId를 key값으로 하고 value 값으로 likedId 로 Redis에 저장하고 있다.
만약 이미 좋아요 되어있는데 좋아요 api를 호출하거나
if (Boolean.TRUE.equals(likeCountRedisTemplate.opsForSet().isMember(userLikesKey,likedId))) {
throw new IllegalStateException("Already liked");
}
좋아요 되어있지 않은데 좋아요 취소 api를 호출할 경우
if (Boolean.FALSE.equals(likeCountRedisTemplate.opsForSet().isMember(userLikesKey, likedId))) {
throw new IllegalStateException("Not liked yet");
}
에러 메시지를 남기는데 현재 likedId 가 2인 회원을 좋아요 하면 "Already liked" 라 나오고 그렇다고 좋아요 취소를 누르면 "Not liked yet" 에러 로그를 호출한다.
또한 Redis에 저장된 데이터를 보면
> get likeCount:2
"\"0\""
> smembers userLikes:tester1
"2"
이렇게 뜬다.
아직 정확한 문제 원인을 찾지는 못하였지만 추측 상 likeCount 부분에 저장된 값을 보면 역슬래쉬가 들어가 있는 것을 볼 수 있다. 기존에 userLikes 처럼 문자 하나만 들어가는게 맞지만 어떠한 이유인지 몰라도 이스케이프 문자가 같이 들어가서 발생한 문제 같다.
로컬에서 테스트 하였을 때 Redis에 저장된 데이터를 지운 후 다시 진행하니 제대로 동작하는걸 볼 수 있다.
> get likeCount:2
"1"
> smembers userLikes:tester1
1) "\"2\""
일단 likeCount에 이스케이프 문자가 포함될 경우 오류가 발생한다고 생각하고 문제를 찾아봐야겠다.
2025.01.06
Redis에 큰 따옴표가 한 번 더 저장되기에 이스케이프 문자가 포함되는 것을 알았고 관련된 문제로 찾아보니
RedisConfig 쪽 문제인 것을 확인하였다.
@Bean
public StringRedisTemplate stringRedisTemplate() {
final StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
stringRedisTemplate.setConnectionFactory(redisConnectionFactory());
GenericToStringSerializer<Object> genericToStringSerializer = new GenericToStringSerializer<>(Object.class);
stringRedisTemplate.setValueSerializer(genericToStringSerializer);
return stringRedisTemplate;
}
위의 코드가 기존에 사용하던 코드인데 직렬화 방식으로 GenericToStringSerializer<Object>를 사용하였다.
GenericToStringSerializer<Object>는 객체를 문자열로 변환하는 데 사용된다. 하지만 GenericToStringSerializer<Object>를 사용하면 여러 타입의 객체를 처리할 수 있지만, 숫자를 다룰 때에는 여러 문제가 발생할 수 있다.
예를 들어, 어떤 객체를 JSON 문자열로 변환할 때, 문자열 안의 큰 따옴표는 이스케이프 처리되어 "\"1\""와 같이 저장될 수 있고, 이처럼 이스케이프 처리되어 저장된 문자를 increment 함수로 실행하려 하니 숫자로 변환이 안되어 ERR value is not an integer or out of range 가 발생된 것이다.
이러한 이유로 StringRedisTemplate에서는 숫자 데이터를 저장할 때 StringRedisSerializer를 사용하는 것이 더 안전하고 직관적이며 StringRedisSerializer는 문자열 데이터만 처리하므로, 숫자를 문자열로 변환하여 저장하고, 다시 읽을 때도 문자열로 처리되기 때문에 일관성을 유지할 수 있기에 StringRedisSerializer 로 수정하였다.
@Bean
public StringRedisTemplate stringRedisTemplate() {
final StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
stringRedisTemplate.setConnectionFactory(redisConnectionFactory());
stringRedisTemplate.setValueSerializer(new StringRedisSerializer());
return stringRedisTemplate;
}