TTL을 걸었는데 메모리가 줄지 않아 당황한 이유
문제 발생
캐시 키에 전부 TTL을 걸었는데 used_memory가 기대만큼 떨어지지 않았습니다. TTL로 확인하면 만료된 키인데, 메모리 사용량은 한참 뒤에야 줄었습니다.
반대의 사고도 있었습니다. INCR로 카운터를 올리던 키가 어느 날부터 영원히 남기 시작했습니다.
원인 분석
두 가지 서로 다른 동작입니다.
첫째, 만료는 즉시 회수가 아닙니다. Redis 문서의 "How Redis expires keys" 절이 방식을 설명합니다 — 키는 수동(passive) 방식과 능동(active) 방식 두 가지로 만료된다. 클라이언트가 접근하려 할 때 시간이 지난 키는 수동으로 만료된다. 그러나 다시 접근되지 않을 만료 키들도 있으므로, 주기적으로 Redis는 만료가 설정된 키들 중 무작위로 몇 개를 검사해 이미 만료된 키를 키스페이스에서 삭제한다.
즉 만료 시각이 지났다고 그 순간 메모리가 반환되지는 않습니다. 아무도 접근하지 않는 키는 표본 추출에 걸릴 때까지 남습니다.
둘째, 명령에 따라 TTL이 유지되거나 사라집니다. 문서가 정확히 구분합니다 — 타임아웃은 키의 내용을 삭제하거나 덮어쓰는 명령(DEL, SET, GETSET, 모든 *STORE 명령)에 의해서만 지워진다. 키에 저장된 값을 새 값으로 대체하지 않고 개념적으로 변경하는 모든 연산은 타임아웃을 그대로 둔다. INCR, LPUSH, HSET이 그 예로 명시돼 있습니다.
그래서 SET key value로 값을 갱신하는 순간 TTL이 사라집니다 — 카운터가 영원히 남은 이유입니다.
해결 방안
- 값을 갱신하면서 TTL을 유지하려면
KEEPTTL을 씁니다.
SET cache:user:1 "..." KEEPTTL-
갱신할 때마다 만료를 다시 겁니다. 세션처럼 "마지막 활동 이후 N분"이 필요하면 매 접근마다
EXPIRE를 새로 걸어 슬라이딩 만료로 만듭니다. -
조건부 만료 옵션을 활용합니다.
EXPIRE의NX(만료가 없을 때만),XX(있을 때만),GT/LT(더 크거나 작을 때만)로 의도를 코드에 드러냅니다. 문서 설명대로 non-volatile 키는GT판단에서 무한 TTL로 취급됩니다. -
메모리 상한과 정책을 함께 정합니다. 만료만으로 메모리를 통제하려 하지 않습니다.
maxmemory와 eviction 정책이 실제 안전장치입니다. -
시계에 의존한다는 점을 인지합니다. 문서 경고대로 만료는 절대 유닉스 타임스탬프로 저장되므로 컴퓨터 시각이 크게 어긋나면 이상한 일이 벌어집니다 — 서버 시각을 미래로 옮기면 키가 즉시 만료됩니다.
-
복제 환경에서는 마스터가 판단합니다. 문서 설명대로 키가 만료되면 마스터가
DEL을 합성해 AOF와 복제본에 전파하고, 복제본은 스스로 만료시키지 않고 그DEL을 기다립니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.