캐싱 (Caching)
목차
캐싱이란
캐싱(Caching) 은 자주 조회되는 데이터를 원본 저장소보다 더 빠른 계층에 복사해 두고 재사용하는 기법입니다. 보통 데이터베이스, 외부 API, 파일 스토리지처럼 상대적으로 느린 원본 앞단에 두어 응답 시간을 줄이고 부하를 분산합니다.
캐시를 설명할 때 가장 중요한 전제는 다음 두 가지입니다.
- 원본 데이터가 기준: 캐시는 원본의 복사본이며, 정합성 판단 기준은 원본 데이터베이스나 원천 시스템입니다.
- 성능과 정합성의 교환: 캐시를 강하게 쓰면 성능은 좋아지지만, 만료 정책과 무효화 설계를 잘못하면 오래된 데이터를 노출할 수 있습니다.
캐시를 쓰는 이유
- 응답 시간 단축: 메모리 기반 캐시는 디스크 기반 저장소나 네트워크 호출보다 훨씬 빠른 응답을 제공합니다.
- 원본 저장소 부하 감소: 동일한 조회 요청이 반복될 때 데이터베이스 QPS와 CPU 사용량을 줄일 수 있습니다.
- 트래픽 급증 완충: 인기 상품, 메인 페이지, 세션 정보처럼 반복 조회가 많은 데이터를 흡수해 스파이크에 대응합니다.
- 비용 효율 개선: 데이터베이스 증설보다 캐시 도입이 더 저렴한 경우가 많습니다. 다만 캐시 자체도 운영 비용과 복잡도를 추가합니다.
캐시 배치 위치
애플리케이션 로컬 캐시
애플리케이션 프로세스 내부 메모리에 두는 방식입니다.
- 장점: 네트워크 홉이 없어 가장 빠릅니다.
- 단점: 인스턴스별 데이터가 달라질 수 있어 일관성 관리가 어렵습니다.
- 적합한 경우: 정적 설정값, 짧은 TTL의 소형 참조 데이터, 계산 비용이 큰 값
분산 캐시
Redis, Memcached처럼 여러 애플리케이션 인스턴스가 공유하는 별도 캐시 계층입니다.
- 장점: 여러 서버에서 같은 캐시를 공유할 수 있어 운영이 단순합니다.
- 단점: 네트워크 비용이 있고, 장애 시 전체 애플리케이션에 영향을 줄 수 있습니다.
- 적합한 경우: 세션, 조회 결과 캐시, 인기 데이터, 레이트 리밋, 분산 락
리버스 프록시와 CDN
HTTP 응답 자체를 캐싱하는 방식입니다.
- 리버스 프록시: Nginx, Varnish 등으로 API 응답이나 HTML을 캐싱
- CDN: 이미지, JS, CSS, 동영상처럼 정적 콘텐츠를 사용자 가까운 엣지에 캐싱
- 장점: 원본 서버까지 요청이 도달하지 않도록 줄일 수 있습니다.
- 단점: 캐시 무효화와 배포 전략을 함께 설계해야 합니다.
대표 캐싱 전략
Cache-Aside
가장 많이 쓰는 패턴입니다. 애플리케이션이 먼저 캐시를 조회하고, 캐시 미스면 원본에서 읽은 뒤 캐시에 채웁니다.1
- 캐시 조회
- 미스면 데이터베이스 조회
- 조회 결과를 캐시에 저장
- 다음 요청부터 캐시 히트
- 장점: 필요한 데이터만 캐시에 올라가서 메모리 효율이 좋습니다.
- 단점: 첫 요청은 느리고, 쓰기 후 캐시 무효화가 늦으면 stale data가 발생할 수 있습니다.
Write-Through
원본 저장소에 쓰는 시점에 캐시도 함께 갱신하는 전략입니다.1
- 장점: 읽기 시 캐시 히트 확률이 높고, 최신 데이터가 캐시에 있을 가능성이 큽니다.
- 단점: 자주 읽히지 않는 데이터까지 캐시에 기록되어 캐시 오염이 생길 수 있습니다.
Write-Behind
먼저 캐시에 쓰고, 이후 비동기로 원본 저장소에 반영하는 방식입니다.
- 장점: 쓰기 지연 시간이 짧고, 쓰기 burst를 흡수하기 좋습니다.
- 단점: 캐시 장애나 비동기 플러시 실패 시 데이터 유실 위험이 있습니다.
- 적합한 경우: 로그 집계, 통계 적재, 약간의 지연 허용이 가능한 워크로드
Write-Around
쓰기 요청은 원본 저장소로 바로 보내고, 캐시는 읽기 시점에만 채우는 방식입니다.
- 장점: 자주 읽히지 않는 데이터를 캐시에 올리지 않아 메모리를 아낄 수 있습니다.
- 단점: 쓰기 직후 첫 읽기는 캐시 미스로 인해 느릴 수 있습니다.
| 전략 | 읽기 성능 | 쓰기 비용 | 정합성 관리 | 주요 사용처 |
|---|---|---|---|---|
| Cache-Aside | 높음 | 낮음 | 무효화 설계 중요 | 일반 조회 캐시 |
| Write-Through | 높음 | 중간 | 상대적으로 단순 | 읽기 비중 높은 핵심 데이터 |
| Write-Behind | 매우 높음 | 낮음 | 장애 대응 복잡 | 로그, 집계, 버퍼링 |
| Write-Around | 중간 | 낮음 | 단순 | 읽기 편중이 약한 데이터 |
캐시 설계에서 중요한 포인트
- 캐시 키 설계:
user:123,product:list:category:books처럼 충돌이 없고 의미가 드러나는 키를 사용합니다. - TTL 설정: 너무 짧으면 히트율이 낮고, 너무 길면 오래된 데이터가 남습니다. 데이터 성격에 따라 TTL을 다르게 가져가야 합니다.
- 무효화 전략: 데이터 변경 시
delete후 재조회하게 할지, 새 값을 즉시 갱신할지 결정해야 합니다. - Eviction 정책: 메모리 한도에 도달하면 어떤 키를 제거할지 정해야 합니다. Redis는
maxmemory와 eviction policy를 설정할 수 있습니다.2 - 예열: 배포 직후나 장애 복구 직후 주요 키를 미리 채워 cold start를 줄일 수 있습니다.
- 관측 지표: hit ratio, miss ratio, eviction 수, latency, key 크기 분포, hot key 여부를 같이 봐야 합니다.
운영 중 자주 나오는 문제
- 캐시 불일치: DB는 갱신됐는데 캐시는 예전 값인 상태입니다. 가장 흔한 원인은 쓰기 후 무효화 누락입니다.
- 캐시 스탬피드: 인기 키가 동시에 만료되며 많은 요청이 원본으로 몰리는 현상입니다. 요청 병합, single flight, jittered TTL로 완화합니다.
- 캐시 페네트레이션: 존재하지 않는 키를 계속 조회해 캐시를 우회하는 문제입니다. null caching, Bloom filter, rate limiting으로 대응합니다.
- 캐시 아발란치: 많은 키가 같은 시점에 만료돼 전체 트래픽이 원본으로 쏠리는 현상입니다. TTL 분산이 중요합니다.
- Hot Key 문제: 특정 키 하나에 과도한 트래픽이 몰리는 상황입니다. key 분산, local cache 병행, 응답 캐싱 계층 추가를 검토합니다.
- 장애 전파: 분산 캐시가 느려지면 애플리케이션 전체 응답 시간이 함께 나빠질 수 있습니다. 타임아웃과 fallback이 필요합니다.
면접 포인트
- 캐시는 성능 최적화 수단이지 진실의 원천이 아닙니다.
- 읽기 비중이 큰 서비스에는 Cache-Aside가 기본 선택지입니다.
- 정합성이 중요한 데이터는 TTL만 믿지 말고 쓰기 시 무효화를 함께 설계해야 합니다.
- 히트율만 보면 부족하고, miss 시 원본이 얼마나 버틸 수 있는지도 함께 봐야 합니다.
- 캐시 도입은 평균 성능보다 피크 트래픽과 장애 상황을 얼마나 완화하는지가 더 중요합니다.