전자상거래 서비스를 유지관리하는 개발자 또는 조직으로썬 순간적으로 트래픽을 끌어모으는 마케팅이 부담스러울 때가 있다. 우아한 형제들 기술 블로그에 나오는 사례는 그러한 고민을 잘 보여준다.
이 사례에서는 꽤나 개발과 준비를 많이 하는데 빼빼로데이라는 특성상 그럴 필요가 있었던 듯 하다. 하지만 모든 이벤트가 이 정도의 트래픽을 끌여들이는 건 아니다. 단순히 특정 상품을 목 좋은 앱 상단화면에 노출시킨다던가 마케팅 규모가 좀더 작을 때는 평소보다 수 배에서 수십 배의 트래픽만 감당하면 된다. 그런데 이러한 상황은 빼빼로데이 건과는 여러 점에서 다르다.
- 전술한 바와 같이 트래픽이 상대적으로 작다.
- 그리고 상품구성이 수시로 바뀌고
- 주제를 조금씩 바꿔서 지속적으로 이벤트를 진행한다.
이러한 까닭에 우아한형제들의 사례와는 달리 별도의 시스템까지 구축해서 마케팅 이벤트를 매번 대응하기는 힘들고 그럴 필요도 없다. 이러한 상시적인 이벤트 상황에서는 시스템 계층 사이사이에 캐싱을 적절히 넣어서 전체 처리량(Throughput)을 늘리는 편이 낫다.
일반적인 이벤트의 특성상 수초 내에 이벤트 페이지 또는 특정 상품 상세 페이지로 트래픽이 과하게 유도된다. 그러므로 해당 페이지 또는 해당 페이지가 사용하는 API에 마이크로캐싱을 적용하는 방법이 꽤나 유용하다.
Kubernetes로 인프라스트럭처를 운영하는 데일리의 경우는 Nginx Ingress를 웹 애플리케이션 서버와 로드밸런서 사이에 끼워넣고 마이크로캐싱을 제공하는 방법을 이용한다. 이렇게 하면 기존 애플리케이션과 아키텍처에는 거의 손을 대지 않아도 된다. Ingress만 살짝 손봐서 올리면 짜잔~ 하고 적당히 괜찮은 성능을 확보할 수 있다. 결론부터 말해 트래픽이 열 배 내외로 늘어나는 상황은 간단히 처리가능하다. 성능 테스트 결과 중 하나를 예로 살펴보면 Nginx 마이크로캐싱 도입 전후로 다음과 같이 성능이 차이난다.
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 22 69.9 0 525
Processing: 77 220 49.2 213 569
Waiting: 77 220 49.2 212 569
Total: 77 242 85.5 217 802
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 2 23.5 0 435
Processing: 40 101 25.6 99 570
Waiting: 40 101 25.6 99 569
Total: 40 102 33.3 99 570
한 API에 대해 초당 트래픽을 늘려도 대체로 성능은 무난히 유지된다. 물론 Nginx 설정에 따라 개선여지가 많지만 보수적으로 설정해도 이 정도는 가능하다.
자, 그럼 Nginx Ingress에 마이크로캐싱을 어떻게 적용하는가? 그게 사실은 생각보다 귀찮다. 자세히 설명하기 전에 우선 Nginx Ingress 구현체가 하나가 아니라는 점부터 알고 가자. nginxinc/kubernetes-ingress의 README 문서에서 지적하듯
This repo provides an implementation of an Ingress controller for NGINX and NGINX Plus. This implementation is different from the NGINX Ingress controller in kubernetes/ingress repo.
kubernetes/ingress라는 게 오래 전에 나왔고 그 후에 nginxinc/kubernetes-ingress가 나온 걸로 안다. 아무튼 데일리는 전자를 사용하며 지금부터는 kubernetes/ingress에 포함된 nginx ingress를 기준으로 설명한다.
Nginx Ingress는 주로 ConfigMap, Annotation, 그리고 템플릿 파일을 수정하여 세부 설정을 조절할 수 있다. Annotation만 수정하는 상황이 가장 편하고 ConfigMap으로 문제가 해결된다면 그 또한 나쁘지 않다.
하지만 이 둘을 조합해서는 Nginx의 Microcaching을 켤 수가 없다. 그러므로 우리는 템플릿 파일 자체를 수정하여 문제를 해결하기로 한다. Nginx Ingress가 제공하는 Annotation 기능 등이 망가지지 않게 하려면 nginx.tmpl
을 복사해서 필요한 부분만 살짝 수정해야 한다. 그런 후 수정한 템플릿 파일을 ConfigMap으로 만들고
# https://github.com/kubernetes/ingress/tree/master/examples/customization/custom-template
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-template
namespace: kube-system
data:
nginx.tmpl: |
{{ $cfg := .Cfg }}
{{/* 하략 */}}
아래 예제와 같이 마운트해서 사용하면 된다.
https://github.com/kubernetes/ingress/blob/master/examples/customization/custom-template/custom-template.yaml
이제 제일 중요한 nginx.tmpl
파일을 어떻게 수정하는지 살펴보자. 설정이 상당히 길기 때문에 수정한 부분만 집중적으로 발췌해 설명한다. 먼저 http
블럭 첫 줄에서 Nginx cache zone을 열고
http {
proxy_cache_path /var/cache/nginx keys_zone=cache:10m levels=1:2 inactive=600s max_size=100m;
아래와 같이 캐시를 적용할 URL 패턴과 @cache_location
블럭을 server
블럭 안에 추가하면 된다.
error_page 418 = @cache_location;
location /api/v1/calendar {
return 418;
}
location ~ /api/v1/items/([0-9]+) {
return 418;
}
location @cache_location {
{{/* 중략 : location {{ $path }} 블록 내용을 베끼되 proxy_buffering 값을 on 으로 바꾸고 아래 코드를 이어서 적는다 */}}
{{/* NGINX Microcaching */}}
proxy_cache cache;
proxy_cache_valid 200 1s;
proxy_cache_min_uses 3;
proxy_cache_methods GET HEAD;
proxy_cache_use_stale updating;
add_header X-Proxy-Cache $upstream_cache_status;
proxy_cache_bypass $http_cache_control;
proxy_ignore_headers X-Accel-Expires Expires Cache-Control Set-Cookie;
proxy_ignore_headers Set-Cookie;
proxy_ignore_headers Cache-Control;
proxy_hide_header Cache-Control;
proxy_hide_header Set-Cookie;
}
이렇게 하면 Nginx Ingress 고유의 기능에는 큰 영향을 주지 않고 특정 API 또는 웹 페이지에만 Microcaching을 적용할 수 있다.
링크가 다 깨졌네요!
가장 활발한 오픈소스 프로젝트 중 하나이다 보니 하루가 멀다하고 바뀝니다. 오늘 수정해도 내일 바뀔 거라 그냥 둘 생각입니다.