
Cache는 데이터를 미리 저장해두고 필요할 때 빠르게 접근할 수 있도록 돕는 메커니즘이다. 자주 조회되지만 자주 변경되지 않는 데이터, 한 번 읽은 뒤 여러 번 재사용되는 데이터에 캐시를 적용하면 시스템 전반의 성능을 크게 개선할 수 있다. 데이터베이스 접근, 외부 API 호출은 모두 비용이며, 이 비용을 줄이기 위한 대표적인 수단이 캐시 전략이다.
Cache 전략 개념 정리
일반적인 서버 구조에서는 애플리케이션 서버가 데이터베이스에 직접 접근하여 읽기와 쓰기 작업을 수행한다. 하지만 같은 데이터를 반복적으로 조회하는 상황에서 매번 데이터베이스를 호출하는 것은 불필요한 리소스 낭비가 된다. 특히 데이터베이스뿐 아니라 외부 API 호출 역시 네트워크 비용과 응답 지연이라는 부담을 가진다. 이러한 문제를 완화하기 위해 중간 계층에 캐시를 두는 구조가 등장했다.
캐시는 주로 메모리 기반 저장소를 사용한다. 디스크 기반 저장소보다 접근 속도가 빠르기 때문에 응답 시간을 크게 단축할 수 있다. 또한 데이터베이스에 집중되던 트래픽을 분산시켜 부하를 줄이고, 인프라 비용 절감 효과도 기대할 수 있다. 모든 데이터를 캐시에 넣을 수는 없기 때문에 어떤 데이터를 캐시할 것인지에 대한 전략이 중요하다.
여기서 중요한 전제는 캐시의 용량은 제한적이라는 점이다. 따라서 캐시는 전체 데이터 중 ‘가장 자주 사용되는 일부 데이터’를 대상으로 설계해야 한다. 파레토 법칙처럼 전체 요청의 대부분은 소수의 데이터에서 발생한다. 이 특성을 잘 활용하면 적은 캐시 용량으로도 큰 성능 개선 효과를 얻을 수 있다.
Cache 전략 읽기 방식
읽기 전략은 데이터를 조회할 때 캐시와 데이터베이스를 어떤 순서로 사용하는지를 정의한다. 모든 읽기 전략의 공통 전제는 “캐시에 데이터가 있다면 캐시에서 바로 반환한다”는 점이다. 이를 기준으로 여러 전략이 파생된다.
Cache-Aside(Look-Aside)는 가장 널리 사용되는 전략이다. 애플리케이션 서버가 먼저 캐시에 데이터를 조회하고, 캐시에 데이터가 없을 경우 직접 데이터베이스에 접근한다. 이후 데이터베이스에서 읽은 데이터를 다시 캐시에 저장한다. 캐시 제어를 애플리케이션이 직접 담당하기 때문에 구현이 유연하지만, 캐시 미스 발생 시 ‘캐시 조회 → DB 조회 → 캐시 저장’이라는 추가 비용이 발생한다.
Read-Through 전략은 캐시가 데이터베이스 접근을 대신 수행한다. 애플리케이션은 오직 캐시만 조회하며, 캐시에 데이터가 없을 경우 캐시가 직접 데이터베이스에서 값을 가져와 저장한다. 이 방식은 서비스 로직이 단순해지는 장점이 있지만, 캐시 서버 장애 시 전체 시스템이 영향을 받는 SPOF 구조가 될 수 있다.
Cache-Warming은 읽기 전략을 보완하는 개념이다. 초기 트래픽이 몰리는 상황을 대비해 미리 캐시에 데이터를 적재해두는 방식이다. 초기 캐시 미스로 인한 DB 부하를 줄일 수 있지만, 캐시 용량 관리와 데이터 선별이 매우 중요하다. TTL 설정을 함께 고려하지 않으면 오히려 캐시 효율이 떨어질 수 있다.
Cache 전략 쓰기 방식
쓰기 전략은 데이터를 저장할 때 캐시와 데이터베이스를 어떤 순서로 갱신할지를 정의한다. 읽기 전략과 마찬가지로 쓰기 전략 역시 성능과 정합성 사이의 선택 문제다.
Write-Back 전략은 쓰기 성능을 극대화하는 방식이다. 애플리케이션은 캐시에만 데이터를 저장하고, 캐시는 일정 주기마다 데이터베이스에 반영한다. 쓰기 요청이 몰리는 환경에서 데이터베이스 부하를 크게 줄일 수 있지만, 캐시 장애 발생 시 데이터 유실 위험이 존재한다. 이를 보완하기 위해 Redis의 AOF, RDB 같은 영속성 옵션을 함께 사용하는 경우가 많다.
Write-Through 전략은 캐시와 데이터베이스에 동시에 데이터를 저장한다. 항상 최신 데이터를 유지할 수 있어 데이터 정합성이 뛰어나지만, 하나의 쓰기 요청이 사실상 두 번의 쓰기 작업이 되기 때문에 성능은 상대적으로 떨어진다. 또한 모든 데이터가 캐시에 저장되므로 캐시 효율을 고려한 설계가 필요하다.
Write-Around 전략은 데이터를 데이터베이스에만 저장하고, 이후 읽기 요청에서 캐시 미스가 발생할 때만 캐시를 갱신하는 방식이다. 쓰기 성능은 좋지만, 읽기 시점의 데이터 정합성에 주의해야 한다. 쓰기 빈도가 높고, 읽기 패턴이 예측 가능한 경우에 적합하다.