Redis 캐시(Cache) 전략
- 캐시 전략은 웹 서비스 환경에서 시스템 성능 향상을 기대할 수 있는 중요한 기술
- 일반적으로 캐시는 주기억장치(RAM)을 사용하기 때문에 데이터베이스보다 훨씬 빠르게 데이터 응답이 가능
- 단, RAM은 용량이 한정적이기 때문에 모든 데이터를 캐시에 저장할 경우 용량 부족 상태가 발생
- 따라서, 아래의 기준을 정해두어야 함
- 어느 종류의 데이터를 저장할 것인가?
- 얼만큼 데이터를 캐시에 저장할 것인가?
- 얼마나 오래된 데이터를 캐시에서 삭제할 것인가?
[ DB I/O vs Redis 캐시 ]
ㅇ Database와 Redis의 저장 위치
- 데이터베이스의 저장 위치 : 일반적으로 보조기억장치(HDD or SSD)
- Redis(In-memory Data Store)의 저장 위치 : 주기억장치(RAM)
> Redis는 모든 데이터가 메모리에 저장되어 읽기/쓰기가 빠르고, 데이터 접근이 메모리에서 이루어지기 때문에 성능이 매우 우수
> Database는 디스크에서 읽기/쓰기를 수행하기 때문에 메모리(RAM)에 비해 상대적으로 속도가 느리며, 디스크 I/O는 물리적 제약과 병목 현상이 발생할 수 있음
따라서, Redis 캐시 전략 사용 시 아래의 장점을 가질 수 있음
- 빠른 데이터 액세스 : 데이터 액세스 시간이 크게 단축
- 부하 분산 : DB에 대한 요청 수를 줄여 데이터베이스의 부하를 분산
- 응답 시간 단축 : 사용자 요청에 대한 응답 시간이 크게 개선
Cache Hit / Cache Miss
- Redis 캐시에는 Cache Hit와 Cache Miss라는 개념이 존재
- Cache Hit : 캐시 스토어(Redis)에 데이터가 있을 경우 (빠름)
- Cache Miss : 캐시 스토어(Redis)에 데이터가 없을 경우 Database에서 가져옴 (느림)
캐싱 전략 패턴 종류
- 캐시를 이용할 때 발생하는 문제점은 데이터 정합성 문제
- 데이터 정합성 ⇒ 어느 한 데이터가 캐시와 데이터베이스 두 곳에 같은 데이터임에도 불구하고 데이터의 값이 다른 현상을 의미
- Ex) 캐시의 재고 데이터 3개, 데이터베이스의 재고 데이터 30개
- 기존에 데이터베이스만 이용할 때에는 데이터베이스에 읽기/쓰기를 하였기 때문에 데이터 정합성 문제가 발생하지 않았지만, 두 개의 저장소를 사용하다 보니 데이터 정합성 문제가 발생한 것
- 따라서, 적절한 캐시 읽기 전략(Cache Read Strategy)과 캐시 쓰기 전략(Cache Write Strategy)을 통해 캐시와 DB간의 데이터 불일치 문제를 극복하고 빠른 성능까지 가지도록 해야 함
캐시 읽기 전략(Cache Read Strategy)
Look Aside(Cache Aside) 패턴
- 데이터 조회 → 캐시 확인 → 캐시에 없을 경우 DB 조회 → 조회한 데이터 캐시에 업데이트
- 반복적인 읽기가 많은 호출에 적합
- 캐시와 DB가 분리되어 가용되기 때문에 원하는 데이터만 별도로 구성하여 캐시에 저장
- 캐시와 DB가 분리되어 가용되기 때문에 캐시 장애 대비 구성이 되어 있기 때문에 만일 redis가 다운 되더라도 DB에서 데이터를 가져올수 있어 서비스 자체는 문제가 없음
- 단, 캐시에 붙어있던 connection이 많을 경우 redis가 다운된 순간 순간적으로 DB로 몰려서 부하 발생
- Redis Cache에 검색하는 데이터가 있는 지 확인 (Cache Hit)
- Redis Cache에 검색하는 데이터가 없을 경우 Database 확인 (Cache Miss)
- DB에서 조회한 데이터를 Redis Cache에 업데이트 (Server → Redis)
- Look Aside Cache 패턴은 애플리케이션에서 캐싱을 이용할 때 일반적으로 사용되는 기본적인 캐시 전략
- 이 방식은 캐시에 장애가 발생하더라도 DB에 요청을 전달함으로써 캐시 장애로 인한 서비스 문제는 대비 가능
- 단, Cache Store와 Data Store(DB)간 정합성 유지 문제가 발생할 수 있으며, 초기 조회 시 무조건 Data Store를 호출 해야 하므로 단건 호출 빈도가 높은 서비스에 적합하지 않음
- 즉, 반복적으로 동일 쿼리를 수행하는 서비스에 적합한 아키텍처
- 이런 경우 DB에서 캐시로 데이터를 미리 넣어주는 작업을 하기도 하는데 이를 Cache Warming이라고 함
[ Cache Warming ]
- 미리 Cache로 DB의 데이터를 밀어 넣어두는 작업을 의미
- 이 작업을 수행하지 않으면 서비스 초기에 트래픽 급증시 대량의 cache miss 가 발생하여 데이터베이스 부하가 급증 할 수 있음 (Thundering Herd)
- 다만, 캐시 자체는 용량이 작아 무한정으로 데이터를 들고 있을수는 없어 일정시간이 지나면 expire되는데, 그러면 다시 Thundering Herd가 발생될 수 있기 때문에 캐시의 TTL을 잘 조정해야 함
Read Through 패턴
- 데이터 조회 → 캐시 확인 → 캐시에 없을 경우 DB 조회 → 조회한 데이터 캐시에 업데이트 → 서버에 반환
- 캐시에서만 데이터를 읽어오는 전략 (inline cache)
- Look Aside 와 비슷하지만 데이터 동기화를 라이브러리 또는 캐시 제공자에게 위임하는 방식이라는 차이
- 따라서 데이터를 조회하는데 있어 전체적으로 속도가 느림
- 또한 데이터 조회를 전적으로 캐시에만 의지하므로, redis가 다운될 경우 서비스 이용에 차질이 생길수 있음
- 대신에 캐시와 DB간의 데이터 동기화가 항상 이루어져 데이터 정합성 문제에서 벗어날수 있음
- 역시 읽기가 많은 워크로드에 적합
- Redis Cache에 검색하는 데이터가 있는 지 확인 (Cache Hit)
- Redis Cache에 검색하는 데이터가 없을 경우 Database를 조회하여 자체 업데이트 (Cache Miss)
- Cahe에서 데이터를 가져옴
- Read Through 방식은 Cache Aside 방식과 비슷하지만, Cache에 저장하는 주체가 Server이냐 또는 Cache 자체이냐에서 차이 발생
- 이 방식은 직접적인 데이터베이스 접근을 최소화하고 Read에 대한 소모되는 자원을 최소화 가능
- 하지만 캐시에 문제가 발생하였을 경우 이는 바로 서비스 전체 중단으로 빠질 수 있기 때문에 redis와 같은 구성 요소 Replication 또는 Cluster로 구성하여 가용성을 높여야 함
이 방식 또한 Cache Warming을 수행하는 것이 좋다고 함
캐시 쓰기 전략(Write Cache Strategy)
Write Back(Write Behind) 패턴
- 데이터 전송 → Redis에 저장 → 일정 시간(스케쥴링)마다 DB에 저장
- 캐시와 DB 동기화를 비동기하기 때문에 동기화 과정 생략
- 데이터를 저장할때 DB에 바로 쿼리하지않고 캐시에 모아서 일정 주기 배치 작업을 통해 DB에 반영
- 캐시에 모아놨다가 DB에 쓰기 때문에 쓰기 쿼리 회수 비용과 부하를 줄일 수 있음
- Write가 빈번하면서 Read를 하는데 많은 양의 Resource가 소모되는 서비스에 적합
- 데이터 정합성 확보
- 자주 사용되지 않는 불필요한 리소스 저장
- 캐시에서 오류가 발생하면 데이터 영구 소실
- 모든 데이터를 Cache에 저장
- 일정 시간마다 DB에 Query 전송
- Write Back 방식은 데이터를 저장할때 DB가 아닌 먼저 캐시에 저장하여 모아놓았다가 특정 시점마다 DB로 쓰는 방식으로 캐시가 일종의 Queue 역할
- 캐시에 데이터를 모았다가 한 번에 DB에 저장하기 때문에 DB 쓰기 횟수 비용과 부하가 감소될 수 있으나 데이터를 옮기기 전에 캐시 장애가 발생하면 데이터 유실이 발생할 수 있다는 단점이 존재
- 단, 오히려 반대로 데이터베이스에 장애가 발생하더라도 지속적인 서비스를 제공할 수 있도록 보장하기도 함
[ Tip ]
- 이 전략 또한 캐시에 Replication이나 Cluster 구조를 적용함으로써 Cache 서비스의 가용성을 높이는 것이 좋음
- 캐시 읽기 전략인 Read-Through와 결합하면 가장 최근에 업데이트된 데이터를 항상 캐시에서 사용할 수 있는 혼합 워크로드에 적합
Write Through 패턴
- 데이터 전송 → Redis에 저장 → 즉시 DB에 저장
- 데이터베이스와 Cache에 동시에 데이터를 저장하는 전략
- 데이터를 저장할 때 먼저 캐시에 저장한 다음 바로 DB에 저장 (모아놓았다가 나중에 저장이 아닌 바로 저장)
- Read Through 와 마찬가지로 DB 동기화 작업을 캐시에게 위임
- DB와 캐시가 항상 동기화 되어 있어, 캐시의 데이터는 항상 최신 상태로 유지
- 캐시와 백업 저장소에 업데이트를 같이 하여 데이터 일관성을 유지할 수 있어서 안정적
- 데이터 유실이 발생하면 안 되는 상황에 적합
- 자주 사용되지 않는 불필요한 리소스 저장
- 매 요청마다 두번의 Write가 발생하게 됨으로써 빈번한 생성, 수정이 발생하는 서비스에서는 성능 이슈 발생
- 기억장치 속도가 느릴 경우 데이터를 기록할 때 CPU가 대기하는 시간이 필요하기 때문에 성능 감소
- DB에 저장할 데이터가 있을 경우 Cache에 먼저 저장
- 즉시 Cache에서 DB에 저장
- Write Through 패턴은 Cache에도 반영하고 Data Store에도 동시에 반영하는 방식
(Write Back은 일정 시간을 두고 나중에 한꺼번에 저장) - 그래서 항상 동기화가 되어 있어 항상 최신정보를 가지고 있다는 장점을 가짐
- 하지만 결국 저장할때마다 캐시 저장 → DB저장의 2단계 과정을 거쳐치기 때문에 상대적으로 느리며, 일단 무조건 Cache에 저장하기 때문에 캐시에 넣은 데이터를 저장만 하고 사용하지 않을 가능성이 있어서 리소스 낭비 가능성이 있음
[ Tip ]
Write Throuth 패턴과 Write Back 패턴 둘 다 모두 자주 사용되지 않는 데이터가 저장되어 리소스 낭비가 발생되는 문제점을 안고 있기 때문에, 이를 해결하기 위해 TTL을 반드시 사용하여 사용되지 않는 데이터를 반드시 삭제해야 함 [expire]
Write Around 패턴
- Write Through 보다 훨씬 빠름
- 모든 데이터는 DB에 저장 (캐시를 갱신하지 않음)
- Cache miss가 발생하는 경우에만 DB와 캐시에도 데이터를 저장
- 따라서 캐시와 DB 내의 데이터가 다를 수 있음 (데이터 불일치)
- 모든 데이터는 DB에 저장
- Write Around 패턴은 속도가 빠르지만 cache miss가 발생하기 전에 데이터베이스에 저장된 데이터가 수정되었을 때 사용자가 조회하는 cache와 데이터베이스 간의 데이터 불일치가 발생
- 따라서 데이터베이스에 저장된 데이터가 수정, 삭제될 때마다 Cache 또한 삭제하거나 변경해야 하며, Cache의 expire를 짧게 조정하는 식으로 대처
[ Tip ]
- Write Around 패턴은 주로 Look aside, Read through와 결합해서 사용
- 데이터가 한 번 쓰여지고, 덜 자주 읽히거나 읽지 않는 상황에서 좋은 성능을 제공
캐시 읽기 + 쓰기 전략 조합
Look Aside + Write Around 조합
- 가장 일반적으로 쓰이는 조합
- 전체 흐름 요약
- 조회 시: 캐시에서 데이터를 먼저 조회하고, 없으면 데이터베이스에서 조회. 단, 조회 시에는 캐시에 저장하지 않음
- 저장 시: 데이터베이스에 직접 저장하고, 캐시는 업데이트하지 않음
- 캐시 갱신: 캐시는 다시 데이터가 조회될 때만 갱신
Read Through + Write Around 조합
- 항상 DB에 쓰고, 캐시에서 읽을때 항상 DB에서 먼저 읽어오므로 데이터 정합성 이슈에 대한 완벽한 안전 장치를 구성 가능
- 전체 흐름 요약
- 데이터 조회 : 캐시에서 먼저 데이터를 찾고, 없으면 캐시가 데이터베이스에서 데이터를 조회하여 반환하고 캐시에도 저장
- 데이터 저장 시 : 데이터베이스에만 저장하고, 캐시는 업데이트하지 않음
- 캐시 갱신 : 캐시된 데이터가 변경되지 않은 상태로 남아있다가, 다음에 해당 데이터를 조회할 때 갱신
Read Through + Write Through
- 데이터를 쓸 때 항상 캐시에 먼저 쓰므로, 읽어올때 최신 캐시 데이터 보장
- 데이터를 쓸 때 항상 캐시에서 DB로 보내므로, 데이터 정합성 보장
- 전체 흐름 요약
- 데이터 조회 시 : 캐시에서 데이터를 먼저 조회. 캐시 미스가 발생하면 캐시가 데이터베이스에서 데이터를 가져와 캐시에 저장하고 클라이언트에 반환
- 데이터 저장 시 : 클라이언트가 데이터를 저장할 때, 캐시와 데이터베이스 모두 동시에 저장되어 항상 최신 상태 유지
- 캐시 갱신 : 데이터가 변경될 때마다 캐시도 즉시 업데이트되므로, 캐시와 데이터베이스는 항상 일관성을 유지
참고 자료
'Language > Spring' 카테고리의 다른 글
[JPA/Hibernate] Hibernate 기초 (1) | 2024.11.09 |
---|---|
[JPA] JPA의 개념 / JPA를 사용해야하는 상황과 그렇지 못한 상황 (1) | 2024.11.08 |
[JWT] Refresh Token을 이용한 Access Token 재발급 (0) | 2024.08.28 |
[JWT] JwtUtil 사용 예제 및 설명 (0) | 2024.08.27 |
[JWT] JWT 등장 배경, Access Token, Refresh Token (0) | 2024.08.27 |