← 아티클 목록

캐시 무효화 전략 — 쓰기 시점 갱신과 TTL 설계

2027-05-03#database#캐시#Redis

상품 가격을 DB에서 9000원으로 바꿨는데 사용자 화면엔 한참 동안 10000원이 보입니다. 캐시에 남은 옛 값(stale)이 갱신되지 않은 것입니다. "컴퓨터 과학에서 어려운 두 가지는 캐시 무효화와 이름 짓기"라는 말이 있을 만큼, 캐시를 언제·어떻게 비우느냐는 까다롭습니다. 캐시 무효화 전략은 DB가 바뀌었을 때 캐시를 정합성 있게 맞추는 규칙입니다.

세 가지 기본 접근

전략쓰기 시 동작장점약점
Cache-aside + invalidateDB 갱신 후 캐시 키 삭제단순, 가장 흔함다음 읽기 때 미스 발생
Write-throughDB와 캐시를 같이 갱신읽기 즉시 최신쓰기 경로 복잡·느림
TTL 만료일정 시간 뒤 자동 폐기코드 단순TTL 동안 stale 허용

실무에서 가장 널리 쓰는 기본형은 읽을 때 캐시를 채우고(cache-aside), 쓸 때는 갱신이 아니라 삭제(invalidate) 하는 조합입니다. 캐시를 직접 새 값으로 덮으면 동시 쓰기 사이에 오래된 값이 끼어들 수 있어, 삭제 후 다음 읽기에서 다시 채우는 쪽이 어긋남이 적습니다.

Python
# 쓰기: DB 먼저, 그다음 캐시 삭제
db.update_price(product_id, 9000)
cache.delete(f"product:{product_id}")

순서와 경쟁 조건

순서를 거꾸로 해 캐시를 먼저 지우고 DB를 갱신하면, 그 틈에 다른 요청이 읽기를 하며 옛 DB 값을 다시 캐시에 채워 넣는 경쟁 조건이 생깁니다. 그래서 "DB 갱신 → 캐시 삭제" 순서를 지키고, 더 엄격히는 삭제를 한 번 더 거는 지연 이중 삭제(delayed double delete)를 씁니다.

진단할 때는 이렇게 봅니다.

  1. stale가 보이면 먼저 TTL이 지나치게 긴지 확인합니다. 가격처럼 정합성이 중요한 데이터에 하루 TTL은 위험합니다.
  2. 쓰기 코드에 캐시 삭제 호출이 빠졌는지 봅니다. 가장 흔한 원인입니다.
  3. 삭제는 하는데 DB보다 먼저 지우는 순서 버그가 아닌지 봅니다.

TTL은 안전망으로 둔다

invalidate가 누락되거나 실패할 수 있으므로, TTL을 함께 둬서 최악의 경우에도 일정 시간 뒤엔 stale가 사라지게 합니다. 정합성 요구가 강하면 짧게(수십 초~수 분), 거의 안 바뀌는 데이터면 길게 둡니다. 또 대량 키가 동시에 만료돼 DB로 요청이 쏠리는 캐시 스탬피드를 막으려면 TTL에 약간의 무작위 편차(jitter)를 더합니다.

Python
import random
ttl = 300 + random.randint(0, 60)  # 5분 ± jitter
cache.setex(key, ttl, value)

요점 정리

  • 기본은 cache-aside + 쓰기 시 삭제(덮어쓰기 아님).
  • 순서는 항상 "DB 갱신 → 캐시 삭제", 경쟁 조건엔 지연 이중 삭제.
  • TTL은 invalidate 누락에 대비한 안전망으로 두고, 정합성 요구에 맞춰 길이를 정한다.
  • 동시 만료 쏠림은 TTL jitter로 완화한다.

캐시와 DB 정합성을 직접 깨뜨려 보고 고치는 실습은 데이터베이스 트랙에서 회원가입 없이 무료로 해볼 수 있습니다.