fos-blog/study
01 / 홈02 / 카테고리03 / 시리즈
01 / 홈02 / 카테고리03 / 시리즈

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • agent 페이지로 이동
    • claude-code 페이지로 이동
    • harness 페이지로 이동
    • llm 페이지로 이동
    • ops 페이지로 이동
    • practice 페이지로 이동
  • algorithm 페이지로 이동
    • Alias Method: 가중치 랜덤 선택을 전처리 O(n), 추출 O(1)로 바꾸기
    • Welford's Online Algorithm: 값을 저장하지 않고 평균과 분산 계산하기
  • architecture 페이지로 이동
    • distributed-systems 페이지로 이동
    • domain 페이지로 이동
    • evolution 페이지로 이동
    • patterns 페이지로 이동
  • database 페이지로 이동
    • milvus 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • postgresql 페이지로 이동
    • qdrant 페이지로 이동
    • redis 페이지로 이동
    • vespa 페이지로 이동
    • DB Connection Pool Saturation과 Thread Pool 격리
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • gaps and islands: SQL 창 함수로 이어진 구간 찾기
    • MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 한국어 형태소 분석기 Nori vs Lindera — OpenSearch에서 Milvus로 갈 때 어휘 검색은 유지되나
    • 정규화와 역정규화: 무결성과 조회 비용 사이의 선택
    • 벡터 DB 5종, 아키텍처는 어떻게 다른가
    • 벡터 DB 5종을 실제로 벤치마크했다 — 같은 recall에서 QPS는 얼마나 갈리나
    • 벡터 DB 어떻게 고를까 — OpenSearch · Milvus · Qdrant · Vespa · pgvector 비교
    • 벡터 DB를 실제로 도입한 사례 — 빅테크 프로덕션
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • Envoy Proxy
    • Graceful Shutdown
    • 종료 신호는 어디서 멈추는가
  • http 페이지로 이동
    • HTTP Connection Pool
    • HTTPS와 TLS: 핸드셰이크, 인증서, 종료 지점
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • testing 페이지로 이동
    • Alpine 이미지의 Java 에서 링크 바꿔치기(TOCTOU)를 막지 못하는 이유
    • Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • 로그에 traceId 남기기 — MDC 부터 OpenTelemetry 까지
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • `setImmediate()`
  • kafka 페이지로 이동
    • Kafka 클러스터 아키텍처: Broker, KRaft Controller와 복제
    • Kafka 기본 개념: Topic, Partition, Offset과 Segment
    • Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • Kafka 파티션·리밸런스·컨슈머 지연 운영
    • Kafka를 로그로 이해하기: 복제, 재처리, 상태와 데이터 통합
    • Spring Kafka 컨슈머 오프셋 커밋과 트랜잭션 정렬: AckMode, manual ack, 멱등 처리
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • SSH forced command 로 배포 키가 실행할 수 있는 명령 제한하기
  • mlops 페이지로 이동
    • llm-serving 페이지로 이동
    • model-router 페이지로 이동
    • Python CUDA 버전 생태계 — nvidia-smi, nvcc, pip, conda가 다 다른 버전을 말하는 이유
    • GPU 컨테이너의 CUDA 버전 호환성 — nvidia-smi부터 이미지 다이어트까지
    • Kubernetes GPU 노드에서 /run tmpfs가 꽉 차서 Pod가 안 뜰 때
    • GPU·CUDA·MPS 기초 — 자바 백엔드 개발자가 처음 만나는 그림
    • Multi-process GPU 워크로드 — 자바 ThreadPool 사용자가 만나는 모델 차이
    • ML 서비스 성능 분석 워크플로 — 자바 백엔드 트러블슈팅과 다른 점
    • 한 GPU 를 여러 프로세스가 나눠 쓰기 — Time-Slicing 과 MPS
  • network 페이지로 이동
    • Connection reset by peer는 누가 보낸 걸까 — 리버스 프록시 홉마다 TCP 연결은 따로 논다
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • observability 페이지로 이동
    • Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • K8s 위 Spring Boot 앱의 메트릭 수집
  • python 페이지로 이동
    • Python async/await — CompletableFuture·Reactor 와 다른 점, 그리고 blocking I/O 함정
    • Python 의존성 관리 — Java Maven/Gradle 사용자가 만나는 첫 충격
    • FastAPI 기초 — Spring Boot 사용자가 빠르게 익히는 법
    • Java 개발자를 위한 Python 심화 — OOP·데코레이터·컨텍스트 매니저
    • PyTorch 기초 — 텐서, 디바이스, 그리고 모델 로딩이 무거운 이유
    • Java 개발자를 위한 Python 문법 핵심
    • ThreadLocal 에서 contextvars 로 — Python 의 요청 컨텍스트 전파
    • OCR 동작 원리 — Layout · Text · Post-process 3단계
    • Python 서버의 RSS가 줄지 않는 이유: CPython, gc.collect(), malloc_trim
  • rabbitmq 페이지로 이동
    • RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • resume 페이지로 이동
    • 경험 및 경력기술서
    • 김병태 경력기술서
    • 김병태 포트폴리오
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • personal-projects 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • thinking 페이지로 이동
    • 자기주도 학습: 질문에서 시작해 글로 검증하기
    • 좋은 일을 넘어 중요한 일을 하는 법
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • Glossary↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/architecture/캐시 설계 전략 총정리
architecture

