캐싱은 컴퓨팅 전반에서 성능을 끌어올리는 핵심 원리로, 느리지만 큰 저장소와 빠르지만 작은 저장소를 조합해 자주 쓰는 데이터를 빠르게 제공한다.
URL: https://planetscale.com/blog/caching
Title: Caching — PlanetScale
By Ben Dicken | July 8, 2025
컴퓨터를 사용할 때마다 캐시(cache) 는 당신의 경험이 빠르게 유지되도록 작동합니다. CPU에서 명령을 실행하는 것부터 X.com 피드를 요청하는 것, 그리고 지금 이 웹페이지를 로딩하는 것까지, 컴퓨터가 하는 거의 모든 일은 캐싱에 크게 의존합니다.
지금부터 캐싱에 대한 가이드형 인터랙티브 투어를 시작합니다. 캐싱은 컴퓨팅에서 가장 우아하고 강력하며, 어디에나 퍼져 있는 혁신입니다.
컴퓨터로 하는 모든 일에는 데이터 가 필요합니다. 이메일에 로그인할 때 모든 메시지와 연락처는 서버에 데이터로 저장되어 있습니다. iPhone에서 사진 라이브러리를 볼 때 사진은 휴대폰 저장소 및/또는 iCloud에 데이터로 저장되어 있습니다. 이 글을 불러올 때도 웹 서버에서 데이터를 가져와야 했습니다.
데이터를 저장하고 조회하는 시스템을 설계할 때 우리는 용량(capacity), 속도(speed), 비용(cost), 내구성(durability) 사이의 트레이드오프에 직면합니다.
HDD(하드 디스크 드라이브)는 SSD(솔리드 스테이트 드라이브)보다 기가바이트당 비용이 저렴하지만, SSD가 지연 시간(latency)은 더 낮습니다. RAM(랜덤 액세스 메모리)은 위 둘보다 더 빠르지만, 더 비싸고 휘발성(전원이 꺼지면 사라짐)입니다.
같은 예산이라면, 느린 대용량 저장소를 많이 확보할 수도 있고, 빠른 소용량 저장소를 조금 확보할 수도 있습니다. 엔지니어들은 둘을 조합함으로써 이 문제를 우회합니다. 즉, 저렴하지만 느린 저장소를 대량으로 두고, 비싸지만 빠른 저장소를 소량으로 함께 둡니다. 더 자주 접근되는 데이터는 빠른 저장소에 두고, 덜 자주 쓰는 다른 데이터는 느린 저장소에 둡니다.
이것이 캐싱의 핵심 원리입니다.
아래 시각화에서 이를 확인할 수 있습니다. 각 색깔 사각형은 저장하고 조회해야 하는 고유한 데이터(한 조각)를 나타냅니다. "data requester"를 클릭하면 추가 데이터 요청을 보낼 수 있고, "slow + large" 영역의 색깔 사각형 중 아무거나 클릭하면 그 색의 요청을 시작할 수 있습니다.
데이터를 요청할 때마다 요청은 먼저 캐시로 전송됩니다. 캐시에 있으면 캐시 히트(cache hit)라고 하며, 데이터를 빠르게 돌려줍니다.
찾지 못하면 캐시 미스(cache miss)라고 합니다. 이때 요청을 느린 저장소로 넘겨 데이터를 찾고, 그 데이터를 캐시에 넣은 다음, 요청자에게 보냅니다.
"히트율(hit rate)"은 캐시 히트가 발생하는 비율입니다. 즉:
hit_rate = (cache_hits / total_requests) x 100
히트율은 가능한 한 높게 유지하고 싶습니다. 그래야 느린 저장소로 가는 요청 수를 최소화할 수 있기 때문입니다.
아래 설정에서는 캐시가 작고 저장해야 할 항목이 많으며, 항목 요청 패턴도 예측 불가능하게 들어옵니다. 그 결과 히트율이 낮아집니다.
아래 막대 차트에서 위 캐시의 히트율과 시간이 지나며 어떻게 변하는지 볼 수 있습니다.
그렇다면, 요청 패턴이 동일하게 랜덤하더라도 캐시가 데이터 크기에 거의 가까울 정도로 크다면 어떨까요?
이 경우 훨씬 높은 히트율을 달성할 수 있습니다.
캐시 크기를 늘리면 데이터 저장 시스템의 비용과 복잡성이 증가합니다. 결국 트레이드오프의 문제입니다.
컴퓨터에서 가장 널리 퍼진 캐싱 형태 중 하나는 RAM입니다.
컴퓨터의 CPU는 모든 "일"을 하는 부품입니다. 어떤 사람들은 CPU를 컴퓨터의 두뇌라고 부릅니다. 두뇌가 제대로 작동하려면 메모리가 필요합니다. 그렇지 않으면 그다지 쓸모가 없습니다.
컴퓨터가 정보를 영구적으로 저장하려면 하드 드라이브에 저장합니다. 하드 드라이브는 컴퓨터가 꺼지거나 배터리가 없어도 내용을 기억할 수 있어 좋습니다. 문제는 하드 드라이브가 느리다는 점입니다.
아래 시각화에서 "CPU"를 클릭하면 저장소로 데이터 요청을 보낼 수 있습니다. "Hard Drive" 영역의 색깔 사각형을 클릭하면 해당 요소를 요청할 수도 있습니다.
이를 해결하기 위해 CPU와 하드 드라이브 사이의 중간 저장소로 RAM을 사용합니다. RAM은 하드 드라이브보다 비싸고 저장 용량도 작습니다. 하지만 핵심은 RAM에서 데이터를 읽고 쓰는 속도가 하드 드라이브와 상호작용하는 시간보다 자릿수(order-of-magnitude)로 더 빠르다는 점입니다. 그래서 컴퓨터는 하드 드라이브의 데이터 중 일부—더 자주 접근해야 하는 것들—를 RAM에 저장합니다. 많은 데이터 요청은 RAM으로 가서 빠르게 처리됩니다. 가끔 CPU가 RAM에 없는 무언가를 필요로 할 때만 하드 드라이브에 요청합니다.
사실 컴퓨터에는 RAM과 디스크만 있는 것이 아니라 캐시 계층이 더 많습니다. 현대 CPU는 RAM을 위한 하나 이상의 캐시 계층을 갖습니다. RAM도 빠르지만, CPU 안에 직접 내장된 캐시는 더 빠르므로 프로그램 실행 중 자주 쓰는 값과 변수를 그곳에 저장해 성능을 향상시킬 수 있습니다.
대부분의 현대 CPU에는 L1, L2, L3 캐시라고 불리는 여러 캐시 계층이 있습니다. L1이 L2보다 빠르고, L2가 L3보다 빠르지만, L1 용량이 L2보다 작고 L2 용량이 L3보다 작습니다.
이것이 캐싱에서 자주 마주치는 트레이드오프입니다. 더 빠른 데이터 조회는 보통 더 높은 비용 또는 더 엄격한 크기 제한을 의미합니다. 데이터가 요청자에 물리적으로 더 가까이 있어야 하기 때문입니다. 전부 트레이드오프입니다. 컴퓨터에서 최고의 성능을 뽑아내려면 워크로드에 맞게 캐시 계층을 최적으로 튜닝하는 섬세한 균형 감각이 필요합니다.
캐싱은 로컬 컴퓨터에만 국한되지 않습니다. 클라우드에서도 늘 사용되며, 당신이 좋아하는 앱들을 움직이는 동력입니다. 예로 ‘모든 것 앱’인 X.com을 보겠습니다.
X.com의 전체 역사에서 트윗 수는 쉽게 수조(트릴리언) 개에 달합니다. 하지만 𝕏 타임라인은 거의 대부분 지난 약 48시간의 게시물로 채워집니다. 며칠이 지나면 누군가가 다시 게시(re-post)하거나 아주 오래전까지 스크롤하지 않는 한, 거의 조회되지 않습니다. 이로 인해 최근성 편향(recency bias) 이 생깁니다.
Karpathy의 이 명언급 트윗을 생각해봅시다.
이 트윗은 2년 넘게 지난 지금까지도 43,000개 이상의 좋아요와 700만 회의 노출을 받았습니다. 정확한 추세 데이터에 접근할 수는 없지만, 이 트윗의 조회수 추세는 대략 다음과 같았을 가능성이 큽니다.
최근에 게시된 트윗, 사진, 동영상은 오래된 것보다 훨씬 더 많이 요청됩니다. 따라서 캐싱 자원을 최근 게시물에 집중해 더 빠르게 접근할 수 있게 하는 것이 좋습니다. 오래된 게시물은 더 느리게 로드되어도 됩니다.
이는 X.com만의 이야기가 아니라 많은 웹사이트에서 표준적으로 하는 방식입니다. 이런 웹사이트들은 대부분의 콘텐츠(이메일, 이미지, 문서, 동영상 등)를 (Amazon S3 같은) "느린" 저장소에 보관하되, 최신 콘텐츠는 (CloudFront, Redis, Memcached 같은) 더 빠른 인메모리 저장소에 캐시합니다.
아래 캐싱 시각화를 보세요. 더 붉은 사각형은 최근 데이터("따뜻한(warm)" 데이터)를, 더 푸른 사각형은 접근 빈도가 낮은 오래된 데이터("차가운(cold)" 데이터)를 의미합니다. 앱 서버를 클릭해 요청을 보내면 붉은 데이터가 더 자주 접근되는 것을 볼 수 있습니다. 그 결과 캐시는 주로 붉은 데이터를 붙잡고 있게 됩니다.
최근 값들로 캐시를 채운 다음, 데이터베이스 쪽의 더 차가운 값 몇 개를 클릭해보세요. 이것들은 캐시에서 바로 로드되지 않고 데이터베이스에서 로드되어야 합니다.
이 때문에 어떤 사람의 타임라인에서 오래된 게시물을 볼 때, 최근 게시물보다 로딩이 더 오래 걸리는 경우가 많습니다! 거의 필요하지 않은 값을 캐시하는 것은 큰 의미가 없습니다. 누군가의 𝕏 페이지에 가서 아주 오래전까지 스크롤해 보세요. 며칠 또는 몇 주 전을 넘어가면 확실히 느려지는 것을 체감할 수 있습니다.
또 하나의 중요한 고려 사항은 공간적 지역성(Spatial Locality) 입니다.
어떤 데이터 저장 시스템에서는 데이터의 한 덩어리를 읽으면, 그 바로 "앞"이나 "뒤"에 있는 데이터도 함께 읽힐 가능성이 큽니다. 사진 앨범 앱을 생각해보세요. 사용자가 클라우드 사진 저장소에서 사진 한 장을 클릭하면, 다음에 볼 사진은 시간순으로 바로 다음에 찍힌 사진일 확률이 높습니다.
이런 상황에서 데이터 저장 및 캐싱 시스템은 사용자 행동을 활용합니다. 사진 한 장이 로드될 때, 사용자가 다음으로 보고 싶어 할 사진들을 예측 하고, 그 사진들도 캐시에 미리 가져오기(prefetch) 를 할 수 있습니다. 이 경우 다음/이전 사진 몇 장을 함께 로드합니다.
이를 공간적 지역성 이라고 하며, 매우 강력한 최적화 기법입니다.
아래 예시에서 공간적 지역성이 어떻게 동작하는지 볼 수 있습니다. 각 데이터베이스 셀에는 순서대로 번호가 붙어 있습니다. 셀을 클릭하면 해당 셀과 양옆 이웃 두 개가 캐시에 로드됩니다.
이처럼 연관 데이터의 프리패칭은 예측 가능한 데이터 접근 패턴이 있을 때 성능을 향상시키며, 이는 사진 앨범을 넘어 많은 애플리케이션에서 사실입니다.
행성 규모에서의 물리적 거리 또한 캐싱에서 흥미로운 요소입니다. 좋든 싫든 우리는 둘레 25,000마일의 거대한 회전하는 바위 위에 살고 있으며, 데이터가 A에서 B로 이동할 수 있는 속도는 "물리"의 제약을 받습니다.
어떤 경우에는 데이터를 단일 서버에 두고 그 결과를 감수하기도 합니다. 예를 들어 모든 데이터를 미국 동부에 둘 수 있습니다. 그러면 동부에서 요청하는 컴퓨터는 서부보다 지연 시간이 더 낮습니다. 동부 사용자는 1020ms 지연을 경험하는 반면, 서부 사용자는 50100ms를 경험할 수 있습니다. 지구 반대편에서 요청하면 250ms 이상의 지연이 발생할 수 있습니다.
엔지니어들은 이를 돕기 위해 콘텐츠 전송 네트워크(Content Delivery Network, CDN) 를 자주 사용합니다. CDN에서는 데이터의 단일 진실 원천(source-of-truth)은 여전히 하나지만, 전 세계 여러 위치에 데이터 캐시를 추가합니다. 데이터 요청은 지리적으로 가장 가까운 캐시로 전송됩니다. 캐시에 필요한 데이터가 없을 때에만 핵심 진실 원천으로부터 데이터를 요청합니다.
아래에 간단한 시각화가 있습니다. 화살표로 요청자를 지도 위에서 움직여 보면서, 데이터 요청이 어디로 전송되는지 어떻게 달라지는지 확인해보세요.
또는 전체 데이터를 모든 CDN/캐시 노드에 완전히 저장하도록 선택할 수도 있습니다. 이 경우 데이터가 절대 변하지 않는다면 캐시 미스를 아예 피할 수 있습니다! 캐시 미스가 필요해지는 유일한 순간은 원본 데이터가 수정될 때입니다.
캐시가 가득 차서 새 항목을 저장해야 할 때, 교체 정책(replacement policy) 이 어떤 항목을 제거(evict)하여 공간을 만들지 결정합니다.
어떤 값을 언제 제거할지 결정하는 알고리즘은 여러 가지가 있으며, 여기서 올바른 선택을 하는 것은 성능에 큰 영향을 미칩니다.
First In, First Out(FIFO)은 가장 단순한 캐시 교체 정책입니다. 큐(queue)처럼 동작합니다. 새 항목은 앞쪽(왼쪽)에 추가됩니다. 캐시 큐가 가득 차면, 가장 먼저(가장 오래전에) 추가된 항목이 제거됩니다(오른쪽).
이 시각화에서 데이터 요청은 위쪽에서 들어옵니다. 항목이 요청되었을 때 캐시 히트라면, 즉시 위로 미끄러지듯 올라가 응답을 나타냅니다. 캐시 미스가 발생하면 새 데이터가 왼쪽에서 추가됩니다.
FIFO는 구현은 단순하지만, 사용 패턴을 고려하지 않기 때문에 대부분의 캐싱 시나리오에서 최적은 아닙니다. 이를 해결하기 위한 더 똑똑한 기법들이 있습니다.
Least Recently Used(LRU)는 인기 있는 선택이며, 많은 캐싱 시스템에서 업계 표준입니다. FIFO와 달리, LRU는 가장 최근에 요청되지 않은(가장 오래전에 사용된) 항목을 항상 제거합니다. 캐시 히트를 극대화하기 위한 합리적인 선택이며, 실제 데이터 접근 패턴의 시간적 지역성과도 잘 맞습니다.
캐시 히트가 발생하면 해당 항목의 위치가 큐의 맨 앞으로 갱신됩니다. 이 정책은 최근에 접근된 항목이 곧 다시 접근될 가능성이 높은 많은 실제 상황에서 잘 작동합니다.
LRU 알고리즘은 여러 방식으로 시간 민감하게(time-sensitive) 확장할 수 있습니다. 구체적인 방식은 유스케이스에 따라 다르지만, 보통은 LRU에 더해 캐시의 각 요소에 타이머를 부여합니다. 시간이 끝나면 해당 요소를 제거합니다!
예를 들면 이런 경우에 유용할 수 있습니다:
사용자의 열람 패턴을 활용해 요소에 자동 만료를 설정할 수 있습니다. 아래 시각화에서는 이 방식을 보여줍니다. 이 캐시는 LRU를 사용하지만 각 요소는 사용 패턴과 무관하게, 만료 타이머가 끝나면 제거됩니다.
특정 상황에서 쓰이는 멋지고/틈새(niche) 알고리즘들도 있습니다. 한 가지 흥미로운 예는 Least-Frequently Recently Used입니다. 이는 두 개의 큐를 관리합니다. 하나는 우선순위가 높은 항목용, 다른 하나는 우선순위가 낮은 항목용입니다. 우선순위가 높은 큐는 LRU 알고리즘을 사용하고, 어떤 요소를 제거해야 할 때 그 요소를 우선순위가 낮은 큐로 이동시킨 뒤, 그 큐에서 더 공격적인 교체 알고리즘을 적용합니다.
또한 단순한 시간 인지 LRU보다 더 복잡한 타이밍 동작을 하도록 캐시를 맞춤 구성할 수도 있습니다.
캐시는 느린 쿼리 결과를 캐싱하기 위한 메커니즘으로 데이터베이스 앞단에 자주 배치됩니다. 하지만 Postgres, MySQL 같은 DBMS도 구현 내부에서 캐싱을 사용합니다.
Postgres는 2계층 캐싱 전략을 구현합니다. 첫째, 테이블 정보를 담는 데이터 페이지를 위한 내부 캐시인 shared_buffers를 사용합니다. 이는 자주 읽는 로우(row) 데이터를 메모리에 두고, 덜 자주 접근하는 데이터는 디스크에 남겨 둡니다. 둘째, Postgres는 운영체제의 파일시스템 페이지 캐시에 크게 의존합니다. 이는 커널 수준에서 디스크 페이지를 캐시합니다. 그 결과 데이터가 Postgres의 shared_buffers와 OS 페이지 캐시 양쪽에 존재할 수 있는 더블 버퍼링(double-buffering) 시스템이 됩니다. 많은 배포에서는 shared_buffers를 사용 가능한 RAM의 약 25%로 설정하고, 나머지 캐싱 작업의 상당 부분은 파일시스템 캐시가 처리하도록 둡니다. shared_buffers 값은 postgres 설정 파일에서 구성할 수 있습니다.
MySQL도 버퍼 풀(buffer pool)로 비슷한 일을 합니다. Postgres와 마찬가지로, 이는 최근 사용된 데이터를 RAM에 유지하기 위한 내부 캐시입니다.
어떤 의미에서는, 이들은 "일반" 캐시보다 더 복잡합니다. 완전한 ACID 의미론과 데이터베이스 트랜잭션을 유지한 채로 동작해야 하기 때문입니다. 두 데이터베이스 모두 데이터가 변화함에 따라 이 페이지들이 정확한 정보와 메타데이터를 담도록 보장하기 위해 신중한 조치를 취해야 합니다.
캐싱은 이 글이 거의 표면만 훑었다고 말해도 과장이 아닙니다. 캐싱 시스템에서 쓰기(write)와 업데이트를 어떻게 처리하는지라는 주제를 완전히 피했습니다. Redis, Memcached 등 캐싱에 쓰이는 구체적인 기술들도 거의 다루지 않았습니다. 일관성(consistency) 문제, 샤딩된(sharded) 캐시, 그리고 많은 재미있는 디테일들도 다루지 않았습니다.
이 글이 캐싱에 대한 좋은 개요와, 컴퓨팅의 모든 계층에 얼마나 널리 퍼져 있는지에 대한 감을 주었기를 바랍니다. 당신이 사용하는 거의 모든 디지털 기술은 캐시에 의존하며, 이제 당신은 그에 대해 조금 더 알게 되었습니다.
이 글이 마음에 들었다면, 대형 테크 기업들이 캐싱을 어떻게 구현하는지에 대한 다음 사례 연구도 흥미로울 수 있습니다: