InfoGrab DocsInfoGrab Docs

A/B/n 실험 구현하기

요약

먼저 개발용 기능 플래그를 생성할 때 사용하는 bin/feature-flag 명령을 이용해 기능 플래그를 생성하되, 타입에 experiment를 지정해야 합니다. 원하는 기능 플래그를 생성한 후에는 코드에서 바로 실험을 구현할 수 있습니다.

실험 구현하기#

예시

먼저 개발용 기능 플래그를 생성할 때 사용하는 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 객체를 다음과 같은 세부 정보와 함께 사용할 수 있습니다:

  • 할당된 변형.

  • 클라이언트 추적 이벤트를 위한 컨텍스트 키.

또한 실험이 실행되면 실험 :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))

GitLab은 gitlab-experiment gem의 퍼센트 롤아웃 전략을 확장하여 커스텀 롤아웃 메커니즘을 구현합니다.

기능 플래그가 비활성화된 경우, 실험은 전혀 실행되지 않습니다. 사용자는 변형 할당이나 추적 없이 기본 코드 경로를 따르게 됩니다.

기능 플래그가 활성화된 경우, 설정된 퍼센트가 비제어 변형에 배분되며, 나머지 퍼센트는 제어(control)에 할당됩니다. 사용자는 변형에 적극적으로 할당되어 실험 참여자로 추적됩니다.

환경에서 실험 기능 플래그를 롤아웃하려면 ChatOps를 사용하여 다음 명령을 실행하세요(이에 대한 자세한 내용은 GitLab 개발의 기능 플래그 문서에서 다룹니다). 이 명령은 실험을 접하는 사람의 절반이 control에, 25%는 red 변형에, 25%는 blue 변형에 할당되는 시나리오를 만듭니다:

/chatops gitlab run feature set pill_color 50 --actors

이 예시에서 균등한 배분을 위해서는 명령을 50% 대신 66%로 변경하세요.

실험을 즉시 중지하려면 /chatops gitlab run feature set pill_color false 명령을 사용하세요.

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인 경우 "클라이언트")에게 "고정"됩니다.

  • { project: project }: 변형을 할당하며 현재 보고 있는 프로젝트에 "고정"됩니다. 특정 사용자가 어떤 프로젝트를 보는지보다 프로젝트를 볼 때 실험을 실행하는 것이 더 유용한 경우 이 방식을 고려하세요.

  • { group: group }: 프로젝트 예시와 유사하지만 더 넓은 범위의 프로젝트와 사용자에게 적용됩니다.

  • { actor: current_user, project: project }: 변형을 할당하며 지정된 프로젝트를 보고 있는 사용자에게 "고정"됩니다. 이는 current_user가 보는 모든 프로젝트에 대해 서로 다른 변형 할당 가능성을 생성합니다. 애플리케이션의 트래픽이 많은 부분에 이러한 실험이 있으면 캐시 크기가 커질 수 있음을 유의하세요.

  • { wday: Time.current.wday }: 현재 요일에 따라 변형을 할당합니다. 이 예시에서는 금요일에 일관되게 하나의 변형을, 토요일에는 잠재적으로 다른 변형을 할당합니다.

컨텍스트는 실험을 정의하고 보고하는 방식에 매우 중요합니다. 실험 구현 방식을 선택할 때 일반적으로 가장 중요한 측면이므로 신중하게 고려하고, 필요한 경우 더 넓은 팀과 논의하세요. 또한 선택한 컨텍스트가 캐시 크기에 영향을 미친다는 점도 고려하세요.

위의 예시들을 통해 일반적인 경우를 설명할 수 있습니다: 특정하고 일관된 컨텍스트가 주어지면, 일관된 경험을 제공하고 해당 경험에 대한 이벤트를 추적할 수 있습니다. 구현 세부 사항을 좀 더 깊이 살펴보면: 제공된 컨텍스트로부터 컨텍스트 키가 생성됩니다. 이 컨텍스트 키를 사용하여:

  • 할당된 변형을 결정합니다.

  • 해당 컨텍스트 키에 대해 추적된 이벤트를 식별합니다.