캐시 설계 전략 총정리

캐싱은 RAM 기반의 빠른 저장소를 활용해 DB 부하를 줄이고 응답 속도를 높이는 기술이다. RAM은 DB보다 수십수백 배 빠르지만 용량이 제한적이기 때문에 어떤 데이터를 어떻게 저장하고 갱신할지에 대한 전략이 필요하다. 기본 용어: Cache Hit: 캐시에 데이터가 있어 바로 반환 (빠름) Cache Miss: 캐시에 없어 DB에서 조회 후 캐시에 저장...

2026.09.03·12 min read·69 views

캐싱은 RAM 기반의 빠른 저장소를 활용해 DB 부하를 줄이고 응답 속도를 높이는 기술이다. RAM은 DB보다 수십~수백 배 빠르지만 용량이 제한적이기 때문에 어떤 데이터를 어떻게 저장하고 갱신할지에 대한 전략이 필요하다.

기본 용어:

  • Cache Hit: 캐시에 데이터가 있어 바로 반환 (빠름)
  • Cache Miss: 캐시에 없어 DB에서 조회 후 캐시에 저장 (느림, 비용)

캐싱 전략은 읽기 전략과 쓰기 전략으로 나뉘며, 실제로는 조합해서 쓴다.


읽기 전략 (Read Cache Strategy)

1. Look-Aside (Cache-Aside)

애플리케이션이 캐시와 DB를 직접 관리하며, 요청 시 캐시를 먼저 확인하고 미스 시에만 DB에서 읽어오는 방식이다. Lazy Loading이라고도 부른다.

동작 흐름

캐시 Hit:

plaintext
클라이언트
   ↓
[Cache-Aside Logic]
   ↓
캐시 조회 → Hit!
   ↓
캐시된 데이터 반환

캐시 Miss:

plaintext
클라이언트
   ↓
[Cache-Aside Logic]
   ↓
캐시 조회 → Miss
   ↓
DB 조회 (비용)
   ↓
데이터 캐시 적재 (TTL 설정)
   ↓
데이터 반환

쓰기:

plaintext
클라이언트 (Update 요청)
   ↓
DB 업데이트
   ↓
캐시 무효화 (Delete)
   ↓
다음 읽기 요청 시 자동 갱신

쓰기에서는 DB가 신뢰할 수 있는 소스(Source of Truth)가 되고, 캐시는 보조 역할만 수행한다.

