Framework/Spring

[SpringBoot] @RedisHash를 이용한 Spring Data Redis Repository 적용기

MINGYUM 2023. 10. 7. 11:04

Redis란? 

Key, Value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터 베이스 관리 시스템 (DBMS)이다. 

데이터베이스, 캐시, 메세지 브로커로 사용되며 인메모리 데이터 구조를 가진 저장소이다. 

 

String, List, Sets, SortedSets, Hashes 자료 구조를 지원하며 데이터를 디스크에 쓰는 구조가 아닌 메모리에서 데이터를 처리하기 때문에 속도가 빠르다. 

 

Spring Boot에서 Redis 사용하기

Spring Boot에서 Redis 인메모리 저장소를 사용하는 것에는 2가지 방법이 있다. 

RedisTemplate

String refreshToken = jwtTokenProvider.createRefreshToken();

ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
valueOperations.set(rRTKey + user.getIdx(), refreshToken);
log.info("redis RT : {}", valueOperations.get(rRTKey + user.getIdx()));

RedisTemplate의 opsForValue() 함수를 사용해 문자열로 이루어진 key-value 쌍을 문자열로 저장소에 저장할 수 있다. 

간단한 객체를 저장하고 사용하기 위해 편리하게 사용할 수 있는 방법이다.

 

@RedisHash

나는 Spring Data Redis에서 제공하는 라이브러리를 사용해 레디스 저장소를 구성하였다. 

 

build.gradle에 의존성 추가하기

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

 

entity 생성하기

@Getter
@AllArgsConstructor
@RedisHash(value = "refreshToken")
public class RefreshToken {
    @Id
    private String uid;

    @Indexed
    private String tokenValue;

    @TimeToLive
    private Long expiration;
}

간단하게 생성한 RefreshToken에 대한 entity이다. 

 

1. @RedisHash(value)

리프레시 토큰이 생성된 후 Redis에 저장될 Domain Object를 Redis Hash 자료구조로 변환하는 방식이다.

@RedisHash 어노테이션의 value에 특정한 값을 넣어줌으로써, 데이터가 저장될 때 key의 prefix에 붙을 문자열이 정해질 수 있다. 또한, timeToLive 옵션을 통해 입력한 숫자만큼 초 단위로 유효기간을 지정할 수 있다.

@RedisHash(value = "refreshToken", timeToLive=60)

2. @Id

@Id가 적용된 필드의 값은 Redis 해시의 키로 사용되어, 객체를 고유하게 식별되는 것에 사용된다. (prefix:id)와 같은 형식으로 저장된다.

3. @Indexed

tokenValue가 Secondary Indexes의 역할을 하도록 하는 역할이다. 즉 tokenValue가 데이터 저장소에서 공식 Primary-Key는 아니지만, 필요 시 TokenValue로 데이터를 조회하기 위해 인덱싱을 해주는 것이다. 이에 따라, 데이터가 저장될 때마다 인덱스로 따로 저장이 되고, 삭제될 때 인덱스도 함께 삭제된다. 이 어노테이션이 선언된 데이터를 저장하면 (prefix:field_name:value)의 형태를 갖도록 저장하게 된다.

이때 저장되는 Secondary Indexes의 자료형은 Sorted Set이다. Sorted Set은 가중치에 따라 정렬되어 사용되기 때문에  특정 필드 값에 따른 객체들을 빠르게 조회할 수 있다.

Secondary Index의 내부에는 대응되는 UID가 저장되어있다.

4. @TimeToLive

앞서 @RedisHash에서 TTL 옵션을 사용할 수 있다고 언급하였는데, @TimeToLive 어노테이션이 붙은 숫자 자료형 데이터를 함께 저장하면 Redis에 데이터가 저장되는 만료 기한을 설정할 수 있다.

TTL은 Redis keyspace notfiication을 구독해 데이터의 expire 이벤트를 수신하는 방법으로 데이터를 삭제한다. 

 

Spring Data Redis 예제

데이터 저장 테스트

