Varnish Cache #1 : React Page Cache

네이버 검색창에서 “OO 항공권" 으로 검색하면 아래와 같이 항공권을 조회할수 있는 화면이 나오는데요. 네이버 통합검색에 노출되는 페이지이다 보니 성능적으로 아래 요구사항들을 충족시켜야 했습니다

  • 속도 : 사용자가 항공권 검색을 했을때 1초 미만으로 페이지 노출
  • 처리량: 사용자들이 몰려도 서비스 안정성 유지

해당 요구사항들을 충족시티는 방법으로 Cache 를 적용하기로 결정했는데, 여러가지 Cache 솔루션을 검토한 결과 Varnish Cache를 적용하게 되었습니다

많은 Cache 솔루션중에 왜 Varnish 를 사용하기로 했을까요?

그전에 Varnish 가 생소하신 분들을 위해 Varnish에 대해 간단히 설명드리도록 하겠습니다

Varnish 가 뭘까?

한마디로 말하자면 Varnish 는 reverse caching proxy 서버입니다. 개념이 생소하신 분들을 위해 단어별로 나눠서 간단히 설명드리도록 하겠습니다

reverse proxy

reverse proxy 서버는 유저와 원본서버(ex. React Web App Server) 사이에서 요청,응답을 중계해주며
보통 load balancing, caching 같은 추가 기능들을 제공합니다

알고계시는 Apache, Nginx 도 reverse proxy server 라고 할수있습니다

cache

cache server 로서 무엇을 캐싱할까요? 동적으로 생성된 웹 페이지를 cache 해주는 역할을 합니다.
항공권 서비스를 예로들자면, 유저 질의에 대해 React Server 에서 응답으로 전달하는 html 을 캐싱하게 됩니다

궁금하신 분들을 위해 살짝 짚고 넘어갈 부분이 있는데요
Nginx 에서도 캐싱이 가능한데, 항공권 서비스에 있는 Nginx에서는 정적인 리소스 를 캐싱합니다. 바꿔말하면 서버에 파일로 존재하는 js,css,html 등의 리소스를 캐시한다고 이해하시면 됩니다

Nginx가 유저 질의별로 dynamic 하게 web app server에서 생성되는 output(html response) 을 캐싱하는 역할로 사용되고 있지는 않았고,
그래서 유저 요청에 대해 동적으로 생성되는 html response 를 캐싱하기 위해 Varnish를 사용하게 되었습니다

참고로 nginx 에서도 fastcgi 를 사용하면 html response 캐싱이 가능할거라고 판단됩니다

Varnish = reverse caching proxy

위에서 이야기했던 내용을 바탕으로 정리를 해 보자면
유저의 요청에 의해(통합검색 질의 input) 원본서버가 반환한 html 결과물을 캐싱해서 유저에게 전달하는 중계 서버
라고 정리할수 있겠습니다

FE Cache 를 적용했을때 발생할수 있는 이슈

Varnish 에 대해 설명드리기에 앞서
Cache 를 적용하게 되는 경우 발생할수 있는 이슈를 먼저 말씀드리고 넘어가려고 합니다

어떤 Cache 시스템을 사용하던지(ex, Redis),
신규 버전 배포후 Cache 에 이전 버전의 값이 남아있어 오류가 발생하는 케이스들이 있는데요
구체적으로 어떤 상황인지를 설명드리고, Varnish 로 해당 이슈를 어떻게 해결했는지 말씀드리도록 하겠습니다

Cache 를 사용하지 않은 경우 : 문제없음

React FE 서버에서 A 라는 요청에 대해
아래와 같은 html A 페이지를 만들어서 response 로 보내준다고 해봅시다
그리고 html A 에서 사용하는 js, css 파일도 같이 전송합니다

참고로 React 에서 render 함수를 통해 html 을 동적으로 생성하지만
빌드된 js,css 리소스는 어플리케이션 서버에 static 파일로 저장됩니다

그런데 기능이 추가 개발되어 A 라는 요청에 대해 새로운 html B 페이지를 response 로 보내도록 변경되어 배포가 되었습니다
이제 html A 가 아닌 html B 가 전달이 되고
html B 페이지와 연관된 새로운 js,css 리소스들이 전달되게 됩니다

이해를 돕기 위해서 html 자체가 변경되었을 경우를 가정했지만
html 이 변경되지 않더라도 rebuild 할때 timestamp suffix 가 달라지면서 js, css 참조경로가 변경되어 앞서 말한것과 같은 상황이 발생합니다

A js file : main-bundle-933c4c4f.js

