Apacheで圧縮してコンテンツ配信しよう

最近、スマートフォン向けのサイトなどが多くなりつつありますが、スマートフォンの場合、「(携帯キャリアの3G回線などの)細い回線」かつ「(JavaScriptなどを含んだ)リッチコンテンツ」という事情から、コンテンツのダウンロードが重く感じるということがあります。


その対策として、Webサイトの高速化 ルール4 コンポーネントを圧縮しよう! にある通り、JavaScriptCSSの外部ファイルを圧縮して送信することで、パフォーマンスを向上しようという話です。


但し、導入時には、運用しているサーバのCPUリソース状況を確認する必要があります。なぜなら、圧縮する度にCPU消費する事になり、結果CPU使用率は上がるからです。CPU使用率は余裕で、かつ、ネットワーク帯域は節約したい場合にはいい手段だと思います。


以降で、Apacheへの圧縮用モジュール追加、設定内容、動作確認の順で説明していきます。

Apacheへのモジュール追加

mod_so の確認

まず、モジュール追加するには、「mod_so」というモジュールが必須ですので、その存在確認を行います。*1

[root]# /usr/local/apache2/bin/httpd -l
  ・
 ・
  mod_alias.c
  mod_so.c
追加モジュールのコンパイル

今回利用するモジュールは、以下の2つです。

  • mod_deflate.so(※要モジュール追加)
    • コンテンツを圧縮するモジュール
  • mod_filter.so(※Apache本体に組み込まれているはずなので追加不要)
    • コンテンツタイプによって、圧縮対象を指定するためのモジュール

apxs を使って、mod_deflate.so をコンパイルします。「-c」オプションはコンパイル、「-i」オプションはインストール(/usr/local/apache/modules に移動などしてくれる)という意味です。
参考にしたサイトでは、「-a」オプションがつけられていることが多かったのですが、個人的には勝手に、httpd.confにLoadModuleディレクティブを追加されたりするのは嫌なのでつけていません。

[root]# /usr/local/apache2/bin/apxs -c -i 
            /root/modules/httpd-2.2.11/modules/filters/mod_deflate.c

/usr/local/apache2/build/libtool --silent --mode=compile gcc -prefer-pic
    -DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -D_LARGEFILE64_SOURCE -g -O2
    -pthread -I/usr/local/apache2/include  -I/usr/local/apache2/include
    -I/usr/local/apache2/include   -c -o 
    /root/modules/httpd-2.2.11/modules/filters/mod_deflate.lo
    /root/modules/httpd-2.2.11/modules/filters/mod_deflate.c && 
    touch /root/modules/httpd-2.2.11/modules/filters/mod_deflate.slo
/usr/local/apache2/build/libtool --silent --mode=link gcc -o 
    /root/modules/httpd-2.2.11/modules/filters/mod_deflate.la  
    -rpath /usr/local/apache2/modules -module -avoid-version
    /root/modules/httpd-2.2.11/modules/filters/mod_deflate.lo
/usr/local/apache2/build/instdso.sh SH_LIBTOOL='/usr/local/apache2/build/libtool'
    /root/modules/httpd-2.2.11/modules/filters/mod_deflate.la
    /usr/local/apache2/modules
    /usr/local/apache2/build/libtool --mode=install 

cp /root/modules/httpd-2.2.11/modules/filters/mod_deflate.la
    /usr/local/apache2/modules/
cp /root/modules/httpd-2.2.11/modules/filters/.libs/mod_deflate.so
    /usr/local/apache2/modules/mod_deflate.so
cp /root/modules/httpd-2.2.11/modules/filters/.libs/mod_deflate.lai
    /usr/local/apache2/modules/mod_deflate.la
cp /root/modules/httpd-2.2.11/modules/filters/.libs/mod_deflate.a
    /usr/local/apache2/modules/mod_deflate.a

chmod 644 /usr/local/apache2/modules/mod_deflate.a
ranlib /usr/local/apache2/modules/mod_deflate.a
PATH="$PATH:/sbin" ldconfig -n /usr/local/apache2/modules
                                                                                                                                          • -
