본문 바로가기

카테고리 없음

혹시 redisTemplate의 set을 잘못 호출하고 있지는 않나요

너무 어이없는 실수를 하고 오랜 시간을 낭비해서, 잊지 않기 위해 기록해두는 글.


Redis에 같은 value의 값을 서로 다른 키로 저장하고자 했었다.

TTL을 다르게 해서 관리하고 싶었기 때문이다.

 

하지만 분명히 같은 인스턴스를 value로 넣어주고 있음에도 꼭 다음과 같은 에러가 발생했다.

Caused by: io.lettuce.core.RedisCommandExecutionException: ERR string exceeds maximum allowed size (proto-max-bulk-len)

 

 

원인을 모르고 한참을 헤매다가, redis-cli를 통해 실제 저장된 값을 확인해 보았다.

하나의 키만 성공적으로 저장되어 있었다.

다만 원래 저장되어야 할 값이 "hello"라고 하면, "\x00\x00\x00\x00\x00\x00...hello"와 같이 엄청난 null 값이 앞에 붙어 저장되어 있었다. 

 

Serializer 문제인지, 내가 저장하고자 하는 객체의 데이터가 너무 큰건지 한참을 고민하고 삽질했지만 결국 원인은 매우 단순했다.

위 사진은 set 메서드 종류에 따른 파라미터 목록이다.

 

ttl을 설정하기 위해 long타입 값을 기입했었는데, TimeUnit을 기입하지 않아 offset으로 해석된 것이다.

 

비록 값이 정상적으로 저장되어 있지 않아도, 항상 첫 번째 시도가 성공한 것은 이 때문이었다.

 

나는 세 번째 파라미터 자리에 6 * 60 * 60 * 1000 = 21,600,000 라는 값을 넣어주었었다.

그리고 redis의 proto-max-bulk-len 설정값의 기본 값은 512MB이다.

 

즉, 512,000,000 byte 를 넘지 않아 에러가 터지지 않고, 21,600,000 번째 자리에서부터 값이 저장되어 앞에 매우 많은 null문자가 포함되었던 것이다.

 

그럼 두 번째 시도가 실패하는 것도 설명이 된다.

두 번째 값을 저장할 때는 offset이 512MB를 초과하는 값이었다...

 

// 이전 예외발생 코드
valueOps.set(key, value, 21600000L); // => offset으로 해석됨

// 수정 후
valueOps.set(key, value, 21600000L, TimeUnit.MILLISECONDS);

따라서 위와 비슷한 방식으로 수정해 해결했다.


한동안 Duration으로만 ttl 설정을 했기에 timeout과 unit으로 ttl을 설정하는 법을 잊고 있었다.

 

RedisTemplate의 set()은 오버로드가 많다. 세 번째 파라미터에 long 타입 값을 넣었다면, 그게 offset인지 timeout인지 반드시 확인하자. TTL 설정 시에는 TimeUnit을 빼먹지 말 것.