📚FOS Study
홈카테고리
홈카테고리

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • agents 페이지로 이동
    • BMAD Method — AI 에이전트로 애자일 개발하는 방법론
    • Claude Code의 Skill 시스템 - 개발자를 위한 AI 자동화의 새로운 차원
    • Claude Code 멀티 에이전트 — Teams
    • 멀티모달 LLM (Multimodal Large Language Model)
  • architecture 페이지로 이동
    • 캐시 설계 전략 총정리
    • 디자인 패턴
    • 분산 트랜잭션
  • css 페이지로 이동
    • FlexBox 페이지로 이동
  • database 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • redis 페이지로 이동
    • 김영한의-실전-데이터베이스-설계 페이지로 이동
    • 커넥션 풀 크기는 얼마나 조정해야할까?
    • 인덱스 - DB 성능 최적화의 핵심
    • 역정규화 (Denormalization)
    • 데이터 베이스 정규화
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • k8s-in-action 페이지로 이동
    • monitoring 페이지로 이동
  • go 페이지로 이동
    • Go 언어 기본 학습
  • http 페이지로 이동
    • HTTP Connection Pool
  • interview 페이지로 이동
    • 210812 페이지로 이동
    • 뱅크샐러드 AI Native Server Engineer
    • CJ 올리브영 지원 문항
    • CJ 올리브영 커머스플랫폼유닛 Back-End 개발 지원 자료
    • 마이리얼트립 - Platform Solutions실 회원주문개발 Product Engineer
    • NHN 서비스개발센터 AI서비스개발팀
    • nhn gameenvil console backend 직무 인터뷰 준비
    • 면접을 대비해봅시다
    • Tossplace Node.js Developer
    • 토스플레이스 Node.js 백엔드 컬처핏
  • java 페이지로 이동
    • jdbc 페이지로 이동
    • opentelemetry 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • 더_자바_코드를_조작하는_다양한_방법 페이지로 이동
    • Java의 로깅 환경
    • MDC (Mapped Diagnostic Context)
    • OpenTelemetry 란 무엇인가?
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • Data_Structures_and_Algorithms 페이지로 이동
    • Heap 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node.js
    • npm vs pnpm 선택기준은 무엇인가요?
    • `setImmediate()`
  • kafka 페이지로 이동
    • Kafka 기본
    • Kafka를 사용하여 **데이터 정합성**은 어떻게 유지해야 할까?
    • 메시지 전송 신뢰성
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • tmux — Terminal Multiplexer
  • network 페이지로 이동
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • react 페이지로 이동
    • JSX 페이지로 이동
    • VirtualDOM 페이지로 이동
    • v16 페이지로 이동
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • the-future-company 페이지로 이동
📚FOS Study

개발 학습 기록을 정리하는 블로그입니다.

바로가기

  • 홈
  • 카테고리

소셜

  • GitHub
  • Source Repository

© 2025 FOS Study. Built with Next.js & Tailwind CSS

목록으로 돌아가기
🏗️architecture

캐시 설계 전략 총정리

약 11분
2026년 3월 27일
GitHub에서 보기

캐시 설계 전략 총정리

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

기본 용어:

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

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


읽기 전략 (Read Cache Strategy)

1. Look-Aside (Cache-Aside)

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

동작 흐름

캐시 Hit:

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

캐시 Miss:

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

쓰기:

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

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

장점

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

단점

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

적합 시나리오

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

2. Read-Through

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

동작 흐름

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

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

장점

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

단점

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

적합 시나리오

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

쓰기 전략 (Write Cache Strategy)

3. Write-Through

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

동작 흐름

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

장점

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

단점

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

적합 시나리오

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

4. Write-Behind (Write-Back)

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

동작 흐름

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

장점

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

단점

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

필수 조치

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

적합 시나리오

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

5. Write-Around

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

동작 흐름

클라이언트 (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는 업데이트됐는데 캐시가 아직 만료되지 않아 구 데이터를 제공하는 현상이다.

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를 조회하려는 현상이다.

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

해결 방법:

a) Lock 기반 접근

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

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

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

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제거 안 함 → 메모리 부족 시 에러 반환데이터 유실이 절대 안 될 때

동시성과 데이터 정합성

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

문제 시나리오

서버 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) 패턴 적용

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

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

관련 문서: MSA 환경에서 도메인 데이터 연동 전략


캐시 가용성 설계 원칙

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

  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) 설계 전략 지침 총정리
architecture 카테고리의 다른 글 보기수정 제안하기

댓글

댓글을 불러오는 중...
목차
  • 캐시 설계 전략 총정리
  • 읽기 전략 (Read Cache Strategy)
  • 1. Look-Aside (Cache-Aside)
  • 동작 흐름
  • 장점
  • 단점
  • 적합 시나리오
  • 2. Read-Through
  • 동작 흐름
  • 장점
  • 단점
  • 적합 시나리오
  • 쓰기 전략 (Write Cache Strategy)
  • 3. Write-Through
  • 동작 흐름
  • 장점
  • 단점
  • 적합 시나리오
  • 4. Write-Behind (Write-Back)
  • 동작 흐름
  • 장점
  • 단점
  • 필수 조치
  • 적합 시나리오
  • 5. Write-Around
  • 동작 흐름
  • 장점
  • 단점
  • 필수 조치
  • 적합 시나리오
  • 조합 전략
  • Look-Aside + Write-Around (가장 일반적)
  • Read-Through + Write-Through
  • Read-Through + Write-Around
  • 전략 비교표
  • 주요 문제와 해결책
  • 1. Cold Start (캐시 워밍 필요)
  • 2. Stale Data (캐시 데이터 불일치)
  • 3. Cache Stampede (캐시 폭풍)
  • a) Lock 기반 접근
  • b) Probabilistic Early Expiration (확률적 조기 갱신)
  • c) TTL 길게 설정
  • 캐시 제거 정책 (Eviction Policy)
  • TTL 설정의 중요성
  • Redis Eviction 정책 (메모리 부족 시 자동 제거)
  • 동시성과 데이터 정합성
  • 문제 시나리오
  • 해결 방법
  • 캐싱 대상 선택 기준
  • 파레토 법칙 (80:20 법칙) 적용
  • 캐싱에 적합한 데이터
  • 캐싱에 부적합한 데이터
  • 실제 사례: 올리브영 MSA 환경
  • 캐시 가용성 설계 원칙
  • 구현 시 체크리스트
  • 참고