Ghost를 블로깅 플랫폼으로 선택할 때까지 사실 엄청나게 많은 고민을 했어야 했습니다. 백업도 마크다운에 비해서 쉽지 않고 물론 SEO 등 기능이 굉장히 강력하지만 동시에 클러스터링이 불가능하고 느린 속도로 인해 선택을 하기까지 시간이 많이 걸렸습니다. 그런데도 현재 다이내믹 컨텐츠를 렌더링하고 있는 이 Ghost 블로그를 조금이나마 안전하고 빠르게 만들 수 있는 방법을 소개해보고자 합니다.

안 느린 것 같은데요?!

사실 Ghost가 빠르지는 않습니다. 현재는 캐싱을 적절히 구성하여서 캐싱없이 55ms라는 빠른 접속 시간을 만들어냈지만 원래는 로컬이였음에도 불구하고 300ms 이상까지 걸렸을 수도 있다고 생각합니다.

Ghost는 클러스터링 못하나요?

사실 Ghost가 클러스터나 분산 처리 지원을 했다면 이러한 고민을 하지 않았을 것입니다. 그러나 Ghost는 클러스터를 구성할 수 없는 구조로 이루어져 있습니다. 강제로 클러스터를 여러개를 구성하는 경우에는 쓸데없이 많이 데이터베이스 연결이 이루어져 더더욱 시스템 리소스를 낭비하게 됩니다. 그래서 Ghost에서는 대신 앞단에 캐싱 레이어를 추가하여야 한다고 공식 문서에서도 설명하고 있습니다.

Clustering, sharding, HA and other multi-server setups in Ghost - Developers
Ghost does not support clustering or multi-server setups of any description, there should only be one Ghost instance per site.

Ghost는 Static-like한 웹 사이트입니다

의외로 Ghost의 거의 모든 페이지는 정적 사이트로 내부적으로도 캐싱됩니다. 실제로 현재 블로그에는 댓글 등의 방문자가 직접 참여할 수 있는 기능도 많이 없다는 점을 참고하면 정말로 정적인 웹 사이트입니다. 그래서 관리자 페이지만 제외한다면 현실적으로 정말 좋은 static-like한 dynamic 웹 사이트가 될 수 있지 않을까 합니다.

Nginx로 무엇을 할 수 있을까?

Node.JS, Golang 등 여러 언어에서 로드밸런싱과 최소한의 보안 등을 위해 리버스 프록시를 사용하는 일은 흔하며 그 설정 또한 대부분 알다시피 할 정도로 쉽습니다. 그런데 가끔 저는 스스로 Nginx의 강력한 성능을 제대로 누리지 못하고 있다고 생각하면서도 막상 잘 사용할 생각은 하지 않았던 적이 많았던 것 같습니다. 비슷한 비교군으로 HaProxy가 있고 실제로 괜찮은 대안이지만 하지만 Nginx는 "웹 서버"이고 HaProxy는 "로드밸런서"입니다. 이 시점에서 Nginx가 웹 서버라는 것은 저에게 더더욱 유틸라이징이 쉽고 그 목표에 타겟팅이 되어 있다는 것을 알려주었습니다.

NGINX Docs | NGINX Content Caching
Cache both static and dynamic content from your proxied web and application servers, to speed delivery to clients and reduce the load on the servers.

그래서 Nginx의 공식 설정 가이드라인을 보면서 어떻게 캐싱을 해야 하는지 살펴보았습니다. 저는 전문가가 아니기 때문입니다. 적어도 할 수는 있어도 저보다 더 뛰어난 결과물을 보여주는 예제가 많습니다.

실제 Nginx 캐싱 설정을 만들어보자

이제 무엇을 해야 할 지 알았으니 하나하나 스탭을 밟아나갈 차례입니다.

http 블럭에 캐싱 영역 정의

가장 먼저 해야 할 일은 웹 사이트의 server 블럭보다 더 상위 블럭인 http 블럭에 캐싱 영역을 정의하는 것입니다.

http {
    ...
    proxy_cache_path /etc/nginx/cache levels=1:2 keys_zone=cache_typedsh:5m max_size=5g inactive=12h use_temp_path=off;
}

저와 같은 경우에는 /etc/nginx/cache 경로에 캐시를 생성하고 Nginx 문서에서 명시된대로 1:2 계층으로 캐싱을 진행하였습니다. 계층이 많아지면 세세히 캐싱이 가능하지만 현재 하드디스크 위에서 서비스가 데굴데굴 굴러가고 있으므로 큰 무리를 주지 않기로 했습니다. 그 외에 max_size 값과 inactive 값을 크게 잡아주었습니다. 거의 Static한 Ghost이기 때문입니다.

