자바 및 spring boot

레디스 캐시 쏠림, 쓰레싱

dani717 2024. 6. 2. 15:56

레디스(Redis)에서 많은 키가 동시에 만료되었을 때 데이터베이스(DB)에 부하가 걸리는 상황을 방지하기 위해 몇 가지 전략을 사용할 수 있습니다. 이러한 상황은 캐시 쏠림(Cache Stampede) 또는 쓰레싱(Thrashing)이라고도 불리며, 이로 인해 많은 트래픽이 한꺼번에 DB로 몰리게 됩니다. 이를 방지하는 몇 가지 방법은 다음과 같습니다:

### 1. TTL을 랜덤화
TTL을 설정할 때 동일한 만료 시간을 가지지 않도록 약간의 랜덤성을 추가합니다. 이렇게 하면 대량의 키가 동시에 만료되는 것을 피할 수 있습니다.

#### 예시
```java
int baseTTL = 3600; // 기본 TTL 값 (예: 1시간)
int randomTTL = baseTTL + new Random().nextInt(600); // 0-10분 랜덤 추가
redisTemplate.expire(key, randomTTL, TimeUnit.SECONDS);
```

### 2. 캐시 예열(Cache Warming)
캐시 만료 전에 주기적으로 데이터를 갱신하는 작업을 수행합니다. 이를 통해 키가 만료되기 전에 미리 데이터를 로드해 캐시를 예열할 수 있습니다.

#### 예시
스케줄러를 사용하여 주기적으로 캐시를 갱신합니다.
```java
@Scheduled(fixedRate = 300000) // 5분마다 실행
public void preloadCache() {
    List<String> keys = fetchKeysFromDB();
    for (String key : keys) {
        String value = fetchValueFromDB(key);
        redisTemplate.opsForValue().set(key, value, 1, TimeUnit.HOURS);
    }
}
```

### 3. 슬라이딩 윈도우 기법(Sliding Window)
TTL을 갱신할 때마다 기존 TTL에 연장하여 설정합니다. 이를 통해 키의 만료 시간을 분산시킬 수 있습니다.

#### 예시
```java
String key = "someKey";
String value = fetchValueFromDB(key);
redisTemplate.opsForValue().set(key, value, 1, TimeUnit.HOURS);

// 데이터 접근 시마다 TTL을 연장
redisTemplate.expire(key, 1, TimeUnit.HOURS);
```

### 4. 캐시 미스 보호(Cache Miss Protection)
여러 클라이언트가 동시에 캐시 미스를 발생시키는 경우, 첫 번째 요청만 DB로 접근하고 나머지 요청은 캐시 갱신이 완료될 때까지 대기하도록 합니다. 이를 통해 DB 부하를 줄일 수 있습니다.

#### 예시
```java
String key = "someKey";
String value = redisTemplate.opsForValue().get(key);

if (value == null) {
    synchronized (this) {
        value = redisTemplate.opsForValue().get(key);
        if (value == null) {
            value = fetchValueFromDB(key);
            redisTemplate.opsForValue().set(key, value, 1, TimeUnit.HOURS);
        }
    }
}
```

### 5. 백그라운드 캐시 리프레시(Background Cache Refresh)
캐시 만료 후 DB 접근 전에 백그라운드에서 캐시를 갱신합니다. 이를 통해 요청이 들어오기 전에 미리 캐시를 업데이트할 수 있습니다.

#### 예시
```java
@Async
public void refreshCache(String key) {
    String value = fetchValueFromDB(key);
    redisTemplate.opsForValue().set(key, value, 1, TimeUnit.HOURS);
}
```

### 6. 캐시 일관성 유지
캐시의 일관성을 유지하기 위해 DB와 캐시 사이에 일관성 유지 전략을 사용합니다. 예를 들어, DB 업데이트 시 캐시를 무효화하거나 갱신하는 방법입니다.

#### 예시
```java
public void updateValue(String key, String newValue) {
    updateValueInDB(key, newValue);
    redisTemplate.opsForValue().set(key, newValue, 1, TimeUnit.HOURS);
}
```

### 결론
이러한 방법들은 각각의 상황에 맞게 적용할 수 있으며, 필요에 따라 조합하여 사용할 수도 있습니다. 중요한 것은 캐시 만료로 인한 대규모 DB 접근을 방지하고, 시스템의 성능과 안정성을 유지하는 것입니다. 각 방법을 적용하기 전에 충분한 테스트를 통해 시스템에 미치는 영향을 확인하는 것이 필요합니다.