nginxで広告(HTML断片)をランダムに入れ替える
このようなHTMLで広告を表示するとしよう:
この画像リンクの部分をページロードする度に次のHTML断片の一つに入れ替えたい。
<div class="ad"><a href="..."><img src="..." /></a></div>
<a href="/redirect?dest=FOO"><img src="/img/FOO.png"></a>
<a href="/redirect?dest=BAR"><img src="/img/BAR.png"></a>
<a href="/redirect?dest=BAZ"><img src="/img/BAZ.png"></a>
高速化のためにキャッシュしておいたHTMLを、一部を書き換えるために再び生成するのは効率が悪い。HTMLの一部を前もって用意された幾つかのHTML断片の一つにおきかえるという簡単な操作なので、スクリプト言語によるバックエンドでなく、高速なCコードにやらせたい。幸いにnginxはこれを可能にしてくれる。
ssiとhttp_random_index_moduleを組み合わせれば、nginx内でページの一部をランダムに入れ替えることができる。
nginxをhttp_random_index_moduleを組み込んで構築する
./configure --with-http_random_index_modulessiはディフォルトで有効になっているので、ここで指定する必要はない。
random_index_moduleの設定
random_ads.conf
location /ad/banner/ { # このURLのリスポンスがランダムに入れ替わる。
random_index on;
alias /var/www/hoge/ad/html/banner/; # ここに表示するHTML断片ファイルを置いておく。
}
HTML断片管理
/var/www/hoge/ad/html/banner/にHTML断片を置く設定になっているが、そこにこのようにHTML断片ファイルを入れておく。ファイルの内容は上のHTML断面だ。
/ad/banner/からは foo.html, bar.html, baz.html のうちの一つの内容が返される。
html/banner/foo.html
html/banner/bar.html
html/banner/baz.html
この例ではHTML断片から参照される画像はこのように置いてある。nginx設定にこれに相当するalias文を入れておく。
img/banner/foo.jpg
img/banner/bar.jpg
img/banner/baz.jpg
nginx設定
- HTML断片を組込むURLのロケーションでssiを有効にする
location / {
...
ssi on;
...
HTMLにSSI文を入れる
<div class="ad"><!--# include virtual="/ad/banner/" --></div>
random index URLの作動を確認
何度もリクエストするとランダムに入れ替わるはず。
$ curl http://localhost:8000/ad/banner/
<a href="/redirect?dest=foo"><img src="/ad/img/banner/foo.jpg" /></a>
$ curl http://localhost:8000/ad/banner/
<a href="/redirect?dest=bar"><img src="/ad/img/banner/bar.jpg" /></a>
$ curl http://localhost:8000/ad/banner/
<a href="/redirect?dest=foo"><img src="/ad/img/banner/foo.jpg" /></a>
ページレベルでの確認
広告HTML断片がランダムに入れ替わっていることを確認。
$ curl -s http://localhost:8000/ | grep banner
<a href="/redirect?dest=foo"><img src="/ad/img/banner/foo.jpg" /></a>
$ curl -s http://localhost:8000/ | grep banner
<a href="/redirect?dest=foo"><img src="/ad/img/banner/foo.jpg" /></a>
$ curl -s http://localhost:8000/ | grep banner
<a href="/redirect?dest=bar"><img src="/ad/img/banner/bar.jpg" /></a>
結論
nginx+ssi+http_random_index_moduleで高速、効率的そして安定した広告ロテーションができるようになった。
背景
安く高速に多大なトラフィックを処理するウェブアプリの秘訣はとにかくスクリプト言語によるバックエンドをトラフィックに直接さらさないことだと思う。一般的にはmemcacheなどでこの問題を解決しているようだが、この方法は用いていない。単独サーバの設定だとファイルシステムが十分キャッシュの役割をはたす。あとキャッシュにおいてはリクエストを受けたページがないとバックエンドにクライアントが行くことになる。資源の乏しい環境ではこれが問題をおこす。(これについては後に詳しく書きたい) そこでバックエンドのウェブアプリはモデルの更新毎に静的なドキュメントをファイルシステムに書き込むようにしてある。それをnginxがクライアントに送る。つまり、ウェブアプリはリクエストに応えるサーバではなく、イベントに反応して静的なサイトを更新するdaemonとして機能している。アプリケーションとしてのウェブサービスでは無理だが、比較的安定したコンテンスを多くの人に見せる出版系サイトならこの手法が使える。
このように静的なファイルとして管理されたサイトで広告ローテーションを導入しようとした際にこのssi+http_random_index_module方をみつけた。せっかくnginxだけでページを送り返せるようにしたのに、広告の入れ替えのためにpython/djangoのような重いプロセスをその過程にかかわらせたくなかった。幸い同じような問題に直面した人がいたらしく、http_random_index_moduleがnginxの標準モジュールとして存在していた。