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 객체가 다음과 같은 세부 정보와 함께 사용할 수 있습니다.

  • 할당된 변형.
  • 클라이언트 추적 이벤트에 대한 컨텍스트 키.

또한, 실험이 실행되면 실험 :assignment를 위해 이벤트가 추적됩니다. 이후 이벤트, 추적 및 클라이언트 계층에 대해 더 다루겠습니다.

로컬 개발 환경에서는 피처 플래그 인터페이스를 사용하여 실험을 활성화할 수 있습니다. 또한 피처 플래그를 활성화하는 호출에 해당 실험을 제공하여 특정 케이스를 대상으로 할 수 있습니다.

# 모든 사용자에게 활성화
Feature.enable(:pill_color)

# `experiment` 메소드 가져오기 - 이미 컨트롤러, 뷰 및 메일러에 사용 가능합니다.
include Gitlab::Experiment::Dsl
# 처음 사용자에게만 활성화
Feature.enable(:pill_color, experiment(:pill_color, actor: User.first))

환경에서 실험 피처 플래그를 롤아웃하려면 ChatOps를 사용하여 다음 명령을 실행하세요(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 플래그를 사용하는 것을 강력히 권장합니다. 그렇지 않으면 변형 할당 캐싱 처리 방식으로 인해 이상한 동작이 발생할 수 있습니다.

이 실험을 HAML 파일에 HTML 래핑을 사용하여 구현할 수도 있습니다.

#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이면 “클라이언트”)에 “스티키(sticky)”로 지정됩니다.
  • { project: project }: 보는 프로젝트에 변형을 할당하고 “스티키(sticky)”로 지정됩니다. 특정 사용자가 아니라 프로젝트를 보는 경우에 실험 실행이 더 유용하면 이 접근 방식을 고려하세요.
  • { group: group }: 프로젝트 예시와 유사하지만 더 넓은 범위의 프로젝트 및 사용자에 적용됩니다.
  • { actor: current_user, project: project }: 지정된 프로젝트를 보고 있는 사용자에게 변형을 할당하고 “스티키(sticky)”로 지정됩니다. 이렇게 하면 current_user가 본 모든 프로젝트에 대해 다른 변형 할당 가능성이 만들어집니다. 이 경우 캐시 크기가 크게 증가할 수 있음을 이해하세요.
  • { wday: Time.current.wday }: 현재 요일에 따라 변형을 할당합니다. 이 예시에서는 금요일에 항상 한 가지 변형을 할당하고 토요일에는 잠재적으로 다른 변형을 할당합니다.

컨텍스트는 실험을 정의하고 보고하는 방식에 중요합니다. 실험을 구현하는 방식을 신중히 고려하고 필요한 경우 전체 팀과 논의하세요. 또한 선택한 컨텍스트가 캐시 크기에 어떤 영향을 미치는지 고려하세요.

위의 예시들을 통해 일반적인 경우를 설명할 수 있습니다: 특정하고 일관된 컨텍스트가 주어지면 해당 경험에 대해 일관된 경험을 제공하고 해당 경험에 대해 이벤트를 추적할 수 있습니다. 구현 세부 정보에 조금 더 자세히 들어가면: 제공된 컨텍스트에서 컨텍스트 키가 생성됩니다. 이 컨텍스트 키를 사용하여 다음을 수행하세요.

  • 할당된 변형 결정.
  • 해당 컨텍스트 키에 대해 추적된 이벤트 식별.

우리는 이를 컨텍스트 키에 의해 추적된 상호 작용 및 결과를 뜻하는 경험이라고 생각할 수 있습니다. 컨텍스트 키는 상호 작용 및 경험 결과를 추적하는 데 사용됩니다. 이러한 개념은 초반에는 다소 추상적이고 이해하기 어려울 수 있지만, 이 접근 방식은 사용자 행동 이상으로 대화할 수 있도록 우리에게 넓은 것으로 실험에 대해 의사 소통할 수 있도록 만들어줍니다.

note
actor:를 사용하면 current_user가 nil이면 쿠키를 사용합니다. 그러나 쿠키가 필요하지 않은 경우 - 즉 노출된 기능이 인증된 사용자에게만 표시된다는 의미 - { user: current_user }도 동일한 효과를 낼 수 있습니다.
caution
변형 할당 캐싱은 이 컨텍스트를 사용하여 수행되므로 실험을 정의할 때 캐시 크기에 미치는 영향을 고려하세요. { time: Time.current }를 사용하면 매번 실험이 실행될 때마다 캐시 크기가 증가합니다. 또한 실험은 “스티키(sticky)”하지 않으며 이벤트가 해결되지 않을 수 있습니다.

고급 실험

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

  1. 이전에 설명한 기본 실험 스타일.
  2. 실험 클래스가 제공되는 더 고급 스타일.

고급 스타일은 명명 규칙에 의해 처리되며 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
note
experiment 메소드에 블록을 전달할 때 run이 호출된 것처럼 암시적으로 호출됩니다.

세분화 규칙

실행 시 세분화 규칙을 사용하여 예를 들어 컨텍스트를 특정 변형으로 세분화할 수 있습니다. segment 메서드는 before_action과 같은 콜백이므로 블록 또는 메서드 이름을 제공할 수 있습니다.

이 예에서 'Richard'라는 사용자는 항상 빨간 변형에 할당되고, 2주 이상 된 계정은 파란 변형에 할당됩니다.

class PillColorExperiment < ApplicationExperiment
  # ...등록된 동작들
  
  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'라는 사용자는 항상 빨간 변형에 할당됩니다. 반대로 로직을 바꾸려면 순서를 변경하세요.

note
세분화 규칙을 정의할 때 유의해야 할 점은 truthy 결과 후, 최적의 성능을 위해 나머지 세분화 규칙이 건너뜁니다.

제외 규칙

제외 규칙은 세분화 규칙과 유사하지만, 컨텍스트가 실험에 대해 검토하고 추적해야 하는지를 결정하는 것을 목적으로 합니다. 제외는 주어진 컨텍스트와 관련된 이벤트에 신경 쓰지 않는다는 것을 의미합니다.

이러한 예제는 'Richard'라는 모든 사용자 및 2주 이상 된 계정을 제외합니다. 이들은 제어 동작을 받을 뿐 아니라 이러한 경우에는 이벤트도 추적되지 않습니다.

class PillColorExperiment < ApplicationExperiment
  # ...등록된 동작들
  
  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
  # ...등록된 동작들
  
  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 이벤트가 추적됩니다.

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 구성요소에서 gitlab-experiment의 이름이나 일치하는 : 슬롯을 사용할 수 있습니다.

<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>