contents
캐싱(Caching) 은 자주 접근하는 데이터의 복사본을 임시 고속 저장소(캐시)에 저장하여, 향후 동일한 데이터 요청을 더 빠르게 처리하는 기술입니다. 캐싱의 주된 목표는 원본 데이터가 저장된 느린 저장소까지 데이터를 가지러 가는 데 필요한 시간과 리소스를 줄여 성능을 향상시키는 것입니다.
필요할 때마다 매번 식료품 가게에 가는 대신, 가장 자주 사용하는 재료들을 부엌 조리대에 보관하는 것과 같습니다.
캐싱이 해결하는 핵심 문제: 속도 차이 ⚡️
컴퓨터는 속도, 크기, 비용이 각기 다른 저장 장치들의 계층 구조로 만들어져 있습니다.
-
CPU 레지스터 및 캐시: 극도로 빠름, 매우 작음, 매우 비쌈.
-
RAM (메인 메모리): 빠름, 중간 크기, 적당히 비쌈.
-
SSD/하드 드라이브 (디스크): 느림, 큼, 저렴함.
-
네트워크 저장소/API: 매우 느림, 사실상 무한대, 가변 비용.
CPU와 디스크나 네트워크 같은 느린 저장 장치 사이에는 엄청난 속도 차이가 존재합니다. RAM에서 데이터를 접근하는 것은 SSD보다 100배, SSD에서 데이터를 접근하는 것은 네트워크를 통해 원격 서버에서 가져오는 것보다 1,000배 더 빠를 수 있습니다.
캐싱은 데이터를 필요로 하는 구성 요소 바로 옆에 작고 빠른 "식료품 저장실"을 만들어, 느리고 비용이 많이 드는 "슈퍼마켓"(주 데이터 저장소)까지 가는 횟수를 줄임으로써 이 문제를 해결합니다.
캐싱의 기본 작동 원리
이론적으로 과정은 간단합니다.
-
어떤 구성 요소("클라이언트")가 데이터 조각을 요청합니다.
-
시스템은 먼저 캐시를 확인하여 해당 데이터의 복사본이 있는지 확인합니다.
-
캐시 히트(Cache Hit): 데이터가 캐시에서 발견되면 즉시 반환됩니다. 이는 빠르고 성공적인 작업입니다.
-
캐시 미스(Cache Miss): 데이터가 캐시에서 발견되지 않으면, 시스템은 원본의 느린 데이터 소스(예: 데이터베이스나 외부 API)에서 데이터를 가져와야 합니다. 그런 다음 클라이언트에게 데이터를 반환하고, 다음 사용을 위해 캐시에 복사본을 저장합니다.
-
캐시의 효율성은 히트율(Hit Ratio) 로 측정됩니다.
히트율=총 요청 수 (히트 + 미스)캐시 히트 수
높은 히트율(예: 95%)은 캐시가 매우 효과적으로 작동하고 있음을 의미합니다.
캐싱이 사용되는 곳: 캐시의 종류
캐싱은 단일 개념이 아니라, 컴퓨터 시스템의 거의 모든 계층에서 적용되는 기술입니다.
-
하드웨어 레벨 (CPU 캐시): 컴퓨터의 프로세서는 자체적으로 작은 L1, L2, L3 캐시를 가지고 있어 RAM에서 자주 사용하는 명령어와 데이터를 저장합니다. 이를 통해 CPU가 느린 메인 메모리를 기다리는 것을 방지합니다.
-
운영체제 레벨 (디스크 캐시): OS는 최근에 디스크에서 읽은 파일들을 RAM에 보관합니다. 파일을 두 번째로 열 때 즉시 로드되는 경우가 많은데, 이는 이 OS 레벨 캐시에서 읽어오기 때문입니다.
-
애플리케이션 레벨: 개발자들이 적극적으로 캐싱 시스템을 설계하는 영역입니다.
-
인메모리 캐시 (In-Memory Cache): 애플리케이션이 캐시된 데이터를 자체 메모리 내에 저장합니다(예: 해시맵이나 Guava Cache 같은 라이브러리 사용). 매우 빠르지만 애플리케이션이 재시작되면 사라집니다.
-
분산 캐시 (Distributed Cache): 여러 애플리케이션 서버를 위한 공유 캐시 역할을 하는 별도의 서비스(Redis나 Memcached 등)입니다. 이를 통해 애플리케이션의 여러 인스턴스가 캐시를 공유할 수 있고, 캐시는 애플리케이션이 재시작되어도 유지될 수 있습니다.
-
-
웹 캐싱 (Web Caching):
-
브라우저 캐시: 웹 브라우저는 정적 자산(이미지, CSS, 자바스크립트)의 복사본을 로컬 디스크에 저장하여 사이트를 방문할 때마다 다시 다운로드할 필요가 없도록 합니다.
-
CDN (콘텐츠 전송 네트워크): 사용자에게 더 가까운 곳에 정적 콘텐츠를 캐싱하는 지리적으로 분산된 서버 네트워크입니다. 서울의 사용자가 미국 기반 웹사이트의 이미지를 요청할 때, CDN은 서울에 있는 서버에서 이미지를 제공하여 지연 시간을 크게 줄일 수 있습니다.
-
-
데이터베이스 캐싱: 데이터베이스는 자주 접근하는 데이터 페이지를 메모리에 보관하는 버퍼 풀이나 공통 쿼리의 결과를 저장하는 쿼리 캐시 등 여러 계층의 내부 캐싱을 가집니다.
캐시 제거 정책: 공간 만들기 🗑️
캐시는 크기가 제한되어 있습니다. 캐시가 가득 차서 새 항목을 추가해야 할 때, 기존 항목 하나를 제거해야 하는데 이를 제거(evict) 한다고 합니다. 어떤 항목을 내보낼지 결정하는 데 사용되는 알고리즘을 제거 정책이라고 합니다.
-
LRU (Least Recently Used): 가장 일반적인 정책입니다. 가장 오랫동안 접근되지 않은 항목을 제거합니다. 최근에 접근된 데이터는 곧 다시 접근될 가능성이 높다는 원칙에 따라 작동합니다. (비유: 책장 뒤편의 먼지 쌓인 책을 버리는 것)
-
LFU (Least Frequently Used): 가장 적게 접근된 항목을 제거합니다. 어떤 데이터가 매우 자주 접근되지만 반드시 최근은 아닐 때 유용합니다.
-
FIFO (First-In, First-Out): 캐시에 가장 오래 있었던 항목이 접근 빈도나 최근 사용 여부와 상관없이 가장 먼저 제거됩니다. 단순하지만 종종 LRU보다 덜 효과적입니다.
-
무작위 (Random): 무작위로 항목을 제거합니다. 구현은 쉽지만 지능적인 최적화는 제공하지 않습니다.
캐싱 전략 (디자인 패턴)
애플리케이션이 캐시 및 주 데이터베이스와 상호작용하는 방식은 캐싱 전략에 의해 정의됩니다.
-
Cache-Aside (Lazy Loading): 가장 일반적인 패턴입니다.
-
애플리케이션이 데이터를 요청합니다.
-
먼저 캐시를 확인합니다.
-
캐시 미스가 발생하면, 애플리케이션은 데이터베이스에서 데이터를 읽어옵니다.
-
그런 다음 이 데이터를 캐시에 기록하고 반환합니다.
-
장점: 단순하고 캐시 장애에 강합니다(앱은 여전히 DB와 통신 가능).
-
단점: 캐시 미스가 발생하면 세 번의 통신(캐시 -> DB -> 캐시)이 발생하여 약간의 지연이 생깁니다.
-
-
Read-Through: 애플리케이션이 캐시를 주 데이터 소스로 취급합니다. 캐시 라이브러리 자체가 캐시 미스 시 데이터베이스에서 데이터를 가져와 자신을 채우는 책임을 집니다. 이는 애플리케이션 코드를 단순화합니다.
-
Write-Through:
-
데이터를 쓸 때, 애플리케이션은 캐시와 데이터베이스에 동시에 데이터를 씁니다.
-
두 쓰기가 모두 성공해야 작업이 완료된 것으로 간주됩니다.
-
장점: 캐시와 데이터베이스의 데이터가 항상 일관성을 유지하도록 보장합니다.
-
단점: 두 시스템을 모두 기다려야 하므로 쓰기 작업에 지연이 추가됩니다.
-
-
Write-Back (또는 Write-Behind):
-
애플리케이션은 데이터를 캐시에만 씁니다.
-
그런 다음 캐시가 일정 시간 후 비동기적으로 해당 데이터를 데이터베이스에 다시 씁니다.
-
장점: 매우 빠른 쓰기 작업이 가능합니다.
-
단점: 데이터가 데이터베이스에 기록되기 전에 캐시에 장애가 발생하면 데이터가 손실될 위험이 높습니다.
-
도전 과제 및 고려 사항 🤔
캐싱은 강력하지만 공짜는 아닙니다. 복잡성과 잠재적인 문제를 야기합니다.
-
캐시 무효화 (오래된 데이터): 이는 컴퓨터 과학에서 가장 어려운 두 가지 문제 중 하나로 유명합니다. 데이터베이스의 데이터가 업데이트될 때, 캐시에 있는 오래된 " stale" 복사본을 어떻게 제거하거나 업데이트할 수 있을까요? 그렇지 않으면 애플리케이션이 오래된 정보를 제공할 수 있습니다.
-
Thundering Herd 문제: 매우 인기 있는 캐시 항목이 만료되면 어떻게 될까요? 수천 개의 동시 요청이 동시에 캐시 미스를 겪고, 모두 한꺼번에 데이터베이스에 몰려들어 데이터베이스를 압도할 수 있습니다.
-
캐시 크기 결정: 캐시를 얼마나 크게 만들어야 할까요? 너무 작으면 히트율이 낮아지고, 너무 크면 비싼 메모리 리소스를 낭비하게 됩니다.
-
"콜드" 캐시: 애플리케이션이 처음 시작될 때 캐시는 비어 있습니다. 초기 요청은 모두 미스가 되어, 캐시가 데이터로 채워져 "워밍업"될 때까지 성능이 저하됩니다.
references