Redis 캐시 스탬피드: 잠금 및 TTL 지터로 방지하는 방법

만료된 캐시 키에 대한 갑작스러운 요청 급증이 데이터베이스에 과부하를 일으키는 방법과 Redis에서 뮤텍스 잠금, 확률적 조기 재계산 및 TTL 지터를 사용하여 이를 중지하는 방법을 알아보세요.

Redis 캐시 스탬피드: 잠금 및 TTL 지터로 방지하는 방법

캐시 스탬피드는 만료된 캐시 키에 대한 동시 요청이 급증하여 데이터베이스에 과부하를 일으키는 현상입니다. 이 문서에서는 Redis를 사용하여 이 문제를 방지하는 세 가지 주요 전략을 다룹니다: 뮤텍스 잠금, 확률적 조기 재계산, TTL 지터.

뮤텍스 잠금

뮤텍스 잠금은 첫 번째 요청만 데이터베이스에서 데이터를 가져오고 캐시를 업데이트하도록 보장합니다. 다른 요청은 잠금이 해제될 때까지 대기합니다.

import redis
import time

r = redis.Redis()

def get_data(key):
    # 캐시에서 데이터 확인
    data = r.get(key)
    if data is not None:
        return data
    
    # 잠금 시도
    lock_key = f"lock:{key}"
    if r.setnx(lock_key, "locked"):
        # 잠금 획득 성공, 데이터베이스에서 데이터 가져오기
        data = fetch_from_database(key)
        r.setex(key, 3600, data)  # 1시간 TTL
        r.delete(lock_key)
        return data
    else:
        # 다른 요청이 데이터를 가져오는 중, 대기 후 재시도
        time.sleep(0.1)
        return get_data(key)

확률적 조기 재계산

이 방법은 캐시가 만료되기 전에 일부 요청이 데이터를 미리 재계산하도록 허용합니다. 이는 만료 시점의 부하를 분산시킵니다.

import random

def get_data_probabilistic(key):
    data = r.get(key)
    if data is not None:
        ttl = r.ttl(key)
        # TTL이 0에 가까울수록 재계산 확률 증가
        if ttl < 60 and random.random() < (60 - ttl) / 60:
            # 확률적으로 조기 재계산
            new_data = fetch_from_database(key)
            r.setex(key, 3600, new_data)
            return new_data
        return data
    else:
        # 캐시 미스, 정상적으로 가져오기
        data = fetch_from_database(key)
        r.setex(key, 3600, data)
        return data

TTL 지터

TTL 지터는 각 캐시 키에 약간 다른 만료 시간을 추가하여 동시 만료를 방지합니다.

import random

def set_with_jitter(key, data, base_ttl=3600):
    jitter = random.uniform(0, 300)  # 0-5분 지터
    ttl = base_ttl + jitter
    r.setex(key, int(ttl), data)

결론

캐시 스탬피드는 고트래픽 시스템에서 심각한 문제가 될 수 있습니다. 뮤텍스 잠금, 확률적 조기 재계산, TTL 지터를 조합하여 사용하면 데이터베이스 부하를 효과적으로 분산하고 시스템 안정성을 유지할 수 있습니다.