우리가 렌더링한 경험으로 생각할 수 있으며, 이는 컨텍스트 키에 의해 좌우되고 추적됩니다. 컨텍스트 키는 해당 컨텍스트 키에 렌더링한 경험의 상호작용과 결과를 추적하는 데 사용됩니다. 이러한 개념들은 다소 추상적이어서 처음에는 이해하기 어렵지만, 이 접근 방식을 통해 단순한 사용자 행동을 넘어서는 더 넓은 개념으로 실험을 다룰 수 있습니다.

actor:를 사용하면 current_user가 nil인 경우 쿠키를 사용합니다. 그러나 쿠키가 필요하지 않은 경우 - 즉, 노출된 기능이 인증된 사용자에게만 표시되는 경우 - { user: current_user }도 마찬가지로 효과적입니다.

변형 할당의 캐싱은 이 컨텍스트를 사용하여 수행되므로, 실험을 정의할 때 캐시 크기에 미치는 영향을 고려하세요. { time: Time.current }를 사용하면 실험이 실행될 때마다 캐시 크기가 증가합니다. 뿐만 아니라 실험이 "고정"되지 않아 이벤트를 해석할 수 없게 됩니다.

고급 실험#

실험을 구현하는 방법에는 두 가지가 있습니다:

  • 앞서 설명한 기본 실험 스타일.

  • 실험 클래스를 제공하는 고급 스타일.

고급 스타일은 명명 규칙으로 처리되며, Rails에서 기대하는 방식과 유사하게 동작합니다.

ApplicationExperiment의 기본값을 재정의할 수 있는 커스텀 실험 클래스를 생성하려면 Rails 제너레이터를 사용하세요:

rails generate gitlab:experiment pill_color control red blue

이 명령은 제너레이터에 제공한 behaviors와 함께 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

실험 클래스에서 정의한 behaviors는 기본 구현을 나타냅니다. 블록 구문을 사용하여 이러한 behaviors를 재정의할 수도 있으므로, 다음과 같은 코드도 유효합니다:

experiment(:pill_color, actor: current_user) do |e|
  e.control { '<strong>control</strong>' }
end

experiment 메서드에 블록을 전달하면 run이 호출된 것처럼 암묵적으로 실행됩니다.

세분화 규칙#

런타임 세분화 규칙을 사용하여 컨텍스트를 특정 변형으로 분류할 수 있습니다. segment 메서드는 (before_action과 같은) 콜백이므로 블록이나 메서드 이름을 제공할 수 있습니다.

이 예시에서 이름이 'Richard'인 모든 사용자는 항상 red 변형에, 2주 이상된 계정은 blue 변형에 할당됩니다:

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

실험이 실행되면 세분화 규칙이 정의된 순서대로 실행됩니다. 첫 번째로 참(truthy) 결과를 반환하는 세분화 규칙이 변형을 할당합니다.

이 예시에서 이름이 'Richard'인 사용자는 계정 나이에 관계없이 항상 red 변형에 할당됩니다. 반대 로직을 원한다면 순서를 바꾸면 됩니다.

세분화 규칙을 정의할 때 유의하세요: 참 결과가 나오면 최적의 성능을 위해 나머지 세분화 규칙은 건너뜁니다.

제외 규칙#

제외 규칙은 세분화 규칙과 유사하지만, 컨텍스트를 실험에 포함하고 이벤트를 추적할 대상으로 고려해야 하는지 여부를 결정하는 데 사용됩니다. 제외는 주어진 컨텍스트와 관련된 이벤트에 관심이 없다는 것을 의미합니다.

다음 예시는 이름이 'Richard'인 모든 사용자와 2주 이상된 계정을 제외합니다. 이들은 아무것도 없을 수 있는 제어(control) behavior를 받을 뿐만 아니라, 이 경우에는 이벤트도 추적되지 않습니다.

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

이벤트 추적#

실험에서 가장 중요한 측면 중 하나는 데이터를 수집하고 보고하는 것입니다. track 메서드를 사용하여 실험 구현 전반에 걸쳐 이벤트를 추적할 수 있습니다. 실험에 동일한 컨텍스트를 제공하면 실험에 일관되게 이벤트를 추적할 수 있습니다. 컨텍스트를 이해하지 못했다면 지금 컨텍스트에 대해 읽어보세요.

