Cache2026년 6월 5일14분 읽기

캐싱 완벽 정리: 무엇을 캐싱하지 않을지부터 정한다

캐시가 왜 빠른가를 넘어, 왜 어렵고 무엇을 포기하는지까지. 지역성과 메모리 계층, Cache-Aside/Write-Through/Write-Back, eviction 정책, 관통·눈사태·스탬피드 같은 장애 패턴, 정합성 패턴, 그리고 '언제 캐싱하지 말아야 하는가'의 결정 감각까지 정리했다. 주니어와 함께 토론할 질문도 담았다.

#Cache#Redis#정합성#성능#Backend#Architecture

캐싱 완벽 정리: 무엇을 캐싱하지 않을지부터 정한다

목표는 "왜 빠른가"를 넘어 **"왜 어렵고, 무엇을 포기하는가"**까지 설명할 수 있게 만드는 것이다. 사내 스터디에서 주니어들과 함께 다룬 내용을 정리했고, 중간중간 토론 포인트를 그대로 남겨뒀다.

관련 글: Kafka 깊은 이야기 (page cache·offset cache) · 큐 완벽 정리 (버퍼링 관점 비교) · MSA 시리즈 (분산 캐시) · 이벤트 드리븐 아키텍처 (CDC 기반 무효화)

함께 보면 좋은 인터랙티브 시각화 3개를 만들어 뒀다. 글 중간에 막히면 직접 단계를 눌러가며 보면 빠르게 잡힌다.


§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) — 항목의 유효 기간
  • Staleness — 캐시 값이 원본과 달라진 상태 (stale data)
  • Warm-up / Cold Cache — 비어있는 초기 상태(cold)를 미리 채우는 것(warm-up)

토론 포인트 2: 적중률 99%인 캐시가 항상 좋은 캐시인가? 적중률만 높고 hit당 절약 비용이 작으면 무의미하다. 반대로 적중률 60%여도 miss 1건의 비용(외부 API 1초)이 크면 충분히 가치 있다. **"적중률 × hit당 절약 비용"**으로 봐야 한다.


§4. 캐시는 어디에나 있다: 계층별 예시

주니어가 "캐시 = Redis"라고 생각하기 쉬운데, 실제로는 요청 경로 곳곳에 깔려 있다.

① 클라이언트 / 프론트엔드

  • 브라우저 캐시 — 이미지·JS·CSS를 로컬 디스크에 저장. 네이버 로고는 매번 다운로드하지 않음
  • 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 글의 zero-copy가 이걸 활용)

⑤ 하드웨어

  • CPU L1/L2/L3 캐시 — 가장 원초적인 캐싱

토론 포인트 3: 로컬 캐시(Caffeine) vs 분산 캐시(Redis), 언제 무엇을?

  • 로컬: 가장 빠름(네트워크 없음). 단 인스턴스마다 값이 다를 수 있고(정합성 어려움) 메모리가 중복된다.
  • 분산: 모든 인스턴스가 같은 값을 공유. 단 네트워크 비용 + 단일 장애점 위험이 있다.
  • 실무에서는 L1 로컬 + L2 Redis를 겹친 다층(multi-tier) 캐시도 흔하다.

§5. 읽기/쓰기 전략 (Caching Patterns)

가장 중요한 부분이자 토론거리가 많은 부분이다. 어디서 캐시를 채우고, 쓰기는 누가 책임지느냐의 차이다.

5-1. Cache-Aside (Look-Aside): 가장 흔함

앱이 캐시를 직접 관리한다.

text
읽기:
  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: 우리 서비스의 "조회수/좋아요"는? 쓰기 폭주 + 약간의 손실 허용이 가능하다면 Write-Back / 배치 집계가 후보다. 반대로 결제 잔액이라면? 절대 Write-Back을 쓰면 안 된다. Write-Through나 synchronous만 가능하다.


§6. 스케줄링 정책 (Eviction)

캐시 공간은 유한하므로 무엇을 버릴지 정해야 한다.

정책기준특징
LRU (Least Recently Used)가장 오래 안 쓴 것가장 흔함, 시간 지역성 활용
LFU (Least Frequently Used)가장 적게 쓴 것인기 데이터 유지에 강함. 과거 인기에 갇히는 단점
FIFO먼저 들어온 것단순. 지역성 무시
TTL 기반만료된 것시간 기준. 정합성 도구로도 쓰임
Random무작위의외로 나쁘지 않고 매우 쌈
W-TinyLFULFU+LRU 혼합Caffeine 기본. 현대적이고 적중률 우수

Redis는 maxmemory-policyallkeys-lru, allkeys-lfu, volatile-ttl 등을 설정한다.

세 정책이 같은 접근 패턴에서 서로 다른 항목을 버리는 걸 직접 보고 싶다면 Eviction 정책 시각화에서 LRU·LFU·FIFO를 나란히 돌려볼 수 있다.

