Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

ngx_pagespeed : Nginx의 PageSpeed 모듈

Google에 만든 PageSpeed는 웹사이트의 성능을 분석하고 최적화 방법을 알려주는 분석도구이다. 예전에는 Yahoo!의 YSlow가 대표적이었지만 이제는 PageSpeed가 더 대중적이 되었다. PageSpeed Insights를 이용하면 원하는 웹사이트를 PageSpeed로 분석해 볼 수 있고 크롬 익스텐션으로 분석해 볼 수 있다. PageSpeed Insights Rules에 따라서 분석해주고 점수로 보여주는데 이 규칙만 잘 이해해도 웹사이트를 어떻게 만들어야 성능이 좋은지 이해할 수 있다.

PageSpeed 로고

PageSpeed 자체는 계속 사용하고 있었지만 얼마 전에 블로그를 최적화하면서 PageSpeed Module을 사용해봤다. 모듈은 Apache(mod_pagespeed)와 Nginx(ngx_pagespeed)용이 있는데 웹서버 차원에서 PageSpeed가 권장하는 최적화를 한다. 나는 웹서버를 Nginx를 주로 사용하므로 ngx_pagespeed만 사용해 봤다.

ngx_pagespeed 설치

Nginx에 새로운 모듈을 추가할 때는 항상 그렇듯이 ngx_pagespeed를 사용하려면 nginx를 ngx_pagespeed와 함께 다시 빌드해야 한다. 설치문서대로 ngx_pagespeed 소스와 psol(PageSpeed Optimization Libraries)을 받고 Nginx를 빌드할 때 --add-module로 함께 빌드하면 된다.

ngx_pagespeed 설정

나도 사용한 지 얼마 안 되었고 모든 기능을 다 테스트해 본 것은 아니므로 사용해본 기능 위주로만 설명한다.(문서를 보면 정말 많은 기능이 있다.) ngx_pagespeed로 Nginx를 빌드했다고 하더라도 사용하려면 nginx.confserver부분에서 다음과 같이 PageSpeed를 켜주어야 한다.(다 테스트해보지는 못했는데 일부 설정은 각 server부분이 아니라 다른 부분에 설정해야 하는 설정도 있는 것으로 보인다.)

server {
    # ...
    pagespeed on;

    pagespeed FileCachePath "/path/to/cache";
    # ...
}

pagespeed on;로 활성화를 해야 하고 FileCachePath는 필수값이라서 설정하지 않으면 오류가 난다. 속도를 위해서 PageSpeed가 캐시파일을 생성하는데 이를 저장할 위치를 지정해야 한다. Nginx를 시작하면 [1024/162206:INFO:google_message_handler.cc(35)] No threading detected. Own threads: 1 Rewrite, 1 Expensive Rewrite.같은 오류메시지가 나오는데 실제 오류는 아니므로 신경안 써도 된다.

<html>
<head>
    <title>ngx-pagespeed test</title>
    <link rel="stylesheet" href="bootstrap.css" media="screen"/>
    <link rel="stylesheet" href="common.css" media="screen"/>
    <link rel="stylesheet" href="style.css" media="screen"/>
    <link rel="stylesheet" href="print.css" media="screen"/>
    <link rel="stylesheet" href="page.css" media="screen"/>
    <script type="text/javascript" src="init.js"></script>
    <script type="text/javascript" src="inline.js"></script>
</head>
<body>
    <h1>Hello PageSpeed</h1>
    <img src="image1.jpg">
    <script type="text/javascript" src="jquery-2.1.1.js"></script>
    <script type="text/javascript" src="bootstrap.js"></script>
    <script type="text/javascript" src="common.js"></script>
    <script type="text/javascript" src="plugin.js"></script>
</body>
</html>

다음과 같은 HTML이 있다고 해보자. 간단한 HTML이지만 CSS 파일도 많고 JavaScript 파일도 많은 데다가 헤더와 바디에 나누어져 있어서 최적화가 안 되어 있다. 여기에 PageSpeed를 적용하면 다음과 최적화를 해준다.

<html>
<head>
  <title>ngx-pagespeed test</title>
  <link rel="stylesheet" href="A.bootstrap.css.pagespeed.cf.uQnrHqVB52.css" media="screen"/>
  <link rel="stylesheet" href="A.common.css+style.css+print.css+page.css,Mcc.-X_l7tLwo2.css.pagespeed.cf.U60M2ysh0E.css" media="screen"/>
  <script src="init.js+inline.js.pagespeed.jc.-l4ywmKJ4l.js"></script>
  <script>eval(mod_pagespeed_PTslYvo__n);</script>
  <script>eval(mod_pagespeed_WewiyizJBt);</script>