실험은 한두 곳에서 실행하지만, 이벤트는 여러 곳에서 추적할 수 있다고 가정할 수 있습니다. 추적 호출은 Snowplow를 사용하여 이벤트를 추적할 때 보통 사용하는 인수와 함께 동일하게 유지됩니다. Ruby에서 이벤트를 추적하는 가장 간단한 예시는 다음과 같습니다:

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

지금까지의 예시로 실험을 실행하면 기본적으로 :assignment 이벤트가 자동으로 추적됩니다. 실험에서 추적된 모든 이벤트에는 특별한 실험 컨텍스트가 추가됩니다. 이를 통해 - 일반적으로 데이터 팀이 - 주어진 실험의 이벤트들 간의 연결을 생성할 수 있습니다.

사용자가 실험을 아직 접하지 않은 경우(즉, 실험이 실행되는 위치를 방문하지 않은 경우)에도 이벤트를 추적하면, 변형이 할당됩니다. 이 사용자가 나중에 실험을 접하게 되면 해당 변형이 표시되며, 그때 :assignment 이벤트가 추적됩니다.

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에 푸시된 변형 이름과 일치하는 슬롯을 정의할 수 있습니다.

다음과 같이 정의된 behaviors와 일치하는 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>

주어진 실험 이름에 대해 window.gl.experiments 객체에 실험 데이터가 없는 경우, control 슬롯이 있으면 해당 슬롯이 사용됩니다.

추적 믹스인으로 추적하기#

Tracking.mixin을 사용하여 실험 컨텍스트를 자동으로 포함하는 track 메서드를 Vue 컴포넌트에 추가할 수 있습니다. 컴포넌트에서 this.track()을 호출하면 올바른 실험 컨텍스트와 함께 이벤트가 발생합니다.

<script>
import Tracking from '~/tracking';

export default {
  mixins: [Tracking.mixin({ experiment: 'pill_color' })],
  mounted() {
    this.track('show_form', {
      label: 'pill_color_form',
    });
  },
};
</script>

<template>
  <form>
    <!-- form content -->
  </form>
</template>

컴포넌트가 마운트되면 track 호출이 pill_color 실험 컨텍스트를 포함한 show_form 이벤트를 발생시키며, 데이터 팀은 이를 사용하여 실험 할당과 상호작용 이벤트를 연결할 수 있습니다.

실험 개발 모범 사례#

스테이징 전에 로컬에서 이벤트 구조 검증하기#

이벤트 구조 검증은 스테이징이나 프로덕션이 아닌 로컬 개발 중에 이루어져야 합니다. 스테이징과 프로덕션에서는 이벤트가 예상대로 Snowplow에 수신되는지만 확인하세요.

Snowplow Micro를 사용하여 로컬에서 이벤트 구조를 검증하세요:

  • 로컬 GDK 환경에서 Snowplow Micro를 설정하세요.

  • 각 변형(control 및 candidate)에 대해 실험 플로를 통해 모든 추적 이벤트를 트리거하세요.

  • 올바른 이벤트 구조의 증거로 원시 Snowplow Micro 출력을 실험 롤아웃 이슈에 붙여넣으세요.

실험이 스테이징에 도달할 때쯤에는 이벤트 구조가 이미 검증되어 있어야 합니다. 스테이징과 프로덕션 검증은 이벤트가 파이프라인을 통해 흐르고 Tableau 대시보드에 나타나는지 확인하는 데만 집중합니다.

다중 페이지 실험#

실험이 여러 페이지에서 실행되는 경우, 부분 롤아웃으로 모든 페이지를 방문하여 동일한 변형이 유지되는지 확인해야 합니다:

Feature.enable_percentage_of_actors(:my_experiment, 50)

보조 실험 블록에 only_assigned 사용하기#

실험의 진입점이 아닌 실험 블록은 only_assigned 옵션을 사용해야 합니다. 이는 사용자가 실험 플로의 중간에 진입하는 것을 방지합니다.

예를 들어, 실험이 등록 페이지에서 시작되어 환영 페이지로 이어지는 경우, 환영 페이지 실험 블록은 only_assigned: true를 사용해야 합니다:

experiment(:my_experiment, actor: current_user, only_assigned: true).track(:render_welcome)

only_assigned 없이는 등록 페이지를 우회한 사용자가 환영 페이지에서 변형에 할당될 수 있어 일관성 없는 경험이 발생할 수 있습니다.

