캐시는 대역폭 낭비와 응답 지연을 방지하고자 사본을 임시로 저장하는 기술이다.
데이터를 캐시에 저장하는 행위
일반적으로 캐싱은 캐시에 저장된 데이터에 접근하는 시간에 비해 원본 데이터에 접근하는 시간이 오래 걸리는 경우 사용한다.
HTTP 캐싱을 활용하면 웹 사이트의 로딩 시간을 개선할 수 있다.
이미지, 혹은 JS, CSS 같은 파일들은 자주 변하지 않는다.
캐시를 사용하지 않으면 자주 변하지 않는 데이터라도 요청하고 새롭게 다운로드 해야한다.
이는 불필요한 네트워크 비용 발생을 야기하고, 서버에 추가적인 부담을 준다.
결국 느린 웹페이지 로딩 속도로 직결되고, 좋지 않은 사용자 경험을 낳을 것이다.
Private Cache
웹 브라우저에 저장되는 캐시이며, 다른 사람이 접근할 수 없다.
단, 서버 응답에 Authorization
헤더가 포함되어 있다면 Private Cache에 저장되지 않는다.
Shared Cache
웹 브라우저와 서버 사이에서 동작하는 캐시를 의미한다.
다시 Proxy Cache와 Managed Cache 2가지로 나뉜다.
Proxy Cache
(포워드) 프록시에서 동작하는 캐시이다.
Managed Cache
AWS Cloudfront 혹은 Cloudflare와 같은 CDN 서비스, 그리고 리버스 프록시에서 동작하는 캐시이다.
이런 서비스들의 관리자 패널에서 직접 캐시에 대한 설정을 관리하거나 리버스 프록시 설정으로 관리할 수 있으므로 Managed Cache
라고 부른다.
여기서 포워드 프록시, 리버스 프록시 등 프록시가 무엇인지 알아보자.
클라이언트와 서버 사이에 대리로 통신을 수행하는 것을 가리켜 프록시(proxy)라고 하며, 그 중계 기능을 하는 서버를 프록시 서버라고 한다.
클라이언트, 혹은 반대로의 서버가 다른 네트워크에 간접적으로 접속할 수 있기 때문에, 보안, 캐싱을 통한 성능, 트래픽 분산 등의 장점이 있다.
클라이언트에서 사용하고 저장하는 캐시를 private cache
라고 하며, 프록시 캐시를 public
이라고 한다.
프록시를 사용하면 보안을 강화할 수 있고, 통신 성능을 높여주고 통신 비용을 절약할 수 있다.
프록시는 프록시 서버의 위치에 따라 유형이 나뉜다.
유형에 따라 각각의 용도도 조금씩 다르며, 크게 포워드 프록시
와 리버스 프록시
2가지로 나뉜다.
인터넷 연결 속도가 느려질 수 있다.
인터넷에 연결할 때 프록시를 통해 수행하는 단계가 늘어나므로 그만큼 시간이 걸린다.
익명성이 높은 프록시는 엑세스 속도를 크게 저하시키는 경향이 있다.
일반적으로 사람들이 떠올리는 프록시의 유형이다.
포워드 프록시는 클라이언트와 인터넷 사이에 위치해있으며, 이로인해 클라이언트의 정보가 서버측에 노출되지 않는다.
캐싱
프록시를 사용하면 자주 사용되는 HTML
, JS
, CSS
, 이미지
와 같은 정적 파일들을 원 서버로부터 캐싱하고, 클라이언트가 요청할 때마다 원 서버로 요청하는 대신 가지고 있는 캐시로 응답할 수 있다.
이는 원 서버에 대한 부하 감소, 네트워크 병목 현상 감소, 전송 시간 절약의 이점이 있다.
특정 컨텐츠 액세스 제한
학교 혹은 사내의 내부망에 프록시 서버를 두어 특정 컨텐츠에 대한 엑세스를 제한할 수 있다.
학교에서는 학생들에게 유해한 웹사이트를 프록시 서버를 통해 차단할 수 있다. 클라이언트의 모든 요청은 프록시를 거쳐 인터넷으로 연결되기 때문이며, 사내망에서도 보안을 위해 특정 웹사이트에 대한 접속을 차단할 수 있다.
익명성 확보
모든 요청이 프록시 서버를 통해 발생하므로 클라이언트는 인터넷에서 자신을 숨길 수 있다.
클라이언트의 실제 IP 주소가 노출되는 것을 원치 않을 경우 프록시 서버를 경유할 수 있다.
속도 저하
포워드 프록시는 요청을 우회해야 하기 때문에 네트워크 속도가 느려질 수 있다.
특히, 여러 사용자가 동시에 프록시를 사용할 경우, 프록시 서버에 부하가 걸려 성능이 저하될 수 있다.
설정 관리가 필요하며, 잘못 설정될 경우 보안 위협이 될 수 있다.
포워드 프록시는 보안 기능을 제공할 수 있지만, VPN과 같은 더 강력한 보안 기능을 제공하지 못한다.
예를 들어, 프록시는 단순히 IP를 숨길 뿐 데이터 암호화는 제공하지 않는 경우가 많아, 중간자 공격에 취약할 수 있다.
일부 웹사이트에서는 호환성 문제로 인해 프록시 서버를 거치는 접근을 차단하기도 한다.
포워드 프록시는 간단한 프라이버시 보호나 IP 우회를 위한 용도로는 적합하지만, 더 높은 보안 요구사항이나 성능을 원하는 경우 다른 솔루션이 필요하다.
서버와 인터넷 사이에 위치한 프록시 서버이다.
클라이언트의 요청을 서버 대신 받게 된다.
이런 구조에서는 클라이언트는 프록시 뒤의 서버의 존재를 모르게 되며, 웹 서버에 요청하듯이 프록시 서버에 요청하게 된다.
로드 밸런싱
하나의 서버를 두어 수백만명의 트래픽을 감당하기엔 무리가 있다. 따라서 수평 확장(scale out)하여 다중 서버 환경을 구축하게 된다.
이때 프록시는 여러 서버에 부하를 분산하여 한 서버에 집중적으로 부하가 몰리는 상황을 방지하는 역할을 할 수 있다.
또한 헬스 체크를 하여 서버 장애가 발생했을 시 해당 서버로 요청을 보내지 않도록 할 수도 있다.
무중단 배포
리버스 프록시를 사용하면 서비스를 새로 배포할 때에도 중단 없이 배포할 수 있다.
한 서버가 배포중일 때는 프록시가 그 서버로 요청을 전달하지 않고 한 번에 하나의 서버에만 배포하게 되면 사용자는 서비스 이용이 중단되는 경험 없이 그대로 서비스를 이용할 수 있으며 자연스럽게 새로운 배포 버전을 사용할 수 있게 된다.
DDoS 등의 공격으로부터 보호
서버 앞단에 프록시를 두게 되면 서버는 인터넷으로부터 숨겨진다.
외부에서는 서버의 IP를 알 수 없게 된다.
즉, 공격자는 DDoS 등의 공격 대상으로 실제 서버를 두기 어렵게 된다. 공격자는 리버스 프록시 서버만 공격 대상으로 설정할 수 있게 되며, 이로 인해 실제 서버를 여러 공격으로부터 보호할 수 있다.
SSL 암호화
리버스 프록시에만 SSL을 적용하면, 실제 서버는 SSL 통신에서 오는 추가 비용을 없앨 수 있다.
캐싱
포워드 프록시와 동일하게 리버스 프록시도 캐싱 기능을 수행할 수 있다.
단일 장애점
리버스 프록시는 모든 클라이언트 요청이 프록시 서버를 통해 전달되기 떄문에, 프록시 서버에 장애가 발생하면 전체 시스템에 문제가 발생할 수 있다.
이를 방지하기 위해서는 프록시 서버의 고가용성 설정이 필요하다.
지연 시간 증가
클라이언트 요청을 실제 서버로 전달하기 전에 요청을 처리하고 변환하는 과정이 포함되기 떄문에, 추가적인 지연 시간이 발생할 수 있다.
캐시한 데이터는 사본이다.
언제든지 서버의 원본 데이터가 변경될 수 있다.
원본 데이터가 변경되었는데도 계속해서 캐시된 사본 데이터를 참조하다보면 문제가 발생할 수 있다.
캐시된 사본 데이터가 얼마나 최신 원본 데이터와 유사한지를 캐시 신선도
라고 표현하기도 한다.
신선도를 유지하는 가장 기본적인 방법은 '캐시된 데이터에 유효 기간을 설정하는 방법'이다.
응답 메시지의 Expires 헤더와 Cache-Control 헤더의 Max-Age 값(초)을 사용할 수 있다.
이 max-age
라는 디렉티브를 사용하면 캐시의 최대 수명을 설정할 수 있다.
HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: max-age=3600
Content-Length: 157
<!DOCTYPE HTML>
<html lang="ko">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
</head>
<body>
Hello, World!
</body>
</html>
웹 브라우저가 특정 리소스에 최초로 요청했을 경우, 서버는 Cache-Control
헤더가 포함된 응답을 보낸다.
응답을 받은 웹 브라우저는 응답 결과를 3600초 (1시간) 동안 캐시에 저장한다.
이후 웹 브라우저가 같은 리소스에 요청을 보내면, 실제 웹 서버에 요청을 보내는 것이 아니라 캐시에 저장한 사본 데이터를 사용자에게 제공한다.
max-age
디렉티브에 명시한 캐시 유효 시간이 지난 이후 동일한 리소스를 요청하면, 그때는 웹 서버에 리소스를 요청한다.
캐시의 유효 기간이 지난 다음 다시 서버에 리소스를 요청하면 둘 중 하나이다.
이때, 전자의 경우는 트래픽을 낭비한 것이 된다.
이를 위해 실제 원본 데이터가 수정되었을 때만 리소스를 내려 받는 것이 바람직하다.
이런 과정을 캐시 유효성 검증
및 조건부 요청
이라고 한다.
캐시의 신선도를 재검사하는 방법은 크게 두 가지 방법이 있다.
클라이언트는 If-Modified-Since
, Last-Modified
헤더를 통해 리소스의 마지막 갱신 시각을 기준으로 캐시의 유효성을 검증할 수 있다.
최초 요청 시 응답
HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: max-age=3600
Last-Modified: Sat, 03 Sep 2022 00:00:00 GMT
Content-Length: 157
<!DOCTYPE HTML>
<html>
...
Last-Modified
헤더가 추가되었다.
이 헤더는 요청한 리소스가 마지막으로 수정된 시점을 나타낸다.
'이 이후에 자원이 변경되었니? 변경 되었을 경우에만 새 자원으로 응답해 줘'라는 요청 메시지와 같다.
HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: max-age=3600
Last-Modified: Sat, 03 Sep 2022 00:00:00 GMT
Content-Length: 157
<!DOCTYPE HTML>
<html>
...
자원이 변경되었다면, 서버는 상태 코드 200(OK)와 함께 새로운 자원을 반환한다.
요청받은 자원이 삭제되었다면, 서버는 상태 코드 404(Not Found)를 통해 요청한 자원이 존재하지 않음을 알린다.
이와 유사하게 If-Modified_Since
헤더도 서버에게 특정 시점 이후로 원본 데이터에 변경이 있었는지 물어본다.
변경이 있었다면 그때만 새 자원으로 응답하도록 서버에게 요청하는 헤더이다.
앞선 방법은 밀리 세컨드 단위로 시각을 설정할 수 없다는 한계점이 존재한다.
ETag
란 특정 버전의 리소스를 식별하기 위해 사용하는 식별자이다.
즉, 자원이 변경될 때마다 자원의 버전을 식별하는 ETag 값이 변경된다.
반대로 변경되지 않았다면 ETag 값도 변경되지 않는다.
최초 요청 시 응답
HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: max-age=3600
ETag: "abcdefg"
Content-Length: 157
<!DOCTYPE HTML>
<html>
...
리소스에 대한 최초 요청 시 ETag
라는 응답 헤더가 돌아온다.
요청한 리소스의 현재 버전에 대한 식별자이며 브라우저는 이 ETag
값을 저장한다.
클라이언트가 ETag 값이 부여된 자원을 캐시할 때 캐시 신선도를 검사하기 위해 서버에게 '이 ETag 값과 일치하는 자원이 있니?'와 같이 물어볼 수 있다.
이를 위해 사용하는 헤더가 If-None-Match
이다.
GET /index.html HTTP/1.1
Host: www.example.com
If-None-Match: 'abcdefg'
'혹시 ETag 값이 abcdefg인 자원이 있니? 자원이 변경되었다면 (Etag 값이 바뀌었다면) 그때만 새 자원으로 응답해줘' 라는 요청 메시지와 같다.
이때도 서버의 자원은 크게 셋 중 하나이다.
세 경우 모두 날짜 기반 유효성 검증과 같은 응답을 반환하게 된다.
유저에게 항상 최신 버전의 리소스만을 제공하고 싶을 경우가 있다.
이때, 항상 최신 버전의 리소스를 캐시하거나, 혹은 아예 캐시 자체를 하지 않는 방법이 있다.
이런 옵션을 위해 웹 브라우저가 요청을 보낼 때 Cache-Control
헤더에 no-cache
혹은 no-store
디렉티브를 포함할 수 있다.
no-cache
는 캐시를 아예 생성하지 않는 옵션이 아니다.
리소스에 대한 캐시를 생성하지만, 리소스를 요청할 때 원 서버에 항상 캐시 유효성 검증을 하는 옵션이다.
no-store
는 리소스에 대한 캐시를 생성하지 말라는 가장 강력한 Cache-Control
디렉티브이다.
저장하면 안되는 민감한 정보일 때 사용한다.
Cache-Control
헤더의 private
또는 public
디렉티브를 사용하여 캐시 허용 범위를 지정할 수 있다.
public
디렉티브를 사용하면 Shared Cache에서도 캐싱을 허용하고, private
디렉티브를 사용하면 사용자 브라우저에게만 캐싱을 허용한다.
Cache-Control: max-age=3600, private;
웹 캐시에 대해 설명해주세요
웹 캐시는 자주 사용되는 자원의 사본을 보관하는 장치입니다.
만약 어떤 HTTP 요청에 대한 사본이 캐싱되어 있으면, 서버까지 요청이 가지 않고 바로 캐시로부터 자원을 제공받게 됩니다.
캐시를 사용하면 반복되는 요청을 줄여줘서 서버의 부하를 줄여주고, 갑작스러운 트래픽을 대처하게 해주며
네트워크 병목을 줄여주고
먼 거리로 인한 전송 지연을 줄여줍니다.
프록시 서버에 대해 설명해주세요
프록시 서버는 클라이언트와 서버 사이에 존재하는 중개자입니다.
그렇기에 프록시는 클라이언트로 동작하기도 하고, 서버로 동작하기도 합니다.
프록시는 데이터를 필터링 또는 변환하거나
방화벽을 세우거나 또는 캐시 서버로 동작할 수 있습니다.
포워드 프록시에 대해 설명해주세요
포워드 프록시는 클라이언트 앞에 위치하는 프록시입니다.
포워드 프록시를 사용해서 캐싱을 포함해 익명으로 서버에 접근할 수 있으며 특정 컨텐츠를 제한할 수도 있습니다.
리버스 프록시에 대해 설명해주세요
리버스 프록시는 서버 앞에 위치하는 프록시입니다.
실제 서버 앞에 리버스 프록시를 둠으로써 DDoS 등의 공격으로부터 보호할 수 있으며 로드 밸런싱 및 무중단 배포가 가능합니다.
네트워크로 데이터를 가져오는건 느리다.
데이터가 지구 반대편에 위치한다면 더욱 느려진다.
그렇기에 데이터를 더 빠르게 가져올 방법이 필요하다.
그 중에 하나가 HTTP Cache
를 활용하는 것이다.
프론트엔드 개발자로서 어떤 사이트든 개발자 도구로 분석해보면 보이지 않는 곳에 HTTP cache가 유용하게 사용되고 있는 것을 확인할 수 있다.
HTML을 가져올 때, 이미지나 비디오를 가져올 때, Javascript, CSS 등 critical resources에도 HTTP cache는 사용된다.
그럼 왜 HTTP cache가 필요한걸까?
HTTP cache를 사용하면 느린 네트워크를 통해 데이터를 가져올 때 사용자가 가까운 브라우저, 웹 서버 등으로부터 데이터를 가져올 수 있기 때문에 더 빠르게 데이터를 가져온다.
느린 네트워크는 다음과 같은 좋지 않은 사용자 경험의 원인이 된다.
사용자로부터 시작해 여러가지 cache layer에 대해 알아보자.
아래의 이미지는 사용자로부터 어떤 cache layer가 있는지 확인할 수 있다.
browser
- CDN(proxy) - origin serverCache invalidation
을 원활히 하기 위해서는 고유한 파일 이름을 사용하는 것이 좋다.
이를 fingerprint
라고 한다.
예를 들어, 페이지가 로드되기 위해 script.js
라는 파일이 있고 이를 1년 동안 캐싱하도록 설정했다고 하자.
그런데 일주일 후, 요구사항이 변겨오대서 script.js
파일이 변경됐다.
유저의 브라우저에는 이전에 캐싱한 파일이 남아있고 이를 1년동안 사용하도록 했기 때문에 변경된 코드는 새로 배포해도 변경사항이 적용되지 않을 것이다.
이를 해결하기 위해 고유한 파일 이름을 사용한다.
처음 배포한 파일의 이름이 script.102938.js
라고 하자.
파일의 content로부터 고유한 값을 생성하기 때문에 만약 변경점이 있다고 하면 script.95837.js
와 같이 파일의 이름이 바뀌게 된다.
즉, script.102938.js
를 1년 동안 캐싱하더라도 새로 요청할 파일의 이름은 script.95837.js
이기 때문에 변경된 파일을 가져올 수 있게 된다.
과정은 아래와 같다.
index.html
)을 가져온다.script.102938.js
를 가져온다. 캐시 설정이 1년이기 때문에 이를 브라우저에 캐싱한다.script.95837.js
를 바라보는 html 파일을 웹 서버로 배포한다.script.95837.js
를 요청한다. 이 파일은 캐싱되지 않았기 때문에 변경점이 잘 반영된다.script 파일을 예로 들었지만, image, vedeo, css 등 여러 파일에도 똑같이 fingerprint를 적용할 수 있다.
실제로 토스에서 fingerprint를 사용하고 있다고 한다.
VPN(virtual private network, 가상 사설망)이란 프록시 서버와 마찬가지로 원격 서버를 통해 인터넷 트래픽을 재라우팅하고 실제 IP주소를 가상 IP주소로 대체함으로써 웹사이트 측에서 사용자의 실제 IP주소와 위치를 확인할 수 없도록 한다.
프록시 서버(응용 프로그램 수준에서 동작)와 달리 VPN은 운영체제 수준에서 동작하므로 브라우저나 백그라운드 앱에서 발생하는 모든 트래픽을 리디렉션할 수 있다는 차이점이 있다.
NEXT. HTTP 쿠키와 세션