コピーは原本が変わった瞬間に古くなる
한국어 원문으로 표시합니다.
한 줄 요약
캐시 전략은 스테일과 미스 사이의 줄타기다. 정답은 없고 데이터의 성격에 맞는 균형점만 있다.
왜 이게 필요했나
캐시가 잘 작동하는 이유는 두 가지 성질 덕분입니다. 시간 지역성 — 방금 쓴 데이터는 곧 또 쓰일 가능성이 높다. 공간 지역성 — 어떤 데이터를 쓰면 그 근처 데이터도 곧 쓰일 가능성이 높다.
문제는 캐시가 원본의 복사본이라는 데서 나옵니다. 복사본은 원본이 바뀌는 순간 낡습니다. 여기서 두 가지 나쁜 상황을 구분해야 합니다. 스테일은 캐시가 원본보다 오래된 값을 들고 있는 것이고, 사용자가 틀린 값을 봅니다. 미스는 캐시에 값이 없어 원본까지 다녀와야 하는 것이고, 느립니다.
스테일을 줄이려 자주 버리면 미스가 늘고, 미스를 줄이려 오래 보관하면 스테일이 늘어납니다. 이 줄타기가 캐시의 모든 어려움의 근원입니다.
어떻게 동작하나
가장 단순한 무효화는 TTL 입니다. 각 항목에 수명을 붙이고 지나면 버립니다. 매력은 단순함과 예측 가능성입니다 — 무효화 로직을 따로 짤 필요가 없고, 최악의 경우에도 스테일은 TTL 을 넘지 않습니다. 그래서 잠깐 낡아도 괜찮은 데이터에 잘 맞습니다. 뉴스 목록, 인기 게시물, 환율. 반대로 계좌 잔액 같은 것은 TTL 만으로 부족하고 명시적 무효화가 필요합니다.
명시적 무효화에는 두 갈래가 있습니다. 이벤트 기반은 원본이 바뀌면 관련 캐시를 즉시 지웁니다. 최신성은 좋지만 의존성 추적이 어렵습니다 — "상품 42"는 상품 상세 캐시에도, 카테고리 목록 캐시에도, 검색 결과 캐시에도 있을 수 있습니다. 놓친 의존성 하나가 조용한 스테일 버그가 됩니다.
버전 키는 훨씬 견고한 실용적 기법입니다. 키 자체에 버전을 심어 무효화를 삭제 대신 이동으로 처리합니다. user:42:v7:profile 을 쓰다가 데이터가 바뀌면 버전을 v8 로 올립니다. v7 키는 아무도 찾지 않으니 TTL 로 자연히 정리됩니다. 삭제를 직접 하지 않아도 되고, 무효화와 재조회가 엇갈리는 경쟁 조건에도 강합니다. 옛 키와 새 키가 물리적으로 다르기 때문입니다.
현장에서 만나는 모습
무효화가 어려운 진짜 이유는 셋입니다. 첫째, 분산 — 캐시는 여러 서버, 여러 계층에 흩어져 있어 원자적으로 무효화하는 것이 분산 합의 문제가 됩니다. 둘째, 경쟁 조건 — 캐시를 지우는 순간과 다른 요청이 옛 값을 다시 읽어 넣는 순간이 엇갈리면 방금 지운 값이 되살아납니다. 셋째, 의존성의 복잡성.
그래서 실무는 완벽한 무효화를 포기하고 "얼마나 오래 스테일을 견딜 수 있는가"를 정해 TTL 과 명시적 무효화를 섞습니다.
캐시 패턴 네 가지
읽기·쓰기를 누가 하느냐로 갈립니다. 이름을 알아 두면 팀 대화가 짧아집니다.
| 패턴 | 읽기 | 쓰기 | 특징 |
|---|---|---|---|
| Cache-aside | 앱이 캐시 확인 → 없으면 DB → 캐시에 넣음 | 앱이 DB 쓰고 캐시 무효화 | 가장 흔하다. 첫 요청은 항상 느리다 |
| Read-through | 캐시가 DB 를 대신 읽어 줌 | — | 앱 코드가 단순해진다 |
| Write-through | — | 캐시와 DB 를 함께 씀 | 캐시가 항상 최신. 쓰기가 느리다 |
| Write-behind | — | 캐시에 쓰고 나중에 DB 로 | 쓰기가 빠르다. 유실 위험 |
실무의 95% 는 첫 줄입니다. 나머지는 캐시 계층이 DB 접근을 감싸는 라이브러리를 쓸 때 나옵니다. Write-behind 는 캐시가 죽으면 아직 DB 에 안 간 쓰기가 사라지므로 금전 데이터에는 쓰지 않습니다.
무엇을 캐시하지 말아야 하나
- 자주 바뀌는데 정확해야 하는 것 — 재고 수량, 잔액. 어긋나면 사고가 됩니다.
- 사용자마다 다른 큰 객체 — 적중률이 낮아 메모리만 먹습니다.
- 계산이 원래 싼 것 — 캐시 왕복(0.5ms)이 계산보다 비쌀 수 있습니다.
- 개인정보 — 캐시는 대개 암호화되지 않고 보존 정책이 느슨합니다.
세 번째를 자주 놓칩니다. 인메모리 계산 1μs 짜리를 Redis 에 넣으면 500배 느려 집니다. 캐시는 느린 것(DB 조회, 외부 API, 무거운 계산)에만 값이 있습니다.
캐시 키를 설계하는 규칙
labhub:v3:user:42:profile
│ │ │ │ └ 무엇
│ │ │ └ 식별자
│ │ └ 종류
│ └ 스키마 버전 ← 형식이 바뀌면 올린다. 옛 키는 TTL 로 사라진다
└ 애플리케이션 접두 ← 같은 Redis 를 여러 앱이 쓸 때 충돌을 막는다
버전을 넣는 것 이 핵심입니다. 캐시에 담는 객체 구조가 바뀌면 옛 값을 읽다가
깨지는데, 버전을 올리면 새 키를 쓰게 되고 옛 키는 TTL 로 자연히 사라집니다.
FLUSHALL 로 전부 지우는 것보다 안전합니다 — 그것은 모든 캐시를 동시에 비워
DB 에 스탬피드를 일으킵니다.
와일드카드 삭제도 조심합니다. KEYS user:* 는 전체를 훑는 O(n) 연산 이라
Redis 를 멈춥니다. SCAN 을 쓰거나, 애초에 태그용 집합(Set)을 함께 관리합니다.
다음 확인에서 볼 것
먼저 퀴즈에서 스테일과 미스의 거래 관계, TTL 이 허용되는 데이터 조건을 확인합니다. 다음 모듈부터 Redis 자료구조를 하나씩 만져 보고, 그다음 랭킹과 캐시 패턴과 스탬피드 방어를 각각 실습합니다.