スポンサーリンク
Nginxをリバースプロキシとして利用している際に、キャッシュも考慮してABテストを実施する方法についてメモ。裏で動作しているアプリケーション側(Apache)に、どのパターンを選択したかをヘッダとして渡すので、それに応じて振る舞いを変更することができます。あまり検証できていないのですが、とりあえず参考にはなるかと思います。
目次
スポンサーリンク
やりたいこと
今回は、Apache上でアプリケーション(自分はWordPress)を動かしていて、なおかつNginxをリバースプロキシとして利用している状況で、ABテストを行いたい状況を考えます。
ABテストの振り分けは、Nginxで行います。具体的には、NginxがApacheに渡すヘッダ(X-Wp-Templateヘッダ)を新しく作成し、そこにケースごとに異なる文字列を与えてApache側でそのヘッダを見て生成するページを変更させます。
今回の具体例では、現在のデザインのままにするパターン(80%、ヘッダは空白)、新パターン1(10%、ヘッダの値は"did2memo-net-template-v1")、新パターン2(10%、ヘッダの値は"did2memo-net-template-v2")という3つの振り分けを考えます(2つでもよかったけど、3つのほうが設定の一般形が見やすいので)。
ユーザーごとに振り分ける
さて、ユーザーごとにパターンを振り分けたいのですが、ユーザーごとに一定割合で異なる変数を生成する、ぴったりのモジュール(標準)があります。ngx_http_split_clients_module モジュールです。
「split_clients」というディレクティブを提供してくれています。
split_clients "abc${remote_addr}${http_user_agent}${date_gmt}" $template { 10% did2memo-net-template-v1; 10% did2memo-net-template-v2; * ""; }
最初の「abc${remote_addr}${http_user_agent}${date_gmt}」という文字列が、振り分け用キーになります。キーが同じであれば、同じ値が設定されてしまうので、ある程度はちゃんとバラけるようにしておきます。
そして、$templateが、振り分けによって、設定してほしい変数です。
この例では、10%でv1、10%でv2、デフォルト(*)で空白、になるように、自動設定してくれます。
ただし、これだとユーザーがページを遷移するごとに変数の値が再計算されるので、ページ遷移ごとにデザインが切り替わってしまいます。
これを回避するために、振り分けに使用するkeyを調節してもそれなりの精度になるかもしれないのですが、より汎用性を高めるために、ここでは振り分け結果の保存先にCookieを使うことにします。
location / { ... add_header Set-Cookie "template=$template;Path=/;"; ... }
これで、templateという名前のCookieに、$template変数の中身が設定されます。
これを利用して、場合分けすることにします。
Cookieが未設定、または設定一覧に存在しない古い設定の場合は、振り分けを採用し、Cookieがちゃんと設定されている場合には、そのままその値を使用することにします。
map $cookie_template $template { default $_template; did2memo-template-v1 did2memo-template-v1; did2memo-template-v2 did2memo-template-v2; }
defaultの指定と、mapの設定したい変数(変数名)がかぶると変数が壊れてしまうようなので、ここではdefaultに$_templateという変数名を指定しました。なので、先ほどのsplit_clientsでも、その変数名を使って
split_clients "abc${remote_addr}${http_user_agent}${date_gmt}" $_template { 10% did2memo-net-template-v1; 10% did2memo-net-template-v2; * ""; }
とします。
ここで、「ifを使えば」と思ってしまうのですが、前回の設定が残っている場合を考慮しておきたいので、それらをdefaultに押し込めるmapを採用しています。繰り返し書く形になってしまっていますが。
「map」は、
map (条件分岐に使う変数) (設定したい変数) { (条件にする値) (設定する値) ... default (どれにも当てはまらないときの値) }
のように使う、ngx_http_map_moduleに含まれるディレクティブです。とても汎用です。
また、cookieの値の取り出しは、nginxの場合、「$cookie_cookiename」という形式で取得できます。cookieの名前が「wp_template」だった場合は、「$cookie_wp_template」という変数でアクセス(取得)できます。
upstream(Apache)にヘッダとして変数を渡す
最後に、A/Bテスト用に振り分け設定した変数を、nginxの裏側で実際にページを生成しているApacheに、HTTPリクエストヘッダ(独自ヘッダ)として渡します。
proxy_set_header X-Template $template;
ここでは、ここまでで作成した「$template」の中身を、「X-Template」というヘッダに設定して、upstreamサーバ(Apache)へ渡します。
Apache側で振り分ける
あとは、そのヘッダを見て振り分けるだけです。
Apache側のPHPで、ヘッダの中身を受け取る(参照する)方法は「Nginxの変数をproxy_set_headerでApacheに渡してPHPで受け取る方法」で解説しています。
キャッシュのキーを変更する
A/Bテストを行うので、生成されるページの内容は異なることが予想されます。
しかし、現状では、パターンAもパターンBも、同じものとしてキャッシュされてしまいます。つまり、最初にキャッシュされたものが表示され続けてしまいます。
この、「同じもの」かどうかを判定するのに使う識別子が proxy_cache_key で、「proxy_cache_key」というディレクティブで指定しています。
たいてい、URLと引数、自分の場合なんかはPCかmobileか、などの変数の結合で表現されます。つまり、それらのどれかが異なれば、別のコンテンツだと判断して、キャッシュを別々に作成してくれます。
ここに、今回の変数も追加してあげれば、A/Bテストのテストパターンごとに別のキャッシュを作成してくれます。
というわけで、現状の自分のproxy_cache_key(proxy_cache_key "http://did2memo.net$uri$is_args$args$mobile";)の末尾に、変数を追加しました。
proxy_cache_key "http://did2memo.net$uri$is_args$args$mobile$template";
完成
これで、Apacheで動作しているアプリケーションに、一定割合ではこっち、残りはこっち、のような値を渡すことができるようになりました。
残る問題
WordPressで使っていると、以前やったこの設定との衝突が問題:「WordPressプラグインNginx Cache ControllerでPC用キャッシュとスマホ用キャッシュの両方を削除する設定メモ」
参考
スポンサーリンク
スポンサーリンク