1. 레디스와 캐시
1.1 캐시란?
- 캐시는 데이터 원본보다 빠르고 효율적으로 액세스 할 수 있는 임시 데이터이다.
- 캐시 사용을 통해 성능을 향상할 수 있는 경우
- 원본 데이터를 찾기 위한 시간이 오래 걸리는 경우, 매번 계산이 필요한 경우
- 캐시에서 데이터를 가져오는 것이 원본 조회보다 빠른 경우
- 캐시에 저장된 값이 잘 변하지 않는 경우
- 캐시에 저장된 데이터 조회가 빈번한 경우
- 캐시 장점
- 데이터 조회 시간 단축
- 원본 데이터 저장소와의 커넥션 축소
- CPU, memory 등 사용 리소스 절약
- 원본 데이터 저장소 장애에도 데이터 조회 가능
1.2 캐시로서의 레디스
- 장점
- 키-값 형태로 저장되는 간편함
- 다양한 자료구조 사용 가능
- 빠른 읽기/쓰기 (in-memory 저장소, 작업속도 1ms 미만)
- 레디스 센티널, 클러스터를 통한 고가용성 확보
- 마스터 노드 장애 자동 감지를 통한 fail-over
- 간편한 scail-out
1.3 캐싱 전략
1.3.1 읽기 전략 - look aside
- 데이터 조회 시 캐시를 선 조회 후 없다면 원본 조회하는 방식(이 때문에 lazy loading이라고도 한다). 원본 조회 후 데이터 반환 전 캐시에 저장.
- 장점
- 레디스에 장애가 발생해도 원본 데이터를 조회할수 있다.
- 다만, 이로 인해 너무 많은 커넥션이 원본 데이터베이스에 몰려 응답 지연, 리소스 과다 차지 등 문제 발생 가능
- 레디스에 장애가 발생해도 원본 데이터를 조회할수 있다.
- 문제점: 새로운 원본 데이터가 대량으로 있을 때 대량의 cache miss 발생 가능
- 해결책: 사전에 원본 데이터를 캐싱하는 cache warming 사용
1.3.2 쓰기 전략과 캐시 일관성
- 캐시와 원본데이터는 동일한 값을 가져야 한다.
- 원본 데이터 변경으로 캐시와 원본데이터가 달라지는 것을 cache inconsistency라 한다. Cache inconsistency를 해결하기 위한 쓰기 전략은 다음과 같다.
- write through
- 원본 데이터가 업데이트 될때 마다 캐시 데이터도 같이 업데이트한다.
- 단점: 다시 사용되지 않을 데이터도 캐시에 저장할 수 있다 → TTL 설정 권장.
- cache invaliation
- 원본 데이터 업데이트 시 캐시 삭제
- write through 단점 보완
- write behind(write back)
- 우선 레디스에 데이터를 저장한 뒤, 비동기적으로 원본데이터 업데이트 (배치 사용)
- 저장되는 데이터가 실시간으로 정확하지 않아도 될 경우 사용
2. 캐시에서의 데이터 흐름
- 캐시는 데이터 중 자주 사용하는 데이터를 임시 저장하는, 데이터 베이스의 서브셋이다.
- 레디스는 메모리에 데이터를 저장하기에 원본 데이터베이스보다 저장 가능한 데이터양이 작다. → 메모리가 가득차지 않게 관리할 필요가 있다.
2.1 만료시간 (TTL - Time To Live)
- 데이터 저장 시 TTL을 설정해 데이터 수명 관리, 메모리 공간 관리에 사용할 수 있다.
- 레디스 키는 만료 이후 바로 삭제되지 않는다.
- 이를 통해 삭제에 사용하는 리소스를 줄일 수 있지만, 만료된 키가 최악의 경우 전체 메모리에 1/4을 차지한다는 문제가 존재.
- 레디스 키는 passive 방식과 active 방식으로 삭제된다.
- passive: 클리이언트가 키에 접근 시 키가 만료되었다면 삭제.
- active: TTL 값이 설정된 키 20개를 랜덤하게 뽑아 만료된 키를 삭제한다. (전체 과정을 1초에 10번씩 실행)
- 삭제된 키가 20개 중 25%를 초과하면 다시 20개를 랜덤하게 뽑아서 확인한다
- 삭제된 키가 20개 중 25% 미만이면 기존 20개를 다시 확인한다.
2.2 메모리 관리와 maxmemory-policy 설정
- 메모리가 다 찼을 때 어떤 방식으로 키를 삭제할지를 결정한다.
- Noeviction: 데이터를 저장할 수 없다는 에러 반환.
- 캐시를 레디스가 자체 삭제하는 것이 위험하다고 판단되면 사용.
- 경우에 따라 캐시를 저장하지 못한 에러가 장애로 이어질 수 있어서 권장하지 않는다.
- LRU(Least-Recently Used): 가장 최근에 사용되지 않은 데이터부터 삭제
- LRU의 두가지 설정값
- volaitle-lru: TTL이 있는 키에 한해 LRU 동작.
- 만료시간이 있는 데이터는 언젠가 삭제될것이라는 전제하에 동작
- 삭제하면 안되는 데이터가 있을 때 유용
- 단점: 모든 데이터에 TTL이 없다면 Noeviction과 같은 문제 발생
- allkeys-lru: 모든 키에 대해 LRU 방식 삭제
- volaitle-lru: TTL이 있는 키에 한해 LRU 동작.
- LRU의 두가지 설정값
- LFU(Least-Frequently Used): 키에 접근한 빈도에 따른 삭제 방식.
- LRU와 다르게 최근에 접근이 없어도 과거에 사용 빈도가 높으면 삭제하지 않는다.
- LFU도 LRU와 같은 방식으로 설정값이 나뉜다
- volitiel-lfu: TTL이 있는 키에 한해 LFU 동작
- allkeys-lfu: 모든 키에 대해 LFU 동작
- RANDOM: 임의기 키를 삭제한다.
- 키 선정에 대한 리소스를 줄일 수 있지만, 나중에 사용될 가능성이 높은 캐시를 삭제할 수도 있다.
- random 도 LRU와 같은 방식으로 설정값이 나뉜다
- volitiel-random: TTL이 있는 키에 한해 LFU 동작
- allkeys-random: 모든 키에 대해 LFU 동작
- volatile-ttl: 만료 시간이 가장 작은 키를 삭제한다.
LRU, LFU, volatile-ttl는 모두 근사치 알고리즘을 사용한다. 정확한 계산보다 리소스 사용을 줄이는 것이 더 효윩적이기 때문이다.
2.3 캐시 스탬피드 현상
-
캐시 스탬피드는 캐시가 만료됐을 때 다수의 애플리케이션이 같은 원본 데이터를 조회해서 여러번 캐시를 중복 쓰기하는 현상이다. look-aside 패턴 사용시 발생한다.
- 복잡한 연산이 필요한 데이터가 캐시되었다면 데이터베이스에 부하를 주고, 이는 서버 장애로 이어질 수 있다.
- 결과적으로 다른 데이터에도 영향을 주어서 계단식 실패(cascading failure)라고도 부른다.
- 해결책
- 적절한 만료시간 설정: 만료시간을 충분히 길게 설정해서 hit rate를 높인다.
- 선 계산: 캐시가 만료되기 전에 미리 값을 갱신한다.
- 캐시 스탬피드는 캐시 만료 시점에 여러 애플리케이션이 동시에 원본에 접근해 발생한다.
- 랜덤한 확률로 만료 전에 원본 데이터를 다시 캐싱하면 해결할 수 있다.
- PER(Probabilistic Early Recomputation) 알고리즘
- 캐시값이 만료되기 전에 언제 원본 값을 읽어오면 되는지 최적값을 계산한다.
- 간단히 다음과 같은 공식으로 요약할 수 있다.
- currentTIme - (timeToCompute * beta * ln(rand()) > expiry
- currentTime: 현재 시간
- timeToCompute: 캐시된 값을 다시 계산하는 데 걸리는 시간
- 이 값은 실시간으로 만료 데이터를 다시 캐싱하는데 걸리는 시간 데이터를 수집해 결정
- beta: 1.0 보다 큰 값
- 값이 클 수록 더 일찍 갱신한다.
- rand: 0 ~ 1 사이의 랜덤 값
- ln(rand())는 항상 음수
- expiry: 만료 시점 (현재 시간 + TTL)
- 만료 시간이 다가올 수록 조건이 참이될 확률이 높아져서 캐시 갱신 확률이 높아진다.
- currentTIme - (timeToCompute * beta * ln(rand()) > expiry
3. 세션 스토어로서의 레디스
- 세션은 서비스를 사용하는 클라이언트의 정보이다
- 다중 서버에 환경에서 세션을 사용하기 위한 방법으로는 sticky session, 모든 웹 서버에 세션을 복제하는 all-to-all 방법이 있다. 하지만 이들은 특정 서버에 트래픽이 몰리거나 세션 데이터 복사로 인한 저장 공간 낭비 등의 문제가 있다.
- 레디스를 세션 스토어로 사용하면 이런 문제를 해결할 수 있다.
- hash는 레디스에 세션을 저장하기 적절한 자료구조다.
3.1 캐시와 세션 차이
- 캐시
- 원본 데이터베이스의 서브셋으로서 동작 → 캐시가 유실되어도 원본 데이터베이스에서 조회 가능
- 여러 애플리케이션에 공유할 수 있다.
- 세션
- 사용이 특정 사용자에 한정된다.
- 유저 로그인 시 세션이 저장되고, 로그아웃 후 세션이 종료된다. 이 때, 데이터 종류에 따라 세션에 저장된 데이터를 영구보관할 수도 있다 (ex - 장바구니 리스트).
출처 - 개발자를 위한 레디스