장점

  • 캐시 장애 내결함성: 캐시가 다운돼도 DB에서 직접 읽으면 되므로 서비스가 계속 동작한다 (성능 저하는 있지만 가용성 유지)
  • 실제 사용 데이터만 캐싱: 모든 데이터를 무조건 올리지 않으므로 메모리 효율적
  • 유연한 TTL 관리: 데이터 특성별로 다른 만료 시간 설정 가능
  • 구현 단순: 캐시 로직이 코드에 명시적으로 드러나 이해하고 디버깅하기 쉽다

단점

  • Cold Start: 서비스 시작 직후 모든 요청이 캐시 미스 → DB 부하 급증
  • Stale Data: 캐시 무효화를 놓치면 구 데이터를 계속 제공할 수 있음
  • Cache Stampede: 만료 순간 많은 요청이 동시에 DB로 몰림

적합 시나리오

  • 반복적인 동일 쿼리가 많은 서비스
  • 읽기가 쓰기보다 훨씬 많은 경우
  • 캐시 장애 시에도 서비스가 유지되어야 하는 경우

2. Read-Through

캐시 라이브러리(또는 캐시 프로바이더)가 DB 동기화를 담당하는 방식이다. 애플리케이션은 항상 캐시에서만 읽고, 미스 발생 시 캐시 계층이 자동으로 DB를 조회해 캐시를 채운 뒤 반환한다.

동작 흐름

plaintext
클라이언트
   ↓
캐시 조회
   ↓ (Miss)
[캐시 계층이 자동으로]
   ↓
DB 조회
   ↓
캐시에 저장
   ↓
데이터 반환

애플리케이션 코드 입장에서는 항상 캐시에서 데이터를 가져오는 것처럼 보인다.

장점

  • 캐시-DB 항상 동기화: 데이터 일관성이 높음
  • 애플리케이션 로직 단순: 캐시 미스 처리 코드를 직접 작성하지 않아도 됨

단점

  • 캐시 장애 = 서비스 장애: Look-Aside와 달리 DB 폴백이 없어 캐시 다운 시 즉시 중단
  • 첫 요청은 느림: 마찬가지로 Cold Start 문제 존재
  • 고가용성 필수: Replication/Cluster 구성이 사실상 필수

적합 시나리오

  • 읽기 위주의 서비스
  • 캐시 운영 안정성이 충분히 확보된 환경
  • 데이터 동기화 로직을 직접 관리하고 싶지 않을 때

쓰기 전략 (Write Cache Strategy)

3. Write-Through

쓰기 요청이 들어오면 캐시와 DB를 동시에(동기) 업데이트하는 방식이다. 항상 두 곳이 같은 데이터를 가진다.

동작 흐름

plaintext
클라이언트 (Write 요청)
   ↓
캐시에 저장
   ↓
즉시 DB에 저장 (동기)
   ↓
완료 응답

장점

  • 데이터 일관성 최고: 캐시와 DB가 항상 동기화된 상태
  • 데이터 유실 없음: 캐시 장애 시에도 DB에 데이터 존재

단점

  • 쓰기 지연: 매 쓰기마다 캐시 + DB 두 번 처리하므로 응답 시간이 길어짐
  • 미사용 데이터 낭비: 한 번 쓰고 다시 읽지 않는 데이터도 캐시에 남아 메모리 압박
  • TTL 설정 필수: 미사용 데이터를 자동으로 정리하지 않으면 캐시 용량 문제 발생

적합 시나리오

  • 데이터 손실이 절대 허용되지 않는 중요 정보
  • Read-Through와 함께 쓸 때 가장 효과적 (AWS DynamoDB Accelerator 등)

4. Write-Behind (Write-Back)

캐시에 먼저 쓰고, 일정 시간 이후 배치 작업으로 DB에 비동기 반영하는 방식이다. 캐시가 임시 큐 역할을 한다.

동작 흐름

plaintext
클라이언트 (Write 요청)
   ↓
