Redis 와 캐싱 · TTL 과 만료 정책 · 이론
TTL 을 안 건 키가 만드는 것
한 줄 요약
만료는 시각이 되면 즉시 일어나지 않는다. 접근할 때 지우거나(지연 삭제) 백그라운드가 표본을 뽑아 지운다(능동 만료).
왜 이게 필요했나
Redis 인스턴스의 메모리가 계속 자라는데 원인을 못 찾는 상황이 자주 있습니다. 대개 답은 하나입니다 — TTL 을 안 건 키가 섞여 있습니다.
문제는 그것이 눈에 안 띈다는 점입니다. 캐시 코드는 대부분 SETEX 를 쓰는데, 어딘가 한 군데에서 SET 만 쓰고 있거나, SET key val 로 갱신하면서 원래 있던 TTL 을 날려 버립니다. 이 두 번째가 특히 교묘합니다 — SET 은 기존 TTL 을 제거합니다. TTL 을 유지하려면 KEEPTTL 옵션이 필요합니다.
어떻게 동작하나
만료 메커니즘부터 정확히 알아야 합니다. Redis 는 두 가지 방식을 함께 씁니다. 지연 삭제는 키에 접근하는 순간 만료를 확인하고 지웁니다. 능동 만료는 백그라운드에서 TTL 있는 키를 무작위 표본으로 뽑아 만료된 것을 지웁니다. 그래서 만료 시각이 지났는데도 아무도 접근하지 않는 키는 한동안 메모리에 남아 있습니다. DBSIZE 가 예상보다 큰 이유가 이것입니다.
maxmemory 에 도달하면 축출 정책이 작동합니다. 선택지가 여덟 가지인데 실무에서 중요한 건 셋입니다.
noeviction 은 기본값이고, 한도에 닿으면 쓰기를 거부합니다. 캐시로 쓸 때 이 설정이면 어느 날 갑자기 모든 쓰기가 실패합니다.
allkeys-lru 또는 allkeys-lfu 는 모든 키를 축출 대상으로 봅니다. 순수 캐시 용도라면 이쪽입니다. 저자의 권고는 allkeys-lfu 입니다 — 최근성보다 빈도가 캐시 적중률에 더 잘 맞기 때문입니다.
volatile-lru 는 TTL 이 설정된 키만 축출합니다. 캐시와 영구 데이터가 한 인스턴스에 섞여 있을 때 씁니다. 다만 이 설정에서 캐시 키에 TTL 을 빠뜨리면, 그 키는 영원히 축출되지 않으면서 메모리를 먹습니다.
현장에서 만나는 모습
TTL 에 지터를 넣는 것도 중요합니다. 배포 직후 캐시를 한꺼번에 채우면 TTL 이 정확히 같아서 한 시간 뒤 전부 동시에 만료됩니다. 그 순간이 스탬피드입니다. ttl = base + random(-spread, +spread) 한 줄이면 만료가 시간축에 퍼집니다.
그리고 감사가 필요합니다. SCAN 으로 순회하며 TTL 이 -1 인 키(만료 없음)를 세는 스크립트를 주기적으로 돌리면, 새로 들어온 코드가 TTL 을 빠뜨렸을 때 바로 잡힙니다.
다음 실습에서 할 것
TTL 을 걸고 확인하고, SET 이 TTL 을 날리는 함정을 재현하고, KEEPTTL 로 막고, 축출 정책을 바꿔 실제로 키가 축출되는 것을 확인하고, 마지막에 TTL 없는 키를 찾아내는 감사 스크립트를 만듭니다.