캐시 (Cache)
Server개념
자주 사용하는 데이터나 값을 미리 저장해 놓는 임시 저장소
서버나 데이터베이스에 가해지는 부하를 줄이고 성능을 높이기 위해 사용한다.
자주 변경되고 삭제되는 데이터에 적용하면 오히려 성능 저하를 일으킬 수 있다.
기본 동작
- 클라이언트가 데이터를 요청하면 캐시에 해당 데이터가 있는지 확인한다.
- 캐시에 데이터가 있다면(Cache Hit) 캐싱 데이터를 반환한다.
- 캐시에 데이터가 없다면(Cache Miss) 실제 데이터를 가져와서 반환하고 해당 데이터를 캐시에 저장한다.
적용 대상
호출 횟수에 상관 없이 주어진 입력(인자)에 대해 동일한 출력(결과)이 보장된다.
자주 변동되지 않고 반복적으로 호출된다. (ex: 썸네일)
데이터베이스에서 조회하는 시간이 오래 걸린다.
계산이 복잡하여 처리가 오래 걸린다.
캐시 적중률이 높다.
- 캐시 적중률(Cache Hit Ratio) = 캐시 적중 횟수 / (캐시 적중 횟수 + 캐시 부적중 횟수) x 100
= 캐시 적중 횟수 / 전체 요청 및 접근 횟수 x 100
종류
Local Cache
서버 내부에 저장한다.
- 서버 내부에서 동작하기 때문에 속도가 빠르다.
- 서버 Resource(Memory, Disk)를 사용한다.
서버 간에 데이터를 공유할 수 없다.
- 분산 서버인 경우 데이터 정합성이 깨지는 문제가 발생할 수 있다.
종류
- Ehcache, Caffeine
Global Cache
Cache Server를 별도로 사용한다.
서버 간에 데이터를 공유할 수 있다.
- 네트워크 트래픽이 발생하기 때문에 Local Cache보다 속도가 느리다.
종류
- Redis, Memcached
CDN (Content Delivery Network), Web Caching
프록시 서버(Proxy Server)를 물리적으로 분산하여 사용자 위치를 기준으로 가장 가까운 프록시 서버에서 캐싱되어 있는 웹 콘텐츠를 제공함으로써 웹 페이지 로드 속도를 높이는 서버 네트워크
트래픽이 각 서버로 분산된다.
고려 사항
일반적으로 캐시는 메모리(RAM)에 저장되기 때문에 무분별하게 저장해버리면 용량 부족 현상이 발생하여 시스템이 다운될 수 있다.
캐시 서버에 장애가 발생하면 트래픽이 데이터베이스로 몰리게 되어 과부하로 인해 데이터베이스가 다운될 수 있다. 캐시 서버가 장애로부터 복구되는 동안 데이터베이스가 버틸 수 있도록 대비해야 한다. (캐시 서버를 계층적으로 구축하기도 한다)
캐시를 언제까지 유지시킬 건지 Expire Time 또는 Time-To-Live(TTL)정책과 삭제 알고리즘을 고려해야 한다.
- 만료 주기가 너무 짧으면 대규모 트래픽 환경인 경우 Cache Stampede 현상이 발생할 수 있다.
- Cache Stampede: 부하가 높은 상태에서 캐시가 만료되어 순간적으로 데이터베이스 읽기 작업과 캐시 쓰기 작업이 중복으로 발생하는 현상
- 만료 주기가 너무 길면 메모리 부족 현상이 발생하거나 데이터 정합성이 깨질 수 있다.
중요한 정보나 민감한 정보는 저장하지 않는다.
Local Cache VS Global Cache
- 데이터 정합성이 깨져도 비즈니스에 영향이 없는 부분은 Local Cache, 데이터 정합성이 중요한 부분에는 Global Cache를 선택할 수 있다.
- Cloud 환경(Docker, AWS EC2 등)이라면 JVM Memory를 사용하는 Cache는 적합하지 않을 수 있다.
- Local Cache를 첫 번째 수준 Cache로 사용하고 Global Cache를 두 번째 수준 Cache로 사용할 수 있다.
- Local Cache는 Memory를, Global Cache는 네트워크 트래픽을 많이 사용하는 점을 고려하여 하나 이상을 결합해서 사용하면 효율성이 높아질 수 있다.
캐시 읽기 전략 (Read Cache Strategy)
Cache Warming을 미리 수행해두는 편이 좋다.
- 첫 방문자를 위해 인위적으로 캐시에 미리 데이터를 넣어두는 작업
- 항상 Cache Hit를 받도록 한다.
- 이 작업을 해두지 않으면 첫 방문자(트래픽)가 급증할 때 Cache Miss가 대량으로 발생하여 데이터베이스에 큰 부하가 가해질 수 있다. (Thundering Herd)
- TTL을 적절히 사용하여 Thundering Herd를 방지한다.
반복적인 읽기 작업이 많은 경우에 적합하다.
- 동일한 쿼리를 반복적으로 수행하는 서비스
Cache Aside (Look Aside) 패턴
가장 일반적인 전략
처리 순서
- 요청이 들어오면 서버는 캐시를 우선적으로 확인한다.
- 캐시에 데이터가 없으면 데이터베이스를 조회한다.
- 데이터베이스에서 조회해온 데이터를 캐시에 저장하고 반환한다.
캐시와 데이터베이스를 분리해서 가용한다.
- 캐시가 필요한 데이터만 저장할 수 있다.
- 캐시 장애 대비 구성이 되어 있다.
- 데이터 정합성 유지 문제가 발생할 수 있다.
Read Through 패턴
데이터 동기화를 라이브러리 또는 캐시 제공자에게 위임한다.
캐시를 통해서 데이터를 읽는다.
- 직접적인 데이터베이스 접근을 최소화한다.
- 캐시에 문제가 발생하면 서비스 전체 중단으로 빠질 수 있다.
- Replication, Cluster 등을 이용하여 캐시 서비스의 가용성을 높여야 한다.
처리 순서
- 요청이 들어오면 서버는 캐시에 데이터를 조회한다.
- 캐시는 데이터가 없으면 직접 데이터베이스에서 데이터를 조회하여 저장한 후에 반환한다.
캐시 쓰기 전략 (Write Cache Strategy)
자주 사용되지 않는 불필요한 데이터로 인해 리소스가 낭비될 수 있기 때문에 반드시 TTL을 사용하여 정리해야 한다.
읽기 전략인 Read Through 패턴과 같이 사용하면 최신 데이터를 항상 캐시에서 사용할 수 있다.
Write Back (Write Behind) 패턴
데이터를 바로 저장하지 않고 Cache Store에 모아서 배치 스케줄러 작업을 통해 데이터베이스에 저장한다.
쓰기 작업이 많고 읽기 작업이 적은 경우에 적합하다.
처리 순서
- 모든 데이터를 캐시에 저장한다.
- 일정 주기마다 캐시에 있는 데이터를 데이터베이스에 저장한다.
캐시가 Queue 역할을 겸하여 데이터베이스가 받는 Write 부하를 줄여준다.
캐시에서 장애가 발생하면 데이터를 유실할 수 있다. (데이터 손실 가능성이 높다)
- Replication, Cluster를 이용하여 캐시 서비스의 가용성을 높여야 한다.
Write Through 패턴
데이터 동기화를 라이브러리 또는 캐시 제공자에게 위임한다.
쓰기 작업과 읽기 작업이 많은 경우에 적합하다.
처리 순서
- 모든 데이터를 캐시에 저장한다.
- 캐시는 바로 데이터베이스에 저장한다.
쓰기 작업이 두 번 발생한다.
- Write Back 보다 쓰기 속도가 느리다.
- 쓰기 작업이 적은 서비스에 적합하다.
데이터 정합성이 안정적이다.
비교
Ehcache | Caffein | Redis | Memcached | |
---|---|---|---|---|
Type | Local | Local | Global | Global |
Base | Off Heap Memory | In Memory | In Memory | In Memory |
Thread | Thread-safe X | Thread-safe | Single Thread | Multi Thread |
Share | O | X | O | O |
JSR-107 | O | O | Redis Client Redisson | ? |
삭제 알고리즘 | LRU(Least Recent Used) LFU (Least Frequency Used) FIFO(First In First Out) | Window TinyLfu 제거 정책 | TTL, LRU, LFU, RANDOM | TTL, LRU |
장점 | 기능이 많다. | Local Cache 중 저장, 제거 성능이 가장 좋다. | 데이터 영구 보존 가능채팅, 실시간 스트리밍, SNS 피드, 서버 상호 통신에 유리 | 간단하다. 빠르다 |
단점 | Caffein 대비 성능이 낮다.설정이 번거롭다. | 단순 캐시 기능만 제공한다. | 메모리 파편화, 단일 스레드 등 기본 기능을 보강해야 하는 부분이 많음 | 데이터 영구 보존 불가 String만 저장 가능 |
- Local Cache(Ehcache, Caffein)는 JVM Memory를 사용하여 Cloud 환경인 경우 배포할 때마다 Cache가 사라진다.
- Memcached는 다중 스레드이며 빠르다. 값이 최대 1MB인 String만 저장 가능하다. 만료 시간이 최대 30일로 제한되어 있다.
- Redis는 Memcached보다 더 많은 기능을 지원하고, 대중적이어서 자료가 많다. 다양한 자료구조와 삭제 알고리즘을 사용할 수 있다. Read/Wirte 속도 차이가 거의 없지만 검색 속도는 Redis가 더 빠르다.
Reference
스프링부트 Caching 도입하기(Redis, Ehcache)
[REDIS] 캐시(Cache) 설계 전략 지침 총정리