캐시에 저장 (즉시 응답)
   ↓
[비동기 - 일정 시간 후]
   ↓
배치로 DB에 반영

장점

  • 쓰기 성능 최고: 캐시에만 쓰고 즉시 응답하므로 쓰기 응답 속도가 매우 빠름
  • DB 부하 대폭 감소: 여러 쓰기를 모아서 배치 처리하므로 DB 쿼리 횟수 감소

단점

  • 데이터 유실 위험: 캐시 장애 시 아직 DB에 반영되지 않은 데이터 영구 손실 가능
  • 일시적 데이터 불일치: DB에 즉시 반영되지 않으므로 다른 시스템이 DB를 직접 읽으면 구 데이터를 볼 수 있음
  • 구현 복잡도 높음: 배치 처리 로직, 장애 복구 로직이 별도로 필요

필수 조치

  • 캐시 Replication/Cluster 구성 (단일 장애점 제거)
  • TTL 설정으로 미사용 데이터 정리

적합 시나리오

  • 쓰기가 매우 빈번한 서비스 (좋아요, 조회수, 실시간 통계)
  • 데이터 일시적 불일치를 허용할 수 있는 경우

5. Write-Around

모든 쓰기를 DB에만 저장하고, 캐시는 읽기 요청 시 미스가 발생할 때만 채워지는 방식이다.

동작 흐름

plaintext
클라이언트 (Write 요청)
   ↓
DB에 직접 저장 (캐시 우회)
   ↓
캐시는 갱신 안 함
 
클라이언트 (Read 요청)
   ↓
캐시 조회 → Miss
   ↓
DB 조회
   ↓
캐시에 저장

장점

  • 쓰기 성능 최고: 캐시를 거치지 않으므로 쓰기 속도 빠름
  • 불필요한 캐싱 방지: 한 번 쓰고 다시 읽지 않는 데이터가 캐시를 차지하지 않음

단점

  • 캐시-DB 불일치 위험: DB는 업데이트됐는데 캐시는 그대로인 상태 발생
  • TTL 만료 전까지 구 데이터 제공: 명시적으로 캐시를 삭제하지 않으면 오래된 데이터를 계속 반환

필수 조치

  • 데이터 변경 시 캐시 명시적 삭제 (Invalidation)
  • 짧은 TTL 설정으로 자동 만료

적합 시나리오

  • 쓰기 후 해당 데이터를 곧바로 읽는 경우가 드문 경우
  • Look-Aside와 조합해서 쓸 때 일반적 (가장 흔한 조합)

조합 전략

단일 전략보다는 읽기/쓰기 전략을 조합해서 사용하는 것이 일반적이다.

Look-Aside + Write-Around (가장 일반적)

  • 읽기: 캐시 먼저 확인 → 미스 시 DB 조회 후 캐시 저장
  • 쓰기: DB에만 저장, 캐시 무효화
  • 특징: 구현이 단순하고 캐시 장애에도 서비스 지속 가능
  • 용도: 대부분의 일반 웹 서비스

Read-Through + Write-Through

  • 읽기: 캐시 계층이 자동으로 DB 동기화
  • 쓰기: 캐시+DB 동시 업데이트
  • 특징: 데이터 정합성이 가장 높은 조합
  • 사례: AWS DynamoDB Accelerator (DAX)

Read-Through + Write-Around

  • 읽기는 캐시 우선, 쓰기는 DB 직행
  • DB 접근 횟수를 최소화하면서도 정합성 안전장치 유지

전략 비교표

전략읽기 성능쓰기 성능데이터 정합성DB 부하구현 복잡도캐시 장애 시
Look-Aside중간 (첫 요청 느림)보통약함 (Stale 위험)높음낮음서비스 유지 (성능 저하)
Read-Through높음보통높음낮음중간서비스 중단
Write-Through보통낮음 (2배 처리)최고낮음중간데이터 안전
Write-Behind높음최고약함 (비동기)최저높음데이터 유실 위험
Write-Around중간최고약함 (캐시 우회)높음낮음서비스 유지