스테이징에서 수동 테스트#

스테이징에서 수동으로 실험을 테스트할 때, 100% 롤아웃은 candidate 또는 control 세션을 찾을 필요성을 없애줍니다. 그러나 전체 롤아웃은 제외 버그나 일관성 없는 컨텍스트로 인해 사용자가 실험에 예기치 않게 참여하거나 이탈하는 문제를 숨길 수도 있습니다.

이러한 문제를 발견하려면 낮은 롤아웃 퍼센트에서 테스트하고, 전체 실험 라이프사이클에 걸쳐 사용자가 할당된 변형에 유지되는지 확인하세요.

다중 페이지 실험을 위한 기능 스펙 작성#

단위 테스트는 여러 실험 블록에 걸쳐 있는 실험의 문제를 잡지 못할 수 있습니다. 실험 컨텍스트(특히 등록 중인 사용자)가 블록 간에 변경되어 재세분화가 발생할 수 있기 때문입니다. 여러 페이지에 걸친 실험의 경우 전체 실험 라이프스팬을 포괄하는 기능 스펙을 작성하세요.

:experiment_tracking RSpec 메타데이터를 사용하여 추적 이벤트가 발생하는지 확인하고 예상되는 세분화 횟수를 확인하세요. 대부분의 실험에는 experiment_tracking: 1을 전달하세요.

experiment_tracking 메타데이터는 추적 이벤트를 감시합니다. 사용자 생성 중에는 쿠키 값으로) 생성 전과 생성 후(사용자 모델로) 두 번 할당이 발생합니다. 이러한 경우에는 experiment_tracking: 2를 전달하세요.

context 'when candidate experience', experiment_tracking: 1 do
  before do
    stub_feature_flags(my_experiment: true)
    stub_application_setting_enum('email_confirmation_setting', 'hard')
  end

  it 'completes the experiment flow' do
    # test contents

    is_expected.to have_tracked_experiment(:my_experiment, [
      :assignment,
      :completed_trial_form,
      :completed_identity_verification,
      :render_welcome,
      { action: :completed_group_project_creation, namespace: namespace },
      :render_get_started
    ])
  end
end

A/B/n 실험 구현하기

GitLab v19.1
원문 보기
요약

먼저 개발용 기능 플래그를 생성할 때 사용하는 bin/feature-flag 명령을 이용해 기능 플래그를 생성하되, 타입에 experiment를 지정해야 합니다. 원하는 기능 플래그를 생성한 후에는 코드에서 바로 실험을 구현할 수 있습니다.

실험 구현하기#

예시

먼저 개발용 기능 플래그를 생성할 때 사용하는 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 객체를 다음과 같은 세부 정보와 함께 사용할 수 있습니다:

  • 할당된 변형.

  • 클라이언트 추적 이벤트를 위한 컨텍스트 키.

또한 실험이 실행되면 실험 :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))

GitLab은 gitlab-experiment gem의 퍼센트 롤아웃 전략을 확장하여 커스텀 롤아웃 메커니즘을 구현합니다.

기능 플래그가 비활성화된 경우, 실험은 전혀 실행되지 않습니다. 사용자는 변형 할당이나 추적 없이 기본 코드 경로를 따르게 됩니다.

기능 플래그가 활성화된 경우, 설정된 퍼센트가 비제어 변형에 배분되며, 나머지 퍼센트는 제어(control)에 할당됩니다. 사용자는 변형에 적극적으로 할당되어 실험 참여자로 추적됩니다.

환경에서 실험 기능 플래그를 롤아웃하려면 ChatOps를 사용하여 다음 명령을 실행하세요(이에 대한 자세한 내용은 GitLab 개발의 기능 플래그 문서에서 다룹니다). 이 명령은 실험을 접하는 사람의 절반이 control에, 25%는 red 변형에, 25%는 blue 변형에 할당되는 시나리오를 만듭니다:

/chatops gitlab run feature set pill_color 50 --actors

이 예시에서 균등한 배분을 위해서는 명령을 50% 대신 66%로 변경하세요.