Libraries have been installed in: /usr/local/apache2/modules If you ever happen to want to link against installed libraries in a given directory, LIBDIR, you must either use libtool, and specify the full pathname of the library, or use the `-LLIBDIR' flag during linking and do at least one of the following: - add LIBDIR to the `LD_LIBRARY_PATH' environment variable during execution - add LIBDIR to the `LD_RUN_PATH' environment variable during linking - use the `-Wl,--rpath -Wl,LIBDIR' linker flag - have your system administrator add LIBDIR to `/etc/ld.so.conf' See any operating system documentation about shared libraries for more information, such as the ld(1) and ld.so(8) manual pages.
                                                                                                                                          • -
chmod 755 /usr/local/apache2/modules/mod_deflate.so

mod_deflate.so ができたかどうか確認してみます。

[root]# pwd
/usr/local/apache2/modules

[root]# ls -ltr
 -rwxr-xr-x 1 root root   129705  5月 14  2009 mod_rewrite.so
 -rwxr-xr-x 1 root root    42292  5月 14  2009 mod_headers.so
 -rwxr-xr-x 1 root root    30367  5月 14  2009 mod_expires.so
 -rwxr-xr-x 1 root root 13191193  5月 14  2009 libphp5.so
 -rwxr-xr-x 1 root root    48770 11月 12 12:44 mod_deflate.so

Apache設定変更

モジュールのコンパイルが完了したので、httpd.confを修正します。設定の際に配慮するのは、以下の4点です。

  • 追加モジュールのロード
  • 圧縮コンテンツ非対応ブラウザ(古いブラウザ)への配慮
  • 画像コンテンツは圧縮しない(元々圧縮されているので)
  • 対象コンテンツのタイプを指定して圧縮を行う
[root]# vi /usr/local/apache2/conf/httpd.conf

##### 追加モジュールのロード #####
# mod_filterは「BASE」モジュールなので含まれているはずです。
# LoadModule filter_module modules/mod_filter.so*2
LoadModule deflate_module modules/mod_deflate.so

##### 圧縮コンテンツ非対応ブラウザ(古いブラウザ)への配慮 #####
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

##### 画像コンテンツは圧縮しない(CPUを節約) #####
SetEnvIfNoCase Request_URI\.(?:gif|jpe?g|png)$ no-gzip dont-vary

##### 対象コンテンツのタイプを指定して圧縮 #####
FilterDeclare Compression CONTENT_SET
FilterProvider Compression DEFLATE Content-Type $text/css
FilterProvider Compression DEFLATE Content-Type $application/javascript
FilterChain Compression

[設定内容の補足]

  • 環境変数「no-gzip」「gzip-only-text/html」
    • これらの環境変数mod_deflateモジュール内で参照され、no-gzipが設定されていれば、自動的にフィルターが外れます。
  • FilterDeclareディレクティブ
    • Compressionという名前のフィルター群のタイプがCONTENT_SETであると宣言しています。
  • FilterProviderディレクティブ
    • Compressionにフィルターを追加しています。
      • 第一引数はフィルター群に付ける名前(今回はCompression)
      • 第二引数はフィルターのプロバイダー名*3
      • 第三引数はフィルターを実行する条件を指定*4
  • FilterChainディレクティブ
    • Compressionフィルター群がApacheのリクエストを通じて呼び出されるようにします。*5


修正後、Apacheプロセスを再起動します。

[root@sidgen01 conf]# /etc/init.d/httpd restart
apache restart use /usr/local/apache2/conf/httpd.conf...   OK.

動作確認

FireFoxの「Live HTTP headers」で確認できます。確認の観点は以下の3点です。

  • HTTPリクエストヘッダー
    • Accept-Encoding: gzip,deflate
      • このリクエストヘッダーを送信されていること。送信されないブラウザでは圧縮されません。
  • HTTPレスポンスヘッダー
    • Content-Encoding:
      • gzip」となっていること
    • Content-Length:
      • 元ファイルのサイズよりも削減されていること

以下が「Live HTTP headers」での確認例です。

http://example.com/prototype.js

