Spring Boot

[Spring] RedisSerializer 알고 쓰기! 종류 별 특징과 주의 사항

kittity 2024. 12. 4. 22:25

목차

  1. RedisSerializer 란?
  2. RedisSerializer 구현체 종류와 특징
    2.1 JdkSerializationRedisSerializer
    2.2 StringRedisSerializer
    2.3 Jackson2JsonRedisSerializer
    2.4 GenericJackson2JsonRedisSerializer
  3. 그럼 우리는 어떻게 사용해야 할까?

 

Spring Boot에서 RedisConfig 파일을 작성한 예시를 검색하다 보면 블로그마다 설정이 다른 것을 볼 수 있다.

Serializer 설정을 안하는 케이스, StringRedisSerializer, Jackson2JsonRedisSerializer, GenericJackson2JsonRedisSerializer 를 사용하는 케이스..

 

과연, 어떤 차이가 있고 우리는 어떤 선택을 해야 할까?

 

아무 생각 없이 사용했다가 실무에서 이슈가 생길 수도 있다. 한번만 알아두면 되니 지금부터 하나씩 살펴보자.

 

1. RedisSerializer 란?

먼저, Redis를 사용하는데 Serializer 는 왜 있는걸까?

프레임워크 관점에서 Redis에 저장되는 데이터는 바이트일 뿐이다. Redis는 다양한 자료구조를 지원하지만, 이것은 단지 저장되는 방식을 의미하고 이것은 사용자에 의해 결정된다. Redis는 따로 직렬화/역직렬화 기능을 제공하지 않기 때문에 Spring Data Redis의 org.springframework.data.redis.serializer 패키지를 통해 직렬화/역직렬화를 해야 한다.

 

이제 직렬화/역직렬화를 적용하기 위해 그 구현체에 대해 알아보자.

 

2. RedisSerializer 구현체 종류와 특징

대표적인 4가지 구현체의 특징에 대해 알아보자. 각 방식마다 직렬화되어 저장된 형식이 다르다. 그리고 다음 Member 클래스를 사용한 예시를 함께 살펴보자.

 

  • Member.java
    @Entity
    @Table(name = "members")
    @Getter
    @NoArgsConstructor
    @Builder
    public class Member implements Serializable {
        @Id
        @Column(name = "member_id")
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long memberId;
    
        @Column(name = "member_name")
        private String memberName;
    
        @Column(name = "description")
        private String description;
    
        public Member(Long memberId, String memberName, String description) {
            this.memberId = memberId;
            this.memberName = memberName;
            this.description = description;
        }
    
        public static Member createMember(Long memberId, String memberName, String description) {
            return new Member(memberId, memberName, description);
        }
    
        @Override
        public String toString() {
            return "Member [memberId=" + memberId + ", memberName=" + memberName + ", description=" + description + "]";
        }
    }
  • RedisSerializerTest.java
    @SpringBootTest
    class RedisSerializerTest {
        @Autowired
        private RedisTemplate<String, Member> redisTemplate;
    
        @Test
        public void redisSerializerTest() throws Exception {
            //given
            Member member = Member.createMember(1L, "happy", "안녕하세요~ happy 입니다.");
    
            //when
            redisTemplate.opsForValue().set("member:happy", member);
    
            //then
            System.out.println(redisTemplate.opsForValue().get("member:happy"));
        }
    }

 

2.1 JdkSerializationRedisSerializer (Default)

JdkSerializationRedisSerializer 는 가장 처음 만들어졌으며, 따로 설정하지 않아도 default로 설정되는 구현체이다. Java의 기본 직렬화 방식을 사용하여 JDK 직렬화의 문제점을 그대로 갖는다. 장점도 있지만 단점이 더 크다.

  • 장점
    • Java 기본 직렬화 방식을 사용하여 구현이 간단
    • 모든 Java 객체를 직렬화 가능
    • 타입 안정성이 보장됨
  • 단점
    • Serializable 구현
      - 클래스가 Serializable 인터페이스를 구현해야 직렬화/역직렬화가 가능하다.
    • 호환성
      - 다른 언어나 시스템과의 호환성이 떨어지고 Java 시스템 간에서만 사용이 가능하다.
    • 역직렬화 문제
      - 변수 타입이 바뀌는 경우, 클래스가 수정되는 경우, 클래스의 경로나 패키지가 변경되는 경우에는 역직렬화 불가능하다.
      - 변경에 취약하고, serialVersionUID를 관리해야 한다.
    • 용량 문제
      - 직렬화된 데이터에 클래스의 메타 정보가 포함되어 있어 용량이 크다.
    • 보안 문제
      - 클래스의 모든 메타 정보가 직렬화되어 저장되어 민감아 정보가 노출될 수도 있다.
  • ✅ 적용 예시
    • RedisConfig.java
      @Bean
      public RedisTemplate<String, Member> redisTemplate() {
          RedisTemplate<String, Member> redisTemplate = new RedisTemplate<>();
          redisTemplate.setConnectionFactory(redisConnectionFactory());
          redisTemplate.setKeySerializer(new StringRedisSerializer());
          redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
          return redisTemplate;
      }
    • Member.java (Serializable 인터페이스를 구현해야한다)
      public class Member implements Serializable {
      ...
      }
    • Redis 저장 결과
      Redis에 저장된 결과를 보면, 클래스의 경로와 함께 저장된 것을 볼 수 있다.

