Proctor: Indeed の A/B テスト・フレームワーク

(編集より: この記事は、 Indeed が提供する、 A/B テストのオープンソース・フレームワークである Proctor についての連載シリーズの1回目となります。)

Indeed における A/B テスト

「 We Help People Get Jobs (人々の仕事探しのお手伝いをする)」 ― これが Indeed のミッションだ。

「求職者にとっての最善とは何か?」

常日頃掲げるこの問題に対し、私たちは何でもテストし結果を測定する事で、答えを見つけ出す。

Indeed ではあらゆるプロダクトの新機能や改善点を、全てテストするように努めている。そうする事で、それらの変化の影響を測り、ミッション達成に確実に役立つようにしているのだ。

2013年 1月に、トム・バーグマンとマット・シュメルは、自社開発した A/B テスト・フレームワークである Proctor について @IndeedEng テックトークを行い、そこで、 Proctor がオープンソースとして利用可能になった事を発表した。

以来、 Proctor Web アプリのオープンソース化も実現している。こちらは Proctor のテスト定義を管理する Web アプリケーションだ。

 

10 月のテックトークで、トムはシンプルな A/B テストの例を挙げていた。テスト内容は、背景色変更が、 UX を向上するか判断する、というもの。

図 1 は、コントロールグループ A とテストグループ B を表している。 A は、 Find Jobs (求人検索)ボタンに変更を加えていないもの。一方 B は、ボタンの背景色が青に変更されている。

simple-a-b-test

図 1 : 現行のボタン(A) VS. 背景色を青に変更したボタン(B)

Logrepo についてのテックトーク記事でも説明されているように、 Indeed ではあらゆるログを取る。プロダクトを分析し、そこから学び、改善するためだ。

上に挙げたシンプルなテストを行うために、まず Indeed サイトにアクセスした各ユーザーのグループ( A または B )とその後派生するクリック数のログを取った。そして、自社の分析ツールを使用したところ、明らかになったのは、この場合はテストグループの方が、より多くの検索、そしてさらなるユーザー・エンゲージメントをもたらす、という事だった。

 

上記の例は、1つのテストの挙動だが、通常僕らは 1 件のテストにつき代替案となる複数の挙動をテストする。このテストでは、おそらく背景色を 2 つ以上は試すかと思われる。

また、複数のアイデアを同時にテストするのも可能だ。図 2 の例を見ると、一方はボタンのテキストのテストで、もう一方は背景色のテストとなっている。

このように、機能の中である特定の部分に対して、複数の変数 (テキストや色) をテストすることは、 「多変量テスト」 として知られている。

 

multiple-variable-a-b-test

図2:  1 つのボタンに対して同時に 2 つのテストを実行する

Indeed ではA/B テストを何年も取り入れている。その経験が、 Proctor 開発に役立った。10 月に行われたテックトークでは、Proctor の設計についてや、 A/B テスト以外でのProctor の用途について、トムとマットがさらに詳しく語っている。

今回の記事では、 Proctor の主要な機能とコンセプトを中心に、 Indeed の基本的なProctor の利用法を述べていく。

Proctor   その機能とコンセプト

標準表現

Proctor は標準の JSON 表現を使用したテスト定義を提供しており、テスト定義の修正が、コードとは独立にデプロイできるようになっている。
テスト定義の集合全体は「テスト・マトリックス」と呼ぶ。テスト・マトリックスは 1 つのファイルとして、複数のアプリケーションに分散が可能である。これにより、テスト管理の小回りが効くようになって、複数のアプリケーション間で安定してテスト定義を共有できるようになる。

図 3 はとてもシンプルなバージョンのボタン・テストを表している。この図では、 50% のユーザーがコントロールグループ A ( バケット 0 ) に割り当てられ、残りの 50% はテストグループ B(バケット1)に割り当てられている。

"buttontst": {
    "description": "backgroundcolortest",
    "salt": "buttontst",
    "buckets": [
    {
        "name": "control",
        "value": 0,
        "description": "current button treatment (A)"
    },
    {
        "name": "altcolor",
        "value": 1,
        "description": "test background color (B)"
    }
    ],
    "allocations": [
    {
        "ranges": [
        {
            "length": 0.5,
            "bucketValue": 0
        },
        {
            "length": 0.5,
            "bucketValue": 1
        }
        ]
    }
    ],
    "testType": "USER"
}

