Redis TTL 동작 원리 — 만료 전략과 메모리 관리
TTL이란?
TTL(Time To Live)은 Redis 키에 수명을 부여하는 기능이다. 설정된 시간이 지나면 키가 자동으로 삭제된다.
SET session:user123 "data"
EXPIRE session:user123 3600 # 3600초(1시간) 후 만료
또는 한번에:
SET session:user123 "data" EX 3600
관련 명령어
| 명령어 | 설명 |
|---|---|
EXPIRE key seconds |
초 단위 TTL 설정 |
PEXPIRE key ms |
밀리초 단위 TTL 설정 |
EXPIREAT key timestamp |
Unix 타임스탬프로 만료 시점 지정 |
TTL key |
남은 TTL 확인 (초) |
PTTL key |
남은 TTL 확인 (밀리초) |
PERSIST key |
TTL 제거 (영구 키로 전환) |
TTL 반환값
TTL mykey
# -1 : 키가 존재하지만 TTL이 없음 (영구)
# -2 : 키가 존재하지 않음
# 양수 : 남은 초
만료 처리 전략
Redis는 키 만료를 두 가지 전략으로 처리한다.
1. Passive Expiration (수동 만료)
클라이언트가 키에 접근할 때 만료 여부를 확인한다.
Client: GET session:user123
Redis: (내부) 만료 시간 확인 → 지남 → 삭제 → nil 반환
- 장점: CPU 부하 없음
- 단점: 아무도 접근하지 않는 키는 영원히 메모리에 남음
2. Active Expiration (능동 만료)
Redis가 주기적으로(초당 10회) 만료된 키를 찾아 삭제한다.
동작 방식:
매 100ms마다:
1. 만료 시간이 설정된 키 중 랜덤으로 20개 샘플링
2. 샘플 중 만료된 키 삭제
3. 만료된 키가 25% 이상이면 → 즉시 1번부터 반복
4. 25% 미만이면 → 다음 주기까지 대기
핵심: 만료 키가 많으면 적극적으로 정리하고, 적으면 느긋하게 동작한다. 이를 통해 CPU 사용량과 메모리 낭비 사이에서 균형을 맞춘다.
두 전략의 조합
┌──────────────────────────────────┐
│ Redis 키 만료 │
├──────────────┬───────────────────┤
│ Passive │ Active │
│ (접근 시) │ (백그라운드) │
│ │ │
│ 100% 정확 │ 확률적 샘플링 │
│ CPU 0 │ CPU 약간 사용 │
│ 누수 가능 │ 누수 방지 │
└──────────────┴───────────────────┘
↓ 조합하면 ↓
효율적이면서 메모리 누수 최소화
내부 구현: redisDb 구조
Redis는 내부적으로 두 개의 딕셔너리를 관리한다.
typedef struct redisDb {
dict *dict; // 모든 키-값 저장
dict *expires; // TTL이 설정된 키 → 만료 시각 매핑
} redisDb;
EXPIRE key 60 을 실행하면:
expires딕셔너리에key → (현재시각 + 60초)를 저장dict에는 변화 없음
만료 확인 시:
expires에서 키의 만료 시각을 조회- 현재 시각과 비교
- 지났으면
dict와expires양쪽에서 삭제
TTL과 메모리 정책 (Eviction)
TTL 만료와 메모리 초과 시 퇴거(eviction)는 별개 메커니즘이다.
maxmemory-policy 옵션
maxmemory 한도에 도달했을 때 어떤 키를 제거할지 결정한다.
| 정책 | 설명 |
|---|---|
noeviction |
쓰기 거부 (에러 반환) |
allkeys-lru |
모든 키 중 LRU 제거 |
volatile-lru |
TTL 있는 키만 LRU 제거 |
allkeys-lfu |
모든 키 중 LFU 제거 (Redis 4.0+) |
volatile-lfu |
TTL 있는 키만 LFU 제거 |
allkeys-random |
랜덤 제거 |
volatile-random |
TTL 있는 키 중 랜덤 제거 |
volatile-ttl |
TTL이 가장 짧은 키 먼저 제거 |
실무 권장
# redis.conf
maxmemory 4gb
maxmemory-policy allkeys-lfu
- 캐시 용도:
allkeys-lru또는allkeys-lfu - 세션 저장소:
volatile-lru(TTL 없는 키는 보호)
TTL 관련 주의사항
1. RENAME과 TTL
SET a "hello" EX 100
SET b "world"
RENAME a b
TTL b # → 100 (a의 TTL이 b로 이전됨)
RENAME하면 TTL이 따라간다. 대상 키(b)에 TTL이 있었든 없었든, 원본(a)의 TTL로 덮어씌워진다.
2. SET은 TTL을 제거한다
SET mykey "v1" EX 100
SET mykey "v2" # TTL 사라짐!
TTL mykey # → -1
SET은 기존 키를 완전히 덮어쓰므로 TTL이 제거된다. 값만 변경하려면 SET mykey "v2" KEEPTTL (Redis 6.0+)을 사용한다.
3. INCR, LPUSH 등은 TTL에 영향 없음
SET counter 0 EX 100
INCR counter
TTL counter # → 여전히 약 100 (보존됨)
키의 값을 변경하는 명령어는 TTL을 유지한다. 키 자체를 재생성하는 명령어(SET, GETSET 등)만 TTL에 영향을 준다.
4. RDB/AOF에서의 TTL
- RDB: 스냅샷 저장 시 만료 시각도 함께 저장. 로딩 시 이미 만료된 키는 무시
- AOF:
EXPIREAT(절대 시각) 형태로 기록. 재시작 시 정확한 만료 처리 가능
실무 활용 패턴
세션 관리
SET session:abc123 "{userId:1, role:admin}" EX 1800
# 사용자 활동 시 갱신
EXPIRE session:abc123 1800
캐시 + TTL
SET cache:product:100 "{name:...}" EX 300
# 5분 캐시, 만료 후 DB 재조회
Rate Limiting
SET ratelimit:user:123 1 EX 60 NX # 1분 윈도우 시작
INCR ratelimit:user:123 # 요청마다 증가
# 값이 100 초과 시 → 차단
정리
| 항목 | 핵심 |
|---|---|
| 만료 방식 | Passive(접근 시) + Active(백그라운드 샘플링) |
| 내부 구조 | expires 딕셔너리에 만료 시각 저장 |
| SET 주의 | SET은 TTL 제거 → KEEPTTL 사용 |
| 메모리 초과 | TTL 만료와 별개로 maxmemory-policy가 동작 |
| 복제 환경 | 마스터에서 만료 → 레플리카에 DEL 전파 |