Cloudflare Tiered Cache를 이용해서 R2 응답속도 30% 줄이기

Cloudflare Tiered Cache, Cache Rules를 이용해서 CDN 캐싱 성능을 최적화하는 방법을 알아봅시다.
CDN 캐싱
CDN 캐싱이란 자주 요청되는 정적 리소스(HTML 파일, JavaScript, 이미지)를 사용자와 가까운 서버에 복사 해놓고 빠르게 응답해서, 원본 서버를 덜 귀찮게 만들어 웹사이트 성능을 높이는 기술입니다.
결국 핵심은
- 캐시 서버가 사용자와 가까운 곳에 있어야 하고
- 원본 서버와 사용자 사이에 최대한 캐싱 계층을 많이 배치해서 원본에 최대한 요청이 가지 않아야 하고
- 네트워크 경로의 혼잡을 제어해서 요청을 최대한 분산
이렇게 정리할 수 있는데요, Cloudflare에서는 최종 사용자와 최대한 가까운 위치에서 통신하기 위해 전 세계 330개의 지역에 CDN 서버를 배치해놨다고 합니다.
Edge Cache
엣지 캐시(Edge Cache)란 말 그대로 최종 사용자와 가장 가까운 CDN 서버를 지칭하는데요, 현재 저희는 전 세계를 상대로 콘텐츠를 배포하는 게 아닌, 한국 사용자들을 대상으로 작고 소중한 블로그를 운영 중이니 한국을 기준으로 알아보겠습니다.
한국에서 클라우드플에어 CDN 서비스를 사용하려면 망 사용료에 대해 알고 있어야 합니다. 클라우드플레어는 한국 통신사의 망 사용료가 비싸다고 주장하는 입장이여서 무료 사용자들에게는 한국 데이터센터가 아닌 해외로 트래픽을 우회시켜서 CDN 서비스를 제공 중입니다.
한국 사용자들의 요청은 보통 일본, 홍콩, 싱가포르 정도로 라우팅 되어서 처리됩니다.
- Fukuoka, Japan - (FUK)
- Tokyo, Japan - (NRT)
- Osaka, Japan - (KIX)
- Hong Kong - (HKG)
- Singapore, Singapore - (SIN)
이전 포스팅에서 Origin 서버로 R2를 이용해서 이미지 호스팅 서버를 구축했는데요, 이렇게 R2에 사용자 지정 도메인을 연결하면 특정 HTTP 응답 코드마다 TTL이 적용되어서 엣지 서버에 콘텐츠가 캐싱 됩니다.
문제점
# request 1
curl https://img.day1swhan.com/hello-r2.webp
HTTP/2 200
,..
cf-cache-status: MISS
cf-ray: 970f1f1e398ad1be-KIX # Osaka - Japan
# request 2
curl https://img.day1swhan.com/hello-r2.webp
HTTP/2 200
...
cf-cache-status: MISS
cf-ray: 970f1f0d9fa9fcce-FUK # Fukuoka - Japan
curl 명령어를 이용해서 확인해 보면, 사용자의 요청은 클라우드플에어 측의 네트워크 상황에 따라서 여러 엣지 서버로 동적으로 라우팅 되는 것을 보실 수 있는데요, 여러 엣지 서버로 요청이 간다는 건 가용성이 높다는 장점이 있습니다.
하지만 엣지 서버에 콘텐츠가 캐시 되어있지 않을 때는(Cache Miss), 물리적인 스토리지(SSD, HDD)로 이루어진 R2에 접근하는 빈도가 증가하게 되면서 레이턴시가 늘어나는 문제점도 가지고 있습니다.
Tiered Cache Topology
이런 문제점 때문에 클라우드플레어는 계층형 캐시(Tiered Cache) 사용을 권장하는데요, Tiered Cache란 클라우드플러어 데이터센터와 오리진 서버가 통신하는 계층을 지정할 수 있는 옵션입니다.
오리진 서버와 가까울수록 Upper Tier, 사용자와 가까울수록 Lower Tier라고 부르는데요, 각 계층 먼저 가볍게 알아보겠습니다.
- Smart Tiered Cache: Argo Smart Routing 기반으로 오리진과 가까운 가장 성능 좋은 POP 단 하나을 Upper Tier로 선택
- Generic Global Tiered Cache: 지역별로 큰 POP을 Upper Tier로 선택
- Regional Tiered Cache: 지역 단위로 묶인 중간 캐싱 계층. Smart Tiered Cache와 같이 사용함. (ex. 서울 사용자 요청 → 도쿄 Regional Tier → (Smart Tier) 싱가포르 Upper Tier → AWS 싱가포르 오리진)
- Custom Tiered Cache: Upper Tier, Lower Tier 수동 설정 가능
이렇게 여러 옵션들이 존재하지만 무료 사용자는 Smart Tiered Cache 옵션만 설정 가능하니 이것만 알아보겠습니다.
Smart Tiered Cache
여러 엣지 서버들이 동시에 오리진에 접근하면 병렬성은 높지만, 캐시가 분산되기 때문에 캐시 HIT 률이 낮아지는 문제가 있습니다. Smart Tiered Cache는 이걸 희생하고 오리진 접근 지점을 단일화해서 효율을 끌어올리는 구조인데요, Smart Tiered Cache를 사용하면 클라우드플레어가 내부적으로 오리진 서버의 레이턴시를 측정해서 오리진 서버 앞에다 동적으로 가장 성능 좋은 단 하나의 상위 캐시 계층(Upper Tier)를 지정하게 됩니다.
이제 엣지 서버들은 이 상위 캐시 계층을 통해서만 오리진 서버에 접근하게 되니 캐시 HIT 률 향상되어서 응답 성능이 높아지게 됩니다.
그림으로 보시면 작동 구조가 더 쉽게 이해 가능한데요, 이렇게 R2 앞단에 캐싱 계층을 두고, 엣지 서버들이 이 계층을 통해서만 통신하도록 작동하면
사용자 A의 첫 요청에 의해 Upper Tier에 한번 캐싱 되면, 사용자 B가 다른 엣지 서버를 통해서 요청을 보내도 R2에 직접 요청하는 게 아닌, Upper Tier에서 캐싱된 콘텐츠를 바로 가져올 수 있어서 응답 속도가 더 빨라지게 됩니다.
Cache Rules
Tiered Cache Topology를 이용해서 물리적인 최적화가 가능해졌으니 이제 Cache Rules을 이용한 소프트웨어적인 최적화를 알아보겠습니다. 캐시 규칙(Cache Rules)은 콘텐츠를 어디에, 얼마나 캐싱 할지 정의해서 사용자의 요청을 더 정교하게 제어할 수 있습니다.
위에서 R2에 사용자 지정 도메인을 연결하면 엣지 서버에 특정 HTTP 응답 코드마다 TTL을 적용해서 콘텐츠를 캐싱 해놓는다고 말씀드렸죠? 정확히는 오리진 서버가 응답할 때 Expires(리소스의 만료되는 절대 시간)
또는 Cache-Control(현재 시간을 기준으로 캐싱 할 기간을 초 단위로 설정)
헤더를 따로 추가해 주지 않을 때 작동하는 건데요, R2는 기본적으로 캐싱 관련 헤더를 제공하지 않으니 webp 이미지에 대해서만 작동하도록 캐싱 정책을 설정해 보도록 하겠습니다.
- Edge Cache TTL: 엣지 서버에 캐싱 할 최대 기간. 무료 플랜은 최소 2시간부터 설정 가능
- Browser Cache TTL: 사용자 브라우저에 캐싱 된 리소스 만료 시간. 무료 플랜은 최소 2시간부터 설정 가능.
대시보드 - Caching - Cache Rules - 규칙 생성 탭 들어가셔서
수신 요청이 일치하는 경우
- 사용자 설정 필터 식
- 필드: 파일 확장명
- 연산자: 같음
- 값: webp
- 캐시 적합성: 캐시에 적합
- 캐시 제어 헤더 무시 및 덮어쓰기
- Edge 기본 TTL: 2시간
- 상태 코드 TTL: 200 → 2시간, 301 → 4시간
- 캐시 제어 헤더 무시 및 덮어쓰기
- Browser 기본 TTL: 1일
- 캐시 키: 캐시 속임수 방어, 쿼리 문자열 무시 활성화
주의: Browser Cache TTL은 Edge Cache TTL보다 높아야 됩니다.
검증하기
curl 명령어 이용해서 총 요청 시간 먼저 확인해 보겠습니다.
# reqeust 1
curl -w "@curl-format.txt" -o /dev/null -s -D - https://img.day1swhan.com/hello-r2.webp
HTTP/2 200
...
cache-control: max-age=86400
cf-cache-status: MISS
cf-ray: 9710a04eaaa4fccb-FUK
== curl timing ==
dns: 0.002411s
tcp: 0.146601s
ssl: 0.293156s
start: 0.590000s
total: 0.631238s
# reqeust 2
curl -w "@curl-format.txt" -o /dev/null -s -D - https://img.day1swhan.com/hello-r2.webp
HTTP/2 200
...
cache-control: max-age=86400
cf-cache-status: HIT
cf-ray: 9710a066480afccf-FUK
== curl timing ==
dns: 0.001596s
tcp: 0.140970s
ssl: 0.281622s
start: 0.430657s
total: 0.436369s
- 캐시 적용 후 총 요청 시간 0.63 → 0.43초, 약 30% 감소
- DNS/TCP/SSL은 거의 차이 없음 → 네트워크 레벨은 그대로
- 주된 차이는 start transfer 시간에서 발생 → 즉, 서버가 응답을 빠르게 돌려주기 시작
브라우저 TTL도 잘 적용되었는지 확인해 보도록 하겠습니다. 이미지 주소를 직접 요청하면 브라우저 캐싱이 작동하지 않으니 img 태그 적용된 HTML 파일 하나 만들어서 확인해 보면
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Browser Cache TTL 테스트</title>
</head>
<body style="font-family: Apple SD Gothic Neo">
<div style="max-width: 700px; margin: 0px auto; padding: 20px; border: 1px solid blue">
<h2 style="text-align: center">Browser Cache TTL 테스트입니다.</h2>
<img src="https://img.day1swhan.com/hello-r2.webp" style="max-width: 100%" />
</div>
</body>
</html>
이렇게 첫 요청에서는 CDN 서버에서 응답받고, Cache-Control 헤더에 저희가 위에서 Cache Rules Browser TTL에서 설정한 1일(86400초) 잘 적용된 거 보이고
새로고침하면 브라우저에 캐싱 된 콘텐츠 보여주면서 요청 자체를 시도하지 않는 것을 보실 수 있습니다.
마무리
이렇게 버튼 딸깍 몇 번만으로도 충분히 쓸만한 성능이 나오도록 CDN 캐싱 최적화가 가능한 것을 확인해 봤습니다. 통신사, CDN 업체 양측의 입장이 이해가 되는 만큼 한국에서도 어서 빨리 망 중립성, 망 사용료 문제가 해결되어서 더 좋은 서비스들이 많이 탄생하길 바라며 포스팅 마치겠습니다.