포트폴리오/Eighteen

[트러블 슈팅] redis 관련 문제 발생(2025.01.03)

do_hyuk 2025. 1. 3. 23:34

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;
}