B js file : main-bundle-56sx3g67.js

중간에 Cache를 설정하지 않은경우에는 배포가 완료되었을때 의도한대로
A 라는 요청에 대해 html A 대신 html B를 전달하게 되고 이로 인해 발생하는 이슈는 없습니다

Cache 를 사용하게 되는 경우 : 이전 버전 html 을 참조하는 문제

하지만 성능향상을 위해서 중간에 Cache 를 도입하게 되는 경우 문제가 조금 복잡해집니다

처음에 Cache를 도입하게 되면 아래 그림처럼 Cache 시스템이 HTML A 를 메모리에 저장해두고 반환하게 됩니다

설정된 cache ttl 이 만료되면 다시 갱신해서 저장합니다

문제는 여기서 A라는 요청에 대해 html B 를 반환하기로 한 새로운 어플리케이션이 배포되었을 경우입니다
기대한대로라면 A 라는 요청에 html B 가 반환되어야 하지만
중간에 있는 Cache 서버에 있는 값은 아직 갱신되지 않았기 때문에
예전버전인 HTML A 를 반환하게 됩니다

이렇게 되면 예전 버전의 html 을 반환하는것도 문제가 되지만
더 심각한 문제는 html A 가 참조하고 있는 .js, .css 파일이 이제 더이상 서버에 남아있지 않아
js,css 리소스를 찾을수 없게 되어 에러가 발생하는 것입니다

별도의 리소스 저장소를 통한 해결법

지금까지 말씀드렸던 FE 서비스에서 HTML Cache 를 사용할때 문제되는 상황을 요약해보자면
1. 애플리케이션을 재배포했을때 캐싱된 이전 버전의 html 결과물을 반환하고
2. 예전 html 결과물이 참조하는 js,css 파일은 원본서버에 남아있지 않아
3. 필요한 리소스를 찾을수 없어 에러가 난다

여기서 이런 생각을 해볼수 있습니다.
1번은 부분은 어쩔수 없다고 하더라도
2번, 즉 ‘이전 html 결과물이 참조하는 js,css 파일은 원본서버에 남아있지 않다’ 라는 부분을 해결해볼수는 없을까?

새로 배포가 되어도 이전 js,css 결과물들이 남아있도록
파일들을 어플리케이션 서버가 아니라, 배포에 영향받지 않는 다른 저장소에 쌓아둔다면?

그래서 js,css 를 CDN 에 저장하는 방법을 많은곳에서 사용하고 있습니다
어플리케이션을 빌드할때 js,css 를 CDN 에 업로드하고
어플리케이션에서 생성하는 html 에서는 CDN 에 있는 js,css 를 참조하도록 하는 방식입니다

CDN : content delivery network, 자세한 설명은 문서 참조

이렇게 되면 재배포시 Cache 에서 이전에 있던 html 을 반환하게 되더라도
html 에서 참조하는 이전 js,css 파일이 CDN 에 있어 리소스 반환이 가능해지고 에러를 피할 수 있습니다

한계점

에러상황은 피해갔지만 한계점이 있습니다

우선 CDN 에 garbage 데이터가 쌓이는걸 관리해줘야합니다
앞서 설명했던 예시에서 Html A 와 관련되어있는 js,css 파일은 별도 삭제를 하지 않으면 계속 남아있게 됩니다

또한 일반적으로 CDN 은 다른 네트워크 리소스에 비해 비용이 비싼 자원이고저렴한 다른 네트워크 리소스로 대체 가능하다면(예를들자면 Nginx)
적은 비용으로 같은 성능을 낼 수 있습니다

그리고 배포후에 CDN 캐시가 만료되기 전까지는 새로운 html 이 노출되지 않고, 이전 html 이 계속 노출된다는 점도 문제입니다
결과적으로 Cache 적용후 배포해도 에러가 나지 않도록 했지만
신규 버전의 html 을 바로 전달하지 못하는 이슈는 여전히 남아있게 되죠

Cache On / Off 를 통한 해결법

CDN 보다 저렴한 네트워크 리소스를 사용하면서
캐싱을 했음에도 배포시 새로운 html 만 나오게 할수있는 방법이 없을까 고민하다가

배포시 Cache 를 껐다가 다시 켜면 어떨까? 라는 생각을 해봤습니다
즉 다음과 같은 시나리오로 진행하는 방법입니다

Cache On

Request A 에 대해 Cache 시스템에서 Html A 를 반환합니다

Cache Off