주요 문제와 해결책

1. Cold Start (캐시 워밍 필요)

서비스 시작 직후나 캐시 리셋 후 모든 요청이 캐시 미스가 되어 응답 시간이 크게 증가하는 현상이다.

해결 방법:

  • Pre-warming(사전 로딩): 서비스 시작 시 자주 접근하는 데이터를 미리 캐시에 로드
  • 점진적 트래픽 증가: 배포 후 트래픽을 단계적으로 늘려 캐시를 자연스럽게 워밍
  • 관리 API 제공: 운영자가 필요시 캐시를 수동으로 갱신할 수 있는 엔드포인트

2. Stale Data (캐시 데이터 불일치)

DB는 업데이트됐는데 캐시가 아직 만료되지 않아 구 데이터를 제공하는 현상이다.

plaintext
17:00 - 상품 가격 1,000원으로 업데이트
       → 캐시 무효화 완료
17:00:01 - 사용자 A 조회 → 캐시 Miss → DB에서 1,000원 조회, 캐시 갱신
17:00:59 - 사용자 B 조회 → 캐시 Hit → 1,000원 반환 ✅
 
만약 캐시 무효화를 놓쳤다면?
17:00 - 상품 가격 1,000원으로 업데이트 (캐시 무효화 안 함!)
17:00:01 ~ 17:00:59 - 모든 사용자가 구 가격 900원을 받음 ❌
17:01:00 - TTL 만료 → 다음 요청부터 1,000원 반환

해결 방법:

  • 명시적 캐시 무효화: DB 업데이트 직후 캐시 삭제 코드를 즉시 실행하도록 설계
  • TTL 최소화: 데이터 변경 빈도에 맞춰 적절한 TTL 설정
  • 이벤트 기반 무효화: 도메인 이벤트 발행 → 별도 리스너가 캐시 삭제 (Kafka 등)
  • Refresh-Ahead(백그라운드 갱신): TTL 종료 전 자동으로 데이터를 미리 갱신

3. Cache Stampede (캐시 폭풍)

TTL이 만료되는 순간, 많은 스레드/서버가 동시에 캐시 미스를 감지하고 모두 DB를 조회하려는 현상이다.

plaintext
10,000명의 사용자가 동시에 같은 상품 조회
   ↓
TTL 만료로 모두 캐시 미스 감지
   ↓
10,000개의 DB 쿼리가 동시 실행 ← Duplicate Read
   ↓
10,000개의 캐시 쓰기 동시 발생 ← Duplicate Write
   ↓
DB 연결 풀 고갈 / CPU 스파이크 / 타임아웃

해결 방법:

a) Lock 기반 접근

plaintext
첫 번째 요청: 캐시 미스 감지 → Lock 획득 → DB 조회 → 캐시 저장 → Lock 해제
다른 요청들: 캐시 미스 감지 → Lock 대기 → Lock 해제 후 캐시에서 읽기

안전하지만, Lock 대기 시간에 따라 응답 지연 발생 가능.

b) Probabilistic Early Expiration (확률적 조기 갱신)

plaintext
TTL을 두 부분으로 나눔
- TTL = 100초
- Soft TTL = 80초 (80% 시점)
 
80초 경과 후 일정 확률(예: 5%)로 백그라운드 갱신 시작
→ 대부분의 요청은 캐시 히트, 극소수만 DB 조회
→ 캐시 폭풍 가능성 대폭 감소

c) TTL 길게 설정

가장 단순한 방법. TTL이 너무 짧으면 자주 만료되고 스탬피드 위험이 커진다. 데이터 특성을 고려해 충분히 길게 설정.


캐시 제거 정책 (Eviction Policy)

메모리가 부족할 때 어떤 데이터를 먼저 제거할지에 대한 정책이다.