図 3 : シンプルな Proctor のテスト定義

この例が解りやすくなるように、ここで簡単に Proctor に関連する用語をまとめる。

  • 全てのテストにはテストタイプ (testType) が存在する。もっとも一般的なタイプはユーザー (USER) である。これは、僕らがユーザー識別子を使ってテストのバリエーションをマップする事を意味している。テストタイプについて、後でさらに詳しく述べたいと思う。
  • 各テストはバケット (buckets) の配列とアロケーション (allocations) の配列からなる。
  • バケットは Proctor のテスト定義内のバリエーションまたはグループを指す。各バケットは短い名前 (name) 整数値 (value) 、そして解りやすい説明 ( description ) を含んでいる。
  • アロケーションはバケットのサイズを範囲 (range) の配列として指定する。各範囲は 0 から 1 の間の長さで、バケット用にバケットの値 (bucketValue) への参照を持つ。1つのアロケーションの範囲の合計は1になる必要がある。ルールを使用すれば、複数のアロケーションを持つ事ができる(これも詳しくは後述)。

Proctor Web アプリ

Proctor Web アプリを使用する事で、テスト定義をWeb ブラウザから管理・デプロイする事ができる。

このアプリは、色々な方法でカスタマイズでき、以下のようなシステムやツールとの統合を可能にしてくれるのだ。 

  • テスト変更履歴を維持するバージョン管理システム
  • テスト変更のワークフローを管理するための課題トラッキングシステム
  • ビルドやデプロイシステムなどのその他の外部ツール

proctor-webapp-example

図 4: Proctor Web アプリ内のテスト定義のスクリーンショット

JSON のテスト仕様から Java コードを生成する

Proctor 内のテスト仕様は テスト定義から独立した JSON ファイルである。これは、テスト定義のフルセットの中から、アプリケーションがどのテストとバケットを認識しているかを宣言できるようにしている。

これらは、ビルドプロセスではJava コード生成の際に使用可能だ。実行時には、テストマトリックスの関連部分を読み込む際に使用できる。

 

コード生成は任意だが、コンパイル時のタイプセーフを提供してくれるので、テストとバケット名を含む文字列リテラルを持つコードを捨ててしまう必要がなくなる。

また、生成されたクラスは Java コードやテンプレート言語のテストを書くのを簡単にしてくれる( 図5はJSPでの例 )。

さらに、生成された Java のオブジェクトは、ユーザーがそのテストグループに割り当てられているかどうかを JSON や XML などの形式へとシリアライズする事ができる。

