캐시란?
데이터의 원본보다 더 빠르고 효율적으로 접근할 수 있는 임시 데이터 저장소.
아래의 조건을 만족시킨다면 캐시를 도입했을 때 성능을 효과적으로 개선할 수 있다.
- 원본 DB에서 원하는 데이터를 찾기 위해 검색하는 시간이 오래 걸리거나, 매번 계산을 통해 가져온다.
- 캐시에서 데이터를 가져오는 것이 원본 DB의 데이터를 요청하는 것보다 빠르다.
- 캐시에 저장된 데이터는 잘 변하지 않는다.
- 캐시에 저장된 데이터는 자주 검색되는 데이터다.
캐시로서의 레디스
- 사용이 간단하다. 단순 키-값 형태로 저장하고, 다양한 자료 구조를 제공한다.
- 인메모리 데이터 저장소여서 데이터 접근 시간이 빠르다. 평균 읽기 및 쓰기 작업 속도가 1ms 미만이다.
- 자체적으로 고가용성 기능인 센티널 또는 클러스터를 제공한다.
캐싱 전략
레디스를 캐시로 사용할 때 레디스를 어떻게 배치할 것인지에 따라 서비스의 성능에 큰 영향을 끼칠 수 있다. 캐싱 전략은 캐싱되는 데이터의 유형과 데이터에 대한 액세스 패턴에 따라 다르기 때문에 서비스에 맞는 적절한 전략을 선택하는 것이 중요하다.
읽기 전략
Look aside
앱에서 데이터를 읽어갈 때 주로 사용하는 Look aside 전략은 레디스를 캐시로 사용할 때 가장 일반적으로 배치하는 방법이다.
위와 같이 애플리케이션은 찾고자 하는 데이터가 먼저 캐시에 있는지 확인한 뒤, 캐시에 데이터가 있으면(캐시 히트) 데이터를 읽어온다.
하지만 찾고자 하는 데이터가 없다면(캐시미스) DB로부터 직접 데이터를 가져오고, 이를 캐시에 저장한다.
Look aside 구조의 장점은 레디스에 문제가 생겨 접근을 할 수 없는 상황이 발생하더라도 바로 서비스 장애로 이어지지 않고 DB에서 데이터를 가지고 올 수 있다는 것이다. 하지만 기존에 앱에서 레디스를 통해 데이터를 가져오는 연결이 매우 많았다면, 모든 커넥션이 한꺼번에 DB로 몰려 많은 부하를 발생시켜 DB의 응답이 느려지거나 리소스를 많이 차지하여 성능에 영향을 미칠 수 있다.
찾고자 하는 데이터가 없을 때만 레디스에 데이터가 저장되기 때문에 Lazy loading 이라고도 부른다. 만약 기존에 사용 중인 서비스에 처음 레디스를 투입하거나 DB에만 새로운 데이터를 저장한다면 앱은 레디스에 매번 먼저 접근하고, 그때마다 캐시미스가 일어나 DB와 레디스에 재접근하는 과정을 통해 지연이 초래돼 성능에 영향을 주게 된다.
따라서 이럴 때 미리 DB에서 캐시로 데이터를 밀어주는 캐시 워밍(cache warming) 작업을 하기도 한다.
쓰기 전략과 캐시의 일관성
캐시는 DB에 저장돼 있는 데이터를 단순 복사해 온 값이라서, 원본 DB와 동일한 값을 유지하는 것이 필수적이다. 만약 둘 간의 차이가 발생하다면 이를 캐시 불일치 라고 한다.
쓰기 전략
1. Write through
DB를 업데이트할 때마다 매번 캐시에도 데이터를 함께 업데이트 시키는 방식으로, 항상 최신의 데이터를 가지고 있을 수 있다는 장점이 있지만, 데이터는 매번 2개의 저장소에 저장돼야 하기 때문에 데이터를 쓸 때마다 시간이 많이 소요될 수 있다.
또한 다시 사용되지 않을 데이터도 캐싱되기 때문에 리소스 낭비가 발생할 수 있다. 따라서 만료 시간을 설정하는 것이 권장된다.
2. Cache invalidation
이 전략은 DB에 값을 업데이트할 때마다 캐시에서는 데이터를 삭제한다. 저장소에서 특정 데이터를 삭제하는 것이 새로운 데이터를 저장하는 것보다 훨씬 리소스를 적게 사용하기 때문에 앞선 Write through의 단점을 보완한 방법이다.
3. Write behind (Write back)
만약 쓰기가 빈번하게 발생하는 서비스라면 이 방식을 고려해볼 수 있다. DB에 대량의 쓰기 작성ㅂ이 발생하면 많은 디스크 I/O를 유발해 성능 저하가 발생할 수 있다. 따라서 먼저 데이터를 빠르게 접근할 수 있는 캐시에 업데이트한 뒤, 이후에는 건수나 특정 시간 간격에 따라 비동기적으로 DB에 업데이트하는 전략이다.
이러한 전략은 데이터가 실시간으로 정확하지 않아도 되는 경우에 유용하다. 예를 들어 유튜브의 좋아요 수와 같은 경우 업데이트 마다 RDB에 업데이트 된다면 심각한 성능 저하가 발생할 수 있지만, 이를 집계하여 저장한다면 DB의 성능을 향상시켜서 애플리케이션의 성능도 함께 성능이 향상될 수 있다. 하지만 위의 예시에서, 만약 1분마다 집계를 한다고 한다면 최대 1분동안의 데이터가 날아갈 수 있다는 위험성을 감수해야 한다.
레디스는 키가 만료되었다고 해도 바로 메모리에서 삭제되지 않는다.
키는 Passive, Active 두 가지로 삭제된다.
Passive 방식
클라이언트가 키에 접근하고자 할 때 키가 만료되었다면 메모리에서 수동적으로 삭제한다. 사용자가 접근할 때만 삭제되기 때문에, 접근하지 않는 만료된 키의 경우는 삭제되지 않는다.
Active 방식
TTL이 있는 키 중 20개를 랜덤으로 뽑아낸 뒤, 만료된 키를 모두 메모리에서 삭제한다. 만약 25% 이상의 키가 삭제되었다면 다시 20개의 키를 랜덤으로 뽑은 뒤 확인하고, 아니라면 뽑은 20개의 키 집합에서 다시 확인한다. 이 과정을 1초에 10번 수행한다.
만료된 키를 곧바로 삭제하지 않기 때문에 키를 삭제하는 데에 들어가는 리소스는 줄일 수 있지만, 그만큼 메모리를 더 사용할 가능성이 존재하며, 최악의 경우 전체 메모리의 1/4는 이미 만료된 키일 수 있다.
'TIL ✍️' 카테고리의 다른 글
24년 4월 9일(화요일) 80번째 TIL : JUnit 조건에 따른 테스트 (0) | 2024.04.09 |
---|---|
24년 1월 30일(화요일) - 79번째 TIL :레디스(2) : 복제 & 센티널 (1) | 2024.01.31 |
24년 1월 25일(목요일) - 77번째 TIL : DB 로직 최소화 (0) | 2024.01.31 |
24년 1월 24일(수요일) - 76번째 TIL : StringRedisTemplate (1) | 2024.01.31 |
24년 1월 23일(화요일) - 75번째 TIL (0) | 2024.01.31 |