A/B/n実験の実施

実験の実装

実験例

まず、bin/feature-flag コマンドを使って機能フラグを生成してください。通常の開発機能フラグと同じように、experiment をタイプに使ってください。説明のために、機能フラグ(と実験)の名前をpill_colorとします。

bin/feature-flag pill_color -t experiment

目的の機能フラグを生成したら、すぐにコードで実験を実装することができます。基本的な実験の実装は次のようになります:

experiment(:pill_color, actor: current_user) do |e|
  e.control { 'control' }
  e.variant(:red) { 'red' }
  e.variant(:blue) { 'blue' }
end

このコードが実行されると、実験が実行され、バリアントが割り当てられ、(コントローラやビューの場合)window.gl.experiments.pill_color オブジェクトがクライアントレイヤーで利用可能になります:

  • 割り当てられた variant。
  • クライアント追跡イベントのコンテキストキー。

さらに、実験が実行されると、その実験のイベントがトラッキングされます:assignment 。イベント、トラッキング、クライアントレイヤーについては後で詳しく説明します。

ローカル開発では、機能フラグインターフェースを使用することで、実験をアクティビティにすることができます。機能フラグを有効にする呼び出しに関連する実験を提供することで、特定のケースをターゲットにすることもできます:

# Enable for everyone
Feature.enable(:pill_color)

# Get the `experiment` method -- already available in controllers, views, and mailers.
include Gitlab::Experiment::Dsl
# Enable for only the first user
Feature.enable(:pill_color, experiment(:pill_color, actor: User.first))

環境上で実験機能フラグをロールアウトするには、ChatOpsを使って以下のコマンドを実行します(GitLabドキュメントの開発における機能フラグで詳しく説明されています)。このコマンドは、実験に遭遇した人の半分に_コントロール_、25%に_赤い_バリアント、25%に_青い_バリアントを割り当てるシナリオを作ります:

/chatops run feature set pill_color 50 --actors

この例で均等なディストリビューションにするには、50% ではなく 66% になるようにコマンドを変更してください。

note
実験の実行を直ちに停止するには、/chatops run feature set pill_color false コマンドを使います。
caution
ChatOps コマンドを使うときは、--actors フラグを使うことを強くお勧めします。それ以外のフラグを使うと、バリアント割り当てのキャッシュがどのように処理されるかによって、奇妙な動作をする可能性があるからです。

この実験はHTMLラッパーを使ったHAMLファイルで実装することもできます:

#cta-interface
  - experiment(:pill_color, actor: current_user) do |e|
    - e.control do
      .pill-button control
    - e.variant(:red) do
      .pill-button.red red
    - e.variant(:blue) do
      .pill-button.blue blue

コンテキストの重要性

先ほどの実験例では、コンテキスト(これは重要な用語です)は{ actor: current_user } に設定されたハッシュです。コンテキストは、実験をどのように実行したいかに基づいて一意でなければならず、より低いレベルで理解されるべきです。

レポートを簡略化するために、これらのコンテキストを使用することが期待され、またレポーターにも推奨されます:

  • { actor: current_user }:実験に参加する各ユーザー(current_user が nil の場合は “client”)に variant を割り当て、”sticky” にします。
  • { project: project }:variant を割り当て、閲覧中のプロジェクトに “sticky” になります。特定のユーザーがプロジェクトを見ているときよりも、プロジェクトを見ているときに実験をしたほうが便利な場合は、この方法を検討してください。
  • { group: group }:プロジェクトの例と似ていますが、より広い範囲のプロジェクトやユーザーに適用されます。
  • { actor: current_user, project: project }:variant を割り当て、指定されたプロジェクトを表示しているユーザーに “sticky” です。これはcurrent_user が見るプロジェクトごとに異なる variant 割り当ての可能性を作ります。アプリケーションのトラフィックの多い場所でこのような実験をすると、 キャッシュサイズが大きくなる可能性があることを理解しておいてください。
  • { wday: Time.current.wday }:現在の曜日に基づいて variant を割り当てます。この例では、金曜日には一貫して一つの variant を割り当て、 土曜日には異なる variant を割り当てる可能性があります。

コンテキストは実験をどのように定義し、レポーターするかにとって重要です。通常、実験をどのように実施するかで最も重要な点ですので、慎重に検討し、必要であればより広いチームと話し合ってください。また、選択したコンテキストがキャッシュサイズに影響することも考慮してください。

