情報科学屋さんを目指す人のメモ

方法・手順・解説を書き残すブログ。私と同じことを繰り返さずに済むように。

NginxでABテストを実施する方法メモ(振り分け用ヘッダ作成、キャッシュ対策有り、セッション考慮有り)

Apache (8) Nginx (15)

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、デフォルト(*)で空白、になるように、自動設定してくれます。

Cookieに結果を保存

ただし、これだとユーザーがページを遷移するごとに変数の値が再計算されるので、ページ遷移ごとにデザインが切り替わってしまいます

これを回避するために、振り分けに使用するkeyを調節してもそれなりの精度になるかもしれないのですが、より汎用性を高めるために、ここでは振り分け結果の保存先にCookieを使うことにします。

location / {
    ...
    add_header Set-Cookie "template=$template;Path=/;";
    ...
}

これで、templateという名前のCookieに、$template変数の中身が設定されます。

Cookieを参考に振り分ける

これを利用して、場合分けすることにします。

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用キャッシュとスマホ用キャッシュの両方を削除する設定メモ

参考

コメント(0)

新しいコメントを投稿