location 블럭에 캐싱 구성 정의

그리고 위의 캐싱 설정을 바로 아래의 server 하위 location 블럭에서 사용하도록 하겠습니다.

proxy_cache_valid 200 302 1m;
proxy_cache_revalidate on;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_methods GET HEAD;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_ignore_headers X-Accel-Expires;
proxy_ignore_headers Cache-Control;
add_header X-Cache-Status $upstream_cache_status;
  • proxy_cache_valid, 어떤 페이지를 얼마나 오랫동안 캐싱을 유효화하게 처리할지 설정합니다. 이 구문으로 웹 브라우저의 Pragma 구문 등으로 캐싱을 방지하는 것을 막을 수 있습니다. 혹여나 웹 사이트에 공격이 캐싱을 방지하면서 올 수 있기 때문에 위와 같이 설정하였습니다.
  • proxy_cache_revalidate, 지속적으로 캐시가 유효한지 원본 서버인 Ghost에 확인하기 위해 추가합니다.
  • proxy_cache_use_stale_error, 원본 서버에 문제가 생겼을 때 Nginx에서 캐시된 컨텐츠로 대신 응답하도록 합니다. 이렇게 하면 문제가 생겼을 때도 빠른 응답성을 가질 수 있습니다. 물론, Ghost는 생각보다 정적이기 때문에 문제도 없었습니다.
  • proxy_cache_methods, 여기에 POST 요청이 포함되어서는 안 됩니다. 사용자가 상호작용할 수 있는 요소는 관리자 페이지 뿐이기 때문에 문제가 생길 수 있습니다.
  • proxy_cache_background_update, 백그라운드에서 계속 원본 서버에 요청을 하여 캐시된 컨텐츠가 올바른지 확인하는 과정입니다.
  • proxy_cache_lock, 한 번에 하나의 클라이언트만 유효한 캐시 오브젝트를 서버에 생성할 수 있도록 합니다. 캐시가 생성되지 않은 상태에서 여러 클라이언트가 접근하면 첫 클라이언트가 캐싱을 완료하고 나머지 클라이언트는 캐싱된 컨텐츠를 대신 보도록 합니다.
  • proxy_ignore_headers, 강제로 캐싱을 하기 위해서 몇몇개의 헤더를 방지해줍니다. 여러분의 서버 설정에 따라서 조금조금씩 다를 거예요.

마지막으로는 X-Cache-Status 헤더를 포함시켜서 실제로 어떻게 현재 요청이 이루어지고 있는지 확인할 수 있도록 하였습니다. 혹여나 캐시로 인하여 잘못된 요청이 이루어지고 있을 경우에 빠르게 상황을 확인할 수 있는 지표로 사용할 수 있습니다.

최종적으로는 Typed.sh에서는 다음과 같은 설정을 사용하고 있습니다. 빠르게 작업하여 아직 깔끔하지 못한 부분이 보이는데 조만간 다시 소스코드를 정리하도록 할 것입니다.

대신에 무엇을 포기해야 할까요?

일단 캐싱을 강제로 적용하여 성능을 높이고 당연히 포기해야 하는 것들이 생겼습니다. 가장 먼저 실시간 반응성이 당연하게도 줄었습니다. 위에서는 물론 캐시가 최소한 유효한 기간을 1분 정도로 설정하여서 유저에게 그래도 느리지 않게 배포가 될 수 있도록 장치를 마련했는데 실제 서비스에서는 더더욱 세밀한 설정을 하여서 유저 경험을 정밀하게 조정해야 할 경우가 생기지 않을까 합니다.

그러나 해당 경우에는 Nginx와 Ghost를 밀접하게 다루어야 하는데 현재 프로젝트에서는 안정성이 더더욱 우선적으로 고려되는 상황에서 당장은 포기를 선택했습니다. 만약에 살펴보게 된다면 Nginx의 캐시 오브젝트도 직접 다룰 수 있어야 하지 않을까 싶네요.


Ghost는 왜 그렇게 클러스터링 관련한 요구가 많지 않았는지 모르겠습니다만 배포되는 웹 사이트 자체가 굉장히 정적이여서 그런 것일지도 모르겠네요. 그래도 언젠가 정식으로 클러스터링을 지원했으면 좋겠습니다.