일시적으로 캐시 시스템을 Off 하고, 원본서버 배포를 진행합니다
Request A 에 대해 Cache 시스템이 아닌 원본서버에서 Html 을 반환하기 때문에
맨 처음에 이야기했던것처럼 Cache 가 없을때 신규배포하는것과 동일하게 작동하게 됩니다

Cache On

배포가 완료되면 다시 Cache 를 On 해줍니다
이때 Cache 시스템은 초기화된 상태로 예전 데이터가 없는 상태이고
Request A 에 대해 Html B 응답값을 캐싱하게 됩니다

구현 세부사항

개념만 생각했을때는 간단해보이지만
실제 구현을 생각하면 골치아픈 점들이 몇가지가 있습니다

  • cache on/off 구현하기
  • cache off -> on 으로 다시 활성화했을때 이전 데이터들을 없애고 빈 값으로 초기화 (그래야 새로운 html 이 캐싱됨)
  • cache off 상황일때 request 유입이 origin 서버로 가도록 변경하기

그래서 세부 요구사항들을 구현 가능한 솔루션을 고민하다가
Varnish Cache 로 가능하겠다는 판단이 들어서 도입하게 되었습니다

Varnish Cache On / Off

다음 글에서 자세히 설명드리겠지만 Varnish 는 VCL 파일 설정을 통해서
캐시 방식을 다양하게 설정할 수 있습니다
그래서 심지어는 Cache 시스템임에도 불구하고 Cache를 하지 않도록 설정할수도 있습니다

그래서 저는 두 종류의 vcl 파일
hit.vcl (Cache On) , pass.vcl (Cache Off) 을 만들고
단계별로 Varnish 가 참조하는 vcl 파일을 변경함으로써 Cache On / Off 를 구현했습니다

좀더 세부적으로 설명드리면

Varnish Cache On

Varnish 는 hit.vcl 파일을 참조하고, 파일에 설정된 Cache 정책에 따라 html 을 캐싱합니다

Varnish Cache Off

어플리케이션 배포전에 Varnish Cache 에서 pass.vcl 파일을 참조하도록 변경해 Cache 기능을 해제시킵니다
요청이 기존과 똑같이 Varnish 로 오더라도 Varnish 에서는 아무런 처리를 하지 않고 원본서버로 bypass 시킵니다

마찬가지로 Varnish Cache 가 Off 되어 있으므로
어플리케이션 배포가 완료되면 바로 새로운 html 을 반환하게 됩니다

Varnish Cache On

어플리케이션 배포가 완료되면 다시 Varnish가 hit.vcl 을 참조하도록 변경해
Cache 기능을 활성화시켜서 새로운 html 을 Varnish Cache 에서 반환하도록 합니다

Cache On / Off 전환방법

Varnish Cache On / Off 를 할수 있는 방법을 찾았는데요
만약 배포시마다 이걸 수동으로 해줘야 한다면 여간 번거로운 일이 아닐수 없겠죠?

그래서 Kubernates 사내 플랫폼에 있는 workflow 기능을 사용해 클릭 한번으로 위에 말씀드린 과정을 자동으로 실행하도록 만들었습니다

왜 Varnish 인가?

지금까지 장황하게 설명드리긴 했지만 요약을 한번 해보자면
Varnish cache on / off 기능을 도입하여 아래 장점들을 얻을수 있었습니

  • js,css 리소스 파일을 CDN 을 사용 / 관리하지 않고도 사용 가능
  • 어플리케이션 배포후에도 html 최신본을 보장

하지만 Varnish 를 택한 이유는 단순히 Cache on / off 를 구현할 수 있기때문만이 아닙니다
Redis 와 같이 범용적인 Cache 시스템과 다르게
Varnish는 web cache 용도로 특화해서 만들어진 시스템이어서
web cache 에서 유용하게 쓰일만한 grace mode, request coalescing 같은 기능을 여럿 갖추고 있습니다
또한 탁월한 성능과 풍부한 모니터링, 로깅 기능까지 갖추고 있어서 FE Cache 솔루션으로 Varnish 를 채택하게 되었습니다

학습비용 낮추기

Varnish가 장점이 많지만, 아무래도 처음 접하는거라 Redis 같이 익숙한 Cache 시스템과 비교했을때
낯설게 느껴지시는 부분이 많을수 있다고 생각이 들었습니다. 저도 그랬고요ㅎ

그래서 학습비용을 조금이나마 줄여드리기 위해
추가로 공유드릴 글에서 도움이 될만한 가이드를 드려보려고 합니다

FE Cache 가 필요한 다른 서비스에서도
Varnish 를 활용하실수 있는 계기가 되었으면 좋겠습니다

--

--