토론 포인트 5: LRU의 약점은? Cache pollution이다. 스캔성 대량 조회(예: 야간 배치) 한 번에 인기 데이터가 전부 밀려난다. 그래서 W-TinyLFU 같은 현대적 정책이 등장했다.


§7. 캐싱이 어려운 이유: 장애 패턴

"There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton

캐싱의 난이도는 "넣기"가 아니라 언제 어떻게 무효화하고 방어하느냐에 있다.

아래 세 장애 패턴이 어떻게 트래픽을 DB로 몰아넣는지, 그리고 방어 기법이 그걸 어떻게 막는지는 장애 패턴 시각화에서 단계별로 직접 볼 수 있다.

7-1. 캐시 관통 (Cache Penetration)

존재하지 않는 데이터를 계속 조회하면 캐시가 항상 miss라 매번 DB로 직행한다.

  • 악의적 공격(없는 ID 대량 요청)에 취약하다
  • 대응: null도 짧은 TTL로 캐싱(예: "없음"을 30초간 기억) / Bloom Filter로 "확실히 없는" 키를 사전 차단

7-2. 캐시 눈사태 (Cache Avalanche)

대량의 키가 동시에 만료되거나 캐시 서버가 통째로 다운되면, 트래픽이 한꺼번에 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은 이렇다.

text
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을 허용할 수 있는가"**를 먼저 정한다.

  1. TTL을 짧게 — 가장 단순. "최대 N초 오래될 수 있음"을 명시적으로 허용
  2. 쓰기 시 캐시 삭제 (Cache-Aside invalidation) — 가장 흔한 기본형
  3. Write-Through — 정합성이 중요하고 쓰기 빈도가 낮을 때
  4. 지연 이중 삭제 (Delayed Double Delete) — 쓰기 시 삭제 + 짧은 지연 후 한 번 더 삭제. race로 다시 박힌 stale 제거
  5. 변경 이벤트 기반 무효화 — DB 변경 → 메시지/CDC(이벤트 드리븐·Debezium) → 캐시 무효화. 다중 인스턴스 로컬 캐시 동기화에 유용
  6. 버전/세대(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 노출
메모리 비용·한계RAM은 SSD보다 비싸고 작음. 관리 못 하면 OOM
시스템 복잡도 증가무효화 로직·TTL·정합성 디버깅이 따라붙음
Cold Start 위험캐시 비었을 때 트래픽이 DB로 몰림 (스탬피드)

9-4. 적합한 경우: "Read Heavy, Write Light"

캐시 효율이 극대화되는 건 자주 읽히지만 잘 안 변하는 데이터다.

데이터 유형예시
반복 조회 + 변경 적음공지사항, FAQ, 카테고리, 배너한 번 캐싱하면 hit율 압도적
연산 비용이 큰 결과어제 베스트셀러, 시간 단위 통계 대시보드무거운 집계를 한 번만
일시적 stale 허용실시간 인기 검색어 (1~2분 오차 OK)"관대한 정합성"이 캐시와 궁합
세션·인증 정보JWT, 로그인 세션, 블랙리스트페이지마다 조회 + 짧은 TTL 자연스러움

9-5. 부적합한 경우: 캐시가 독이 되는 곳

물처럼 시시각각 변하거나, 절대적 정확성이 필요한 데이터다.

데이터 유형예시왜 안 되나
Write Heavy배달 라이더 실시간 위치, 채팅 메시지매번 무효화 → 캐시 오버헤드가 이득보다 큼
0원 오차 금지 (금융/결제)계좌 잔액, 주식 호가, 결제 승인 상태캐시 불일치 = 잘못된 송금 = 대형 사고
일회성·극단적 개인화마이페이지 "지난달 영수증"본인만 한 번 보고 끝 → hit 0, 메모리만 차지
보안 민감 정보주민번호, 비밀번호, 카드 CVVRAM 덤프 등 메모리 공격면이 더 큼

9-6. 결정 매트릭스 (2×2)

판단의 두 축은 읽기 빈도 × 쓰기/변경 빈도다.

text
                    쓰기/변경 적음              쓰기/변경 많음
              ┌───────────────────────┬───────────────────────┐
   읽기       │  ⭐ 캐시 최적           │  ⚠️ 신중히             │
   많음       │                       │                       │
              │  공지·FAQ·카테고리       │  좋아요/조회수          │
              │  베스트셀러·랭킹         │  → Write-Back/배치     │
              │  세션·인증              │   (손실 허용 시만)      │
              ├───────────────────────┼───────────────────────┤
   읽기       │  ❓ 캐시 의미 적음      │  ❌ 캐시 금지           │
   적음       │                       │                       │
              │  하루 1회 조회 정적 정보 │  실시간 위치·채팅       │
              │  (TTL 짧게 or 안 함)   │  잔액·결제 (정확성 절대)│
              └───────────────────────┴───────────────────────┘

⭐는 무조건 캐시, ❌는 절대 금지다.

9-7. 결정 체크리스트

캐시를 도입하기 전 스스로 던질 질문 5개다.

  1. 읽기/쓰기 비율은? Read-heavy가 아니면 캐시의 효용이 작다.
  2. stale을 얼마나 허용하나? "초 단위도 안 됨" = 캐시 거의 불가, "분 단위 OK" = 자유.
  3. 캐시가 죽으면 시스템이 견디나? 못 견디면 그건 캐시가 아니라 primary store다.
  4. hit율을 어떻게 측정·모니터링할 것인가? 측정 못 하면 캐시 효과를 모른다.
  5. 장애 패턴(§7)을 어떻게 막을 것인가? 스탬피드·관통·눈사태 방어를 미리.

5개 중 2개 이상 "모르겠음"이면 아직 캐시를 도입할 때가 아니다.


§10. 실사용 코드 (Spring Boot / Kotlin + Redis)

10-1. Spring @Cacheable (Cache-Aside)

kotlin
@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개:

  1. @Cacheable은 프록시 기반 AOP라서 같은 클래스 내부 호출(self-invocation)에는 적용되지 않는다.
  2. @CacheEvict@Transactional 커밋 타이밍 차이로 stale이 생길 수 있어, 커밋 이후 무효화가 필요하면 TransactionSynchronization 또는 @TransactionalEventListener(AFTER_COMMIT)를 고려한다.

10-2. Redis 직접 + 스탬피드 방어 (분산 락 + 지터)

kotlin
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))

