RUMメトリクスを利用した「Amazon CloudWatch Evidently」によるA/Bテスト
こんにちは、NRIデジタルの島です。
過日、以下記事を投稿し、「クラウド時代の分散環境における監視の難しさ」について言及しました。
次の監視標準になるか?Amazon Managed Service for Prometheus / Grafana
クラウド時代においては、分散型で複雑に絡み合うシステム群のどこで問題が発生しているのかを迅速に見極める仕組みや体制が必要になってきます。また、ビジネス要求も変化が早く、常に新しい機能(新しい価値)をユーザに提供し続けていかなければなりません。「オブザーバビリティ(可観測性)」を維持し、エンドユーザへ日々快適な環境を安定的に提供し続けることはもちろん、常に新しい機能をリリースすることで、より良いユーザ体験を提供し続けていくことが求められます。しかしながら、「安定した環境」は環境が変化したタイミングに特に崩れやすい為、新しい機能のリリースは、常に既存環境を悪化させるリスクを伴います。このリスクを軽減する為の仕組みの一つに「A/Bテスト※」があります。A/Bテスト方式を採用することで、「新しい機能に対するユーザの反応(実際のシステム利用状況やコンバージョン率(CVR)など)を測定・評価し、既存機能と比較して良好な場合は開放、そうでない場合は切り戻す」という仕組みを実現することができます。本記事では、オブザーバビリティの必要性を再度確認した上で、このA/Bテストの仕組みをAWS上でどのように実現可能かを検証していきたいと思います。
※ A/Bテストとは、特定の要素に差異のある複数パターンをランダムにユーザーに表示し、それぞれの成果を比較することで、より高い成果を得られるパターンを見つけるテスト手法です。
A/Bテストとは – IT用語辞典
オブザーバビリティの必要性
はじめに、オブザーバビリティの必要性を再度確認してみたいと思います。オブザーバビリティについては、インターネットや書籍などに多くの情報や見解が述べられており、AWSにおいても以下の公式ページやブログなどで公開されております。
モニタリングとオブザーバビリティ
これらの情報をもとに、筆者の見解として「オブザーバビリティの必要性」を従来のモニタリングとの差異で整理すると以下のようになりました。
① 継続的かつ能動的な問題解決
問題ありそうな箇所の単なる可視化機能である従来のモニタリングと比べ、オブザーバビリティは日々継続的かつ能動的に問題に辿り着けるようにする試みや仕組みです。詳細な洞察を与える為、従来のログ、メトリクスに加え「トレース情報」が必要であり、このトレース情報の可視化により、問題箇所へ迅速な到着を可能にします。問題の早期発見、解消はユーザの満足度低下を最小限に食い止めます。
AWSでの3サービス(ログ/メトリクス/トレース)
分散トレース例(AWS X-Ray)
※ AWS X-Rayの特徴より抜粋
② ユーザ視点での情報の取り扱い
従来のモニタリングでは、サーバやネットワークなどのインフラリソースの監視がメインでしたが、CPUやメモリ、ネットワーク遅延などによるユーザへの影響はあくまで「推測」となります。逆に言うと、インフラリソースが安定していてもユーザ環境が快適であるとは限りません。よって、インフラリソースがどうなっているか自体も大切ですが、レイテンシやエラー率をはじめとした「ユーザ視点・切り口で情報をトレースすること」がより重要となります。オブザーバビリティでは、各種ログ・メトリクス・トレース情報をユーザ視点での多様な切り口で集計・分析・可視化することでユーザから見たサービスの健全性を即座に把握することが可能になります。
③ システムよりビジネス
システムを運用しているユーザの最大の関心事は、「システムの安定稼働ではなく、ビジネスの成功」です。オブザーバビリティはシステムメトリクスだけでなく、カスタマージャーニーのようなユーザ行動を可視化することで、機能の効果を分析※できます。
※e.g. 一例として、ECサイトのカート追加後の購入数など
上記の通り、オブザーバビリティの肝となるのは「Digital Experience Management(DEM) = ユーザ視点での監視」です。AWSにおいては、このオブザーバビリティを強化するサービスを提供し始めており、現状以下3サービスがその対象となっています。
Amazon CloudWatch Synthetics
役割:外形監視
対象サイトやアプリケーションの健全性(性能や可用性)をユーザを模倣して定期的にチェックする
https://aws.amazon.com/jp/blogs/news/new-use-cloudwatch-synthetics-to-monitor-sites-api-endpoints-web-workflows-and-more/
Amazon CloudWatch RUM
役割:リアルユーザモニタリング
対象サイトやアプリケーションへのユーザメトリクス(ページロードや画面動線など)を統計分析し、可視化する
https://aws.amazon.com/jp/blogs/news/cloudwatch-rum/
Amazon CloudWatch Evidently
役割:A/Bテスト
機能(フィーチャー)フラグ方式をベースにアプリケーションの新機能の効果やデグレードを測定・評価し、機能開放、閉塞を制御する
https://aws.amazon.com/jp/blogs/news/cloudwatch-evidently/
※機能(フィーチャー)フラグは、「コードを変更せずに、フラグを使って機能を切り替える実装・デプロイ手法」です。概要は以下をご参照くださいフィーチャートグル
上記3サービスへは、管理コンソールのCloudWatch画面左メニュー「アプリケーションのモニタリング」から遷移可能です。
A/Bテスト検証に向けて
では、「オブザーバビリティの必要性」を再認識できたところで、本記事の目的である「A/BテストをAWS上でどう実現できるか」について検証していきたいと思います。コアとなるA/Bテスト機能には、上記Amazon CloudWatch Evidently(以下Evidently)の「A/Bテスト」機能を利用します。また、そのA/Bテストの評価に使用するデータですが、前述した「オブザーバビリティの必要性」の「② ユーザ視点での情報の取り扱い」で述べた通り、ユーザ視点でのメトリクス、つまり上記Amazon CloudWatch RUM(以下RUM)のユーザメトリクスを使用できるのが理想です。公式ドキュメントによると、両サービスを統合可能とのことですので、検証ではRUMも作成し、そのメトリクスをA/Bテストの測定・評価データとして使用していきます。なお、「Amazon CloudWatch Synthetics」については本記事の検証対象外となりますのでご了承ください。
検証順序
理解しやすいように、RUMとEvidently個別に機能確認後、両者を統合して検証していきたいと思います。
- RUMによるサイトのユーザメトリクス確認
- EvidentlyのA/Bテスト機能確認
- RUMユーザメトリクスを利用したEvidentlyによるA/Bテスト
検証対象サンプルアプリケーション
本記事の検証で使用するアプリケーションは以下公式ドキュメントのチュートリアルのWebアプリケーション(野菜販売簡易ECサイト)を使用します。
Tutorial: A/B testing with the Evidently sample application
※本アプリケーションは既にフィーチャーフラグ(True/False)で通常画面と新画面を切り替えるよう実装されています
画面(通常)
画面(新)
※「20%」の割引を適用
RUMによるサイトのユーザメトリクス確認
では、最初にRUM単体での確認をしていきます。手順は簡単で以下の通りです。
① RUMコンソール画面から「アプリケーションモニターを追加」でRUMを作成します
名前を「RumSampleApp」とし、その他はデフォルトまま作成します。
② 作成したRUMの「設定->Javascriptスニペット」のコードスニペットを上記サンプルアプリケーションにコピー&ペーストします
e.g. プルダウンで「HTML」を選択した場合は、以下コードを「public/index.html」へコピー&ペースト
public/index.html
<!DOCTYPE html> <html lang="en"> <head> <script> (function(n,i,v,r,s,c,x,z){x=window.AwsRumClient={q:[],n:n,i:i,v:v,r:r,c:c};window[n]=function(c,p){x.q.push({c:c,p:p});};z=document.createElement('script');z.async=true;z.src=s;document.head.insertBefore(z,document.head.getElementsByTagName('script')[0]);})( 'cwr', '[RUM ID]', '1.0.0', 'ap-northeast-1', 'https://client.rum.us-east-1.amazonaws.com/1.12.0/cwr.js', { sessionSampleRate: 1, guestRoleArn: "[IAM ロール ARN]", identityPoolId: "ap-northeast-1:1111-2222-333-aaa-bbbb", endpoint: "https://dataplane.rum.ap-northeast-1.amazonaws.com", telemetries: ["performance","errors","http"], allowCookies: true, enableXRay: false } ); </script> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#077915" /> ・・・
この場合、以下「Google Tag Manager」のようなタグ管理の仕組みを利用して組み込んでいただく方法でも問題ありません。
タグ マネージャーの概要 – タグ マネージャー ヘルプ
③ 「src/config.js」に認証情報をセットします
※本来は「Amazon Cognito」にて認証することが奨励ですが、ここでは一時的に認証情報発行して使用します。
src/config.js
credential: { accessKeyId: [AWS_ACCESS_KEY_ID], secretAccessKey: [AWS_SECRET_ACCESS_KEY], sessionToken: [AWS_SESSION_TOKEN] }
準備はこれだけです。サンプルアプリケーションを起動して、何度かアクセスしてみます。以下のようにRUMモニター画面へ正常にメトリクスが送信できたようです。
ページロード等のパフォーマンス
ページのロード時間やコンテンツの描画速度などがわかります。また、以下のようにアクセス元のブラウザの種類も統計として取得されています。
ブラウザとデバイス
その他、多様なユーザメトリクスがデフォルトで取得されており、色々な切り口で照会可能です。これらのメトリクスの収集、加工を自前で実装するのはかなり大変です。RUMでは最初からニーズの高いメトリクスを取得してくれていますので、このまま実用可能ですし、必要であればカスタムメトリクスや拡張メトリクスを作成することも可能です。
CloudWatch と CloudWatch Evidently に送信できるカスタムメトリクスと拡張メトリクス – Amazon CloudWatch
EvidentlyのA/Bテスト機能確認
次にEvidentlyを使用して、A/Bテストを実施してみます。ここではまだ測定・評価にRUMメトリクスは使用せず、個々の機能を確認のみしていきたいと思います。
概要図
※DeepDive Seminarから引用
フィーチャー(機能)
まず「フィーチャー(機能)」です。Evidentlyは、「フィーチャーフラグ(ダークローンチ)方式による制御」が大前提となっております。その為、フラグによって実行機能を変更するよう、アプリケーションに事前に実装しておく必要があります。サンプルアプリケーションにおいては既に「真偽値(True/False)」による機能切替えが実装されている為、そのまま動作可能です。以下のようにEvidentlyのフィーチャー設定値を「True」「False」に変えることで表示される画面が切り替わることが確認できました。
Falseをデフォルトとした場合
通常画面が表示される
Trueをデフォルトとした場合
新画面(割引表示あり)が表示される
エクスペリメント(実験)
フィーチャーの確認ができたところで、早速A/Bテストを試してみます。
※Evidentlyにて「A/Bテスト」は「実験」と呼ばれています
「実験」を開始するには以下の手順を実行します。
※手順は基本的に以下チュートリアルに記載のものと同様です
(再掲)Tutorial: A/B testing with the Evidently sample application
① 管理コンソールから「実験」を作成します
フィーチャー(機能)を選択(ここでは上記で作成したものを選択)
対象とトラフィック分割割合を設定(ここでは、セグメントを「すべてのトラフィック」、実際のトラフィック分割を「新旧半々(50%)」に設定)
※返却値(今回の場合真偽値)のことを「バリエーション」と呼びます
※セグメントについては、本記事の最後に説明しています
メトリクスを設定
(ここでは、サンプルアプリケーションに実装済みのカスタムメトリクス(pageLoadTime)を設定)
これで設定した終了時間まで、おおよそ設定したトラフィック分割割合となりました。
なお、返却されるフラグは送信するEvaluateFeature APIの「EntityId」単位となりますので注意してください。仮に、同一ユーザでは変更したくない場合には、EntityIdにログインユーザIDを使用したり、 FingerprintJSのような仕組みを利用する、などの方法があります。逆に、アクセスするたびに異なる挙動にしたい場合は、乱数やタイムスタンプ※などを利用する方法があります。
※ サンプルアプリケーションは、EntityIdにタイムスタンプを使用しておりますので、この結果のように画面へアクセスするたびに毎回判定されます。この後の検証で、RUMユーザメントリクスを使用する為に、以下のようにタイムスタンプからセッションIDに変更しますが、これにより別セッションの場合のみ判定されるようになります。
– id = new Date().getTime().toString();
+ id = sessionId・・・
const evaluateFeatureRequest = {
entityId: id,
feature: ‘showDiscount’,
project: ‘EvidentlySampleApp’
};
ローンチ(起動)
最後にローンチになりますが、こちらについては次項であわせて確認します。
RUMユーザメトリクスを利用したEvidentlyによるA/Bテスト
個々の機能確認ができましたので、RUMメトリクスを使用してEvidentlyのA/Bテストを実施していきたいと思います。RUMメトリクスをEvidentlyに連携させる方法ですが、筆者が確認した限り、「実験のメトリクスにRUMメトリクスを指定する」だけではうまく連携されないようです。連携させるには前述したEvaluateFeature APIのEntityIdにRUMに送信しているセッションIDを指定する必要があるようです。
①「public/index.html」に「cookieAttributes」を追加
・・・ identityPoolId: "ap-northeast-1:1111-2222-333-aaa-bbbb", endpoint: "https://dataplane.rum.ap-northeast-1.amazonaws.com", telemetries: ["performance","errors","http"], allowCookies: true, + cookieAttributes: {path:"/", secure:false}, enableXRay: false ・・・
②「src/App.jsx」で送信しているEntityIdにSessionIdを指定
・・・ useEffect(() => { + const cookie = document.cookie; + const sessionObject = JSON.parse(atob(cookie.split(";")[1].split('=')[1])); + const sessionId = sessionObject['sessionId']; - id = new Date().getTime().toString(); + id = sessionId ・・・
検証時点では、この連携方法は公式ドキュメント等では見つけることができず、手探りで辿り着いた方法になります。今後公開される方法とは異なる可能性がございますので注意してください。
前項の実験作成と同様の手順で、指定するメトリクスのみ「カスタムメトリクス」ではなく「RUMメトリクス」を指定します。
※メトリクスは3つまで指定可能です
※目標は結果として数値が減れば良好な場合(レスポンスタイムなど)は「減少」を数値が増えれば良好な場合(ECサイトのカート購入数など)は「増加」を選択します
今回は「レスポンスタイム」を設定してみます。(目標は「減少」)
サンプルアプリケーションの新旧画面の差異が「割引の有り無し」なので、本来はコンバージョンなどで測定・評価すると思われますが、今回は機能的な検証の為、確認のしやすい「レスポンスタイム」で進めています。
では、A/Bテストを実施します。EntityIdを一意にする為にSessionIdのクリアなどが必要で、手動で実行するのは苦しいので、ブラウザアクセスや制御を自動実行可能なSeleniumを使用して実行します。Javaコードは以下のようなイメージです。
import org.openqa.selenium.Cookie; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; public class VeggyTest{ public static void main(String[] args){ //ChromeDriver System.setProperty("webdriver.chrome.driver", "${CHROME_DRIVER"); for (int i = 0; i < 500; i++) { //Chrome起動 WebDriver driver = new ChromeDriver( new ChromeOptions().addArguments( "--headless" ) ); //指定したURLにアクセス driver.get("http://${HOST}:${PORT}"); try { Thread.sleep(5000); } catch (InterruptedException e) { throw new RuntimeException(e); } //Cookieを削除 driver.manage().deleteAllCookies(); //Chromeを閉じる driver.quit(); } } }
実験開始時に指定した終了日時に到達すると、実験の分析完了※となります。
※分析には最低100のサンプリングデータが必要となりますので注意してください
結果、今回は以下のように分析されています。
新画面(割引表示あり)の方のレスポンスタイムのほうが若干良いレベルですので、分析結果は「決定的ではない」となっております。少なくとも切り替えに致命的な悪影響はなさそうです。もし、結果が「悪い」となった場合、新画面のリリースは要検討です。なお、分析は以下の計算方法や結果判定などに基づき実施されているようです。
How Evidently calculates results – Amazon CloudWatch
ローンチ(起動)
実験は完了しましたが、これで終わりではありません。実験結果により新画面をリリースするか、切り戻しするかを判断した後、起動(ローンチ)機能によりトラフィックの分割割合をスケジューリングします。仮に新画面をリリースする判断をしたとしても、即100%のトラフィックを新画面に寄せるのはリスクが高いかもしれません。その場合は、起動(ローンチ)機能により徐々に100%に寄せていきます。
「起動を作成」を選択
「スケジューリング」を設定
※まず「50%-50%」、次に「20%-80%」、最後に「0%-100%(全て新画面へ)」とする場合
以上、RUMメトリクスを使用してA/Bテストの実行、評価、ローンチの一連の流れが実現できました。このような仕組みを構築することはかなり大変ですので、マネージドにほぼ設定レベルで実現できることはかなりのメリットではないでしょうか。
オーバーライドとセグメント
その他でよく使いそうな機能として、実験や起動にて評価対象を制御する機能がありますので、最後にそれらの挙動について記載しておきます。
オーバーライド
特定のEvidentlyIdに対するバリエーションを事前に定義可能で、あるユーザには常に新機能を見せたい場合などに有効です。例えば、以下画面はEvidently全体の評価順ですが、オーバライドが最初に評価されます。この場合、EvidentlyId(UserId)が「user001」の場合は常に新画面(割引表示あり)が表示されます。
検証時点では表示の不具合があるようで、以下画面のように表示が逆(V1 enable(本来はV2 enable))となっておりますが、機能的には問題ないことを確認済みです
セグメント
セグメントを設定することで、評価対象を絞ることができます。(e.g. 日本からのみ、Chromeブラウザからのみ)
Use segments to focus your audience – Amazon CloudWatch
例えば、以下の対応にて、リクエスト送信元のブラウザが「Chrome」or 「Safari」の時のみ実験の評価対象にすることが可能です。
セグメントの設定
実験作成時にセグメント指定
クライアントのコードにブラウザ判定・送信処理を実装
・・・ const agent = window.navigator.userAgent.toLowerCase(); if (agent.indexOf("edg") != -1) { browser = "Edge"; } else if (agent.indexOf("chrome") != -1) { browser = "Chrome"; } else if (agent.indexOf("safari") != -1) { browser = "Safari"; } else { browser = "Other"; } const evaluationContext = { "Browser": browser }; const evaluateFeatureRequest = { entityId: id, feature: 'showDiscount', project: 'EvidentlySampleApp', evaluationContext }; // Experiment client.evaluateFeature(evaluateFeatureRequest).promise().then(res => { if(res.value?.boolValue !== undefined) { setShowDiscount(res.value.boolValue); } // Send custom metric to Evidently getPageLoadTime() }) ・・・
さいごに
本記事では、オブザーバビリティの必要性について整理し、ユーザ視点の情報をトレースすることが重要であることを確認しました。運用上のあらゆる場面(定常時、リリース時など)で常にそれらの情報をトレースし、意思決定していくことが必要であり、本記事のテーマであるA/Bテスト時の評価にも活用していくべきと考えました。今回の検証で見てきた通り、RUMとEvidentlyを統合すれば少ない労力でユーザ視点の情報を活用したA/Bテスト評価の実現が可能です。興味ある方は是非一度お試しいただければと思います。