GET /prototype.js HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10
Accept: */*
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive

HTTP/1.0 200 OK
Date: Fri, 12 Nov 2010 05:37:27 GMT
Last-Modified: Wed, 24 Oct 2007 01:33:59 GMT
Etag: "41da34-17837-43d331c950fc0"-gzip
Accept-Ranges: bytes
x-ua-compatible: IE=7
Cache-Control: no-cache,max-age=0
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 22037
Content-Type: application/javascript
Connection: close

または、Apacheアクセスログでも確認可能です。例えば、「prototype.js」で絞り込んだ上で、ファイルサイズの部分を確認するといいでしょう。設定変更前よりも、ファイルサイズが小さくなっていると思います。

[root]# grep prototype.js accesslog.20101112
 ・
 ・
192.168.1.1 - - [12/Nov/2010:16:58:29 +0900] "GET /prototype.js HTTP/1.0" 200 96311
192.168.1.1 - - [12/Nov/2010:16:58:31 +0900] "GET /prototype.js HTTP/1.0" 200 96311

 ---------- 設定変更を実施 ----------

192.168.1.1 - - [12/Nov/2010:17:00:57 +0900] "GET /prototype.js HTTP/1.0" 200 22037
192.168.1.1 - - [12/Nov/2010:17:00:57 +0900] "GET /prototype.js HTTP/1.0" 200 22037
負荷検証

今回の対応によるサーバ負荷状況について、以下の観点で確認しておいたほうがいいと思います。

  • CPU消費
  • Apacheの各プロセスのメモリ消費

方法としては、Apacheのabコマンドを使って、負荷をかけます。

[root]# /usr/local/apache2/bin/ab -n 1000 -c 50 
        -H 'Accept-Encoding: gzip,deflate' 'http://localhost/prototype.js'

そして、負荷をかけている状態で、topコマンドを使って、以下の3つの値を確認します。

  • 「%CPU」各プロセスのCPU使用率
  • 「VIRT」各プロセスの仮想メモリサイズ(kbytes単位)
  • 「RES」各プロセスが使用している物理(スワップされていない)メモリサイズ(kbytes単位)

参考URL:httpd.confによるWebサーバの最適化 (3/3):実用 Apache 2.0運用・管理術(3) - @IT


環境によって異なりますが、おそらく、この対応で極端に負荷が上昇することはないと思います。手元の環境では、CPU使用率が元の2割程度(4X%⇒5X%)上昇していました。
但し、サーバ構成として、前段にキャッシュサーバがあれば、圧縮された結果をキャッシュしてくれますので、毎回圧縮処理が走ることにはなりません。JavaScriptCSSの外部ファイルはほとんどの画面で共通に読み込まれるものですので、高いキャッシュヒット率が期待できます。

この対応の効果

大きめのJavaScriptライブラリであるprototype.js(約95K)のサイズが、75%程度削減され、22K程度になっていました。大量にアクセスがあるサイトで考えるとかなりのネットワークリソースの節約になります。サーバのCPUにある程度余裕があれば、ぜひ導入すべき設定だと思います。

おまけ

最近試したところでは、Android端末でも問題なく、圧縮コンテンツを解凍できるようでした。スマートフォン向けには、特に重要な設定になると思います。

*1:mod_so は、apache コンパイル時にしか組み込む事ができないため、もし存在しなければApache本体の再コンパイルになります。

*2:この行を有効にすると、設定ファイル修正後、Apache再起動の際、「httpd: Syntax error on line 19 of /usr/local/apache2/conf/httpd-rewrite.conf: module filter_module is built-in and can't be loaded」というエラーが出ます。つまりもう組み込まれているよというエラーです。「/usr/local/apache2/bin/httpd -l」で確認しておくとよいです。もし無ければ、「/usr/local/apache2/bin/apxs -c -i /root/modules/httpd-2.2.11/modules/filters/mod_filter.c」でコンパイルしましょう。

*3:mod_deflateモジュールを読み込むと、ap_register_output_filter関数でDEFLATEというプロバイダー名が自動的に登録されます(そのプロバイダーがdeflateを行います)

*4:今回はHTTPレスポンスヘッダのContent-typeがtext /plainに部分一致した場合のみ、フィルターを実行するという条件をつけています。

*5:つまりFilterChainディレクティブを使わないと、定義されたフィルター群は実行されません