TTL 설정의 중요성

TTL문제점
너무 짧음데이터가 빨리 제거됨 → 캐시 효율 낮음, Stampede 위험
너무 긺오래된 데이터 계속 제공 → 메모리 부족, 정합성 문제

원칙: 데이터 특성에 따라 차등 설정

  • 자주 변경되는 데이터 → 짧은 TTL
  • 변경이 드문 마스터 데이터 → 길거나 수동 만료

Redis Eviction 정책 (메모리 부족 시 자동 제거)

정책설명적합 시나리오
LRU (Least Recently Used)최근에 사용하지 않은 것부터 제거일반적인 캐시
LFU (Least Frequently Used)사용 빈도가 낮은 것부터 제거핫 데이터 유지가 중요할 때
Random무작위 제거균등한 접근 패턴
TTL 기반만료 임박한 항목부터 제거TTL이 명확히 설정된 캐시
noeviction제거 안 함 → 메모리 부족 시 에러 반환데이터 유실이 절대 안 될 때

동시성과 데이터 정합성

여러 애플리케이션 인스턴스가 동일한 캐시에 동시 접근할 때 발생하는 문제다.

문제 시나리오

plaintext
서버 A: 캐시 조회 → Miss → DB 조회 시작
서버 B: 캐시 조회 → Miss → DB 조회 시작 (동시)
서버 A: DB에서 v1 읽어서 캐시 저장
서버 B: DB에서 v1 읽어서 캐시 저장 (중복 쓰기)
→ 중복 DB 조회 발생 (Duplicate Read)

해결 방법

Optimistic Lock(낙관적 잠금)

  • 데이터 조회 후 변경 전에 버전 확인
  • 다른 곳에서 먼저 변경했으면 재시도
  • 적합: 충돌이 드문 상황, 읽기 위주

Pessimistic Lock(비관적 잠금)

  • 업데이트 전 Lock 획득, Lock 해제까지 다른 접근 차단
  • 적합: 빈번한 업데이트, 충돌이 잦은 소규모 데이터

캐싱 대상 선택 기준

캐시는 제한된 메모리 자원이므로 무엇을 캐싱할지 선택이 중요하다.

파레토 법칙 (80:20 법칙) 적용

"전체 요청의 80%는 전체 데이터의 20%에 집중된다." 상위 20%의 핫 데이터만 캐싱해도 대부분의 요청을 커버할 수 있다.

캐싱에 적합한 데이터

  • 자주 조회되는 데이터 (읽기 빈도 높음)
  • 변경이 드문 데이터 (마스터 데이터, 상품 카테고리 등)
  • 조회 시 복잡한 연산이 필요한 데이터 (집계, 랭킹 등)
  • 일시적 불일치를 허용할 수 있는 데이터

캐싱에 부적합한 데이터

  • 민감 정보, 보안 데이터 (유출 위험)
  • 실시간 일관성이 절대적으로 필요한 데이터 (금융 거래, 실시간 재고)
  • 매우 자주 변경되는 데이터 (캐시 갱신 비용이 캐시 이득을 상회)
  • 접근 빈도가 낮아 캐시 히트율을 기대하기 어려운 데이터

실제 사례: 대규모 커머스 MSA 환경

대규모 커머스 기술 블로그에서 공유된 "MSA 환경에서 도메인 데이터 연동 전략" 사례:

변경이 적은 데이터에 Cache-Aside(Look-Aside) 패턴 적용

plaintext
상품 정보, 카테고리, 옵션 데이터 (변경 빈도: 하루 1~2회)
   ↓
Redis Cache-Aside + TTL 1시간 설정
   ↓
첫 접근 시 캐시 미스 → DB 조회 (비용 발생)
다음 59분간 → 캐시 히트 (매우 빠름)
   ↓
변경 발생 시 명시적으로 캐시 키 삭제
다음 요청 시 자동으로 최신 데이터 캐싱