10-3. HTTP / CDN 캐싱

text
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 — 카프카의 sparse 인덱스는 mmap으로 OS page cache에 상주한다. 즉 인덱스 자체가 "캐시되도록" 설계됐다. 그리고 zero-copy(sendfile)는 page cache에서 NIC로 직송하는, 캐시를 user-space를 거치지 않고 활용하는 극단의 사례다. OffsetStore도 ConcurrentHashMap cache + Log append의 조합이라 본질이 캐싱과 같다.
  • — 큐의 "버퍼링"과 캐시의 "사본 보관"은 역할이 다르다. 큐는 처리 속도 차이를 흡수하고(시간축 분리), 캐시는 같은 데이터를 빠른 계층으로 끌어올린다(공간축 분리).
  • MSA — 분산 환경에선 로컬 캐시 정합성이 어렵다. CDC 기반 무효화(이벤트 드리븐)가 자주 등장한다.

§12. 주니어와 토론할 질문 모음

설명 후 던지면 "암기"인지 "이해"인지가 갈리는 질문들이다.

개념·정합성

  1. 캐시와 일반 저장소(DB)의 본질적 차이는? (사본 vs 원본, 유실 허용 여부)
  2. 적중률이 높으면 무조건 좋은 캐시인가?
  3. "DB 업데이트 후 캐시 업데이트" vs "DB 업데이트 후 캐시 삭제" — 무엇이 더 안전하고 왜?
  4. 로컬 캐시를 여러 인스턴스에서 쓰면 생기는 문제는? 어떻게 동기화할까? (CDC, pub/sub 무효화)

결정·트레이드오프

  1. 좋아요/조회수 / 유저 프로필 / 결제 잔액 — 각각 어떤 캐싱 전략과 TTL이 맞을까? 왜?
  2. 당신이 담당한 기능을 §9-6 2×2 매트릭스에 매핑해보라. ⚠️/❓ 영역에 들어가는 게 있다면, 캐시 도입 여부를 어떻게 결정할까?
  3. §9-3 단점 4개 중 당신 서비스에 가장 큰 리스크는 무엇이라 생각하나? 어떻게 완화할까?
  4. "캐싱하면 빨라진다"가 항상 참인가? 캐싱이 오히려 손해인 경우는? (낮은 적중률, 무거운 직렬화 비용, 정합성 버그로 인한 장애 비용)

장애·운영

  1. 캐시 서버(Redis)가 통째로 죽으면 우리 시스템은 어떻게 되는가? (캐시 의존도 점검 → fallback 전략)
  2. 캐시 스탬피드를 막는 방법을 2가지 이상 말해보라.
  3. CDN의 stale-while-revalidate는 어떤 장애 패턴을 자연스럽게 완화하는가?
  4. Bloom Filter는 false positive는 있지만 false negative는 없다. 이게 캐시 관통 방어에 왜 충분한가?

요약

캐싱은 느린 원본의 사본을 가까운 곳에 둬서 빠르게 만드는 기술이지만, 그 대가로 정합성과 추가적인 장애 표면(failure surface)을 떠안는 트레이드오프다.

그래서 캐시를 도입할 때는 "어떻게 캐싱할까"보다 "무엇을 캐싱하지 않을까", "얼마나 오래됨을 허용할까"를 먼저 따져봐야 한다.

#Cache#Redis#정합성#성능#Backend#Architecture

황호민

Backend Engineer · Java/Kotlin · Spring Boot · Next.js