Cache — 캐싱 깊은 이야기
목표: "왜 빠른가"를 넘어 "왜 어렵고, 무엇을 포기하는가" 까지 설명할 수 있게 만들기
관련: kafka (page cache·offset cache) / queue (버퍼링 관점 비교) / msa (분산 캐시) / eventdriven (CDC 기반 무효화)
§1. 캐싱이란 무엇인가
캐시(Cache)는 "느리고 비싼 원본(origin)"에 대한 접근을 줄이기 위해, "빠르고 가까운 곳"에 결과를 미리 복사해두는 것이다. 캐싱은 그 복사본을 만들고 활용하는 행위 전체.
핵심은 캐시가 원본의 대체물이 아니라 사본이라는 점.
- 원본은 어딘가에 항상 존재한다.
- 캐시는 언제든 비워져도 시스템이 (느려질 뿐) 정상 동작해야 한다.
- 이 전제가 깨지면 그건 캐시가 아니라 그냥 또 하나의 저장소다.
→ 토론 포인트 1: "Redis에만 있고 DB엔 없는 데이터"는 캐시인가? (답: 아니다. 그건 primary storage.)
도서관 비유 (직관)
- DB = 지하 서고 깊숙한 책장 — 느리지만 모든 데이터가 다 있음
- 캐시 = 사서 책상 위의 "잘 나가는 책" 바구니 — 빠르고 가까움
- 손님이 책을 찾으면 매번 지하 서고로 안 내려가고, 책상 위를 먼저 확인.
왜 필요한가
대부분의 시스템에서 비용은 균등하지 않다. 한 번 만든 결과를 다시 만드는 비용이 너무 크기 때문에 캐싱이 필요해진다. 비싼 작업의 예:
- DB 조인이 많은 무거운 쿼리
- 외부 API 호출 (네트워크 RTT + rate limit)
- 복잡한 연산 (집계, 직렬화, 암호화, 렌더링)
- 디스크/원격 스토리지 접근
캐싱이 효과적인 진짜 이유 = 접근 패턴이 편향돼 있기 때문. 파레토(20% 데이터가 80% 요청). 모든 데이터가 균등하게 요청되면 캐시는 의미가 없다.
§2. 동작 원리 — 지역성과 메모리 계층
캐싱이 통하는 근본 이유 = 두 가지 지역성(Locality).
- 시간 지역성(Temporal Locality) — 한 번 접근한 데이터는 곧 다시 접근될 가능성↑ (예: 인기 게시글, 로그인 유저 정보)
- 공간 지역성(Spatial Locality) — 어떤 데이터에 접근하면 그 근처도 곧 접근 (예: 게시글 목록 → 상세 진입)
컴퓨터는 본질적으로 속도-용량-가격이 반비례하는 계층 구조다.
| 계층 | 대략 지연시간 | 특징 |
|---|---|---|
| CPU 레지스터 / L1 | ~1ns | 매우 빠름, 매우 작음 |
| L2 / L3 | 수~수십 ns | |
| 메인 메모리 (RAM) | ~100ns | |
| 로컬 SSD | 수십~수백 μs | |
| 같은 DC 네트워크 (Redis 등) | 0.5~수 ms | |
| 디스크 / 원격 / 외부 API | 수~수백 ms | 느리고 큼 |
숫자는 정확한 벤치마크가 아니라 자릿수(order of magnitude) 감각으로. 핵심은 계층마다 대략 10~1000배씩 차이.
캐싱은 결국 이 계층에서 "더 위(빠른) 계층에 자주 쓰는 데이터를 끌어올려두는 것". CPU 캐시·Redis·CDN은 추상 수준만 다를 뿐 같은 원리다.
§3. 핵심 용어
- Cache Hit — 찾는 데이터가 캐시에 있음
- Cache Miss — 캐시에 없어 원본까지 가야 함
- Hit Ratio(적중률) =
hits / (hits + misses). 캐시 효과를 판단하는 1차 지표 - Cache Line / Entry — 캐시에 저장되는 단위
- Eviction(축출) — 공간 부족으로 기존 항목을 내보내는 것
- TTL(Time To Live) — 항목의 유효 기간
- Stateness — 캐시 값이 원본과 달라진 상태 (stale data)
- Warm-up / Cold Cache — 비어있는 초기 상태(cold)를 미리 채우는 것(warm-up)
토론 포인트 2: 적중률 99%인 캐시가 항상 좋은 캐시인가? 적중률만 높고 hit당 절약 비용이 작으면 무의미. 반대로 적중률 60%여도 miss 1건의 비용(외부 API 1초)이 크면 충분히 가치 있다. → "적중률 × hit당 절약 비용" 으로 보자.
§4. 캐시는 어디에나 있다 — 계층별 예시
주니어가 "캐시 = Redis"라고 생각하기 쉬운데, 실제로는 요청 경로 곳곳에 깔려 있다.
① 클라이언트 / 프론트엔드
- 브라우저 캐시 — 이미지·JS·CSS를 로컬 디스크에 저장. 네이버 로고는 매번 다운 X.
- HTTP 캐시 — 서버가
Cache-Control: max-age=3600헤더로 "1시간 재사용 OK"를 알려줌.ETag·Last-Modified로 변경 검증.
② 네트워크 / 인프라
- CDN (Cloudflare, CloudFront) — 엣지 서버가 원본 콘텐츠를 캐싱. 서울 서버 이미지를 미국 유저가 요청해도 미국 엣지에서 바로 서빙.
③ 애플리케이션 / 백엔드
- 분산 캐시 — Redis, Memcached. RAM 기반이라 매우 빠르고, 모든 인스턴스가 같은 값 공유.
- 실무 예: 공지사항 목록, 실시간 인기 검색어, 세션/토큰, 랭킹.
- 로컬(프로세스 내) 캐시 — Caffeine, Guava,
ConcurrentHashMap. 네트워크 없음 → 가장 빠름. 단 인스턴스마다 따로.
④ 저장소 내부
- DB 버퍼 풀 / shared_buffers — DB가 디스크 페이지를 메모리에 캐싱.
- OS Page Cache — 파일 시스템 읽기 캐싱. (kafka §6 zero-copy가 이걸 활용)
⑤ 하드웨어
- CPU L1/L2/L3 캐시 — 가장 원초적인 캐싱.
토론 포인트 3: 로컬 캐시(Caffeine) vs 분산 캐시(Redis), 언제 무엇을?
- 로컬: 가장 빠름(네트워크 X). 단 인스턴스마다 값이 다를 수 있고(정합성 어려움) 메모리 중복.
- 분산: 모든 인스턴스가 같은 값 공유. 단 네트워크 비용 + 단일 장애점 위험.
- 실무는 L1 로컬 + L2 Redis를 겹친 다층(multi-tier) 캐시도 흔하다.
§5. 읽기/쓰기 전략 (Caching Patterns)
가장 중요한 부분이자 토론거리가 많은 부분. "어디서 캐시를 채우고, 쓰기는 누가 책임지느냐" 의 차이.
5-1. Cache-Aside (Look-Aside) — 가장 흔함
앱이 캐시를 직접 관리한다.
읽기:
1) 캐시 조회
2) hit → 반환
3) miss → DB 조회 → 캐시에 저장 → 반환
쓰기:
1) DB 업데이트
2) 캐시 무효화(삭제) 또는 갱신
- ✅ 단순, 캐시 장애 시에도 DB로 정상 동작(캐시 의존성 낮음)
- ❌ miss 시 지연(3단계), 정합성 관리를 앱이 책임짐
- Spring
@Cacheable의 기본 동작이 이 형태에 가까움
5-2. Read-Through
앱은 캐시만 보고, 캐시 라이브러리가 miss 시 알아서 DB 로딩.
- 흐름은 Cache-Aside와 비슷하나 로딩 책임이 캐시 계층에
- 코드가 깔끔해지지만 캐시 추상화에 강하게 결합
5-3. Write-Through
쓰기 시 캐시와 DB에 동시(동기적) 기록.
- ✅ 캐시가 항상 최신 → 정합성 좋음
- ❌ 쓰기 지연 증가, 안 읽힐 데이터까지 캐싱(낭비)
5-4. Write-Back (Write-Behind)
쓰기를 일단 캐시에만 하고 DB는 나중에 비동기로 모아서.
- ✅ 쓰기 매우 빠름, 쓰기 폭주 흡수(버퍼링)
- ❌ 캐시 유실 시 데이터 손실 — 정합성 가장 위험
- DB 버퍼 풀, 일부 메시지/로그 파이프라인이 이 방식
5-5. Write-Around
쓰기는 DB로만, 캐시는 안 건드림. 다음 읽기 때 비로소 캐싱.
- 한 번 쓰고 잘 안 읽는 데이터에 적합
토론 포인트 4: 우리 서비스의 "조회수/좋아요"는? 쓰기 폭주 + 약간의 손실 허용 OK → Write-Back / 배치 집계가 후보. 반대로 결제 잔액이라면? → 절대 Write-Back 금지. Write-Through/synchronous만.
§6. 스케쥴링 정책
캐시 공간은 유한 → 무엇을 버릴지 정해야 함.
| 정책 | 기준 | 특징 |
|---|---|---|
| LRU (Least Recently Used) | 가장 오래 안 쓴 것 | 가장 흔함, 시간 지역성 활용 |
| LFU (Least Frequently Used) | 가장 적게 쓴 것 | 인기 데이터 유지에 강함. 과거 인기에 갇히는 단점 |
| FIFO | 먼저 들어온 것 | 단순. 지역성 무시 |
| TTL 기반 | 만료된 것 | 시간 기준. 정합성 도구로도 쓰임 |
| Random | 무작위 | 의외로 나쁘지 않고 매우 쌈 |
| W-TinyLFU | LFU+LRU 혼합 | Caffeine 기본. 현대적이고 적중률 우수 |
Redis는 maxmemory-policy로 allkeys-lru, allkeys-lfu, volatile-ttl 등을 설정.
토론 포인트 5: LRU의 약점은? Cache pollution — 스캔성 대량 조회(예: 야간 배치) 한 번에 인기 데이터가 전부 밀려남. 그래서 W-TinyLFU 같은 현대적 정책이 등장.
§7. 캐싱이 어려운 진짜 이유 — 장애 패턴 ⭐
"There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton
캐싱의 난이도는 "넣기"가 아니라 "언제 어떻게 무효화/방어하느냐".
7-1. 캐시 관통
존재하지 않는 데이터를 계속 조회 → 캐시 항상 miss → 매번 DB로 직행.
- 악의적 공격(없는 ID 대량 요청)에 취약.
- 대응:
- null도 짧은 TTL로 캐싱 (예: "없음"을 30초간 기억)
- Bloom Filter로 "확실히 없는" 키를 사전 차단
7-2. 캐시
대량의 키가 동시에 만료되거나 캐시 서버가 통째로 다운 → 트래픽이 한꺼번에 DB로 → DB 과부하/장애.
- 대응:
- TTL에 랜덤 지터(jitter) 추가 (예: 300초 ± 60초)
- 다층 캐시 (L1+L2)
- 캐시 가용성 확보(클러스터, replica)
7-3. 캐시 스탬피드 / Thundering Herd (Hot Key)
인기 단일 키가 만료되는 순간 → 수많은 요청이 동시 miss → 전부 같은 DB 쿼리를 동시에 던짐.
- 대응:
- 분산 락 / 뮤텍스 — 첫 요청만 DB 조회, 나머지는 대기 후 캐시 사용
- Refresh-Ahead — 만료 전에 미리 갱신
- 확률적 조기 만료 — 만료 임박 시 일정 확률로 미리 갱신
7-4. 정합성 (Consistency) — 가장 근본적인 문제
캐시와 DB는 별개 저장소라 둘 사이에 반드시 시차가 생긴다. "DB 업데이트 → 캐시 삭제" 순서에서도 동시성 때문에 stale이 다시 캐싱될 수 있다.
대표적 함정 (race condition):
A: DB read (old=1) ← A가 먼저 읽음
B: DB write (new=2)
B: cache delete
A: cache write(old=1) ← A가 오래된 값을 다시 박아버림 ❌
토론 포인트 6: 캐시를 "업데이트"할까, "삭제"할까? 보통 삭제(invalidate)가 안전하다. 업데이트는 위 race에 더 취약하고, 어차피 안 읽힐 값을 계산하는 낭비도 있다. 삭제 후 다음 읽기에서 자연스럽게 채우는 게 단순·견고.
§8. 정합성을 다루는 현실적 패턴
완벽한 정합성은 비싸다. 실무는 "얼마나 stale을 허용할 수 있는가" 를 먼저 정한다.
- TTL을 짧게 — 가장 단순. "최대 N초 오래될 수 있음"을 명시적으로 허용
- 쓰기 시 캐시 삭제 (Cache-Aside invalidation) — 가장 흔한 기본형
- Write-Through — 정합성 중요하고 쓰기 빈도 낮을 때
- 지연 이중 삭제 (Delayed Double Delete) — 쓰기 시 삭제 + 짧은 지연 후 한 번 더 삭제 → race로 다시 박힌 stale 제거
- 변경 이벤트 기반 무효화 — DB 변경 → 메시지/CDC(eventdriven · Debezium) → 캐시 무효화. 다중 인스턴스 로컬 캐시 동기화에 유용
- 버전/세대(generation) 키 — 키에 버전을 붙여 갱신 시 키 자체를 바꿔버림 (
product:v3:42)
핵심 사고방식: "이 데이터가 1초 / 10초 / 1분 오래되면 사용자/비즈니스에 무슨 일이 생기는가?" 를 먼저 질문한다.
- 잔액·재고: 민감 → 짧은 TTL + Write-Through + 락
- 추천·랭킹·조회수: 관대 → 긴 TTL + Write-Back/배치 OK
- 프로필·공지: 중간 → Cache-Aside + 쓰기 시 삭제
§9. 언제 캐싱하나 — 장단점과 적합·부적합 상황
캐시는 무조건 좋은 게 아니다. 시니어와 주니어를 가르는 가장 흔한 지점이 이 결정 감각이다. "캐시 = 빠르고 좋은 것"으로 외운 채 모든 곳에 박으면 오히려 시스템이 무너진다.
9-1. 한 줄 트레이드오프
캐시는 "속도"를 얻기 위해 "데이터 실시간 정확성"과 "메모리 비용·복잡도"를 맞바꾸는 트레이드오프 기술이다.
이 한 줄을 외우면 나머지는 다 따라온다. "공짜 점심은 없다."
9-2. 장점 — 무엇을 얻나
| 장점 | 설명 |
|---|---|
| 극적 latency 감소 | 디스크/네트워크 안 거치고 RAM에서 → ms → μs |
| DB 부하 감소 | 캐시가 앞에서 막아주는 방패. 커넥션 풀 고갈 예방 |
| 컴퓨팅 비용 절감 | 무거운 집계/조인을 매번 계산하지 않고 한 번 계산 후 재사용 |
| 일시적 가용성 확보 | DB가 잠깐 죽어도 캐시에 남은 값으로 일부 서비스 지속 |
9-3. 단점 — 무엇을 잃나
| 단점 | 설명 | 이 노트의 어디 |
|---|---|---|
| 데이터 불일치 (가장 치명적) | 원본 변경 → 캐시 미반영 → stale 노출 | §7-4, §8 |
| 메모리 비용·한계 | RAM은 SSD보다 비싸고 작음. 관리 못 하면 OOM | §6 eviction |
| 시스템 복잡도 증가 | 무효화 로직·TTL·정합성 디버깅이 따라붙음 | §7 전체 |
| Cold Start 위험 | 캐시 비었을 때 트래픽이 DB로 몰림 (스탬피드) | §7-3 |
9-4. 적합한 경우 — "Read Heavy, Write Light"
캐시 효율 극대화 = 자주 읽히지만 잘 안 변하는 데이터.
| 데이터 유형 | 예시 | 왜 |
|---|---|---|
| 반복 조회 + 변경 적음 | 공지사항, FAQ, 카테고리, 배너 | 한 번 캐싱하면 hit율 압도적 |
| 연산 비용이 큰 결과 | 어제 베스트셀러, 시간 단위 통계 대시보드 | 무거운 집계를 한 번만 |
| 일시적 stale 허용 | 실시간 인기 검색어 (1~2분 오차 OK) | "관대한 정합성"이 캐시와 궁합 |
| 세션·인증 정보 | JWT, 로그인 세션, 블랙리스트 | 페이지마다 조회 + 짧은 TTL 자연스러움 |
9-5. 부적합한 경우 — 캐시가 독이 되는 곳
물처럼 시시각각 변하거나, 절대적 정확성이 필요한 데이터.
| 데이터 유형 | 예시 | 왜 안 되나 |
|---|---|---|
| Write Heavy | 배달 라이더 실시간 위치, 채팅 메시지 | 매번 무효화 → 캐시 오버헤드가 이득보다 큼 |
| 0원 오차 금지 (금융/결제) | 계좌 잔액, 주식 호가, 결제 승인 상태 | 캐시 불일치 = 잘못된 송금 = 대형 사고 |
| 일회성·극단적 개인화 | 마이페이지 "지난달 영수증" | 본인만 한 번 보고 끝 → hit 0, 메모리만 차지 |
| 보안 민감 정보 | 주민번호, 비밀번호, 카드 CVV | RAM 덤프 등 메모리 공격면이 더 큼 |
9-6. 결정 매트릭스 (2×2)
판단 두 축: 읽기 빈도 × 쓰기/변경 빈도.
쓰기/변경 적음 쓰기/변경 많음
┌───────────────────────┬───────────────────────┐
읽기 │ ⭐ 캐시 최적 │ ⚠️ 신중히 │
많음 │ │ │
│ 공지·FAQ·카테고리 │ 좋아요/조회수 │
│ 베스트셀러·랭킹 │ → Write-Back/배치 │
│ 세션·인증 │ (손실 허용 시만) │
├───────────────────────┼───────────────────────┤
읽기 │ ❓ 캐시 의미 적음 │ ❌ 캐시 금지 │
적음 │ │ │
│ 하루 1회 조회 정적 정보 │ 실시간 위치·채팅 │
│ (TTL 짧게 or 안 함) │ 잔액·결제 (정확성 절대)│
└───────────────────────┴───────────────────────┘
→ ⭐는 무조건 캐시, ❌는 절대 금지. ⚠️ / ❓ 영역이 진짜 토론거리.
9-7. 우리 도메인(티켓팅)에 매핑해보기
| 데이터 | 영역 | 권장 전략 |
|---|---|---|
| 공연/좌석 배치도 | ⭐ Read↑ Write↓ | 길게 캐시 + 변경 시 무효화 |
| 좌석 가용 상태 (실시간) | ⚠️ Read↑ Write↑ | 짧은 TTL + Redis 직접 + 락 |
| 결제 트랜잭션 | ❌ | 절대 캐시 X — DB 직접 |
| 유저 프로필 | ⭐ | Cache-Aside + 쓰기 시 무효화 |
| 사용자별 예매 내역 | ❓ | 본인만 가끔 조회 → 굳이 캐시 안 해도 됨 |
| 실시간 인기 공연 랭킹 | ⭐ | 1분 TTL + 지터 + 스탬피드 방어 |
토론 진행 팁 — 위 표처럼 현재 프로젝트의 기능들을 직접 2×2에 매핑하는 연습을 시키면 "캐시 = 빠르고 좋음"이라는 외운 답이 깨지고 진짜 사고가 시작된다. 가장 토론이 뜨거워지는 칸은 ⚠️ (좋아요/조회수 류). 정답이 없고 비즈니스 요구사항에 따라 갈리기 때문.
9-8. 결정 체크리스트
캐시를 도입하기 전 스스로 던질 질문 5개:
- 읽기/쓰기 비율은? Read-heavy가 아니면 캐시의 효용이 작다.
- stale을 얼마나 허용하나? "초 단위도 안 됨" = 캐시 거의 불가, "분 단위 OK" = 자유.
- 캐시가 죽으면 시스템이 견디나? 못 견디면 그건 캐시가 아니라 primary store. (§1)
- hit율을 어떻게 측정/모니터링할 것인가? 측정 못 하면 캐시 효과를 모름.
- 장애 패턴(§7)을 어떻게 막을 것인가? 스탬피드·관통·눈사태 방어 미리.
→ 5개 중 2개 이상 "모르겠음"이면 아직 캐시 도입할 때가 아니다.
§10. 실사용 코드 (Spring Boot / Kotlin + Redis)
9-1. Spring @Cacheable (Cache-Aside)
@Service
class ProductService(
private val productRepository: ProductRepository,
) {
// 조회: 캐시 miss일 때만 DB 접근 후 'products' 캐시에 저장
@Cacheable(cacheNames = ["products"], key = "#id")
fun getProduct(id: Long): ProductDto =
productRepository.findById(id)
.orElseThrow { ProductNotFoundException(id) }
.toDto()
// 갱신: DB 업데이트 후 캐시 항목 삭제(무효화)
@CacheEvict(cacheNames = ["products"], key = "#id")
@Transactional
fun updateProduct(id: Long, req: UpdateProductRequest) {
val product = productRepository.findById(id).orElseThrow()
product.update(req)
// @Transactional 커밋 후 캐시가 비워지는 타이밍 주의
}
}
⚠️ 함정 2개:
@Cacheable은 프록시 기반 AOP라서 같은 클래스 내부 호출(self-invocation)에는 적용되지 않는다.@CacheEvict와@Transactional커밋 타이밍 차이로 stale이 생길 수 있어, 커밋 이후 무효화가 필요하면TransactionSynchronization또는@TransactionalEventListener(AFTER_COMMIT)고려.
9-2. Redis 직접 + 스탬피드 방어 (분산 락 + 지터)
fun getPopularRanking(): Ranking {
redisTemplate.opsForValue().get(KEY)?.let { return it } // ① 캐시 조회
// ② miss → 락 획득한 1개 요청만 DB 계산 (SET NX)
val locked = redisTemplate.opsForValue()
.setIfAbsent(LOCK_KEY, "1", Duration.ofSeconds(5)) == true
return if (locked) {
try {
val ranking = rankingRepository.calculateExpensiveRanking() // 무거운 연산
redisTemplate.opsForValue().set(KEY, ranking, ttlWithJitter())
ranking
} finally {
redisTemplate.delete(LOCK_KEY)
}
} else {
// ③ 락 못 잡은 요청은 잠깐 대기 후 캐시 재조회
Thread.sleep(50)
redisTemplate.opsForValue().get(KEY) ?: rankingRepository.calculateExpensiveRanking()
}
}
// TTL 눈사태 방지: 기본 TTL ± 랜덤 지터
fun ttlWithJitter(): Duration =
Duration.ofSeconds(300 + Random.nextLong(0, 60))
9-3. HTTP / CDN 캐싱
Cache-Control: public, max-age=3600, stale-while-revalidate=60
ETag: "abc123"
- 정적 리소스 / 공개 API 응답을 브라우저·CDN 계층에서 캐싱 → 우리 서버 트래픽 자체를 줄임
ETag+If-None-Match→ 변경 없으면304 Not Modified(본문 전송 생략 = 대역폭 절약)stale-while-revalidate→ 만료돼도 짧은 시간 동안 stale 응답 주면서 백그라운드로 갱신 (스탬피드 자연 완화)
§11. 다른 노트와의 연결
이번 캐시 정리를 다른 스터디 노트와 묶어보면:
- kafka §6 / §3.4 — 카프카의 sparse 인덱스는 mmap으로 OS page cache에 상주. 즉 인덱스 자체가 "캐시되도록" 설계됨. 그리고 zero-copy(
sendfile)는 page cache → NIC 직송 = 캐시를 user-space 안 거치고 활용하는 극단의 사례. - kafka §3.8 OffsetStore —
ConcurrentHashMapcache + Log append의 조합. 시작 시 disk → cache 재구축. 본질이 캐싱과 같다. - queue — 큐의 "버퍼링"과 캐시의 "사본 보관"은 역할이 다르다. 큐는 처리 속도 차이 흡수(시간축 분리), 캐시는 같은 데이터를 빠른 계층으로 끌어올림(공간축 분리).
- msa — 분산 환경에선 로컬 캐시 정합성이 어려움. CDC 기반 무효화(eventdriven)가 자주 등장.
§12. 주니어와 토론할 질문 모음
설명 후 던지면 "암기"인지 "이해"인지 갈리는 질문들.
개념·정합성
- 캐시와 일반 저장소(DB)의 본질적 차이는? (사본 vs 원본, 유실 허용 여부)
- 적중률이 높으면 무조건 좋은 캐시인가? (§3)
- "DB 업데이트 후 캐시 업데이트" vs "DB 업데이트 후 캐시 삭제" — 무엇이 더 안전하고 왜? (§7-4)
- 로컬 캐시를 여러 인스턴스에서 쓰면 생기는 문제는? 어떻게 동기화할까? (CDC, pub/sub 무효화)
결정·트레이드오프 (§9)
- 좋아요/조회수 / 유저 프로필 / 결제 잔액 — 각각 어떤 캐싱 전략과 TTL이 맞을까? 왜?
- 당신이 담당한 기능을 §9-6 2×2 매트릭스에 매핑해보라. ⚠️/❓ 영역에 들어가는 게 있다면, 캐시 도입 여부를 어떻게 결정할까?
- §9-3 단점 4개 중 당신 서비스에 가장 큰 리스크는 무엇이라 생각하나? 어떻게 완화?
- "캐싱하면 빨라진다"가 항상 참인가? 캐싱이 오히려 손해인 경우는?
- 낮은 적중률 (캐시 lookup 오버헤드만 추가)
- 무거운 직렬화/역직렬화 비용
- 정합성 버그로 인한 장애 비용 (디버깅 시간 + 사용자 신뢰)
장애·운영
- 캐시 서버(Redis)가 통째로 죽으면 우리 시스템은 어떻게 되는가? (캐시 의존도 점검 → fallback 전략)
- 캐시 스탬피드를 막는 방법을 2가지 이상 말해보라.
- CDN의
stale-while-revalidate는 어떤 장애 패턴을 자연스럽게 완화하는가? (스탬피드) - Bloom Filter는 false positive는 있지만 false negative는 없다. 이게 캐시 관통 방어에 왜 충분한가?
한 줄 요약
캐싱은 "느린 원본의 사본을 가까운 곳에 둬서 빠르게 만드는 기술" 이지만, 그 대가로 정합성과 추가적인 장애 표면(failure surface)을 떠안는 트레이드오프다.
좋은 엔지니어는 "어떻게 캐싱할까" 보다 "무엇을 캐싱하지 않을까"* 와 *"얼마나 오래됨을 허용할까" 를 먼저 정한다.