アプリケーションの変更をテストする場合、開発テスト ベッドではなく運用環境でのみ測定できる要素がいくつかあります。 例としては、UI の変更がユーザーの行動に与える影響や、全体的なパフォーマンスへの影響などが挙げられます。 一般的なテスト方法はA/B テスト(スプリット テストとも呼ばれます)です。このテストでは、(通常は少数の)ユーザーをアプリケーションの新しいバージョンに誘導し、大部分のユーザーは引き続き現在のバージョンを使用します。
このブログ記事では、Web アプリケーションの新しいバージョンをデプロイするときに A/B テストを実行することが重要な理由と、 NGINXとNGINX Plus を使用してユーザーに表示されるアプリケーションのバージョンを制御する方法について説明します。 構成例では、NGINX および NGINX Plus のディレクティブ、パラメーター、変数を使用して、正確で測定可能な A/B テストを実現する方法を示します。
前述したように、A/B テストを使用すると、2 つのバージョン間のアプリケーションのパフォーマンスまたは有効性の違いを測定できます。 開発チームは、UI 内のボタンの視覚的な配置を変更したり、ショッピング カートのプロセス全体を見直したりしたいと考えているかもしれませんが、その変更が期待どおりのビジネス効果をもたらすかどうかを確認するために、トランザクションの成約率を比較したいと考えています。 A/B テストを使用すると、トラフィックの一定割合を新しいバージョンに送信し、残りを古いバージョンに送信して、アプリケーションの両方のバージョンの有効性を測定できます。
あるいは、ユーザーの行動への影響よりも、パフォーマンスへの影響の方が懸念されるかもしれません。 たとえば、Web アプリケーションに大量の変更をデプロイする予定があり、品質保証環境内でのテストでは運用環境でのパフォーマンスへの影響を正確に把握できないと感じたとします。 この場合、A/B デプロイメントを使用すると、新しいバージョンを少数の定義された割合の訪問者に公開して変更によるパフォーマンスへの影響を測定し、割合を徐々に増やして、最終的に変更されたアプリケーションをすべてのユーザーに展開することができます。
NGINX と NGINX Plus は、Web アプリケーション トラフィックの送信先を制御するためのいくつかの方法を提供します。 最初の方法は両方の製品で使用できますが、2 番目の方法は NGINX Plus でのみ使用できます。
どちらの方法も、クライアントの特性 (IP アドレスなど) またはリクエスト URI (名前付き引数など) をキャプチャする 1 つ以上の NGINX 変数の値に基づいてリクエストの宛先を選択しますが、それらの違いにより、さまざまな A/B テストのユースケースに適しています。
split_clients
メソッドは、リクエストから抽出された変数値のハッシュに基づいてリクエストの宛先を選択します。 すべての可能なハッシュ値のセットはアプリケーション バージョン間で分割され、各アプリケーションにセットの異なる割合を割り当てることができます。 目的地の選択はランダムに決定されます。スティッキー
ルート
メソッドを使用すると、各リクエストの宛先をより細かく制御できます。 アプリケーションの選択は変数値自体(ハッシュではない)に基づいて行われるため、特定の変数値を持つリクエストを受信するアプリケーションを明示的に設定できます。 正規表現を使用して、変数値の一部のみに基づいて決定を下すこともできます。また、決定の基準として、ある変数を他の変数よりも優先的に選択することもできます。split_clients
メソッドの使用この方法では、 split_clients
構成ブロックは、 proxy_pass
ディレクティブがリクエストを送信するアップストリーム グループを決定する変数をリクエストごとに設定しています。 以下のサンプル構成では、 $appversion
変数の値によって、 proxy_pass
ディレクティブがリクエストを送信する場所が決まります。 split_clients
ブロックはハッシュ関数を使用して、変数の値を 2 つのアップストリーム グループ名(version_1aまたはversion_1b)のいずれかに動的に設定します。
http { # ... #applicationバージョン 1a アップストリーム version_1a { server 10.0.0.100:3001; server 10.0.0.101:3001; } #applicationバージョン 1b アップストリーム version_1b { server 10.0.0.104:6002; server 10.0.0.105:6002; } split_clients "${arg_token} " $appversion { 95% version_1a; * version_1b; } server { # ... listen 80; location / { proxy_set_header Host $host; proxy_pass http://$appversion; } } }
最初のパラメータは 分割クライアント
ディレクティブは文字列(「${arg_token}「
このハッシュ値は、各リクエストごとに MurmurHash2 関数を使用してハッシュ化されます (この例では、MurmurHash2 関数が使用されます)。 URI 引数は、 $arg_ name
という変数として NGINX で使用できます。この例では、 $arg_token
変数がtokenという URI 引数をキャプチャします。 ハッシュする文字列として、任意のNGINX 変数または変数の文字列を使用できます。 たとえば、クライアントの IP アドレス ( $remote_addr
変数)、ポート ( $remote_port
)、またはその両方の組み合わせをハッシュできます。 リクエストが NGINX によって処理される前に生成される変数を使用します。クライアントの最初のリクエストに関する情報を含む変数が理想的です。例としては、すでに述べたようにクライアントの IP アドレス/ポート、リクエスト URI、さらには HTTP リクエスト ヘッダーなどがあります。
split_clients
ディレクティブの 2 番目のパラメーター (この例では$appversion
) は、最初のパラメーターのハッシュに従って動的に設定される変数です。 中括弧内のステートメントはハッシュ テーブルを「バケット」に分割し、各バケットには可能なハッシュの割合が含まれます。 バケットはいくつでも作成でき、すべて同じサイズである必要はありません。 ハッシュの数が指定されたパーセンテージに均等に分割できない場合があるため、最後のバケットのパーセンテージは特定の数値ではなく常にアスタリスク (*) で表されることに注意してください。
この例では、ハッシュの 95% をversion_1aアップストリーム グループに関連付けられたバケットに格納し、残りをversion_1bに関連付けられた 2 番目のバケットに格納します。 ハッシュ値の範囲は 0 から 4,294,967,295 までなので、最初のバケットには 0 から約 4,080,218,930 (全体の 95%) までの値が含まれます。 $appversion
変数は、 $arg_token
変数のハッシュを含むバケットに関連付けられたアップストリームに設定されます。 具体的な例として、ハッシュ値 100,000,000 は最初のバケットに含まれるため、 $appversion は
動的にversion_1aに設定されます。
split_clients
構成のテストsplit_clients
構成ブロックが意図したとおりに動作することを確認するために、上記と同じ割合 (95% と残り) で 2 つのアップストリーム グループ間でリクエストを分割するテスト構成を作成しました。 グループ内の仮想サーバーを設定して、どのグループ ( version_1aまたはversion_1b ) がリクエストを処理したかを示す文字列を返すようにしました (テスト設定はここで確認できます)。 次に、 curl を
使用して 20 個のリクエストを生成し、 urandom
ファイルでcat
コマンドを実行して URI 引数トークンの値をランダムに設定しました。 これは純粋にデモンストレーションとランダム化の目的です。 意図したとおり、20 件のリクエストのうち 1 件 (95%) がversion_1bから処理されました (簡潔にするために、リクエストの 10 件のみを示しています)。
# for x in {1..20}; do curl 127.0.0.1?token=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1); doneトークン: p3Fsa86HfJDFwZ9ZnYz4QbaZLzcb70Ka サイト version_1a から提供されました。
トークン: a7z7afUz7zQijXwsLGfj6mWyPU8sCRIZ サイト version_1a から提供されました。
トークン: CjIr6W57NtPzChf4qbYMzD1Estif7jOH サイト version_1a から提供されました。... 10 件のリクエストの出力は省略されています...
トークン: gXq8cbG3jhl1LuYmICPTfDQT855gaO5y サイト version_1a から提供されました。
トークン: VVqGuNN3cRU2slsl5AWOR93aQX8nXOpk サイト version_1a から提供されています。
トークン: z7KnewxTX5Sp6wscT0fgmTDohTsQuCmyサイト version_1b から提供されました!!
トークン: fWOgH9WRrR0kLJZcIaYchpLhceaQgPD1 サイト version_1a から提供されました。
トークン: mTADMXrVnwnr1cd5JE6QCSkgTwfWUnDk サイト version_1a から提供されます。
トークン: w7AzSNmNJtxWZaH6cXe2PWIFqst2o3oP サイト version_1a から提供されます。
トークン: QR7ay0dA39MmVlXtzgOVsj6SBTPi8ECC サイト version_1a から提供されています。
スティッキー
ルート
メソッドの使用場合によっては、NGINX 変数の値のすべてまたは一部に基づいてクライアントのルーティングを決定し、静的ルートを定義する必要があります。 これは、NGINX Plus でのみ使用可能なsticky
ルート
ディレクティブを使用して実行できます。 ディレクティブは、1 つ以上のパラメータのリストを受け取り、リスト内の最初の空でないパラメータの値にルートを設定します。 この機能を使用すると、リクエストのどの変数が宛先の選択を制御するかを優先的にランク付けし、単一の構成で複数のトラフィック分割方法に対応できます。
この方法を使用するには 2 つの異なるアプローチがあります。
User-Agent
などのブラウザ固有の HTTP リクエスト ヘッダーなど、最初にクライアントから直接送信される値を含む NGINX 変数に基づいてルートを選択できます。スティッキー
ルート
ディレクティブはルート インジケーターを抽出し、リクエストを適切なサーバーに転送します。この例では、アプリケーション側のアプローチを使用しています。upstream グループのsticky
ルート
ディレクティブは、
サーバーによって提供される Cookie ( $route_from_cookie
でキャプチャされる) で指定された値にルートを優先的に設定します。 クライアントに Cookie がない場合、ルートはリクエスト URI ( $route_from_uri
) への引数の値に設定されます。 次に、ルート値によって、アップストリーム グループ内のどのサーバーが
リクエストを取得するかが決まります。ルートがa
の場合は最初のサーバー、ルートがb
の場合は 2 番目のサーバーです (2 つのサーバーは、アプリケーションの 2 つのバージョンに対応します)。
アップストリーム バックエンド { ゾーン バックエンド 64k;
サーバー 10.0.0.200:8098 ルート = a;
サーバー 10.0.0.201:8099 ルート = b;
スティッキー ルート $route_from_cookie $route_from_uri;
}
しかし、実際の Cookie または URI では、 a
またはb は
はるかに長い文字列に埋め込まれています。文字だけを抽出するには、Cookie と URI ごとにマップ
構成ブロックを構成します。
マップ $cookie_route $route_from_cookie { ~.(?P<route>w+)$ $route;
}
マップ $arg_route $route_from_uri {
~.(?P<route>w+)$ $route;
}
最初のマップ
ブロックでは、 $cookie_route
変数はROUTEという名前の Cookie の値を表します。 2 行目の正規表現は、 Perl 互換正規表現(PCRE) 構文を使用して、値の一部 (この場合は、ピリオドの後の文字列 ( w+
)) を名前付きキャプチャ グループルート
に抽出し、その名前の内部変数に割り当てます。 この値は最初の行の$route_from_cookie
変数にも割り当てられ、スティッキー
ルート
ディレクティブに渡すことができるようになります。
たとえば、最初のマップ
ブロックはこの Cookie から値「 a 」を抽出し、それを$route_from_cookie
に割り当てます。
ルート=iDmDe26BdBDS28FuVJlWc1FH4b13x4fn .a
2 番目のマップ
ブロックでは、 $arg_route
変数は、リクエスト URI 内のrouteという名前の引数を表します。Cookie と同様に、2 行目の正規表現は URI の一部を抽出します。この場合は、 route引数のピリオドの後の文字列 ( w+
) です。 値は名前付きキャプチャ グループに読み込まれ、内部変数に割り当てられ、 $route_from_uri
変数にも割り当てられます。
たとえば、2 番目のマップ
ブロックはこの URI から値b を抽出し、それを$route_from_uri
に割り当てます。
www.example.com/shopping/my-cart?route=iLbLr35AeAET39GvWK2Xd2GI5c24y5go.b
完全なサンプル構成は次のとおりです。
http { # ...
map $cookie_route $route_from_cookie {
~.(?P<route>w+)$ $route;
}
map $arg_route $route_from_uri {
~.(?P<route>w+)$ $route;
}
アップストリーム バックエンド {
zone backend 64k;
server 10.0.0.200:8098 route=a;
server 10.0.0.201:8099 route=b;
スティッキー ルート $route_from_cookie $route_from_uri;
}
server {
listen 80;
location / {
# ...
proxy_pass http://backend;
}
}
}
スティッキー
ルート
構成のテストsplit_clients
メソッドに関しては、テスト構成を作成しました。ここからアクセスできます。 curl を
使用して、 ROUTEという名前の Cookie を送信するか、URI にルート引数を含めました。Cookie または引数の値は、 urandom
ファイルでcat
コマンドを実行して生成されたランダムな文字列で、 .aまたは.bが付加されます。
まず、 .aで終わる Cookie でテストします。
# curl --cookie "ROUTE=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).a" 127.0.0.1 Cookie 値: R0TdyJOJvxBkLC3f75Coa29I1pPySOeQ.a リクエストURI: / 結果: サイト A - ポート 8089 で実行中
次に、 .bで終わる Cookie でテストします。
# curl --cookie "ROUTE=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).b" 127.0.0.1 Cookie 値: JhdJZrScTnPBLhqmzK3podNRcJAIc8ST.b リクエストURI: / 結果: サイト B - ポート 8099 で実行中
最後に、Cookie を使用せず、代わりに.aで終わるリクエスト URI のルート引数を使用してテストします。 出力は、Cookie がない場合 ( Cookie
値
フィールドが空の場合)、NGINX Plus が URI から派生したルート値を使用することを確認します。
# curl 127.0.0.1?route=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).a Cookie 値:
リクエスト URI: /?route=yNp8pHskvukXK6XqbWefhVUcOBjbJv4v.a 結果: サイト A - ポート 8089 で実行中
ここで説明する種類のテストは、構成によってリクエストが意図したとおりに分散されることを確認するには十分ですが、実際の A/B テストの結果を解釈するには、リクエストの処理方法に関するより詳細なログ記録と分析が必要です。 ログ記録と分析を行う適切な方法は多くの要因に依存し、この記事の範囲を超えていますが、NGINX と NGINX Plus は、リクエスト処理の高度な組み込みログ記録と監視機能を提供します。
log_format
ディレクティブを使用して、任意の NGINX 変数を含むカスタム ログ形式を定義できます。 NGINX ログに記録された変数値は、後で分析に使用できます。 カスタム ログとランタイム モニタリングの詳細については、 NGINX Plus 管理者ガイドを参照してください。
実験や A/B テスト計画を設計するときは、アプリケーション バージョン間でリクエストを分散する方法によって結果が事前に決定されないようにしてください。 完全にランダムな実験をしたい場合は、 split_clients
メソッドを使用して複数の変数の組み合わせをハッシュすると、最良の結果が得られます。 たとえば、リクエストからの Cookie とユーザー ID の組み合わせに基づいて一意の実験トークンを生成すると、クライアントのブラウザーの種類とバージョンのみをハッシュするよりもランダム化されたテスト パターンが提供されます。これは、多くのユーザーが同じ種類のブラウザーとバージョンを使用している可能性が高いため、全員が同じバージョンのアプリケーションに誘導されるからです。
また、多くのユーザーが混合グループと呼ばれるグループに属していることも考慮する必要があります。 彼らは、職場と自宅のコンピューター、さらにはタブレットやスマートフォンなどのモバイル デバイスなど、複数のデバイスから Web アプリケーションにアクセスします。 このようなユーザーには複数のクライアント IP アドレスがあるため、クライアント IP アドレスをアプリケーション バージョンの選択基準として使用すると、アプリケーションの両方のバージョンが表示され、実験結果が破損する可能性があります。
おそらく最も簡単な解決策は、スティッキー
ルート
メソッドの例のように、ユーザーにログインを要求してセッション クッキーを追跡できるようにすることです。 こうすることで、ユーザーを追跡し、ユーザーが最初にテストにアクセスしたときに表示されたのと同じバージョンに常に送信することができます。 これができない場合は、テストの過程で変更される可能性が低いグループにユーザーを配置することが理にかなっている場合があります。たとえば、位置情報を使用して、ロサンゼルスのユーザーに 1 つのバージョンを表示し、サンフランシスコのユーザーには別のバージョンを表示します。
A/B テストは、異なる量のトラフィックを代替サーバー間で分割することにより、アプリケーションの変更を分析および追跡し、アプリケーションのパフォーマンスを監視する効果的な方法です。 NGINX と NGINX Plus はどちらも、A/B テスト用の堅牢なフレームワークを構築するために使用できるディレクティブ、パラメーター、変数を提供します。 また、各リクエストに関する貴重な詳細を記録することもできます。 テストを楽しんでください!
NGINX Plus とスティッキー
ルート
メソッドをぜひお試しください。今すぐ30 日間の無料トライアルを開始するか、弊社にお問い合わせの上、ユースケースについてご相談ください。
「このブログ投稿には、入手できなくなった製品やサポートされなくなった製品が参照されている場合があります。 利用可能な F5 NGINX 製品およびソリューションに関する最新情報については、 NGINX 製品ファミリーをご覧ください。 NGINX は現在 F5 の一部です。 以前の NGINX.com リンクはすべて、F5.com の同様の NGINX コンテンツにリダイレクトされます。"