</head>
<body>
  <h1>Hello PageSpeed</h1>
  <img src="ximage1.jpg.pagespeed.ic.U44YUjkGKM.webp">
  <script type="text/javascript" src="jquery-2.1.1.js.pagespeed.jm.V5dmkPfnRj.js"></script>
  <script src="bootstrap.js+common.js+plugin.js.pagespeed.jc.epuwIIsfqE.js"></script>
  <script>eval(mod_pagespeed_uKtTZWV22S);</script>
  <script>eval(mod_pagespeed_u$r4PZINvp);</script>
  <script>eval(mod_pagespeed_2gPYGymd2v);</script>
</body>
</html>

프론트앤드 최적화를 하는 방법은 여러 가지가 있지만 ngx_pagespeed를 적용하는 것만으로도 서버에서 HTML을 전혀 건드리지 않고 최적화를 수행해 준다. 파일명은 복잡해 보이지만 위에서 보듯이 여러 개의 CSS와 JS 파일을 하나의 파일로 합쳐주고 압축까지 수행해준다. eval()로 된 부분은 파일을 합쳤으므로 이를 순서대로 실행하는 부분으로 보인다. 심지어 JPEG였던 이미지를 WebP로 바꾸어준다. 기본 설정만으로도 이러한 최적화를 수행해 주지만 문서에 따르면 PageSpeed용 파일의 라우팅을 제대로 하도록 다음과 같은 핸들러를 추가하도록 가이드 하고 있다.

server {
    # ...
    pagespeed on;

    pagespeed FileCachePath "/path/to/cache";

    location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" {
      add_header "" "";
    }
    location ~ "^/pagespeed_static/" { }
    location ~ "^/ngx_pagespeed_beacon$" { }
}


캐시 설정

위에서 캐시파일을 저장할 위치를 지정했지만, 서버성능에 맞게 캐시의 상세 설정을 추가할 수 있다.

server {
    # ...
    pagespeed on;

    pagespeed FileCachePath "/path/to/cache";
    pagespeed FileCacheSizeKb            102400;
    pagespeed FileCacheCleanIntervalMs   3600000;
    pagespeed FileCacheInodeLimit        500000;
    pagespeed LRUCacheKbPerProcess     8192;
    pagespeed LRUCacheByteLimit        16384;

    # ...
}

캐시 설정에 대한 옵션은 문서에 잘 나와 있다.

CSS나 JavaScript 파일을 수정해서 캐시를 갱신하려면 /path/to/cache 폴더 아래 cache.flush파일을 생성하면 된다. touch /path/to/cache/cache.flush같은 식으로 파일만 만들면 기존 캐시파일을 지우고 새로운 파일을 만들어 준다.

필터

기본으로 최적화를 해주는 부분이 있지만 필터를 사용하면 상황에 맞게 세부 조정을 하면서 최적화를 할 수 있다. 필터 목록을 보면 아주 많은 필터를 제공하고 있음을 알 수 있다. 필터를 통해서 LocalStorage에 리소스를 저장하게 한다거나 CSS는 스크립트보다 상위에 위치하게 한다든가 헤더가 여러 개인 경우 헤더를 합치는등의 작업을 할 수 있다. 필터는 nginx.confpagespeed EnableFilters FILTER_NAME;같은 형식으로 추가하면 된다. 몇 가지 필터의 동작만 확인해 보자.

자바스크립트 지연 실행

자바스크립트가 헤더나 본문 등에 섞여 있는 경우 자바스크립트가 렌더링을 막기 때문에 렌더링 후에 자바스크립트를 실행하기도 한다. 자바스크립트 지연 실행을 위해 pagespeed EnableFilters defer_javascript;를 추가하면 HTML이 다음과 같이 바뀐다.

<html>
<head>
    <title>ngx-pagespeed test</title>
    <link rel="stylesheet" href="A.bootstrap.css.pagespeed.cf.uQnrHqVB52.css" media="screen"/>
    <link rel="stylesheet" href="A.common.css+style.css+print.css+page.css,Mcc.-X_l7tLwo2.css.pagespeed.cf.U60M2ysh0E.css" media="screen"/>

    <script src="init.js+inline.js.pagespeed.jc.-l4ywmKJ4l.js" type="text/psajs" orig_index="0"></script>
    <script type="text/psajs" orig_index="1">eval(mod_pagespeed_PTslYvo__n);</script>
    <script type="text/psajs" orig_index="2">eval(mod_pagespeed_WewiyizJBt);</script>