실험을 즉시 중지하려면 /chatops gitlab run feature set pill_color false 명령을 사용하세요.

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인 경우 "클라이언트")에게 "고정"됩니다.

  • { project: project }: 변형을 할당하며 현재 보고 있는 프로젝트에 "고정"됩니다. 특정 사용자가 어떤 프로젝트를 보는지보다 프로젝트를 볼 때 실험을 실행하는 것이 더 유용한 경우 이 방식을 고려하세요.

  • { group: group }: 프로젝트 예시와 유사하지만 더 넓은 범위의 프로젝트와 사용자에게 적용됩니다.

  • { actor: current_user, project: project }: 변형을 할당하며 지정된 프로젝트를 보고 있는 사용자에게 "고정"됩니다. 이는 current_user가 보는 모든 프로젝트에 대해 서로 다른 변형 할당 가능성을 생성합니다. 애플리케이션의 트래픽이 많은 부분에 이러한 실험이 있으면 캐시 크기가 커질 수 있음을 유의하세요.

  • { wday: Time.current.wday }: 현재 요일에 따라 변형을 할당합니다. 이 예시에서는 금요일에 일관되게 하나의 변형을, 토요일에는 잠재적으로 다른 변형을 할당합니다.

컨텍스트는 실험을 정의하고 보고하는 방식에 매우 중요합니다. 실험 구현 방식을 선택할 때 일반적으로 가장 중요한 측면이므로 신중하게 고려하고, 필요한 경우 더 넓은 팀과 논의하세요. 또한 선택한 컨텍스트가 캐시 크기에 영향을 미친다는 점도 고려하세요.

위의 예시들을 통해 일반적인 경우를 설명할 수 있습니다: 특정하고 일관된 컨텍스트가 주어지면, 일관된 경험을 제공하고 해당 경험에 대한 이벤트를 추적할 수 있습니다. 구현 세부 사항을 좀 더 깊이 살펴보면: 제공된 컨텍스트로부터 컨텍스트 키가 생성됩니다. 이 컨텍스트 키를 사용하여:

  • 할당된 변형을 결정합니다.

  • 해당 컨텍스트 키에 대해 추적된 이벤트를 식별합니다.

우리가 렌더링한 경험으로 생각할 수 있으며, 이는 컨텍스트 키에 의해 좌우되고 추적됩니다. 컨텍스트 키는 해당 컨텍스트 키에 렌더링한 경험의 상호작용과 결과를 추적하는 데 사용됩니다. 이러한 개념들은 다소 추상적이어서 처음에는 이해하기 어렵지만, 이 접근 방식을 통해 단순한 사용자 행동을 넘어서는 더 넓은 개념으로 실험을 다룰 수 있습니다.

actor:를 사용하면 current_user가 nil인 경우 쿠키를 사용합니다. 그러나 쿠키가 필요하지 않은 경우 - 즉, 노출된 기능이 인증된 사용자에게만 표시되는 경우 - { user: current_user }도 마찬가지로 효과적입니다.

변형 할당의 캐싱은 이 컨텍스트를 사용하여 수행되므로, 실험을 정의할 때 캐시 크기에 미치는 영향을 고려하세요. { time: Time.current }를 사용하면 실험이 실행될 때마다 캐시 크기가 증가합니다. 뿐만 아니라 실험이 "고정"되지 않아 이벤트를 해석할 수 없게 됩니다.

고급 실험#

실험을 구현하는 방법에는 두 가지가 있습니다:

  • 앞서 설명한 기본 실험 스타일.

  • 실험 클래스를 제공하는 고급 스타일.

고급 스타일은 명명 규칙으로 처리되며, Rails에서 기대하는 방식과 유사하게 동작합니다.

ApplicationExperiment의 기본값을 재정의할 수 있는 커스텀 실험 클래스를 생성하려면 Rails 제너레이터를 사용하세요:

rails generate gitlab:experiment pill_color control red blue

이 명령은 제너레이터에 제공한 behaviors와 함께 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

실험 클래스에서 정의한 behaviors는 기본 구현을 나타냅니다. 블록 구문을 사용하여 이러한 behaviors를 재정의할 수도 있으므로, 다음과 같은 코드도 유효합니다:

experiment(:pill_color, actor: current_user) do |e|
  e.control { '<strong>control</strong>' }
end

experiment 메서드에 블록을 전달하면 run이 호출된 것처럼 암묵적으로 실행됩니다.

세분화 규칙#