上記の例の後、私たちは一般的なケースを述べることができます:特定の一貫したコンテキストがあれば、私たちは一貫した経験を提供し、その経験のためのイベントを追跡することができます。実装の詳細をもう少し掘り下げると、提供されたコンテキストからコンテキストキーが生成されます。このコンテキストキーを使って

  • 割り当てられた variant を決定します。
  • そのコンテキストキーに対して追跡されたイベントを特定します。

これは、コンテキスト・キーによって指示され、追跡される、私たちがレンダリングした体験と考えることができます。コンテキスト・キーは、そのコンテキスト・キーに対してレンダリングしたエクスペリエンスのインタラクションと結果を追跡するために使用されます。これらの概念はやや抽象的で、最初は理解しにくいものですが、このアプローチによって、実験を単なるユーザー行動よりも幅広いものとして伝えることができます。

note
current_user が nil の場合、actor: を使用すると Cookie が使用されます。しかし、クッキーを必要としない場合、つまり、公開された機能が認証されたユーザーにしか見えない場合、{ user: current_user }
caution
variant の割り当てのキャッシュはこのコンテキストを使って行なわれるので、 実験を定義するときにはキャッシュサイズへの影響を考慮してください。もし{ time: Time.current } を使うと、実験が実行されるたびにキャッシュサイズが膨れ上がります。それだけでなく、あなたの実験は「スティッキー」にならず、イベントは解決できなくなります。

高度な実験

実験を行うには2つの方法があります:

  1. 前述した基本的な実験スタイル。
  2. より高度な実験クラスを提供するスタイル。

高度なスタイルは命名規則で処理され、Railsで期待されるものと同じように動作します。

ApplicationExperiment 、デフォルトをオーバーライドできるカスタム実験クラスを生成するには、Railsジェネレータを使います:

rails generate gitlab:experiment pill_color control red blue

これにより、app/experiments/pill_color_experiment.rb 、ジェネレータに提供した_ビヘイビアを_持つ実験クラスが生成されます。前回の例をマイグレーションした後のクラスの例を示します:

class PillColorExperiment < ApplicationExperiment
  control { 'control' }
  variant(:red) { 'red' }
  variant(:blue) { 'blue' }
end

run を明示的に呼び出すことで、最初に提供していたブロックを提供する代わりに、実験を実行する場所を次の呼び出しに単純化することができます:

experiment(:pill_color, actor: current_user).run

実験クラスで定義した_ビヘイビアは_デフォルトの実装です。しかし、ブロック構文を使ってこれらの_ビヘイビアを_オーバーライドすることもできます:

experiment(:pill_color, actor: current_user) do |e|
  e.control { '<strong>control</strong>' }
end
note
ブロックをexperiment メソッドに渡すと、run が呼び出されたかのように暗黙的に呼び出されます。

セグメンテーションルール

実行時のセグメンテーションルールを使って、インスタンスでコンテキストを特定のバリアント にセグメンテーションすることができます。segment メソッドは(before_action のような)コールバックなので、ブロック名やメソッド名を指定できます。

この例では、'Richard' という名前のユーザーには常に_赤の_variant が割り当てられ、2 週間以上前のアカウントには_青の_variant が割り当てられます:

class PillColorExperiment < ApplicationExperiment
  # ...registered behaviors

  segment(variant: :red) { context.actor.first_name == 'Richard' }
  segment :old_account?, variant: :blue

  private

  def old_account?
    context.actor.created_at < 2.weeks.ago
  end
end

実験が実行されると、セグメンテーションルールは定義された順番に実行されます。実験が実行されると、セグメンテーションルールは定義された順番に実行されます。

この例では、アカウントの年齢に関係なく、'Richard'という名前のユーザは常に_赤の_バリアントが割り当てられます。逆のロジックにしたい場合は、順番を逆にします。

note
セグメンテーションルールを定義するときに覚えておいてほしいこと: 真実の結果が出た後は、最適なパフォーマンスを得るために残りのセグメンテーションルールはスキップされます。

除外ルール

除外ルールはセグメンテーションルールと似ていますが、あるコンテキストを実験に含め、イベントを追跡する対象として考えるべきかどうかを判断するためのものです。除外とは、与えられたコンテキストに関連するイベントを気にしないということです。

