캐시 기초 & 전략
· 5 min read
Hierarchy of Computer Memory
CPU Cache & Memory
System Bus
Size & Latency
Cache
- 컴퓨팅에서 성능 향상을 위한 핵심 개념
- 자주 사용되는 데이터나 계산 결과를 빠른 저장소에 보관해 두었다가, 같은 데이터가 다시 필요할 때 원본 소스에서 가져오는 대신 캐시에서 빠르게 제공하는 방식으로 작동
Cache Memory
속도가 빠른 장치와 느린 장치 사이에서 속도 차에 따른 병목 현상을 줄이기 위한 범용 메모리
CPU는 L1~L3 캐시 메모리가 있으며 L
은 Level을 뜻함
1차에 없으면 2차, 2차에 없으면 3차, 3차에 없으면 Main Memory
cpu 벤더마다 구조가 상이함
- L2캐시가 코어에 있거나 여러개의 코어가 공유할수 있음
- L3는 프로세서 안에 넣지 않는 경향. 없거나 또는 메인보드에 있을수 있음
Pareto Principle (파레토 원칙))
Cache Coherence (캐시 일관성)
캐시가 갱신 되었다면(또는 삭제) 나머지 캐쉬에 전달하여 일관 유지해야함 멀티 프로세서에서 읽기 보다 쓰기가 어려운 문제
Locality of reference (참조 지역성)
- spatial locality (공간 지역성)
- 현재 접근한 메모리 위치와 인접한 메모리 위치들이 가까운 미래에 접근될 가능성이 큼
- 배열을 순차적으로 접근하거나, 구조체의 멤버들에 연속적으로 접근하는 경우
- temporal locality (시간 지역성)
- 최근에 접근했던 메모리 위치가 가까운 미래에 다시 접근될 가능성이 높다는 특성
- 반복문의 루프 변수, 자주 호출되는 함수의 지역변수들
let spatial = [1,2,3];
let temporal = 0;
for (int i=0; i<3; i++) {
temporal = temporal + spatial[i]
}
가장 최근, 가까운 데이터를 저장
Cache Eviction Policies
Cache Expiration은 시간 기반으로 자동 제거하는데 반해 Cache Eviction은 공간 부족으로 강제 제거
- FIFO(First in First Out)
- 가장 먼저 들어간 캐시를 교체
- Queue
- LFU(Least Frequently Used)
- 사용 횟수가 가장 적은 캐시를 교체
- LRU(Least Recently Used)
- 가장 오랫동안 사용되지 않은 것 교체
- Doubly LinkedList
Cache Stampede
캐시 미스가 동시에 대량 발생했을 때 시스템에 과부하가 걸리는 현상
- 타임 이벤트시 GET /events/{eventNo} , GET /products/{productNo} 부하 발생
- 캐시 미스에 따른 캐시 갱신으로 인하여 모든 요청이 DB로 몰림(중복 리드)
- 캐시 갱신을 위해 여러 서버에서 캐시 데이터 저장(중복 저장)
최근 발생한 Cache Stampede를 극복하기 위하여 아래와 같이 해결함
- 캐시 만료 시간은 1시간 설정
- 타임 이벤트 쇼핑몰일 경우 value값에 만료시간을 두고 어플리케이션에서 체크
- 요청에 대한 응답을 바로 주고 kafka로 캐시 갱신 토픽 발행
- reactive kafka를 사용하여 예상 갱신시간 만큼 윈도우 처리하여 중복 제거
kafkaReceiver.receive()
.map(this::extractMessage)
.window(Duration.ofMinutes(1)) // 1분 윈도우
.flatMap(window ->
window.distinct(Message::getId) // 윈도우 내 중복 제거
.doOnNext(this::processMessage)
)
.subscribe();
용어
- Cache Hit : 캐시에 데이터가 존재할 경우
- Cache Miss : 캐시에 데이터가 존재하지 않을 경우
- Cache Miss Penalty : Miss시 메인메모리에서 조회, 캐시 업데이트
- Cache hit ratio : 캐시 히트 횟수 / (캐시 히트 횟수 + 캐시 미스 횟수)
- Cache Invalidate : 원본이 변경되었을 경우 무효화
- Cache Flush : clean + invalidate
- Cache Expiration : 일정 시간이 지난 후 캐시에서 항목을 제거함. 오래된 데이터를 피하기 위한 전략
- Cache Eviction : 새 항목을 위한 공간을 확보하기 위해 캐시에서 항목을 제거. 용량이 부족한 경우
- Prefetch : CPU가 앞으로 사용할 것으로 예상되는 데이터를 미리 가져다 놓는다.
- Hit Latency : 데이터를 찾아서 반환하는 데 걸리는 시간
Cache Strategies
Read Strategies
Cache Aside
- read가 많을 경우
- 원본 스키마 != 캐시 스키마
- miss시 응답 지연 발생
- 동기화 문제
- 캐시가 장애여도 전체 장애로 전파 X
- 대부분 Redis를 이용하여 Cache Aside전략
Read Through
- read가 많을 경우.
- 원본 스키마 == 캐시 스키마
- 최초 요청시 반드시 cache miss
- origin size == cache size
- CDN, reverse proxy
Write Strategies
Write Around
- db 먼저 기록
- 읽은 데이터만 캐시 저장
- Read Through, Cache-Aside와 결합하여 사용
- 한번 쓰고 가끔 읽는 경우
Write Back
- 캐시에 먼저 기록, 지연후 db 저장
- write가 많을 경우 유리
- Read-Through와 결합 -> 최근 저장, 엑세스 된 데이터를 항상 캐시에서 사용
- db 쓰기 비용 감소
- 캐시 장애시 데이터 영구 소실
Write Through
- Read Through 반대
- db 저장과 동시에 cache 저장
- Read Through와 Write-Through 같이 적용하면 read캐시 이점과 데이터 일관성 보장
- 캐시 size == db size
- 쓰기 지연 증가
캐시 적용시 고려사항
- Capacity (용량)
- Count-based : 엔트리 개수 제한
- Size-based : 메모리 사용량 제한
- Weight-based : 사용자 정의 가중치 기반
- Hit Rate (캐시 적중률)
- 적절한 TTL 조절
- 워밍업 전략
- 프리패칭 구현
- Read-Write Strategies
- 요구사항에 알맞는 읽기, 쓰기 전략 플랜이 필요
- Coherence (일관성)
- Strong Consistency: 모든 읽기에서 최신 데이터 보장
- Eventual Consistency: 최종적으로 일관성 보장
- Weak Consistency: 일관성 보장 없음
- Expiration (만료시간)
- Eviction (교체정책)
- LRU, LFU, FIFO, TTL
Application Cache
Local Cache
- 서버마다 캐시를 따로 저장한다.
- 다른 서버의 캐시를 참조하기 어렵다.
- 서버 내에서 작동하기 때문에 속도가 빠르다.
- 로컬 서버 장비 자원 활용(limit)
- 캐시 데이터가 변경시 일관성 문제
- clustering, replication
- scale out할수록 비용 증가
Global Cache
- 여러 서버에서 캐시 서버에 접근하여 참조 할 수 있다.
- 별도의 캐시 서버를 이용하기 때문에 서버 간 데이터 공유가 쉽다.
- 네트워크 트래픽 발생, 로컬 캐시보다는 느리다.
- 데이터를 분산하여 저장 할 수 있다.
- Replication: 두 개의 이상의 DBMS 시스템을 Mater / Slave 로 나눠서 동일한 데이터를 저장하는 방식
- Sharding: 같은 테이블 스키마를 가진 데이터를 다수의 데이터베이스에 분산하여 저장하는 방법
- 캐시에 저장된 데이터가 변경되는 경우 추가적인 작업 불필요
- Scale-out 할수록, Cache 데이터 크기가 커질 수록 효율이 좋다
Distributed Cache
- 캐시 사이즈, 네트워크 용량이 글로벌 캐시 용량을 넘을 경우 -> Sharding, Redis Cluster
Buffer VS Cache
- 속도차이로 인해 고속의 장치의 기다림을 줄여준다는 공통점이 있어 헷갈릴 수 있다.
- 캐시는 조회 후 삭제하지 않지만 버퍼는 한번 꺼내오면 삭제한다. 버퍼는 캐시에 저장하는 데이터보다 용량이 훨씬 큰 경우가 많다.
- 버퍼의 대표적인 예는 프린트 이다. 인쇄를 하면 프린트 버퍼에 넣고 pc는 다른일을 할수 있다. 프린터는 버퍼를 통하여 인쇄를 한다.
- 버퍼는 캐시보다 일반적으로 용량이 크며, 캐시와 달리 데이터를 저장할 수 없고, 응답에 대한 요청을 저속의 장치에서 출력할 때, 고속의 장치가 저속의 장치로 인해 정지되는것을 막아준다.