Spring

Spring Data Redis 알아보기 (TTL 설정하기)

mysterious dev 2024. 2. 3. 19:57

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 keyprefix 를 지정해 줄 수 있다.

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

저장 직후

 

 

15초 이후

 

 

 

 

 

 

✔️ @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));
    }
}

 

저장 후

 

20초 후

 

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

 

monitor 명령어를 통해 확인할 수 있다.

 

작동 원리는 다음과 같다.

  • 데이터 저장 시 원본과 사본(domain:id:phantom) 데이터를 함께 저장한다. 
  • 이때 사본은 원본 데이터보다 5분 더 오래 살도록 설정한다.
  • 이후 원본 데이터가 만료되면, 해당 이벤트를 수신한 뒤 사본 데이터로부터 만료된 원본 데이터의 정보를 받아 key set에서 지워준다.

 

 

 

 

 

 

참고