Cache — 시각 정리

메모리 계층 · 쓰기 전략 5종 · 장애 패턴 4개 · 2×2 결정 매트릭스 · 의사결정 체크리스트

① 메모리 계층 — 왜 캐시가 빠른가

위로 갈수록 빠르고 작고 비쌈. 계층마다 10~1000배씩 차이가 난다는 자릿수 감각이 핵심.

CPU 레지스터 / L1
~ 1 ns
L2 / L3 캐시
수~수십 ns
메인 메모리 (RAM)
~100 ns
로컬 SSD
수십~수백 μs
같은 DC 네트워크 (Redis)
0.5~수 ms
디스크 / 원격 / 외부 API
수~수백 ms

캐싱 = "더 위 계층에 자주 쓰는 데이터를 끌어올려두는 것". CPU 캐시·Redis·CDN은 추상 수준만 다를 뿐 같은 원리.

② 장점 vs 단점 한눈에

👍 무엇을 얻나

  • 극적 latency 감소 — ms → μs
  • DB 부하 감소 — 커넥션 풀 고갈 예방
  • 컴퓨팅 비용 절감 — 무거운 집계 한 번만
  • 일시적 가용성 — DB 잠시 죽어도 stale 응답으로 버팀

👎 무엇을 잃나

  • 데이터 불일치 — 가장 치명적, stale 노출
  • 메모리 비용·OOM 위험 — RAM은 비싸고 작음
  • 시스템 복잡도 — 무효화 로직·디버깅 부담
  • Cold Start — 캐시 비면 트래픽이 DB로 몰림

③ 읽기/쓰기 전략 5종

"어디서 캐시를 채우고, 쓰기는 누가 책임지나"의 차이. 색은 쓰기 안정도를 의미 — 초록(안전) → 빨강(위험).

Cache-Aside 기본형 · 가장 흔함

읽기: 캐시 조회 → miss면 DB → 캐시 저장 → 반환
쓰기: DB 업데이트 → 캐시 삭제(무효화)
  • ✓ 단순, 캐시 죽어도 DB로 정상 동작
  • ✗ miss 첫 요청은 느림, 정합성은 앱 책임

Read-Through 캐시가 로딩 책임

앱 → 캐시
        ↓ miss면 캐시 라이브러리가 자동 DB 로딩
  • ✓ 앱 코드 깔끔
  • ✗ 캐시 추상화에 강하게 결합

Write-Through 정합성 좋음

쓰기 → 캐시 + DB (동기·동시)
  • ✓ 캐시가 항상 최신, 정합성 강함
  • ✗ 쓰기 지연 ↑, 안 읽힐 데이터 캐싱 낭비

Write-Back (Write-Behind) 위험 ↑

쓰기 → 캐시만 (즉시)
        ↓
DB 반영은 나중에 비동기·배치
  • ✓ 쓰기 매우 빠름, 폭주 흡수
  • 캐시 유실 = 데이터 손실. 잔액엔 절대 금지

Write-Around 쓰고 안 읽는 데이터

쓰기 → DB만 (캐시 skip)
다음 읽기 → 그때 캐시
  • ✓ 캐시 오염 방지(쓰고 안 읽는 데이터 안 캐시)
  • ✗ 쓰기 직후 첫 읽기는 느림

④ 캐싱이 어려운 진짜 이유 — 장애 패턴 4개

"넣기"는 쉬워도 "언제 어떻게 무효화/방어"가 캐싱의 본 난이도. "There are only two hard things in CS: cache invalidation and naming things." — Phil Karlton

① Cache Penetration — 캐시 관통

존재하지 않는 데이터를 계속 조회 → 항상 miss → 매번 DB 직행. 악의적 ID 대량 요청에 취약.
요청(없는 ID 999999) → 캐시 miss → DB 조회 → 없음
요청(없는 ID 999998) → 캐시 miss → DB 조회 → 없음 ...  → DB 죽음 💀
🛡 방어: ① null도 짧은 TTL로 캐싱 (예: "없음"을 30초간 기억) ② Bloom Filter로 확실히 없는 키 사전 차단

