-
캐시란? (feat 브라우저 캐시)CS/캐시 2024. 11. 15. 01:34
캐시란 말을 들어봤을 것이다. 너무나도 많은 종류에서 사용되고, 그게 그거아냐? 라는 의문이 들 수 있다. (cpu캐시, 디스크 캐시, 브라우저 캐시, CDN 캐시 http캐시 등등) 어떻게 보면 맞는말이고 어떻게 보면 또 다른말이다.
이 글은 내머리 속 산발되어있는 캐시관련 지식을 교통정리하기 위한 생각으로 작성되었다.
본문은 CPU 캐시와 웹 캐시 그리고 Http캐시에 대해 다루려고 한다. 이 셋은 다른 레벨에서 작동하지만, 근본적인 문제 해결 방식과 목적은 매우 유사하여, 같이 알아두면 좋을 것 같아 서론을 CPU캐시로 운을 띄운다.
CPU 캐시
컴퓨터의 3대 핵심 부품이 있다.
중앙처리장치 / 메인메모리 / 보조기억장치
각각
CPU - (Central Processing Unit) /
RAM - (Random Access Memory) /
보조기억장치 - (auxiliary storage)
(보조기억장치를 DISK라고 표기한 이유는, 역사적 맥락으로 초기부터 오랫동안 하드디스크가 주력 저장장치였기 때문이다. 일종의 관행적인 표현이다. - Save to disk)
이 세부품의 협력 과정은
- 프로그램 실행 시 : 보조기억장치 → 주기억장치로 프로그램 로드
- 프로그램 실행 중 : CPU가 주기억장치의 데이터를 읽어 처리
- 작업 결과 저장 : CPU → 주기억장치 → 보조기억장치로 저장
이것이 기본적인 컴퓨터의 작동 방식이다.
하지만 초기 컴퓨터에서는 이 과정이 매우 비효율적으로 이루어졌다. 당시에는 프로그램을 실행하면 보조기억장치에 저장된 프로그램을 메모리에 로드하고, CPU가 필요한 모든 데이터를 그때마다 메모리에서 직접 읽어야 했다.
즉, 모든 데이터 접근마다 메모리의 왕복이 필요했고, CPU는 메모리 접근을 계속 대기하면서 동일한 데이터도 매번 다시 읽어야 했다. 그에따라 CPU 성능의 대부분이 낭비되는 상황이었다.
좀더 쉬운 예시로는 간단한 덧셈을 실행한다고하자.
x = a+ b
CPU의 역할은 덧셈이다. 더하기만 하면된다. 엄청간단하다. 그런데 무엇과 무엇을 더해야하는지 즉, a의 값과 b의 값이 무엇인지 알아야 더할 수 있으니까 RAM 에 접근하여 a와 b를 읽는다. 그런데 a를 읽는데 1분, b를 읽는데 1분이 걸렸다. 반대로 더하기 연산은 1초밖에 걸리지 않았지만 a와 b를 더한 결과값인 x를 다시 Ram에 저장 하는데 1분이 걸렸다
결과적으로 총 소요시간은 3분1초나 걸려버린 것이다. 실제연산은 1초임에도 불구하고 말이다.
다시 정리하자면 RAM접근 대기시간이 무려 3분이나 된것이다.
시간을 극단적으로 부풀린 예시이지만, 초기의 상태는 그만큼 실제 작업 대비 대기 시간이 압도적이였다를 설명하고 싶었다.
그러면 메모리를 고속화 시키면 되는거 아냐?
당시에도 비쌌다.
1. 기술적 한계 - 당시 메모리 제조 기술로는 속도 향상에 한계가 있었다 - 속도를 높이면 가격이 기하급수적으로 상승
2. 경제적 제약 - 고속 메모리는 제조 단가가 매우 높았다 - 대용량으로 사용하기에는 비용이 너무 큼 (지금도 애플 공홈가서 이것 저것 추가하면 화성탐사선 가격이 나온다.)
결국 다른 해결책이 필요하게 됬다.
그러던 중
1960년대 폰 노이만은 병목 현상을 발견했다.
(병목현상이란 bottleNeck : 병의 몸통보다 병의 목부분이 좁음 - 교통 정체 현상, 컴퓨터 성능 저하 현상)
CPU와 메모리 사이의 심각한 속도 차이가 나고, CPU의 성능이 향상됬는데 전체적인 성능 향상으로 이어지지 않음을 알게되었다.
- CPU와 메모리 사이의 데이터 전송 경로가 하나로 제한되어 있어서 - 명령어와 데이터가 같은 버스를 통해 이동해야 하는 구조적 한계 - 이로 인해 발생하는 성능 저하 현상
1. 속도 차이 CPU 처리 속도: 1 나노초 메모리 접근 속도: 100 나노초 2. 버스 경쟁 - 명령어 가져오기와 데이터 가져오기가 경쟁 - 한 번에 하나의 작업만 가능
폰 노이만은 CPU와 메모리 간의 병목 현상을 연구하는 과정에서 프로그램의 메모리 접근 패턴을 분석했고, 프로그램들이 공통적으로 가지는 특징을 발견했는데, 이것이 바로 '참조 지역성(Locality of Reference)' 이다.
참조 지역성의 핵심 패턴은 다음과 같았다:
1. 시간적 지역성 - 한번 접근한 데이터는 곧이어 다시 접근될 가능성이 높음 - 예) 반복문에서 같은 변수를 반복 사용 2. 공간적 지역성 - 접근한 데이터 주변의 데이터도 곧 접근될 가능성이 높음 - 예) 배열의 연속된 원소들을 순차적으로 접근
이러한 패턴의 발견은 매우 중요했다. CPU와 메모리 사이에 작지만 빠른 메모리를 두어, 자주 사용될 것으로 예측되는 데이터를 미리 저장해두면 병목 현상을 크게 완화할 수 있다는 해결책으로 이어졌기 때문이다.
여기서 혁신적인 해결책으로 캐시가 고안되었다.
( 잠깐 여기서 캐시라는 용어 자체에 대한설명을 덧붙이자면 )
cache N 은닉처, 은닉처에 숨겨진 물건 / 캐시(고속 기억 장치) V 은닉하다, 저장하다. / 캐시에 저장하다. 어원적 의미로서는 프랑스어 ‘cacher’에서 유래되었다. 역사적 용례로선 탐험가나 군인들이 식량이나 물품을 숨겨두는 장소를 의미였고 비상시를 대비해 안전하고 빠르게 접근할 수 있는 위치에 보관되었다. (weapone cache → 무기고)
자 다시 캐시의 목적은, 모든 데이터를 빠르게 접근할 수는 없지만 자주 사용하는 데이터만이라도 빠르게 접근할 수 있게해서 병목현상을 줄이자가 핵심 이다.
앞서 말한 참조 지역성을 통해서 ‘아 자주 사용하는 데이터를 CPU 가까이에 두자’ 가 되었고 이때 캐시 메모리의 개념이 탄생한다.
캐시 메모리는 CPU의 처리 속도와 주 기억 장치의 접근 속도차이를 줄이기 위해 사용한다. 참조지역성을 활용하여 메인 메모리에 있는 데이터를 CPU내부의 캐시메모리에 불러와두고, CPU가 필요한 데이터를 메모리 보다 캐시메모리에서 먼저 찾도록하는 기능인 것이다.
이 캐시메모리에서도 계층적 구조를 나눠 먼저 찾는 순서를 정해두었다.
L1 , L2, L3 캐시로 나뉘었다.(Level 1, Level2, Level3)
L1캐시는 Cpu에 가장 가까이 두었다. 즉 가장 작고 가장 빠르다, L2캐시는 L1보다 크고 조금 느리고, L3캐시는 가장 크지만 상대적으로 느리다. 작동원리는 CPU가 데이터 요청을하면 L1확인하고 → 없으면 L2 확인 → 없으면 L3확인 → 없으면 메모리에서 가져온다.
++) 캐시메모리는 어디에 있나요?
참고로 캐시메모리의 위치는 초기부터 CPU 내부에 있었던 것은 아니다. 초기의 캐시메모리(1960년대)에는 CPU와 메인보드 사이에 별도의 칩으로 존재했다. 즉 CPU외부에 위치해 있었고 메인보드에 장착되어있었다. 그러므로 속도 개선에 한계가 존재했다. 이후 발전을 거치며 현대의 캐시 메모리 위치는 CPU 내부에 통합되어있다. 물론 L!, L2, L3 캐시 모두 CPU 칩 안에 위치해있다. 이에 따라 더 빠른 접근 속도 실현, 더 효율적인 데이터 전송이 가능해 진것이다.
여기까지 CPU와 메모리 사이의 병목 현상을 해결하기 위한 캐시 메모리에 대한 이야기이다.
이 캐시의 핵심 아이디어는 '자주 사용하는 데이터를 빠르게 접근할 수 있는 곳에 두자'였고, 이는 참조 지역성이라는 프로그램의 특성을 활용한 해결책이었다. 흥미로운 점은 이러한 캐시의 개념이 컴퓨터 시스템의 다른 영역에서도 동일하게 적용된다는 것인데, 특히 우리가 매일 사용하는 웹 브라우저에서도 비슷한 문제가 존재했다.
CPU와 RAM의 병목현상처럼 웹 환경에서는 브라우저(클라이언트)와 서버 사이의 네트워크 지연이 병목 현상을 일으켰다. CPU 캐시가 메모리 접근 시간을 줄이듯이, 웹 캐시는 서버로의 반복적인 요청을 줄여 네트워크 지연 문제를 해결한다. 다음은 웹에서의 캐시, 더 자세하게는 브라우저 캐시에 대해 얘기해 보려고 한다.
WEB 캐시 (中 브라우저 캐시)
웹 캐시는 웹 페이지, 이미지, API 응답 등을 저장해두는 임시 저장소이다. 동일한 리소스 요청 시 다시 다운로드하지 않고 저장된 것을 재사용한다. 앞선 CPU 캐시와의 유사한점은 자주 사용하는 데이터를 빠르게 접근할 수 있는 곳에 저장하고 원본 데이터에 대한 접근시간을 절약한다. 또한 참조 지역성의 원리를 활용한다.
웹 캐시를 위치별로 분류하면,
클라이언트 측 캐시 vs 서버측 캐시 로 볼 수 있다. 또 클라이언트 측 캐시는 브라우저 캐시와 서비스 워커 캐시로 나눌 수 있고 브라우저 캐시는 메모리캐시와 디스크 캐시로 나눌 수 있다. 서버측 캐시는 프록시 캐시와 CDN 캐시 그리고 애플리케이션 캐시로 나눌 수 있고 프록시 캐시는 또 Forward Proxy Cache 와 Reverse Proxy Cache로 나눌 수 있다.
아래에서는 예시 시나리오와 함께 클라이언트 측 캐시 중 브라우저 캐시에 관하여 다루고자 한다.
초기상태에는 로컬에 아무런 데이터가가없을 것이다. 이 웹에 있는 모든 데이터는 서버에서 다운로드가 필요한 상태이다. → 이건 당연한 것.
브라우저가 서버에 웹페이지를 요청을 하고 서버가 HTML과 필요한 리소스 를 전송한다. 그리고 브라우저가 리소스 를 분석 및 캐시 처리를 결정하게 된다.
- 사용자: example.com 접속
- 브라우저: 서버에 GET 요청
- 서버: HTML 문서 전송
- 브라우저: HTML 파싱 시작
이때 브라우저가 HTML을 분석하면서
- 이미지, CSS, JS 파일 등 발견
- 각 리소스에 대해 서버로 요청
- 서버로부터 응답 받으면서 동시에 : 캐시 저장 여부를 결정하고 저장위치를 결정한다
여기서 캐시 저장 결정과정을 좀 더 자세하게 설명하자면 메모리 캐시로 저장할 것인지, 디스크 캐시로 저장할 것인지 결정하는 것이다.
다음과 같은 리소스들을 각각 저장하게 된다.
메모리 캐시 디스크 캐시 - 로고 이미지처럼 현재 페이지에서 자주 사용
- 작은 크기의 JS, CSS 파일
- 현재 세션에서 자주 필요할 것 같은 데이터
- 큰 이미지 파일
- 대형 JS 번들
- 폰트 파일
- 다음 방문에서도 사용될 것 같은 데이터
다시 정리하자면, 첫방문에는 모든 리소스를 서버에서 다운받고 브라우저가 리소스를 분석하고 캐시 저장을 결정하게 되고, 재방문 하게 된다면, 메모리캐시나 디스크캐시에서 바로 로드하게된다. 그리고 캐시가 없는 항목만 서버에서 다운로드 받는다.
아래는 한눈에 볼 수 있는 예시이다,
첫방문
재방문
마지막으로 상세 비교하자면 표와 같다.
메모리 캐시 디스크 캐시 특징 - RAM에 데이터 저장
- 휘발성 저장소
- 매우 빠른 접근 속도
- HDD/SSD에 데이터 저장
- 영구적 저장 가능
- 상대적으로 느린 접근 속도
장점 - 초고속 접근
- CPU 부하 최소화
- 실시간 처리 적합
- 대용량 저장 가능
- 저렴한 비용
- 영구 보존 가능
단점 - 용량 제한적
- 비용 높음
- 휘발성 (임시 저장)
- 접근 속도 느림
- I/O 부하 발생
- 디스크 공간 차지
주요 사용 사례 - 자주 접근하는 작은 파일
- 현재 페이지의 이미지
- 스크립트, 스타일시트
- 큰 미디어 파일
- 자주 변경되지 않는 리소스
- 장기 보관 필요한 데이터
어? 잠깐 디스크 캐시를 HDD/SSD에 저장한다고?
맞다. 디스크 캐시는 실제로 사용자의 로컬 저장소에 저장된다.
크롬을 사용한다면 보통, 다음의 주소로 저장되어있을 것이다.
Windows: C:\\Users\\[사용자명]\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cache Mac: ~/Library/Caches/Google/Chrome
(해시값으로 파일명 생성하고 압축하여 저장한다. 또한 메타데이터도 별도 관리한다.)
어 근데 내 컴퓨터는 용량이 얼마 없는데 용량차지하는거 아닌가? 란 생각이든다.
실제로 용량은 차지하긴 한다. 디스크의 공간을 사용하기 있기 때문이다.
다만, 전체 용량의 작은 부분만 제한되어 사용하면 서 동시에 자동 정리되기 때문에 무한정 증가하지 않는다.
++) 물론 정리도 막 하지는 않고 다음과 같은 기준으로 정리된다.
1. LRU (Least Recently Used) 가장 오래된 데이터부터 삭제 2. 용량 기반 설정된 최대 용량 도달 시 정리 3. 만료 시간 기반 캐시 유효 기간 만료 시 삭제 왜 디스크에 저장하는지에 대한 의문을, 반대로 모든것을 메모리에 저장했다면 아마 컴퓨터가 꺼졌을 때 다시, 전부 서버에서 받아와야 했을 것이다. 위에서 말했듯이 메모리는 휘발적이기 때문이다.
말이 나온김에 컴퓨터가 꺼졌을 때 다시 아까의 페이지를 재방문 한다면 아래와 같을 것이다.
브라우저의 캐시 확인 순서 a) 메모리 캐시 확인 → 비어있음 (재시작으로 모두 삭제됨)
b) 디스크 캐시 확인 → 이전에 저장된 데이터 존재
c) 유효성 검사 → 캐시된 데이터가 아직 유효한지 확인리소스별 처리 - 디스크 캐시에 있는 유효한 리소스 → 디스크에서 로드
- 나머지 리소스 → 서버에서 새로 다운로드
++) HTTP 캐시
추가적으로 앞선 캐시들을 저장을 하기위해 여러가지 지침들이 있다. 브라우저가 캐시를 저장하고 저장위치를 결정하는데 어떠한 이유와 근거가 존재할 것이다. 이럴 때 클라이언트 측 캐시를 제어하기 위하여 사용되는 것이 HTTP 캐시이다.
HTTP캐시 헤더는 브라우저의 캐싱 동작을 제어하는 방식으로 작동하는데, 서버에 요청을 보내면 HTTP 캐시 헤더와 함께 리스폰스가 온다.
access-control-allow-origin: * age: 0 cache-control: public,max-age=0,must-revalidate <---- content-encoding: br content-type: application/json; charset=utf-8 date: Wed,13 Nov 2024 12:49:12 GMT etag: W/"38c6-tLTQ2Zc/H/Rw+rdgbQNH4mpFrYA" <---- server: Vercel strict-transport-security: max-age=63072000; includeSubDomains; preload x-powered-by: Express x-vercel-cache: MISS x-vercel-id: icn1::icn1::xfztn-1731502152170-bd426eb6f8ca
이와 같은 비슷한 리스폰스 헤더를 본적이 있을 것이다.
여기서 브라우저는 이 헤더를 해석하여 캐싱 여부와 기간을 결정하고 어디에 캐시를 저장할지 판단하는 근거가 된다.
Cache-Control (서버 → 클라이언트)
서버가 리소스를 보낼 때 캐시 정책을 지정 max-age=3600 "이 리소스는 3600초(1시간) 동안 캐시해도 됨" public "모든 중간 캐시(프록시 등)가 이 응답을 캐시해도 됨" private "브라우저만 캐시하고, 중간 캐시는 하지 말 것" no-cache "캐시는 하되, 사용 전에 서버에 유효한지 물어볼 것" no-store "절대 캐시하지 말 것 (민감한 정보 등)" 실제 예시
1. 정적 이미지 파일 응답 Cache-Control: public, max-age=86400 → "24시간 동안 모든 곳에서 캐시해도 됨" 2. 개인정보가 포함된 페이지 Cache-Control: private, no-cache → "브라우저만 캐시하고, 사용 전 확인 필요" 3. 금융 데이터 Cache-Control: no-store → "절대 캐시하지 말 것"
ETag 작동 방식
(특정 버전의 리소스를 식별하는 식별자)
서버 응답: ETag: "abc123" → "이 리소스의 현재 버전은 abc123입니다" 브라우저의 다음 요청: If-None-Match: "abc123" → "abc123 버전을 가지고 있는데 아직 유효한가요?" 서버 응답: 304 Not Modified → "네, 그대로 사용하세요" 또는 200 OK (새로운 컨텐츠) → "새로운 버전이니 이것을 사용하세요"
동작 시나리오
첫 번째 요청: 1. 브라우저: 이미지 요청 2. 서버: 이미지 + 캐시 정책 전송 Cache-Control: max-age=3600 ETag: "v1.0" 한 시간 내 재요청: 1. 브라우저: 캐시된 버전 사용 한 시간 후 재요청: 1. 브라우저: 서버에 확인 요청 2. 서버: 변경 여부 확인 후 응답
여기서 ETag와 Cache-control이 비슷해 보여서 중복적인 기능을 수행하는 느낌이 들지만, 서로 다른 목적과 장점이 존재하여 함께 사용된다.
Cache-control의 특징 ETag의 특징 - 캐시의 수명을 시간 기반으로 제어
- max-age로 캐시 유효 기간을 명시적으로 지정
- 클라이언트가 서버와 통신 없이 캐시 사용 가능
- 리소스의 버전을 해시값으로 관리
- 리소스 내용이 변경될 때만 새로운 값 생성
- 서버와 경량 통신으로 검증 (304 Not Modified)
따라서 이 둘을 적절하게 사용하면 다양한 시나리오를 대응할 수 있게된다.
상황 해결 Case 1 유효기간 내 + 내용 같음 Cache-Control로 캐시 사용 Case 2 유효기간 만료 + 내용 같음 ETag로 304 응답 (본문 전송 불필요 : res헤더만 전송) Case 3 유효기간 내 + 내용 다름 Cache-Control 무시하고 새로운 데이터 요청 가능 Case 4 유효기간 만료 + 내용 다름 새로운 데이터 전체 전송 Case2 번의 경우의 중요한점은, 헤더로 304 not modified 로 본문의 내용없이 헤더만 전송된다.. 이로서 얻는 이점이 있는데 크게 3가지가 있다.
네트워크 대역폭 절약 - 전체 리소스 대신 헤더만 주고받음
- 특히 큰 파일에서 효율적
서버 부하 감소 - 파일 전체를 전송하지 않아도 됨
- 특히 많은 클라이언트가 동시 접속할 때 효과적
응답 시간 단축 - 작은 크기의 헤더만 전송
- 클라이언트는 캐시된 데이터 즉시 사용
따라서 만약 Cache-control만 있다면, 변경된 점이 없더라도 유효기간이 지난 후에는 무조건 전체의 데이터를 다시 받았어야 했을 것이다. 이러한 캐시 정책은 서버에서 설정하지만, 이를 해석하고 따르는 것은 엄연히 클라이언트(브라우저)이다. 서버는 "이렇게 캐시해도 됩니다" 라는 지침을 주는 것이고, 브라우저는 이 지침에 따라 캐시를 관리하게 된다.
다시 정리하자면 HTTP 캐시 헤더는 "규칙"을 제공하는 것이고, 실제로 이 규칙을 해석하고 캐시를 어떻게 저장/관리할지는 브라우저가 결정한다.
++ ) 브라우저의 캐시 관리 프로세스를 보면:
1. HTTP 헤더 해석 - Cache-Control 지시자 확인 - ETag 값 저장 - Expires 날짜 확인 등 2. 저장소 결정 (브라우저 자체 로직) 메모리 캐시에 저장하는 경우: - 작은 크기의 파일 - 자주 접근되는 리소스 - JS, CSS 등 실행 파일 디스크 캐시에 저장하는 경우: - 큰 이미지 파일 - 동영상 등 멀티미디어 - 자주 접근되지 않는 리소스
예를 들어 Chrome의 경우:
chrome://cache/ 에서 확인 가능: - 각 리소스별 저장 위치 - 캐시된 데이터 크기 - 만료 시간 - HTTP 헤더 정보 등
즉, HTTP 헤더는 "이 리소스를 얼마나 오래 캐시해도 되는지"를 알려주는 것이고, "어디에 어떻게 저장할지"는 브라우저가 자체적인 최적화 로직으로 결정한다. 이처럼 살펴본 캐시 시스템들은 각자의 위치에서 서로 다른 방식으로 동작하지만, 근본적으로는 동일한 문제를 해결하고 있음을 알 수 있다.
맺는말
캐시는 컴퓨터 시스템의 여러 계층에서 동일한 문제(병목 현상)를 해결하기 위해 사용되는 중요한 개념이다. CPU 캐시부터 웹 브라우저의 캐시까지, 모두 동일한 원리인 '참조 지역성'을 활용하여 성능을 개선한다. 특히 웹 환경에서의 캐시는 단순한 성능 향상을 넘어 네트워크 대역폭 절약, 서버 부하 감소, 사용자 경험 개선 등 다양한 이점을 제공한다.
캐시의 개념을 이해하는 것은 시스템 설계나 성능 최적화를 위해 필수적이며, 앞으로도 새로운 기술과 패러다임이 등장하더라도 이 기본 원리는 계속해서 중요한 역할을 할 것이라 생각이 든다.