런타임 세분화 규칙을 사용하여 컨텍스트를 특정 변형으로 분류할 수 있습니다. segment 메서드는 (before_action과 같은) 콜백이므로 블록이나 메서드 이름을 제공할 수 있습니다.

이 예시에서 이름이 'Richard'인 모든 사용자는 항상 red 변형에, 2주 이상된 계정은 blue 변형에 할당됩니다:

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

실험이 실행되면 세분화 규칙이 정의된 순서대로 실행됩니다. 첫 번째로 참(truthy) 결과를 반환하는 세분화 규칙이 변형을 할당합니다.

이 예시에서 이름이 'Richard'인 사용자는 계정 나이에 관계없이 항상 red 변형에 할당됩니다. 반대 로직을 원한다면 순서를 바꾸면 됩니다.

세분화 규칙을 정의할 때 유의하세요: 참 결과가 나오면 최적의 성능을 위해 나머지 세분화 규칙은 건너뜁니다.

제외 규칙#

제외 규칙은 세분화 규칙과 유사하지만, 컨텍스트를 실험에 포함하고 이벤트를 추적할 대상으로 고려해야 하는지 여부를 결정하는 데 사용됩니다. 제외는 주어진 컨텍스트와 관련된 이벤트에 관심이 없다는 것을 의미합니다.

다음 예시는 이름이 'Richard'인 모든 사용자와 2주 이상된 계정을 제외합니다. 이들은 아무것도 없을 수 있는 제어(control) behavior를 받을 뿐만 아니라, 이 경우에는 이벤트도 추적되지 않습니다.

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

이벤트 추적#

실험에서 가장 중요한 측면 중 하나는 데이터를 수집하고 보고하는 것입니다. track 메서드를 사용하여 실험 구현 전반에 걸쳐 이벤트를 추적할 수 있습니다. 실험에 동일한 컨텍스트를 제공하면 실험에 일관되게 이벤트를 추적할 수 있습니다. 컨텍스트를 이해하지 못했다면 지금 컨텍스트에 대해 읽어보세요.

실험은 한두 곳에서 실행하지만, 이벤트는 여러 곳에서 추적할 수 있다고 가정할 수 있습니다. 추적 호출은 Snowplow를 사용하여 이벤트를 추적할 때 보통 사용하는 인수와 함께 동일하게 유지됩니다. Ruby에서 이벤트를 추적하는 가장 간단한 예시는 다음과 같습니다:

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

지금까지의 예시로 실험을 실행하면 기본적으로 :assignment 이벤트가 자동으로 추적됩니다. 실험에서 추적된 모든 이벤트에는 특별한 실험 컨텍스트가 추가됩니다. 이를 통해 - 일반적으로 데이터 팀이 - 주어진 실험의 이벤트들 간의 연결을 생성할 수 있습니다.

사용자가 실험을 아직 접하지 않은 경우(즉, 실험이 실행되는 위치를 방문하지 않은 경우)에도 이벤트를 추적하면, 변형이 할당됩니다. 이 사용자가 나중에 실험을 접하게 되면 해당 변형이 표시되며, 그때 :assignment 이벤트가 추적됩니다.

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에 푸시된 변형 이름과 일치하는 슬롯을 정의할 수 있습니다.

다음과 같이 정의된 behaviors와 일치하는 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>

주어진 실험 이름에 대해 window.gl.experiments 객체에 실험 데이터가 없는 경우, control 슬롯이 있으면 해당 슬롯이 사용됩니다.

추적 믹스인으로 추적하기#

Tracking.mixin을 사용하여 실험 컨텍스트를 자동으로 포함하는 track 메서드를 Vue 컴포넌트에 추가할 수 있습니다. 컴포넌트에서 this.track()을 호출하면 올바른 실험 컨텍스트와 함께 이벤트가 발생합니다.

<script>
import Tracking from '~/tracking';

export default {
  mixins: [Tracking.mixin({ experiment: 'pill_color' })],
  mounted() {
    this.track('show_form', {
      label: 'pill_color_form',
    });
  },
};
</script>

<template>
  <form>
    <!-- form content -->
  </form>
</template>

컴포넌트가 마운트되면 track 호출이 pill_color 실험 컨텍스트를 포함한 show_form 이벤트를 발생시키며, 데이터 팀은 이를 사용하여 실험 할당과 상호작용 이벤트를 연결할 수 있습니다.