これらの例では、'Richard' という名前のすべてのユーザーと、2週間以上前のアカウントを除外しています。このような場合、コントロール行動(何もない可能性もある)が与えられるだけでなく、イベントも追跡されません。

class PillColorExperiment < ApplicationExperiment
  # ...registered behaviors

  exclude :old_account?, ->{ context.actor.first_name == 'Richard' }

  private

  def old_account?
    context.actor.created_at < 2.weeks.ago
  end
end

また、should_track? を呼び出して、カスタムトラッキングロジックで除外をチェックする必要があるかもしれません:

class PillColorExperiment < ApplicationExperiment
  # ...registered behaviors

  def expensive_tracking_logic
    return unless should_track?

    track(:my_event, value: expensive_method_call)
  end
end

イベントのトラッキング

実験の最も重要な側面の1つは、データを収集し、それについてレポーターをすることです。track メソッドを使用すると、実験の実装全体でイベントを追跡することができます。実験への呼び出しの間に同じコンテキストを提供すれば、実験に対して一貫してイベントを追跡することができます。コンテキストを理解していない場合は、今すぐコンテキストについて読むべきです。

実験は1箇所か数箇所で実行し、イベントは多くの箇所で追跡すると仮定します。トラッキングの呼び出しはそのままで、snowplow を使ってイベントをトラッキングする際に通常使用する引数を使用します。Rubyでイベントを追跡する最も簡単な例は次のようになります:

experiment(:pill_color, actor: current_user).track(:clicked)

これまでの例で実験を実行すると、デフォルトで:assignment イベントが自動的に追跡されます。実験から追跡されるすべてのイベントには、特別な実験コンテキストが追加されます。このコンテキストは、通常データチームによって使用され、指定されたイベント間の関連を作成します。

:assignment もしユーザーが実験(実験が行われる場所という意味)に遭遇していなくて、イベントを追跡した場合、そのユーザーには variant が割り当てられ、後で実験に遭遇したときにその variant を見ることになります。

note
GitLabはトラッキングに関して敏感であり、お客様を尊重するよう心がけています。そのため、私たちの実験ライブラリでは、識別IDをトラッキングすることなく実験を実施することができます。しかし、実験のレポーターの要件によっては、常にそうできるとは限りません。実験において特定のレコードIDを追跡するよう求められることがあります。どのようなアプローチをとるかは、実験を実施するPMやエンジニア次第です。現時点では、推奨事項はありません。

クライアントレイヤーでの実験

リクエストライフサイクルで実行されたすべての実験は、window.gl.experimentsこのスキーマにマッチするので、クライアントレイヤーで実験を解決するときに使うことができます。

実験用のクラスを定義し、そのバリアントを定義した場合、いくつかの方法でその実験を公開することができます。

最初の方法は実験を実行することです。実験が実行されると、特別なことをしなくてもクライアントレイヤーに表示されます。

つ目の方法は、実験を実行せず、クライアントレイヤーのみで表示させる方法です。そのためには、.publish 。これはロジックを実行しませんが、実験の詳細をクライアントレイヤーに表示させます。

例えば、コントローラのbefore_action 。上記のようにPillColorExperiment クラスを定義しておくと、実験を実行する代わりにパブリッシュすることで、クライアントに実験を見せることができます:

before_action -> { experiment(:pill_color).publish }, only: [:show]

JavaScriptのコンソールでこのサーフェスを見ることができます:

window.gl.experiments // => { pill_color: { excluded: false, experiment: "pill_color", key: "ca63ac02", variant: "candidate" } }

Vue での実験の使用

gitlab-experiment コンポーネントを使うと、window.gl.experiments にプッシュされたバリアントの名前にマッチするスロットを定義できます。

で定義されたビヘイビアにマッチするVueコンポーネントの名前付きスロットを利用できます:

<script>
import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';

export default {
  components: { GitlabExperiment }
}
</script>

<template>
  <gitlab-experiment name="pill_color">
    <template #control>
      <button class="bg-default">Click default button</button>
    </template>

    <template #red>
      <button class="bg-red">Click red button</button>
    </template>

    <template #blue>
      <button class="bg-blue">Click blue button</button>
    </template>
  </gitlab-experiment>
</template>
note
指定された実験名のwindow.gl.experiments オブジェクトに実験データがない場合、window.gl.experiments スロットがあれば、それを使用します。