Spring Data Redis 알아보기 (TTL 설정하기)
Redis Repository 설정
✔️ dependency
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
✔️ application.yml
spring:
data:
redis:
host: localhost
port: 6379
@RedisHash
Redis Repository에 저장할 객체는 @RedisHash를 통해 설정할 수 있다.
@RedisHash가 붙은 객체는 Redis에 hash 형태로 저장된다.
이때 value 속성을 통해 redis key의 prefix 를 지정해 줄 수 있다.
import jakarta.persistence.Id;
import lombok.Getter;
import org.springframework.data.redis.core.RedisHash;
@RedisHash(value = "sample")
@Getter
public class SampleObject {
@Id
private Long id;
private String name;
private int age;
public SampleObject(Long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
}
생성되는 key는 @RedisHash의 value:ID 형식이다.
예를 들어 id가 10인 Sample 객체에 대한 key는 sample:10 이다.
이렇게 설정된 객체에 대한 Repository는 다음과 같이 생성한다.
public interface SampleObjectRepository extends CrudRepository<SampleObject, Long> {
}
@SpringBootTest
class SampleObjectRepositoryTest {
@Autowired
private SampleObjectRepository sampleObjectRepository;
@Test
void save_test() {
sampleObjectRepository.save(new SampleObject(1L, "member1", 20));
sampleObjectRepository.save(new SampleObject(2L, "member2", 22));
}
}
TTL 적용
Redis Repository 사용 시 TTL을 설정하는 방법에 대해 알아보자.
✔️ @RedisHash의 timeToLive 속성 지정
@RedisHash의 timeToLive 속성은 해당 타입의 모든 객체에 대한 TTL을 정의할 때 사용한다.
(0 혹은 음수의 값을 설정하면 만료되지 않는다.)
// timeToLive는 초 단위
@RedisHash(value = "sample", timeToLive = 15)
@Getter
public class SampleObject {
@Id
private Long id;
private String name;
private int age;
public SampleObject(Long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
}
✔️ @TimeToLive
객체별로 다른 ttl을 설정하고 싶다면, @TimeToLive를 사용할 수 있다.
// RedisHash 에 설정된 ttl은 @TimeToLive가 있으면 무시된다.
@RedisHash(value = "sample", timeToLive = 10)
@Getter
public class SampleObject {
@Id
private Long id;
private String name;
private int age;
@TimeToLive
private int ttl;
public SampleObject(Long id, String name, int age, int ttl) {
this.id = id;
this.name = name;
this.age = age;
this.ttl = ttl;
}
}
@SpringBootTest
class SampleObjectRepositoryTest {
@Autowired
private SampleObjectRepository sampleObjectRepository;
@Test
void save_test() {
// 양수가 아닌 값이 설정되면, 만료되지 않는다
sampleObjectRepository.save(new SampleObject(1L, "member1", 20, -1));
sampleObjectRepository.save(new SampleObject(2L, "member2", 20, 0));
sampleObjectRepository.save(new SampleObject(3L, "member3", 22, 20));
sampleObjectRepository.save(new SampleObject(4L, "member4", 22, 30));
}
}
✔️ Secondary Index 사용 시 문제
위에서 사용한 설정만으로는 Secondary Index에 TTL 적용시 아래와 같은 이상 동작이 발생한다.
@RedisHash(value = "sample", timeToLive = 3)
@Getter
public class SampleObject {
@Id
private Long id;
private String name;
private int age;
// Secondary Index 지정
@Indexed
private Long secondId;
public SampleObject(Long id, String name, int age, Long secondId) {
this.id = id;
this.name = name;
this.age = age;
this.secondId = secondId;
}
}
@SpringBootTest
class SampleObjectRepositoryTest {
@Autowired
private SampleObjectRepository sampleObjectRepository;
@Test
void save_test() throws InterruptedException {
sampleObjectRepository.save(new SampleObject(1L, "member1", 20, 10L));
assertThat(sampleObjectRepository.findBySecondId(10L)).isPresent();
assertThat(sampleObjectRepository.count()).isEqualTo(1);
Thread.sleep(5000);
assertThat(sampleObjectRepository.findBySecondId(10L)).isEmpty();
assertThat(sampleObjectRepository.count()).isEqualTo(0);
}
}
TTL 에 설정된 시간이 지나 실제 데이터는 지워졌으나,
key 목록을 관리하는 set의 element가 제거되지 않아 위와 같은 문제가 발생한다.
✔️ @EnableRedisRepositories(enableKeyspaceEvents = ON_STARTUP)
다음 옵션을 통해 키 만료(expire) 이벤트를 받아 set에서 지워냄으로써 위에서 발생한 문제를 해결할 수 있다.
@EnableRedisRepositories(enableKeyspaceEvents = ON_STARTUP)
@Configuration
public class RedisConfig {
}
작동 원리는 다음과 같다.
- 데이터 저장 시 원본과 사본(domain:id:phantom) 데이터를 함께 저장한다.
- 이때 사본을 phantom copy라 한다.
- 이때 사본은 원본 데이터보다 5분 더 오래 살도록 설정한다.
- 이후 원본 데이터가 만료되면, 해당 이벤트를 수신한 뒤 사본 데이터로부터 만료된 원본 데이터의 정보를 받아 key set에서 지워준다.