2.2 StringRedisSerializer

StringRedisSerializer 는 문자열 데이터 처리의 필요성이 증가되면서 만들어진 것으로 문자열 데이터를 Redis에 저장할 때 사용하는 가장 기본적인 Serializer이다.

  • 장점
    • 문자열을 처리하기 위한 것으로 가장 간단하고 명확한 직렬화 방식이다.
    • Redis CLI로 데이터를 직접 확인할 때 가독성이 좋다.
    • 문자열을 byte[]로 변환할 때 UTF-8 인코딩을 사용한다.
  • 단점
    • 문자열 데이터만 처리 가능하고 객체를 저장할 수 없다.
  • 사용하는 경우
    • 주로 Key 값 설정에 사용한다.
    • 간단한 문자열 데이터를 저장할 때 혹은 JSON이나 다른 포맷으로 데이터를 관리할 필요가 없는 경우 사용할 수 있다.
    • ✅ 적용 예시
      • RedisConfig.java
        @Bean
        public RedisTemplate<String, String> redisTemplate() {
            RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(redisConnectionFactory());
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setValueSerializer(new StringRedisSerializer());
            return redisTemplate;
        }
      • RedisSerializerTest.java
        @SpringBootTest
        class RedisSerializerTest {
            @Autowired
            private RedisTemplate<String, String> redisTemplate;
        
            @Test
            public void redisSerializerTest() throws Exception {
                //given
                Member member = Member.createMember(1L, "happy", "안녕하세요~ happy 입니다.");
        
                //when
                redisTemplate.opsForValue().set("member:happy", member.toString());
        
                //then
                System.out.println(redisTemplate.opsForValue().get("member:happy"));
            }
        }
    • Redis 저장 결과

2.3 Jackson2JsonRedisSerializer

Jackson2JsonRedisSerializer는 객체를 JSON 형태로 직렬화하는 구현체이다. RESTful API의 보편화와 함께 JSON 처리의 필요성이 증가하면서 사용하게 되었다.

  • 장점
    • 객체를 JSON 형태로 저장하므로 가독성이 좋다.
    • Class 타입을 지정해야 하므로 타입 안정성이 보장된다.
  • 단점
    • 다형성을 지원하지 않는다.
    • 필요할 때마다 항상 Serializer에 ClassType을 지정해 주어야 한다.
  • 사용하는 경우
    • 데이터의 가독성과 호환성이 중요한 경우나 저장하는 객체 타입이 고정되어 있는 경우 사용할 수 있다.
    • 대부분의 경우 Jackson2JsonRedisSerializer 사용을 권장한다. 단, 압축이 적용되지 않기 때문에 고도화 시에는 압축이 구현된 커스터마이징 Serializer를 사용해야 한다.
  •   적용 예시
    • RedisConfig.java
      @Bean
      public RedisTemplate<String, Member> redisTemplate() {
          RedisTemplate<String, Member> redisTemplate = new RedisTemplate<>();
          redisTemplate.setConnectionFactory(redisConnectionFactory());
          redisTemplate.setKeySerializer(new StringRedisSerializer());
          redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Member.class));
          return redisTemplate;
      }
    • Redis 저장 결과

2.4 GenericJackson2JsonRedisSerializer

GenericJackson2JsonRedisSerializer는 Jackson2JsonRedisSerializer의 한계를 보완한 구현체이다. 다형성을 지원하고 동적 타입에 대한 처리를 할 수 있다.

  • 장점
    • 다형성을 지원하여 하나의 Template으로 여러 타입을 처리할 수 있다.
    • 객체의 클래스 정보(@class)를 저장하므로 역직렬화 시 타입 지정이 필요 없다.
  • 단점
    • 저장된 데이터에 클래스 정보(@class)가 포함되어 있어 클래스 이름이나 패키지 위치가 변경되면 역직렬화가 실패할 수 있다.
    • 저장된 데이터에 클래스 정보(@class)가 포함되어 있어 용량이 더 크다.
  •   적용 예시
    • RedisConfig.java
      @Bean
      public RedisTemplate<String, Member> redisTemplate() {
          RedisTemplate<String, Member> redisTemplate = new RedisTemplate<>();
          redisTemplate.setConnectionFactory(redisConnectionFactory());
          redisTemplate.setKeySerializer(new StringRedisSerializer());
          redisTemplate.setValueSerializer(new GZipJsonRedisSerializer<>(Member.class));
          return redisTemplate;
      }
       
    • Redis 저장 결과
      Redis에 저장된 결과를 보면, 클래스 정보(@class) 와 함께 저장된 것을 볼 수 있다.

3. 그럼 우리는 어떻게 사용해야 할까?

위의 4가지 방법을 토대로 정리하면 다음과 같다.

  1. Default Serializer인 JdkSerializationRedisSerializer는 Java 직렬화를 사용해 장점보다 단점이 많아 사용하지 않기.
  2. (권장) 단순 문자열 데이터 저장 시 StringRedisSerializer사용.
  3. (권장) Jackson2JsonRedisSerializer 선택. 고도화 시에는 압축이 구현된 커스터마이징한 Serializer를 사용하기.
  4. 클래스 변경이 잦은 경우 GenericJackson2JsonRedisSerializer 사용 시 주의. 관리가 어려워 장애가 발생하기 쉬운 지점이 되어 사용하지 않는 것을 권장.

이러한 특징을 고려하여 적용해보기를 바란다.

 

728x90