Cache의 진짜 역할과 오해
“캐시를 쓰면 빠르다”는 말은 반은 맞고, 반은 틀리다.
개발자라면 한 번쯤 이런 말을 들어봤을 겁니다.
"속도가 느리면 캐시 써."
"Redis 넣으면 빨라질 거야."
하지만 시스템 설계에서 캐시는 단순한 성능 향상을 위한 ‘빠른 저장소’ 그 이상입니다.
오늘은 실전 시스템 디자인 관점에서 캐시의 진짜 역할,
그리고 많은 팀들이 빠지는 캐시의 오해를 함께 정리해 보겠습니다.
캐시는 성능 향상이 목적이 아니라, 병목 회피와 구조 보존이 핵심입니다
Redis나 Memcached를 단순히 "속도 빠른 DB"처럼 취급하는 팀이 꽤 많습니다.
그러나 진짜 캐시의 쓰임새는 데이터 소스에 직접 접근하는 비용을 줄이는 데 있습니다.
예를 들어보죠.
글로벌 트래픽이 몰리는 뉴스 피드 API가 있다고 합시다.
모든 요청마다 DB에 실시간으로 접근한다면, 아무리 RDB나 NoSQL을 써도 결국은 병목이 발생합니다.
그래서 응답을 일정 시간 캐시하고, DB 부하를 줄이기 위해 쓰는 것입니다.
즉, 캐시가 없다면 트래픽이 많아질수록 원본 데이터베이스의 안정성도 함께 무너집니다.
캐시는 "속도를 높이기 위한 도구"가 아니라, "병목을 회피하고, 시스템 전체 구조를 보호하는 도구"입니다.
“어디까지 캐시할까보다, 언제 캐시를 포기할지 정하는 게 더 중요합니다.”
많은 개발자들이 캐시를 “성능 최적화의 끝판왕”이라 생각합니다. Redis, Memcached, CDN, Local Memory까지 쌓고 또 쌓지만, 실무에선 이렇게 묻는 게 더 중요합니다.
“이 캐시는 장애 날 때 무너져도 괜찮은가요?”
캐시는 왜 빠른가?
캐시(Cache)는 결국 자주 요청되는 데이터를 더 가까운 곳에 두는 전략입니다. 예를 들어,
DB 쿼리 결과를 Redis에 저장해두고 다음엔 Redis에서 읽기
이미지 리소스를 CDN에 올려서 브라우저가 근처 서버에서 직접 받기
최근 조회한 게시물을 메모리에 저장해서 다시 DB 요청 안 하게 하기
이는 기본적으로 I/O 비용을 줄여서 처리 시간을 낮추는 방식입니다. 메모리는 디스크보다, 디스크는 네트워크보다 빠릅니다.
= 가까울수록 빠르다.
Read-through Cache 전략
캐시에 없으면 DB에서 읽고, 그 값을 캐시에 넣는 방식
장점: 사용자는 항상 최신 데이터와 가까운 값을 봄
단점: 캐시 미스 시 응답 속도 느려짐
Write-through Cache 전략
데이터를 DB와 캐시에 동시에 씀
장점: 캐시와 DB가 항상 동기화됨
단점: 쓰기 성능이 떨어질 수 있음
Write-around Cache 전략
쓰기 시엔 캐시를 건너뛰고 DB에만 저장
장점: 불필요한 캐시 오염 줄임
단점: 읽기 직후 캐시 미스 발생 가능
캐시를 쓰면 안 되는 경우도 많습니다
특히 다음과 같은 경우엔 캐시가 독이 됩니다.
정합성이 중요한 데이터
→ 예시) 결제 잔액, 재고 수량, 실시간 게임 상태 등
→ 이런 데이터를 TTL 기반 캐시에 넣으면 동기화 지연으로 인해 신뢰도가 무너질 수 있습니다.업데이트가 빈번한 데이터
→ Write가 많고 Read가 적은 경우, 캐시 적중률(hit rate)이 낮아지고 오히려 오버헤드만 생깁니다.캐시 무효화 전략이 없는 경우
→ 캐시 무효화(invalidation)를 고려하지 않고 Redis만 도입하는 건, 지뢰를 묻는 것과 같습니다.
실전에서는 Layered Cache 전략이 중요합니다
Netflix, Amazon, Meta는 모두 단일 캐시 계층을 쓰지 않습니다.
보통 다단 캐시(L1, L2, Regional Cache) 구조를 통해 다음과 같은 목적을 달성합니다.
L1 (인메모리, Local cache)
→ 서버 자체에 보관. 네트워크 요청조차 없다. (예: LRU 캐시)L2 (Distributed cache, Redis)
→ 다수 인스턴스가 공유하는 캐시. 네트워크를 타지만 DB보단 빠름.Edge cache (CDN)
→ 글로벌 유저에게 빠르게 콘텐츠 제공. Static한 응답에 최적화.
이렇게 계층화하면 속도 + 확장성 + 장애 회복을 모두 고려한 아키텍처 설계가 가능해집니다.
캐시의 적중률(Hit Rate)보다 중요한 것 - 정확한 사용 목적
많은 사람들이 캐시를 설계할 때 적중률을 90%, 95%로 끌어올리는 데 집중합니다.
하지만 진짜 중요한 질문은 이겁니다
“왜 캐시를 쓰는가?”
“어떤 병목을 막기 위한가?”
“캐시가 실패했을 때 fallback 전략은 있는가?”
이 질문에 답하지 못한 채 Redis만 붙여서 "속도 빨라졌어요"라고 말하는 건
마치 Node.js로 Kafka 연결만 해놓고 "마이크로서비스 완성"이라고 말하는 것과 같습니다.
캐시는 구성요소지 만능열쇠가 아니다
Node.js로 설계된 마이크로서비스에서 EventEmitter나 worker_threads를 쓰는 것이
더 나은 선택일 수 있는 것처럼,
캐시도 마찬가지입니다.
때로는 PostgreSQL의 Materialized View,
RLS 기반의 DB 레벨 필터링,
Precomputed Results나 배치 전처리가 더 나은 해법일 수 있습니다.
즉,
"캐시부터 붙이자"는 생각이 아니라,
"문제의 본질이 캐시로 풀릴 문제인지"를 먼저 진단해야 합니다.
그래서 실무에선 아래와 같은 질문이 먼저 나와야 합니다:
캐시가 없어도 서비스는 돌아가는가?
(아니라면 이건 캐시가 아니라 핵심 저장소입니다)캐시된 데이터는 얼마만큼 오래돼도 괜찮은가?
(초 단위인지, 분 단위인지, 하루 단위인지 기준을 정해야 합니다)누가 캐시를 갱신해야 하나?
(사용자 요청? 배치 프로세스? 백그라운드 워커?)
최적화 방안
TTL(Time To Live)을 전략적으로 설정하라
너무 짧으면 DB 부하 급증
너무 길면 오래된 정보 노출
캐시 Invalidation을 먼저 고민하라
갱신이 안 되는 캐시는 성능 이득보다 더 큰 장애 유발
장애 상황에서 캐시 제거 시 fallback을 설계하라
Redis 터졌다고 전체 서비스 500 나면 안 됨
Prometheus로 캐시 히트율 모니터링하라
캐시 적중률 < 40%면 잘못된 캐시 전략일 가능성
좋은 캐시 설계는 "무언가를 덜어주는 설계"다
Netflix의 캐시는 응답 속도를 높이기 위한 것이 아니라,
백엔드 서비스의 부하 분산과 글로벌 사용자 경험 품질 보장을 위한 전략입니다.
Amazon은 상품 정보의 지역별 TTL 캐싱을 통해
광범위한 읽기 요청에 대한 글로벌 확장성을 확보했습니다.
어떤 이유로 캐시를 쓰고 있나요?
시뮬레이션 시나리오
상황
당신은 XYZ Music이라는 글로벌 음악 스트리밍 플랫폼의 인프라 리드 엔지니어입니다. 사용자가 좋아요(Like) 한 곡을 빠르게 저장하고, 나중에 "좋아요한 곡 목록"을 볼 수 있는 기능이 있습니다. 현재는 MySQL DB에 직접 저장/조회하고 있으며, 유저 수는 5천만 명, 곡 수는 1억 개, 하루 5억 개의 좋아요 이벤트가 발생합니다.
문제
최근 "좋아요한 곡 목록 로딩"이 느리다는 불만이 많아졌고,
일부 유저는 조회 시 타임아웃이 발생하고 있습니다.
PM은 사용자 경험을 개선해달라고 요청했습니다.
좋아요 기능의 조회 성능을 개선하기 위한 아키텍처를 설계해보세요.
단, 좋아요 반영은 5분 이내 eventual consistency여도 됩니다.
좋아요 목록은 최대 1,000개까지만 보여주면 됩니다.
해설
핵심 문제
SELECT성능 병목
원인
유저마다 좋아요한 곡 수가 많음
DB 인덱스 과부하
캐시 미적중 시 DB에 부하 집중
성능 요구 기대치
P95 응답 시간 200ms 미만
조회는 최신 1,000개까지만 필요
5분 이내 eventual consistency 허용 (즉각 반영 아님)
아키텍처 제안
(1) Read Path에 캐시 계층 추가
유저별 좋아요 목록을 Redis Sorted Set에 캐싱
Key:
user:{user_id}:liked_songsValue: Sorted set of song_id by timestamp
Expire: 10분 TTL (갱신 시 연장)
요청 시 Redis에서 최대 1,000개만
ZREVRANGE로 조회Redis 미적중 시 → DB → 캐시에 다시 저장
(2) Write Path는 비동기화 (쓰기 비용 절감)
유저가 좋아요를 눌렀을 때
Kafka에 이벤트 전송
Background Consumer가 DB와 Redis에 비동기 반영
DB는 MySQL Append Only Table
Redis는
ZADD로 Push
장점
조회가 매우 빠르고 확장성 있음 (Redis read: O(log N))
비동기 처리를 통해 write-path 트래픽 분산
eventual consistency 허용으로 데이터 정확도 손해 없이 UX 향상
트레이드오프
Redis 장애 시 fallback이 느려짐 → Circuit Breaker + DB fallback + timeout cache
Kafka consumer 장애 시 이벤트 유실 가능 → Exactly-once delivery 설계 필요
캐시 용량 관리 필요 → LRU 정책 or 사용자 Top-N만 유지
추가적인 스케일링 전략
Redis 클러스터 샤딩 (user_id 기반)
MySQL은 쓰기 전용 Append table + 주기적 Compact
Prometheus + Grafana로 캐시 hit rate 모니터링
Redis eviction 시 메시지 큐를 통해 재warm 처리
면접 대비 질문
Q: Redis 장애 시에는?
A: DB fallback + 타임아웃 캐시 + Circuit Breaker 설계로 UX 보호Q: Kafka 누락 시 consistency는?
A: offset commit 기준 exactly-once 보장, checkpoint 기반 재시도 큐 사용Q: 왜 NoSQL 안 썼는가?
A: 단일 테이블 append 구조가 효율적이고, 정렬된 조회에선 RDB+Redis가 더 간결함


안녕하세요.
질문이 있어서 댓글드립니다.
Write-around Cache 전략
쓰기 시엔 캐시를 건너뛰고 DB에만 저장
장점: 불필요한 캐시 오염 줄임
단점: 읽기 직후 캐시 미스 발생 가능
단점에 “쓰기 직후 캐시 미스 발생 가능” 의 오타가 맞는걸까요?