<c:if test="${groups.buttontstAltColor}">
  .searchBtn { background-color: #2164f3; color: #ffffff; }
</c:if>

図5 : テストグループ・メンバーシップに基づいたJSPテンプレート内の条件付き CSS

ルールに基づくコンテクスチュアル・アロケーション

Proctor のルール定義の言語を使用すると、ランタイム・コンテキストに対してルールを評価することができる。そうする事で、システムはテストならびにテスト・アロケーションを適用する事ができる。

例えば、テスト全部が、特定のセグメントのユーザーにのみ利用可能になるように定義することが可能になる。または、セグメントに応じて、テストグループのアロケーションを調整する事もできる。ある国では、テストを A/B で 50% ずつで行い、その他全ての国で A/B/C/D で 25% ずつにすることもできる。

ルールに基づいたグループの割り当てを行うと、柔軟にテストをロールアウトし、評価することができるようになるのだ。

"allocations" : [
{
    "rule" : "'US' == country && 'en' == userLanguage",
    "ranges": [
    {
        "length": 0.5,
        "bucketValue": 0
    },
    {
        "length": 0.5,
        "bucketValue": 1
    }
    ]
},
{
    "rule" : null,
    "ranges" : [
    {
        "length" : 1.0,
        "bucketValue" : -1
    }
    ]
}
]

図6: 米国英語使用者向け 50/50 テストその他ユーザーには無効なテスト ( bucket -1 ) である。

ペイロード

テスト定義において、テストグループにデータのペイロードを添付可能である事は、コードをシンプルにしてくれる。

図の 7 と 8 では、テストしているボタンの色をテスト定義の中でどのようにペイロードとして指定できるか、指定された色にテンプレートからどうやってアクセスできるかを具体的に示している。

この例では、テンプレートコードの総量は減ってはいないが、もし複数のテストバリエーションがあって、それぞれ別の色を指定していたら、ペイロードを使用することでコードの行数は減るだろう。

"buckets": [
{
    "name": "control",
    "value": 0,
    "description": "current button treatment (A)",
    "payload": {
        "stringValue": "#dddddd"
    }
},
{
    "name": "altcolor",
    "value": 1,
    "description": "test background color (B)",
    "payload": {
        "stringValue": "#2164f3″
    }
}
]

図7 : 色の値を含むデータ・ペイロードをテストグループ B に添付

<style>
  .searchBtn { background-color: ${groups.buttontstPayload}; }
</style>

図8 :  JSP テンプレートを使用した CSS でのデータ・ペイロードのアップデート

柔軟なテストタイプ

Proctor はテストタイプに関して柔軟なコンセプトを持っている。

バケットの決定は以下に基づいて可能だ。

  • ユーザー(通常トラッキング・クッキーの値によって定義される)
  • アカウント ID (デバイス間で固定可能)
  • Eメールアドレス

またはリクエスト間で完全にランダムにすることもできる。

さらに、Proctor はテストタイプをカスタマイズして拡張することも可能だ。

例えば、ページ URL やコンテンツ・カテゴリのような、コンテキストあるいはコンテンツに基づく attributeでテストグループを決めたいときには、カスタマイズしたテストタイプは便利である。

バイアスのない独立したテスト

一様分布のハッシュ関数を使用して、 Proctor は インプットとなる識別子 ( 例.ユーザーID )を整数にマップする。テストにバケットを割り当てるためである。

バケットに対する範囲の割り当てが、各バケットを定義する整数の範囲を決定するのだ。図 9 は 50/50 の割合のコントロール/テストの例だ。ハッシュ関数が一様なので、バケットの指定はバイアスのないものになるはずである。

hash-id-to-buckets

図 9 : ハッシュ関数使用のため、整数の範囲にマップされた 50/50 のコントロール/テスト・バケット

さらに、 Proctor のテストは独立している。つまり、あるテストで特定のテストグループに属していることは、他のテストでどのテストグループに属するかということに全く相関がないのだ。

この独立性は、各テストに異なる salt を割り当てることで成立している。この salt はハッシュ関数への input として識別子と一緒に使用される。

各テスト定義に異なる salt を含める事で、次の2つの高度な機能が使用可能だ。

  1. 同じ salt を複数のテスト間で共有し、異なるテスト間で意図的にバケットをそろえる(相互に依存させる)ことができる。ただし、共有の salt は “&” で始まらなければならない。実際には、この方法で 2 つのテストをそろえる必要があるケースは殆ど目にしない。
  2. salt を変更する事で、テストの配分の「シャッフル」が可能になる。というのは、ハッシュ関数からまったく異なる output がかえってくるからだ。このシャッフルを行う事で、テスト内に偶発的なバイアスがないか再確認する事ができる。

Indeed における Proctor

Indeed のプロダクト開発に対するデータドリブンなアプローチにおいて、Proctor は重要な役割を担うようになった。
現在本番環境には、 100 件以上のテストと 300 件以上のテストのバリエーションが存在する。


次回のポストでは Indeed での Proctor 使用法をより詳細に説明していく。

挑戦者求む:Indeed (2013~2014年) コーディング・デュエル

今年の2月、アメリカと日本から集まった136人の学生プログラマーが、Indeed コーディング・デュエルに挑戦しました。このプログラミング・コンテストは、2013年の秋に日本とアメリカで行われた2つの予備選を勝ち抜いた日米の大学生たちによる「最終決戦」となりました。アメリカからはテキサス大学オースティン校イリノイ大学アーバナ・シャンペーン校マサチューセッツ工科大学、そして日本からは東京大学とその他10校の大学の学生がこのコンテストに参加しました。

2013年秋の予備選は大学ごとに行われましたが、今回の最終戦では予備選を勝ち抜いたファイナリスト同士が直接対決しました。優勝賞金は3000ドル。参加者たちは10問からなる論理問題と数理問題に挑みました。

最終戦までに何が起きたのか?どんなプログラム言語が優位に立ったのか?来年はあなたの大学も挑戦者に!?続きはこちら。

秋の大学対抗戦:コーディング・デュエル アメリカ大会

2013年秋のIndeed コーディング・デュエルには、テキサス大学オースティン校(UT)、イリノイ大学アーバナ・シャンペーン校(UIUC)、マサチューセッツ工科大学(MIT)から約100人が参加しました。このコンテストでは、ACM-ICPC 国際大学対抗プログラミングコンテストで出題されるようなプログラミングの問題が7問出題されました。制限時間は3時間。今回はライバル同士であるUT とUIUC に加え、MIT が新たに参戦しました。スコアボードに結果がリアルタイムで映されるにつれて、会場の雰囲気はますます白熱しました。

最初にスコアボードに正解を出したのはMIT でした。UIUC とUT もすぐにそれを追いかけます。制限時間が残り1時間になると、コンテストはMIT とUIUC の一騎打ちとなりました。終了の30分前には、スコアボードはオフにされ、結果が表示されなくなり、コンテスト終盤の緊張感がいよいよ高まりました。結果は、初出場にもかかわらずMIT が僅差で優勝。UIUC とUT はMIT に僅かに及ばず、それぞれ2位と3位という結果になりました。各大学内における優勝者にはChromebook が、2位と3位の学生にはそれぞれ Nexus 7 とDas キーボードが賞品として贈られました。
UT Austin Indeed Fall 2013 Coding Duel

2013年秋のコーディング・デュエルにに参加したUTの学生たち (写真:ジョリン・カニングハム)

アメリカの大学で行われた今回の Indeed コーディング・デュアルは、そのカジュアルな雰囲気で様々なプログラミング言語とスキルをもつ学生エンジニアたちを魅了しました。下級生たちも将来のコンテストに向けてどう準備したらよいのか、興味津々の様子でした(「習うより慣れろ!」です)。

コーディング・デュエル冬の陣:日本大会

2013年12月8日、リクルートと Indeed はアメリカでのコーディング・デュエルと同様のプログラミング・コンテストを日本で開催しました。このコンテストは日本でコンピュータサイエンスを専攻する全ての学生に参加を呼びかけ、159名(東京から109名、京都から50名)の学生が参加しました。

参加者の興奮は、会場となった教室からもツイッターの#rprocon からもはっきりとわかりました。驚いたことに、34名の参加者が5分以内に最初の問題に対する正解を出したのです。ある挑戦者は開始から1時間以内に8問中7問の問題に解答してしまいました。そこで、アメリカ大会では使われなかった9問目の問題が新たに出題され、優勝者は追加の問題を解いて残り時間を楽しみました。しかしさらにその1時間後、私たちは10問目の問題も追加しなければなりませんでした。驚きの最終結果は、19名が8問以上の問題に解答。そのうちの一人は10問全てに解答しました。アメリカ大会ではMIT、UIUC、UT の学生のうち、7問以上回答した人はゼロ。この結果を受けて、日本からの挑戦者はアメリカでの最終戦で一目置かれる勢力になることは明らかでした。

Indeed/Recruit Coding Duel December 2013

Indeed / リクルート共催コーディング・デュエル日本大会(写真:西川滋)

東西決戦:日本大会の上位入賞者、アメリカに上陸

2014年2月、Indeed はIndeed/リクルート共催の「リクルートプログラミングコンテスト」入賞者をボストンに送り、アメリカ大会のファイナリストであるUT、UIUC、そしてMIT の学生たちとの最終戦に挑んでもらいました。最終戦に参加した136名のうち117名は少なくとも1つの問題に正解することができました。

最終戦参加者の出身大学内訳

  • 日本の各大学 (22)
  • UIUC (26)
  • MIT (35)
  • UT Austin (53)

Indeed のエンジニアリング部門の上級副社長 (SVP) のダグ・グレイは、会場であるMIT の競争的な雰囲気をこのように語ります。「コンテストはMIT の教室で行われました。教室はほぼ満席で、日本からの記者と観客が教室の周りを取り囲んで、キーボードを打つ音以外はとても静かでした。」

Indeed Coding Duel February 2014

MIT会場での最終戦の様子 (写真:秋田毅)

スコアボードには挑戦者たちが問題を解くごとにリアルタイムでスコアが表示され、またUT とUIUC のキャンパスで同時に行われていたコンテストの模様も中継されました。トップ10のファイナリストの内訳は、MIT から2名、UIUC から1名、そして日本の大学から7名となりました。

このコンテストでもっともエキサイティングだったのは、参加者が多様なプログラミング言語を使ったことです。もっともよく使われたプログラミング言語はJava と Python でしたが、C++ やRuby も見かけられました。面白いことに、UT からの参加者は主にJava を、MIT の学生はPython を、そしてUIUC の学生は様々なプログラミング言語の組み合わせを使いました。

最終戦で使われたプログラミング言語の内訳 (解答法別。解答者別ではない)

  • C++ (281)
  • Java (279)
  • Python (234)
  • C (48)
  • Ruby (11)
  • Perl (6)
  • Matlab (3)

最終戦の優勝者は3時間で10問の問題に解答し、賞金3000ドルを手にしました。彼は全ての問題にC++ を用いて解答しました。

また、各コンテストの結果は、日本のIT メディアであるGizmodo と Wired で報じられました。

挑戦者求む

Indeed 主催の次回2014年度のコーディング・デュエルに興味がわきましたか?次回のコンテストに出場するために、Top Coderに参加したり、ACM-ICPC 世界大会の過去問を解いたりして、腕を磨いておきましょう!

ご自分の学校(単位)で来年のIndeedコーディングデュエルに参加したい場合は、リクルートのCODE FESTIVALのサイトで最新情報をチェックしてください!

 

university-tech-recruiting@indeed.com までご連絡ください。

更新(2016年3月9日):採用担当の連絡先を変更

 

util-urlparsing を活用して 効率的にクエリ文字列を パースする方法

今日のお知らせは、 util-urlparsing のオープンソース化についてです!

util-urlparsing は、 Indeed が作成した Java ライブラリで、不要な中間のオブジェクト作成をせずに、 URL クエリ文字列のパースを可能にします。

これは ParseUtils の内部で数字をパースするメソッドを含んでおり、Java の同等のメソッドである Integer.parseInt や Float.parseFloat よりも高速です。

Java のバージョン 1.6 以下は、 String.substring メソッドを使用すると、非効率的なメモリーの消費を引き起こしてしまう、重大な欠陥を含んでいます。

Indeed のログ・レポジトリからデータを処理すると、イベントデータのキーと値 (key/value) のペアを含む、大きい文字列から、小さな部分文字列を抽出しなければいけないのです。

util-urlparsing 内の主要なクラスである QueryStringParser は、中間の文字列オブジェクトを生成することなく、効率的にこのデータをパースできるように書かれました。

これは、コールバックのメカニズムを通して行われるため、大きなクエリ文字列から必要なキーだけをパースすることが可能になります。

Indeed のクエリ・パースのベンチマークテストにおいては、著しくヒープ・スペースを制限した場合、String.split を使用した Java のナイーブな実装に比べて 4 倍近くスピードアップしました。そして、百万個の key-value のペアも、最大 64MB のヒープで、 3 秒以内にパースできます。数字をパースするベンチマークも、同等のメソッドである、 Integer.parseIntFloat.parseFloat に比べて、 2 倍以上スピードアップしました。

util-urlparsing は GitHub または maven.org よりダウンロード可能です。

また、スタートガイドとして、使用例なども記載したドキュメンテーションも利用可能です。ご質問の場合は Q&Aforum にて、 Indeed のオープンソース Java 機能についてもご覧いただけます。

Indeed のログ・レポジトリについて、さらに学びたい!という場合は

2014 年 1月に行われた “Logrepo: Enabling Data-Driven Decisions.”  の

動画とスライドを是非ご覧ください!