실시간성이 필요한 데이터는 별도로 Kafka 이벤트 기반으로 처리해 불필요한 API 호출을 줄이면서도 최신 데이터를 보장했다.

관련 문서: MSA 서비스 간 통신 — Cache-Aside × Kafka 이벤트 하이브리드


캐시 가용성 설계 원칙

"캐시는 성능 향상을 위한 것이지, 데이터 영속성을 보장하는 곳이 아니다."

  1. 데이터 영속성은 DB에 위임 — Write-Through 또는 Write-Around로 DB를 항상 Source of Truth로 유지
  2. 캐시 다운 시에도 서비스 지속 — Look-Aside + DB 폴백 로직 구현
  3. 캐시에 중요 정보 저장 금지 — 손실 가능성을 항상 고려
  4. Replication 또는 Cluster 구성 — 단일 장애점 제거
  5. 모니터링과 알림 설정 — 캐시 히트율, 미스율, 메모리 사용량 추적

구현 시 체크리스트

  • 캐싱 전략 선택 완료 (Look-Aside / Read-Through / 조합)
  • 쓰기 전략 결정 (Write-Through / Write-Behind / Write-Around)
  • 데이터 특성별 TTL 설정 완료
  • 메모리 용량 및 Eviction 정책 결정
  • 캐시 무효화 로직이 쓰기 작업 직후에 즉시 실행되는가?
  • 캐시 미스 시 다중 스레드의 동시 DB 조회를 방지했는가? (Lock 또는 Probabilistic)
  • 캐시 장애 시 폴백 전략 존재 여부 (DB 직접 조회)
  • Replication/Cluster 구성 (고가용성)
  • Cold Start 대응 전략 (Pre-warming 또는 점진적 로딩)
  • 캐시 히트율, 미스율, 응답 시간 모니터링 설정
  • Cache Stampede 방지 메커니즘 구현 여부
  • 데이터 정합성이 중요한 부분에 Lock 또는 버전 관리 적용 여부

참고

  • REDIS 📚 캐시(Cache) 설계 전략 지침 총정리
on this page
  • 01읽기 전략 (Read Cache Strategy)
  • 1. Look-Aside ([Cache-Aside](../../database/redis/cache-aside.md))
  • 2. Read-Through
  • 02쓰기 전략 (Write Cache Strategy)
  • 3. Write-Through
  • 4. Write-Behind (Write-Back)
  • 5. Write-Around
  • 03조합 전략
  • Look-Aside + Write-Around (가장 일반적)
  • Read-Through + Write-Through
  • Read-Through + Write-Around
  • 04전략 비교표
  • 05주요 문제와 해결책
  • 1. Cold Start (캐시 워밍 필요)
  • 2. Stale Data (캐시 데이터 불일치)
  • 3. Cache Stampede (캐시 폭풍)
  • 06캐시 제거 정책 (Eviction Policy)
  • TTL 설정의 중요성
  • Redis Eviction 정책 (메모리 부족 시 자동 제거)
  • 07동시성과 데이터 정합성
  • 문제 시나리오
  • 해결 방법
  • 08캐싱 대상 선택 기준
  • 파레토 법칙 (80:20 법칙) 적용
  • 캐싱에 적합한 데이터
  • 캐싱에 부적합한 데이터
  • 09실제 사례: 대규모 커머스 MSA 환경
  • 10캐시 가용성 설계 원칙
  • 11구현 시 체크리스트
  • 12참고
tags
#study

이런 글도

  • architecture

    코드 제거 작업의 grep 통과 조건과 문서 부패

    2026.10.02
  • architecture

    분산 트랜잭션과 Outbox 패턴 — 왜 2PC를 피하고 어떻게 대신할 것인가

    2026.09.03
  • architecture

    분산 트랜잭션

    2026.09.03
  • architecture

    Event Sourcing과 CQRS — 상태가 아니라 변화를 저장한다는 발상

    2026.09.03

댓글 (0)