</head>
<body>
    <noscript>
        <meta HTTP-EQUIV="refresh" content="0;url='http://test.sideeffect.kr:2000/?PageSpeed=noscript'" />
        <style><!--table,div,span,font,p{display:none} --></style>
        <div style="display:block">Please click <a href="http://test.sideeffect.kr:2000/?PageSpeed=noscript">here</a> if you are not redirected within a few seconds.</div>
    </noscript>
    <h1>Hello PageSpeed</h1>
    <img src="ximage1.jpg.pagespeed.ic.U44YUjkGKM.webp">

    <script pagespeed_orig_type="text/javascript" src="jquery-2.1.1.js.pagespeed.jm.V5dmkPfnRj.js" type="text/psajs" orig_index="3"></script>
    <script src="bootstrap.js+common.js+plugin.js.pagespeed.jc.epuwIIsfqE.js" type="text/psajs" orig_index="4"></script>
    <script type="text/psajs" orig_index="5">eval(mod_pagespeed_uKtTZWV22S);</script>
    <script type="text/psajs" orig_index="6">eval(mod_pagespeed_u$r4PZINvp);</script>
    <script type="text/psajs" orig_index="7">eval(mod_pagespeed_2gPYGymd2v);</script>
    <script type="text/javascript" src="/pagespeed_static/js_defer.pbrP1whUgE.js"></script></body>
</html>

위 코드를 보면 스크립트 부분이 type="text/javascript"대신 type="text/psajs"이 붙어있고 orig_index=""로 순서대로 번호가 붙어 있는 것을 볼 수 있다. 이를 마지막의 js_defer.js에서 실행해서 지연 실행을 할 수 있도록 해준다.

DNS 프리패치

웹페이지에서 다양한 도메인을 처리하는 경우 DNS를 처리하는 시간이 걸리기 때문에 DNS를 미리 처리해주면 속도를 높일 수 있다. 이를 위해 pagespeed EnableFilters insert_dns_prefetch;로 필터를 활성화해 주면 본문에 현재 도메인과 다른 <img src="https://developers.google.com/_static/f12482462b/images/developers-logo.png">같은 리소스가 있는 경우 이를 헤더에 <link rel="dns-prefetch" href="//developers.google.com">같은 식으로 dns-prefetch를 추가해준다.

PageSpeed 어드민 페이지

PageSpeed는 1.8.31.2버전부터 어드민 기능을 제공하고 있다.(현재 버전은 1.9.32.1) 어드민 페이지에서는 PageSpeed의 설정상황이나 통계 등을 파악할 수 있다. 화면이 좀 복잡하긴 하지만 초기에 사용할 때 PageSpeed의 상태를 보면서 최적화 할 때 도움이 된다.

pagespeed GlobalStatisticsPath /ngx_pagespeed_global_statistics;
pagespeed GlobalAdminPath /pagespeed_global_admin;

server {
    # ...
    location /ngx_pagespeed_statistics { allow 127.0.0.1; deny all; }
    location /ngx_pagespeed_global_statistics { allow 127.0.0.1; deny all; }
    location /ngx_pagespeed_message { allow 127.0.0.1; deny all; }
    location /pagespeed_console { allow 127.0.0.1; deny all; }
    location ~ ^/pagespeed_admin { allow 127.0.0.1; deny all; }
    location ~ ^/pagespeed_global_admin { allow 127.0.0.1; deny all; }

    pagespeed StatisticsPath /ngx_pagespeed_statistics;
    pagespeed MessagesPath /ngx_pagespeed_message;
    pagespeed ConsolePath /pagespeed_console;
    pagespeed AdminPath /pagespeed_admin;
}

디렉티브는 StatisticsPath, MessagesPath, ConsolePath, AdminPath, GlobalStatisticsPath, GlobalAdminPath가 있는데 필요한 기능별로 설정해서 사용하면 된다. 여기서 GlobalStatisticsPath, GlobalAdminPath는 전역설정이므로 server부분이 아니라 전역으로 설정해야 한다. 먼저 각 페이지를 받을 URL에 따라 location 설정을 추가하고 아무나 접속하면 안 되니까 자신의 IP만 접속할 수 있도록 설정한다. 이후에 각각 디렉티브를 설정하면서 location에 설정한 경로와 일치하게 설정하면 된다.

이렇게 설정하고 위 설정한 /pagespeed_admin로 접속하면 아래와 같은 화면을 볼 수 있다.

Statistics

Statistics 화면

Configuration

Configuration 화면

Histograms

Histograms 화면

Caches

Caches 화면

Graphs

Graphs 화면

2014/10/25 03:19 2014/10/25 03:19