실험 개발 모범 사례#

스테이징 전에 로컬에서 이벤트 구조 검증하기#

이벤트 구조 검증은 스테이징이나 프로덕션이 아닌 로컬 개발 중에 이루어져야 합니다. 스테이징과 프로덕션에서는 이벤트가 예상대로 Snowplow에 수신되는지만 확인하세요.

Snowplow Micro를 사용하여 로컬에서 이벤트 구조를 검증하세요:

  • 로컬 GDK 환경에서 Snowplow Micro를 설정하세요.

  • 각 변형(control 및 candidate)에 대해 실험 플로를 통해 모든 추적 이벤트를 트리거하세요.

  • 올바른 이벤트 구조의 증거로 원시 Snowplow Micro 출력을 실험 롤아웃 이슈에 붙여넣으세요.

실험이 스테이징에 도달할 때쯤에는 이벤트 구조가 이미 검증되어 있어야 합니다. 스테이징과 프로덕션 검증은 이벤트가 파이프라인을 통해 흐르고 Tableau 대시보드에 나타나는지 확인하는 데만 집중합니다.

다중 페이지 실험#

실험이 여러 페이지에서 실행되는 경우, 부분 롤아웃으로 모든 페이지를 방문하여 동일한 변형이 유지되는지 확인해야 합니다:

Feature.enable_percentage_of_actors(:my_experiment, 50)

보조 실험 블록에 only_assigned 사용하기#

실험의 진입점이 아닌 실험 블록은 only_assigned 옵션을 사용해야 합니다. 이는 사용자가 실험 플로의 중간에 진입하는 것을 방지합니다.

예를 들어, 실험이 등록 페이지에서 시작되어 환영 페이지로 이어지는 경우, 환영 페이지 실험 블록은 only_assigned: true를 사용해야 합니다:

experiment(:my_experiment, actor: current_user, only_assigned: true).track(:render_welcome)

only_assigned 없이는 등록 페이지를 우회한 사용자가 환영 페이지에서 변형에 할당될 수 있어 일관성 없는 경험이 발생할 수 있습니다.

스테이징에서 수동 테스트#

스테이징에서 수동으로 실험을 테스트할 때, 100% 롤아웃은 candidate 또는 control 세션을 찾을 필요성을 없애줍니다. 그러나 전체 롤아웃은 제외 버그나 일관성 없는 컨텍스트로 인해 사용자가 실험에 예기치 않게 참여하거나 이탈하는 문제를 숨길 수도 있습니다.

이러한 문제를 발견하려면 낮은 롤아웃 퍼센트에서 테스트하고, 전체 실험 라이프사이클에 걸쳐 사용자가 할당된 변형에 유지되는지 확인하세요.

다중 페이지 실험을 위한 기능 스펙 작성#

단위 테스트는 여러 실험 블록에 걸쳐 있는 실험의 문제를 잡지 못할 수 있습니다. 실험 컨텍스트(특히 등록 중인 사용자)가 블록 간에 변경되어 재세분화가 발생할 수 있기 때문입니다. 여러 페이지에 걸친 실험의 경우 전체 실험 라이프스팬을 포괄하는 기능 스펙을 작성하세요.

:experiment_tracking RSpec 메타데이터를 사용하여 추적 이벤트가 발생하는지 확인하고 예상되는 세분화 횟수를 확인하세요. 대부분의 실험에는 experiment_tracking: 1을 전달하세요.

experiment_tracking 메타데이터는 추적 이벤트를 감시합니다. 사용자 생성 중에는 쿠키 값으로) 생성 전과 생성 후(사용자 모델로) 두 번 할당이 발생합니다. 이러한 경우에는 experiment_tracking: 2를 전달하세요.

context 'when candidate experience', experiment_tracking: 1 do
  before do
    stub_feature_flags(my_experiment: true)
    stub_application_setting_enum('email_confirmation_setting', 'hard')
  end

  it 'completes the experiment flow' do
    # test contents

    is_expected.to have_tracked_experiment(:my_experiment, [
      :assignment,
      :completed_trial_form,
      :completed_identity_verification,
      :render_welcome,
      { action: :completed_group_project_creation, namespace: namespace },
      :render_get_started
    ])
  end
end