② Cache Avalanche — 캐시 눈사태

대량의 키가 동시에 만료되거나 캐시 서버 통째로 다운 → 트래픽이 한꺼번에 DB로.
00:00:00 시점에 1000개 키가 동시 만료
   ↓
1만 요청이 동시에 캐시 miss → 1만 쿼리가 DB로  → DB 마비 💀
🛡 방어: ① TTL 랜덤 지터 (예: 300 ± 60초로 만료를 분산) ② 다층 캐시(L1+L2) ③ Redis 클러스터·replica

③ Cache Stampede / Thundering Herd — 핫키 동시 갱신

인기 단일 키가 만료되는 순간 → 수많은 요청이 동시 miss → 전부 같은 DB 쿼리.
인기 랭킹 캐시 만료 (0초)
  ↓
요청 A,B,C,D,...,Z 동시 도착 → 전부 miss → 전부 같은 무거운 쿼리 동시 실행 💥
🛡 방어: ① 분산 락(SET NX)로 첫 요청만 DB 조회, 나머지는 대기·재조회 ② Refresh-Ahead로 만료 전 미리 갱신 ③ 확률적 조기 만료

④ Consistency Race — 정합성 race condition

"DB 업데이트 → 캐시 삭제" 순서여도 동시성 때문에 stale 값이 다시 캐싱될 수 있다.
A: DB read (old=1)              ← A가 먼저 읽음
B: DB write (new=2)
B: cache delete
A: cache write(old=1)           ← A가 오래된 값을 다시 박아버림 ❌
🛡 방어: ① 캐시 업데이트 X, 삭제 O (삭제가 race에 더 견고) ② 지연 이중 삭제(쓰기 직후 + 짧은 지연 후 한 번 더) ③ CDC 기반 이벤트 무효화

⑤ 2×2 결정 매트릭스

읽기 빈도 × 쓰기/변경 빈도. ⭐는 무조건 캐시, ❌는 절대 금지. ⚠️/❓ 가 진짜 토론거리.

쓰기/변경 적음쓰기/변경 많음
읽기 많음
⭐ 캐시 최적

공지·FAQ·카테고리·배너
베스트셀러·랭킹·인기 검색어
세션·JWT·인증 정보

⚠️ 신중히 — 토론거리

좋아요 / 조회수 / 댓글 수
→ Write-Back · 배치 집계 후보
(약간의 손실 허용 가능할 때)

읽기 적음
❓ 의미 적음

하루 1회 조회 정적 정보
일회성 마이페이지 영수증
→ 캐시 안 하거나 짧은 TTL

❌ 캐시 금지

실시간 라이더 위치 · 채팅
계좌 잔액 · 주식 호가 · 결제 승인
주민번호 · 비밀번호 (보안)

⑥ 티켓팅 도메인에 매핑

우리 도메인의 실제 데이터를 각 분면에 매핑해보면 결정이 명확해진다.

공연/좌석 배치도
길게 캐시 + 변경 시 무효화
유저 프로필
Cache-Aside + 쓰기 시 삭제
실시간 인기 공연 랭킹
1분 TTL + 지터 + 스탬피드 방어
좌석 가용 상태 (실시간)
짧은 TTL + Redis 직접 + 분산 락
예매 조회수/좋아요
Write-Back + 배치 집계 (손실 허용 시)
사용자별 예매 내역
본인만 가끔 조회 → 캐시 안 해도 됨
결제 트랜잭션
절대 캐시 X · DB 직접
결제 카드 정보
보안 민감 · 메모리 노출 위험

⑦ 의사결정 체크리스트 (인터랙티브)

캐시를 도입하기 전 던져봐야 할 5문항. 모르겠음(체크 안 함)이 2개 이상이면 아직 캐시 도입할 때가 아니다.

5개 모두에 답할 수 있다면 도입 검토 OK