앞서 제시한 Domain Object를 실제로 Redis에 저장해보고, 어떻게 데이터가 생성되는 지 살펴보겠다. 

 예를 들어서, (user1234, eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI) 이런 데이터를 위 Domain Object로 Redis에 저장했다면, @Id 어노테이션에 의해서 uid가 Primary-Key로 지정되고, 실제 저장되는  key-value는 아래와 같다.

key : refreshToken:uid:user1234

value : 필드명 : tokenValue, 필드값 : eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOil

실제로 hgetall 명령어로 지정된 해시의 필드와 값들을 가져와보았다. 

Redis의 Hashes 자료구조를 참고하면, Key는 Hash Key를 의미하고, Field는 Hash Key의 Sub Key를 의미한다. 이를 기반으로 사진에서 출력된 것을 분석해보았다. 

 

필드1 : (uid, 001694.afa45765d8934fb38c2b606f56b0ce18.1453) 

필드2 : (tokenValue, eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI .. )

필드3 : (_class, com.backend.auth.domain.RefreshToken) 

필드4 : (expiration, 1209600) 

 

필드1은 사용자 UID에 대한 정보, 필드2는 TokenValue에 대한 정보이다. 필드3은 Spring Data Redis가 내부적으로 사용하는 메타데이터 필드이고, 저장된 객체의 Java 클래스가 RefreshToken 클래스 인스턴스임을 저장하고 있다. 필드4는 해당 데이터의 Expiration을 나타내고, 1209600초 (2주) 동안 유지될 것을 알리고 있다. 

 

유효시간 만료 테스트

@TimeToLive에 의해서 지정된 유효 시간이 만료되면 어떻게 될까? 

FcmToken은 RefreshToken과는 무관한 다른 객체이므로 무시해주세요 !

유효시간 만료 전
유효시간 만료 후

위 사진을 보면 @Id 어노테이션으로 지정한 identifier property만 삭제되는 것을 확인할 수 있다. 즉 TimeToLive 속성은 @Indexed에 의해 생성된 Secondary Index를 지워주지 못한다는 것이다. 따라서 Secondary Index를 지워주고 싶다면 EventListener를 따로 구현할 필요가 있다. 

 

다른 Key들은 무엇인가요? 

그럼 @Id, @Indexed에 포함되지 않은 나머지 Key들은 무슨 역할을 할까? 

타입을 조회하고 타입에 적합한 명령어를 사용해서 Value를 조회해보았다.

첫 번째 Key는 RefreshToken 클래스의 모든 인스턴스를 추적하는 것에 사용된다. Spring Data Redis에 의해 데이터가 생성되거나 삭제될 떄, 이 키에 연관된 Set 자료형이 업데이트된다. 따라서 이 키를 조회하면 현재 Redis에 저장된 모든 RefreshToken 객체의 ID들을 확인할 수 있다. 

RefreshToken Object를 이용해서 또다른 데이터를 넣으니, 위 사진처럼 UID가 refreshToken이라는 Key 하위에서 관리되는 것을 확인할 수 있었다. 

두 번째 Key는 Secondary Index를 관리하는 데 사용되는 Key이다. 해당 필드 값에 따른 객체 ID들을 Sorted Set 형태로 저장하여, 빠른 검색 성능을 제공한다.

 

이렇게 Spring Data Redis를 사용해보고, 생성, 만료 등의 이벤트에 대해서 데이터가 어떻게 저장되는 지 확인해보았다. 

Spring Data Redis는 CrudRepository를 사용하여 별도의 Data Access 로직을 생성하지 않고도 편리하게 저장, 삭제, 조회 등을 할 수 있다는 점이 장점이었다. 그러나 포스팅에서 확인했던 것처럼 메타 데이터와 자료형 등 신경써야할 부분이 많기 때문에, 편리성을 위해 RedisTemplate을 사용하는 것이 대안이 될 수 있겠다.

 

참고

https://wildeveloperetrain.tistory.com/21

https://wildeveloperetrain.tistory.com/32