InfoGrab DocsInfoGrab Docs

GitLab 개발에서의 피처 플래그

요약

이 페이지는 개발자가 피처 플래그를 통해 GitLab 제품의 개발과 운영에 기여하는 방법을 설명합니다. 새로 도입되는 피처 플래그는 모두 기본적으로 비활성화되어야 하며 액터와 함께 사용해야 합니다. 이 문서는 피처 플래그의 내부 사용 개선 에픽의 일환으로 계속 작업 중입니다.

이 페이지는 개발자가 피처 플래그를 통해 GitLab 제품의 개발과 운영에 기여하는 방법을 설명합니다. 자체 애플리케이션에서 기능을 표시하거나 숨기는 커스텀 피처 플래그를 생성하려면 피처 플래그 생성을 참고합니다. GitLab의 피처 플래그 전체 목록도 제공됩니다.

Warning

새로 도입되는 피처 플래그는 모두 기본적으로 비활성화되어야 하며 액터와 함께 사용해야 합니다.

설계 문서:

이 문서는 피처 플래그의 내부 사용 개선 에픽의 일환으로 계속 작업 중입니다. 제안 사항은 새 이슈로 등록해 해당 에픽에 연결합니다.

피처 플래그 라이프사이클 개요를 확인하거나 피처 플래그 사용 여부 판단에 도움이 필요하면 피처 플래그 라이프사이클 핸드북 페이지를 참고합니다.

피처 플래그를 사용해야 하는 경우#

핸드북의 "피처 플래그를 사용해야 하는 경우" 섹션으로 이동했습니다.

외부 API 소비자에서 피처 플래그를 사용하지 않기#

피처 플래그는 내부 구현 세부 사항이며 공개 API 계약의 일부가 아닙니다. IDE 확장, Duo CLI, CI 통합처럼 GraphQL metadata.featureFlags 필드나 더 이상 사용되지 않는 featureFlagEnabled 필드로 피처 플래그를 조회하는 외부 API 소비자는 특정한 위험에 노출됩니다. 플래그가 모노리스에서 제거될 때 외부 소비자는 아직 업데이트되지 않았을 수 있으며, 그 영향은 사소한 UI 문제부터 고객에게 영향을 주는 인시던트까지 다양합니다.

외부 API 소비자에서 피처 플래그를 다룰 때는 다음 지침을 따릅니다.

  • API 필드나 Application Settings를 우선합니다. 가능하면 외부 API 소비자에서 피처 플래그를 조회하지 않습니다. 대신 소비자가 조회할 수 있는 전용 API 필드나 Application Setting을 도입합니다. 이 값들은 플래그가 제거된 뒤에도 유지됩니다.
  • fail-open 동작을 구현합니다. 외부 API 소비자에서 피처 플래그를 반드시 사용해야 한다면 "fail-open" 메커니즘을 구현합니다. 롤아웃 마일스톤이 확정된 뒤에는 소비자가 플래그를 활성화된 것으로 간주하도록 기본값을 둡니다. 롤아웃 마일스톤이 확인되는 즉시 소비자를 업데이트합니다. GitLab Language Server의 예시를 참고합니다.
  • 제거 전에 사용자의 업그레이드 패턴을 고려합니다. 외부 API 소비자가 사용하는 플래그를 제거하기 전에 사용자가 클라이언트를 얼마나 빠르게 업데이트하는지 평가하고 가장 안전한 제거 시점을 결정합니다.

장기 설정에 피처 플래그를 사용하지 않기#

피처 플래그는 짧은 기간만 유지하도록 설계되었습니다. 오랜 기간에 걸쳐 사용자·그룹·프로젝트 단위로 무언가를 활성화하기 위해 피처 플래그를 추가하려는 경우라면 대신 Cascading Settings나 Application Settings 도입을 고려합니다. 설정은 고객이 GitLab.com 또는 GitLab Self-Managed에서 스스로 기능을 활성화하거나 비활성화할 수 있는 방법을 제공하며, 필요한 기간만큼 코드베이스에 남을 수 있습니다. 반면 사용자는 GitLab.com에서 스스로 피처 플래그를 활성화하거나 비활성화할 수 없고 GitLab Self-Managed 관리자만 피처 플래그를 변경할 수 있습니다. 또한 GitLab Dedicated에서는 피처 플래그가 지원되지 않으며 이 역시 피처 플래그를 설정의 대체재로 사용하면 안 되는 이유입니다.

GitLab 개발에서의 피처 플래그#

피처 플래그를 활용할지 결정할 때는 다음 사항을 고려합니다.

  • 피처 플래그는 기본적으로 비활성화되어야 합니다.
  • 피처 플래그 관리 부담을 줄이기 위해 피처 플래그는 가능한 한 짧은 기간만 코드베이스에 유지합니다.
  • 피처 플래그를 운영하는 담당자는 문서와 다른 이해관계자에게 피처 플래그 뒤에 있는 기능의 상태를 명확히 전달할 책임이 있습니다. 피처 플래그가 필요하다는 점이 분명해지는 즉시 이슈 설명에 피처 플래그 이름과 기본값이 활성화인지 비활성화인지를 업데이트합니다.
  • 피처 플래그를 도입하거나 상태를 변경하거나, 기능이 안정적이라고 판단되어 기존 피처 플래그를 제거하는 머지 리퀘스트에는 반드시 ~"feature flag" 레이블을 지정합니다.

기능 구현이 여러 머지 리퀘스트에 걸쳐 제공되는 경우:

  1. 플래그를 사용하는 첫 번째 머지 리퀘스트에서 기본적으로 비활성화된 새 피처 플래그를 생성합니다. 플래그를 별도로 추가해서는 안 됩니다.
  2. 하나 이상의 머지 리퀘스트로 점진적인 변경을 제출하며, 추가된 새 코드는 피처 플래그가 활성화된 경우에만 실행되도록 합니다. 개발 중에는 로컬 GDK에서 피처 플래그를 활성화한 상태로 둘 수 있습니다.
  3. 기능을 다른 팀원이 테스트할 준비가 되면 초기 문서를 작성합니다. 피처 플래그의 상태에 대한 세부 정보를 포함합니다.
  4. 특정 그룹·프로젝트·사용자에 대해 피처 플래그를 활성화하고 구현에 문제가 없는지 확인합니다. 문서가 없다면 gitlab-org/gitlab 같은 공개 프로젝트에서는 피처 플래그를 활성화하지 않습니다. 팀원과 기여자가 공개 프로젝트에서 활성화된 것을 보면 해당 기능의 사용 방법 문서를 찾으려 할 수 있습니다.
  5. GitLab Self-Managed 인스턴스를 포함한 프로덕션 사용에 기능이 준비되면 머지 리퀘스트 하나를 열어 다음을 수행합니다.
    • 최신 플래그 상태를 설명하도록 문서를 업데이트합니다.
    • 변경 로그 항목을 추가합니다.
    • 피처 플래그를 제거해 새 동작을 활성화하거나, 피처 플래그를 기본 활성화로 전환합니다(ops 및 beta 피처 플래그에만 해당).

피처 플래그 제거가 여러 머지 리퀘스트에 걸쳐 제공되는 경우:

  1. 피처 플래그의 값 변경은 머지 리퀘스트의 유일한 변경이어야 합니다. 피처 플래그가 코드베이스에 존재하는 동안에는 두 상태 모두 완전히 동작해야 합니다(기능이 켜졌을 때와 꺼졌을 때).
  2. 피처 플래그에 대한 모든 언급이 제거된 뒤에는 레거시 코드를 제거할 수 있습니다. 피처 플래그 롤아웃 이슈의 단계를 따라야 하며, 단계를 건너뛰어야 한다면 그 이유를 설명하는 댓글을 이슈에 추가합니다.

피처 플래그가 기능의 릴리스를 최소 한 달(= 한 번의 릴리스)만큼 미룬다고 생각하기 쉽습니다. 그렇지 않습니다. 피처 플래그를 특정 기간(예: 최소 한 번의 릴리스) 동안 유지해야 하는 것은 아닙니다. 대신 기능이 안정적이라고 판단될 때까지 유지합니다. 안정적이라는 것은 장애 같은 문제를 일으키지 않고 GitLab.com에서 동작한다는 의미입니다.

기본 브랜치 손상 위험#

피처 플래그는 반드시 그것을 도입하는 MR에서 사용해야 합니다. 그렇게 하지 않으면 기본 브랜치에서만 실행되는 rspec:feature-flags job 때문에 기본 브랜치 손상 상황이 발생합니다.

피처 플래그 유형#

예상되는 사용 방식에 맞는 피처 플래그 유형을 선택합니다.

gitlab_com_derisk 유형#

gitlab_com_derisk 피처 플래그는 GitLab.com 배포의 위험을 줄이는 데 사용하는 단기 피처 플래그입니다. GitLab에서 사용하는 대부분의 피처 플래그가 gitlab_com_derisk 유형입니다.

제약 조건#

  • default_enabled: true로 설정하면 안 됩니다. 이 종류의 피처 플래그는 GitLab.com의 위험을 낮추기 위한 것이므로, GitLab.com에서 활성화한 뒤에는 코드베이스에 플래그를 유지할 필요가 없습니다. 이 유형의 피처 플래그에서는 default_enabled: true가 아무런 효과를 내지 않습니다.
  • 최대 수명: 기본 브랜치에 병합된 후 2개월
  • 문서화: 이 유형의 피처 플래그는 수명이 짧고 배포와 관련된 것이므로 GitLab의 모든 피처 플래그 페이지에 문서화할 필요가 없습니다.
  • 롤아웃 이슈: Feature flag Roll Out 템플릿에서 생성한 롤아웃 이슈가 반드시 있어야 합니다.

사용법#

gitlab_com_derisk 피처 플래그의 형식은 Feature.<state>(:<dev_flag_name>)입니다.

GitLab Rails 콘솔에서 다음을 실행해 활성화하거나 비활성화합니다.

# To enable it for the instance:
Feature.enable(:<dev_flag_name>)

# To disable it for the instance:
Feature.disable(:<dev_flag_name>)

# To enable for a specific project:
Feature.enable(:<dev_flag_name>, Project.find(<project id>))

# To disable for a specific project:
Feature.disable(:<dev_flag_name>, Project.find(<project id>))

gitlab_com_derisk 피처 플래그의 상태를 확인하려면 다음을 실행합니다.

# Check if the feature flag is enabled
Feature.enabled?(:dev_flag_name)

# Check if the feature flag is disabled
Feature.disabled?(:dev_flag_name)

wip 유형#

일부 기능은 복잡해서 여러 MR에 걸쳐 구현해야 합니다. 완전히 구현되기 전까지는 누구에게도 보이지 않아야 합니다. 이 경우 wip("Work In Progress") 피처 플래그를 사용하면 기능을 아직 사용하지 않은 채로 모든 변경 사항을 main 브랜치에 병합할 수 있습니다.

기능이 완성되면 고객에게 기능을 어떻게 제시하고 문서화할지에 따라 피처 플래그 유형을 gitlab_com_derisk 또는 beta 유형으로 변경할 수 있습니다.

제약 조건#

  • default_enabled: true로 설정하면 안 됩니다. 필요하다면 기능이 완성된 뒤 이 유형을 beta로 변경할 수 있습니다.
  • 최대 수명: 기본 브랜치에 병합된 후 4개월
  • 문서화: 이 유형의 피처 플래그는 대부분 미완성 코드를 숨기는 용도이므로 GitLab의 모든 피처 플래그 페이지에 문서화할 필요가 없습니다.
  • 롤아웃 이슈: wip 피처 플래그는 활성화되기 전에 다른 유형으로 전환되어야 하므로 롤아웃 이슈가 필요하지 않을 가능성이 높습니다.

사용법#

# Check if feature flag is enabled
Feature.enabled?(:my_wip_flag, project)

# Check if feature flag is disabled
Feature.disabled?(:my_wip_flag, project)

# Push feature flag to Frontend
push_frontend_feature_flag(:my_wip_flag, project)

beta 유형#

설계된 모든 사용 사례에 대해 현재 형태의 기능을 확장·지원·유지할 수 있다고 확신하지 못할 때가 있습니다(예시). 또한 기능이 MVC로 보기에 충분히 완성되지 않은 경우도 있습니다. 이럴 때 플래그를 제공하면 엔지니어와 고객이 성능이 충분해질 때까지 새 기능을 비활성화할 수 있습니다.

제약 조건#

  • default_enabled: true로 설정할 수 있습니다. 그러면 확장성 문제가 생겼을 때 비활성화할 여지를 남긴 채 기능을 베타 상태로 모두에게 "릴리스"할 수 있습니다(이상적으로는 특정 온프레미스 설치에서 이 이유로만 비활성화합니다).
  • 최대 수명: 기본 브랜치에 병합된 후 6개월
  • 문서화: 이 유형의 피처 플래그는 반드시 GitLab의 모든 피처 플래그 페이지에 문서화해야 합니다. 해당 페이지는 YAML 정의 파일에서 문서 빌드 중에 자동 생성되므로, 수동으로 편집할 필요가 없습니다.
  • 롤아웃 이슈: 반드시 롤아웃 이슈가 있어야 하며, 이 이슈는 다음 템플릿에서 생성합니다. Feature flag Roll Out 템플릿

사용법#

# Check if feature flag is enabled
Feature.enabled?(:my_beta_flag, project)

# Check if feature flag is disabled
Feature.disabled?(:my_beta_flag, project)

# Push feature flag to Frontend
push_frontend_feature_flag(:my_beta_flag, project)

ops 유형#

ops 피처 플래그는 GitLab 제품 동작의 운영 측면을 제어하는 장기 피처 플래그입니다. 예를 들어 Sidekiq 워커 동작처럼 성능에 영향을 줄 수 있는 기능을 비활성화하는 피처 플래그가 여기에 해당합니다.

이 유형을 사용할 때는 인스턴스·그룹·프로젝트·사용자 설정을 도입하지 않기로 한 의식적인 결정이 선행되어야 합니다.

ops 유형 플래그의 수명에는 제한이 없지만, 12개월마다 여전히 필요한지 평가해야 합니다. 계속 필요하다면 ops 피처 플래그가 여전히 사용 중임을 확인하는 의미로 milestone 필드를 최신 마일스톤으로 업데이트합니다.

제약 조건#

  • default_enabled: 대부분의 경우 false로 두고, 일시적인 확장성 문제를 해결하거나 프로덕션 문제를 디버깅하는 데 도움이 될 때만 활성화합니다.
  • 최대 수명: 제한 없음. 단 12개월마다 평가해야 합니다.
  • 문서화: 이 유형의 피처 플래그는 반드시 GitLab의 모든 피처 플래그 페이지에 문서화해야 하며, 사용할 수 있는 상황을 설명하는 운영 런북과도 연결해야 합니다. 해당 페이지는 YAML 정의 파일에서 문서 빌드 중에 자동 생성되므로, 수동으로 편집할 필요가 없습니다
  • 롤아웃 이슈: 언제 활성화되거나 비활성화될지 예측하기 어렵기 때문에 롤아웃 이슈가 필요하지 않을 가능성이 높습니다.

사용법#

# Check if feature flag is enabled
Feature.enabled?(:my_ops_flag, project)

# Check if feature flag is disabled
Feature.disabled?(:my_ops_flag, project)

# Push feature flag to Frontend
push_frontend_feature_flag(:my_ops_flag, project)

experiment 유형#

experiment 피처 플래그는 GitLab.com에서 A/B 테스트에 사용합니다.

experiment 피처 플래그는 beta 피처 플래그와 같은 기준을 따라야 하지만, 인터페이스에는 차이가 있습니다. experiment 피처 플래그에는 Experiment tracking 템플릿으로 생성한 롤아웃 이슈가 있어야 합니다. 자세한 내용은 실험 가이드에서 확인할 수 있습니다.

제약 조건#

  • default_enabled: true로 설정하면 안 됩니다.
  • 최대 수명: 기본 브랜치에 병합된 후 6개월

worker 유형#

worker 피처 플래그는 Sidekiq job 지연처럼 Sidekiq 워커 동작을 제어하는 데 사용할 수 있는 특수한 ops 플래그입니다.

worker 피처 플래그는 워커 이름 자체로 이름을 동적으로 생성할 수 있어(예: run_sidekiq_jobs_AuthorizedProjectsWorker) YAML 정의가 없는 경우가 많습니다. worker 유형 피처 플래그의 사용 예시는 Sidekiq job 지연에서 확인할 수 있습니다.

markdown_cache 유형#

markdown_cache 피처 플래그는 Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION 상향의 단계적 롤아웃을 이끕니다. worker 플래그와 마찬가지로 YAML 정의가 없으며, 이름은 대상 캐시 버전에서 생성됩니다. 예를 들면 markdown_cache_stochastic_rollout_34 입니다. 상향할 때마다 각자의 플래그를 사용하므로 새 플래그는 비활성화 상태로 시작하고, 이전 상향의 플래그에 설정해 둔 비율은 다음 상향으로 이어지지 않습니다. 롤아웃 절차는 CACHE_COMMONMARK_VERSION 상향의 단계적 롤아웃을 참고합니다.

(사용 중단) development 유형#

development 유형은 gitlab_com_derisk, wip, beta 피처 플래그 유형으로 대체되어 사용이 중단되었습니다.

피처 플래그 정의 및 유효성 검사#

개발(RAILS_ENV=development) 또는 테스트(RAILS_ENV=test) 환경에서는 모든 피처 플래그 사용이 엄격하게 검증됩니다.

이 과정은 코드베이스에서 피처 플래그를 일관되게 사용하도록 보장하기 위한 것입니다. 모든 피처 플래그는 다음 조건을 충족해야 합니다.

  • 알려진 플래그여야 합니다. 명시적으로 정의된 피처 플래그만 사용합니다(experiment, worker, undefined 유형의 피처 플래그는 예외).
  • 두 번 정의하지 않습니다. FOSS 또는 EE 중 한쪽에만 정의해야 하며 양쪽 모두에 정의해서는 안 됩니다.
  • 정의 파일이 없는 피처 플래그는 모든 호출에서 유효하고 일관된 type:을 사용합니다.
  • 소유자가 있어야 합니다.

GitLab이 알고 있는 모든 피처 플래그는 다음 위치의 YAML 파일에 자체 문서화되어 있습니다.

각 피처 플래그는 여러 필드로 구성된 별도의 YAML 파일에 정의됩니다.

필드 필수 여부 설명
name 예 피처 플래그의 이름.
description 예 피처 플래그가 필요한 이유에 대한 짧은 설명.
type 예 피처 플래그의 유형.
default_enabled 예 피처 플래그의 기본 상태.
introduced_by_url 예 피처 플래그를 도입한 머지 리퀘스트의 URL.
milestone 예 피처 플래그를 생성한 마일스톤.
group 예 피처 플래그를 소유한 그룹.
feature_issue_url 아니요 원본 기능 이슈의 URL.
rollout_issue_url 아니요 피처 플래그 롤아웃을 다루는 이슈의 URL.
log_state_changes 아니요 피처 플래그의 상태를 로깅하려면 true로 설정합니다.
Note

RAILS_ENV=production으로 실행할 때는 모든 유효성 검사를 건너뜁니다.

새 피처 플래그 생성#

Note

GitLab Pages는 피처 플래그에 다른 절차를 사용합니다.

GitLab 코드베이스는 bin/feature-flag를 제공합니다. 새 피처 플래그 정의를 생성하는 전용 도구입니다. 이 도구는 새 피처 플래그에 대한 여러 질문을 한 다음 config/feature_flags 또는 ee/config/feature_flags에 YAML 정의를 생성합니다.

YAML 정의 파일이 있는 피처 플래그만 개발 또는 테스트 환경에서 사용할 수 있습니다.

도구가 롤아웃 이슈를 생성할지 물으면 생성된 템플릿을 클립보드에 복사하고, 브라우저에서 New issue 양식을 열고, 이슈 URL을 기다립니다. 도구는 양식을 제출하지 않습니다. 템플릿을 양식에 붙여넣고 제출한 뒤, 프롬프트에 새 이슈 URL을 입력합니다. 롤아웃 이슈는 한 번만 생성합니다. 양식이 열려 있는 동안 API나 다른 도구로 같은 템플릿에서 다른 이슈를 만들면 롤아웃 이슈가 중복되므로 만들지 않습니다.

$ bin/feature-flag my_feature_flag
>> Specify the feature flag type
?> beta
You picked the type 'beta'

>> Specify the group label to which the feature flag belongs, from the following list:
1. group::group1
2. group::group2
?> 2
You picked the group 'group::group2'

>> URL of the original feature issue (enter to skip):
?> https://gitlab.com/gitlab-org/gitlab/-/issues/435435

>> URL of the MR introducing the feature flag (enter to skip and let Danger provide a suggestion directly in the MR):
?> https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141023

>> Username of the feature flag DRI (enter to skip):
?> bob

>> Is this an EE only feature (enter to skip):
?> [Return]

>> Press any key and paste the issue content that we copied to your clipboard! 🚀
?> [Return opens the "New issue" page. Paste the issue content and submit the issue.]

>> URL of the rollout issue (enter to skip):
?> https://gitlab.com/gitlab-org/gitlab/-/issues/437162

create config/feature_flags/beta/my_feature_flag.yml
---
name: my_feature_flag
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/435435
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141023
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/437162
milestone: '16.9'
group: group::composition analysis
type: beta
default_enabled: false

새로 도입하는 피처 플래그는 모두 기본적으로 비활성화해야 합니다.

피처 플래그 뒤에서 개발하고 병합하는 기능에는 변경 로그 항목을 포함하지 않습니다. 해당 항목은 피처 플래그를 제거하는 머지 리퀘스트나 피처 플래그의 기본값을 활성화로 바꾸는 머지 리퀘스트에 추가합니다. 기능에 데이터베이스 마이그레이션이 포함된다면 데이터베이스 변경에 대한 변경 로그 항목을 포함해야 합니다.

Note

EE에서만 사용하는 피처 플래그를 생성하려면 --ee 플래그를 추가합니다: bin/feature-flag --ee

새 플래그 이름 짓기#

새 피처 플래그의 이름을 정할 때는 다음 지침을 고려합니다.

  • 피처 플래그가 담고 있는 기능을 설명합니다.

    • 짧지만 혼란스러운 이름보다 길더라도 설명적인 이름이 낫습니다.
  • _mvc, _alpha, _beta처럼 기능의 상태나 단계를 나타내는 이름은 피합니다.

  • 이름은 스네이크 케이스로 작성합니다(my_cool_feature_flag).

  • 이중 부정으로 생각하거나 문서화하지 않아도 되도록 이름에 disable을 쓰지 않습니다. hide_, remove_, disallow_로 시작하는 이름을 고려합니다.

    소프트웨어 공학에서 이 문제는 "불리언 변수의 부정형 이름"으로 알려져 있습니다. 다만 부정어를 완전히 금지할 수는 없습니다. 플래그를 기본적으로 비활성화된 상태로 도입하거나, 기능을 플래그 뒤로 옮겨 제거하거나, 액터별로 플래그를 선택적으로 비활성화하려면 부정어가 필요합니다.

master(main) 브랜치 손상 위험#

Warning

피처 플래그는 반드시 그것을 도입하는 MR에서 사용해야 합니다. 그렇게 하지 않으면 master 브랜치에서만 실행되는 rspec:feature-flags job 때문에 master 손상 상황이 발생합니다.

피처 플래그 자동 제거를 위한 .patch 파일 선택적 추가#

gitlab-housekeeper는 DeleteOldFeatureFlags keep을 사용해 피처 플래그 코드를 자동으로 제거할 수 있습니다. 이 도구는 주기적으로 실행되어 오래된 피처 플래그를 코드에서 자동으로 정리합니다.

이 도구가 코드에서 피처 플래그 사용처를 자동으로 제거하게 하려면 피처 플래그 YAML 파일 옆에 .patch 파일을 추가할 수 있습니다. 파일 이름은 확장자만 .yml 대신 .patch로 바꾸고 나머지는 완전히 같아야 합니다.

예를 들어 config/feature_flags/beta/my_feature_flag.yml의 패치 파일은 다음 단계로 생성할 수 있습니다.

  1. Git 작업 디렉터리가 깨끗한 상태인지 확인합니다.
  2. config/feature_flags/beta/my_feature_flag.yml을 삭제합니다.
  3. 피처 플래그가 이미 활성화되어 기능이 정식으로 제공되는 것처럼, 로컬에서 코드를 편집해 my_feature_flag 사용처를 모두 제거합니다.
  4. git diff > config/feature_flags/beta/my_feature_flag.patch를 실행합니다. 피처 플래그가 beta 플래그가 아니라면 패치 파일을 해당 피처 플래그를 정의한 YAML 파일과 같은 디렉터리에 둡니다.
  5. config/feature_flags/beta/my_feature_flag.yml 삭제를 되돌립니다.
  6. 피처 플래그 사용처를 제거하려고 편집한 파일의 변경 사항을 되돌립니다.
  7. 피처 플래그를 추가하는 브랜치에 패치 파일을 커밋합니다.

이후 gitlab-housekeeper가 이 패치를 적용해 피처 플래그를 자동으로 정리합니다.

모든 피처 플래그 나열#

ChatOps를 사용해 환경의 모든 피처 플래그를 Slack으로 출력하려면 run feature list 명령을 사용할 수 있습니다. 예를 들면 다음과 같습니다.

/chatops gitlab run feature list --dev
/chatops gitlab run feature list --staging

피처 플래그 토글#

피처 플래그 토글에 대한 자세한 내용은 변경 사항 롤아웃을 참고합니다.

피처 플래그 삭제#

피처 플래그 삭제에 대한 자세한 내용은 피처 플래그 정리를 참고합니다.

ops 피처 플래그를 애플리케이션 설정으로 마이그레이션#

Warning

애플리케이션 설정을 백필하는 변경과 코드에서 그 설정을 사용하는 변경은 같은 마일스톤에 병합해야 합니다.

ops 피처 플래그를 애플리케이션 설정으로 마이그레이션하려면 다음 절차를 따릅니다.

  1. 애플리케이션 설정에서 설정을 저장할 JSONB 칼럼을 새로 만들거나 기존 칼럼을 찾습니다.
  2. 애플리케이션 설정의 기본값은 피처 플래그 YAML 정의의 default_enabled:와 일치해야 합니다
  3. 칼럼을 백필하는 마이그레이션을 작성합니다. 이렇게 하면 기본 동작을 사용하지 않기로 한 인스턴스가 같은 상태를 유지할 수 있습니다. 마이그레이션에서는 Feature.enabled?나 Feature.disabled? 사용을 피합니다. Gitlab::Database::MigrationHelpers::FeatureFlagMigratorHelpers 마이그레이션 헬퍼를 사용합니다. 이 헬퍼는 명시적으로 true 또는 false로 설정된 피처 플래그만 마이그레이션합니다. 피처 플래그가 비율이나 특정 액터로 설정되어 있으면 기본값이 사용됩니다.
  4. Admin 영역에서 기능을 활성화하거나 비활성화하는 설정을 만듭니다.
  5. 모든 곳에서 피처 플래그를 애플리케이션 설정으로 교체합니다.
  6. 관련 문서 페이지를 모두 업데이트합니다. 프론트엔드 변경이 이후 마일스톤에 병합된다면 애플리케이션 설정 API나 Rails 콘솔로 설정을 업데이트하는 방법에 대한 문서를 추가합니다.

JSONB 칼럼의 마이그레이션 예시입니다.

# default_enabled copied from feature flag definition YAML before it is removed
DEFAULT_ENABLED = true

def up
  up_migrate_to_jsonb_setting(feature_flag_name: :my_flag_name,
    setting_name: :my_setting,
    jsonb_column_name: :settings,
    default_enabled: DEFAULT_ENABLED)
end

def down
  down_migrate_to_jsonb_setting(setting_name: :my_setting, jsonb_column_name: :settings)
end

불리언 칼럼의 마이그레이션 예시입니다.

# default_enabled copied from feature flag definition YAML before it is removed
DEFAULT_ENABLED = true

def up
  up_migrate_to_setting(feature_flag_name: :my_flag_name,
    setting_name: :my_setting,
    default_enabled: DEFAULT_ENABLED)
end

def down
  down_migrate_to_setting(setting_name: :my_setting, default_enabled: DEFAULT_ENABLED)
end

피처 플래그로 개발하기#

GitLab 코드베이스에서 피처 플래그를 사용하는 방법은 크게 두 가지입니다.

백엔드#

피처 플래그 인터페이스는 lib/feature.rb에 정의되어 있습니다. 이 인터페이스는 피처 플래그가 활성화되었는지 비활성화되었는지 확인하는 메서드를 제공합니다.

if Feature.enabled?(:my_feature_flag, project)
  # execute code if feature flag is enabled
else
  # execute code if feature flag is disabled
end

if Feature.disabled?(:my_feature_flag, project)
  # execute code if feature flag is disabled
end

설정되지 않은 피처 플래그의 기본 동작은 YAML 정의의 default_enabled:로 제어합니다.

피처 플래그에 YAML 정의가 없으면 개발 또는 테스트 환경에서는 오류가 발생하고, 프로덕션에서는 false를 반환합니다.

정의 파일이 없는 피처 플래그(experiment, worker, undefined 유형에만 허용)는 Feature.enabled?와 Feature.disabled?를 호출할 때 type:을 함께 전달해야 합니다.

if Feature.enabled?(:experiment_feature_flag, project, type: :experiment)
  # execute code if feature flag is enabled
end

if Feature.disabled?(:worker_feature_flag, project, type: :worker)
  # execute code if feature flag is disabled
end
Warning

애플리케이션 로드 시점에는 피처 플래그를 사용하지 않습니다. 예를 들어 config/initializers/*나 클래스 수준에서 Feature 클래스를 사용하면 예기치 않은 오류가 발생할 수 있습니다. 이 오류는 피처 플래그 어댑터가 의존할 수 있는 데이터베이스가 로드 시점에 존재하지 않기 때문에 발생합니다 (특히 새로 설치한 경우). 일부 어댑터는 데이터베이스가 전혀 필요하지 않으므로(예: HTTP 어댑터) 호출 지점에서 데이터베이스 존재 여부를 확인하는 방식은 권장하지 않습니다. 피처 플래그 설정 확인은 Feature 네임스페이스에서 추상화해야 합니다. 또한 이 방식은 피처 플래그가 바뀔 때마다 애플리케이션을 다시 로드해야 합니다. 따라서 프로덕션에서 Web/API/Sidekiq 플릿을 다시 로드해 달라고 SRE에게 요청해야 하며, 변경을 완전히 롤아웃하거나 롤백하는 데 시간이 걸립니다. 이런 이유로 환경 변수(예: ENV['YOUR_FEATURE_NAME'])나 gitlab.yml을 대신 사용합니다.

다음은 피해야 할 패턴의 예시입니다.

class MyClass
  if Feature.enabled?(:...)
    new_process
  else
    legacy_process
  end
end

재귀 감지#

피처 플래그가 많으면 어디에서 호출되는지 항상 분명하지는 않습니다. 한 피처 플래그를 평가할 때 다른 피처 플래그의 평가가 필요한 순환은 피합니다. 순환이 발생하면 순환이 끊기고 기본값이 반환됩니다.

이 재귀 감지가 올바르게 동작하려면 항상 Feature::enabled?로 피처 값에 접근하고 저수준의 Feature::get 사용은 피합니다. 순환이 발생하면 Feature::RecursionError 예외를 오류 추적기로 기록합니다.

프론트엔드#

UI 요소에 피처 플래그를 사용할 때는 해당하는 백엔드 코드가 있다면 그 코드에도 피처 플래그를 함께 사용합니다. 이렇게 하면 기능이 활성화되기 전까지는 어떤 방법으로도 사용할 수 없습니다.

ApplicationController를 상속하는 모든 컨트롤러에서 사용할 수 있는 push_frontend_feature_flag 메서드를 사용합니다. 이 메서드로 피처 플래그의 상태를 노출할 수 있습니다. 예를 들면 다음과 같습니다.

before_action do
  # Prefer to scope it per project or user, for example
  push_frontend_feature_flag(:vim_bindings, project)
end

def index
  # ...
end

def edit
  # ...
end

그런 다음 JavaScript에서 피처 플래그의 상태를 다음과 같이 확인할 수 있습니다.

if ( gon.features.vimBindings ) {
  // ...
}

JavaScript에서 피처 플래그 이름은 항상 camelCase이므로 gon.features.vim_bindings로 확인하면 동작하지 않습니다.

Vue 컴포넌트에서 피처 플래그에 접근하는 방법은 Vue 가이드를 참고합니다.

정의 파일이 없는 피처 플래그(experiment, worker, undefined 유형에만 허용)는 push_frontend_feature_flag를 호출할 때 type:을 함께 전달해야 합니다.

before_action do
  push_frontend_feature_flag(:vim_bindings, project, type: :experiment)
end

피처 액터#

피처 플래그에는 액터를 사용할 것을 강력히 권장합니다. 액터를 쓰면 특정 프로젝트, 그룹, 사용자에 대해서만 피처 플래그를 간단히 활성화할 수 있습니다. 액터를 기준으로 로그와 오류를 필터링할 수 있어 디버깅도 쉬워집니다. 또한 나머지 사용자에게 영향을 주지 않고 gitlab-org나 gitlab-com 그룹에서 먼저 기능을 활성화할 수 있습니다.

액터는 기능을 고정된 방식으로 비율 롤아웃하는 간편한 방법도 제공합니다. 1% 롤아웃에서 특정 액터에 기능이 활성화되었다면 그 액터는 10%, 50%, 100%에서도 계속 활성화된 상태를 유지합니다.

GitLab은 다음 피처 플래그 액터를 지원합니다.

  • User 모델
  • Project 모델
  • Group 모델
  • Ci::Runner 모델
  • 현재 요청

액터는 Feature.enabled? 호출의 두 번째 파라미터입니다. 예를 들면 다음과 같습니다.

Feature.enabled?(:feature_flag, project)

include FeatureGate를 포함한 모델에는 .actor_from_id 클래스 메서드가 있습니다. 모델의 ID가 있고 피처 플래그 상태 확인 외에는 모델이 필요하지 않다면, .actor_from_id를 사용해 모델을 조회하는 데이터베이스 쿼리 없이 피처 플래그 상태를 확인할 수 있습니다.

# Bad -- Unnecessary query is executed
Feature.enabled?(:feature_flag, Project.find(project_id))

# Good -- No query for projects
Feature.enabled?(:feature_flag, Project.actor_from_id(project_id))

# Good -- Project model is used after feature flag check
project = Project.find(project_id)
return unless Feature.enabled?(:feature_flag, project)
project.update!(column: value)

스테이징이나 프로덕션처럼 GitLab이 제공하는 환경에서 ChatOps로 피처 플래그를 선택적으로 활성화하거나 비활성화하는 방법은 ChatOps로 피처 플래그 활성화 및 비활성화를 참고합니다.

플래그 상태는 그룹에서 하위 그룹이나 프로젝트로 상속되지 않습니다. 그룹 계층 전체에서 플래그 상태를 일관되게 유지해야 한다면 최상위 그룹을 액터로 사용하는 방안을 고려합니다. 이 그룹은 어떤 그룹이나 프로젝트에서든 #root_ancestor를 호출해 찾을 수 있습니다.

Feature.enabled?(:feature_flag, group.root_ancestor)

액터 유형 혼용#

일반적으로 특정 피처 플래그의 모든 Feature.enabled? 호출에서는 한 가지 유형의 액터만 사용하고 서로 다른 액터 유형을 섞지 않습니다.

액터 유형을 섞으면 기능이 일관되지 않게 활성화되거나 비활성화되어 버그로 이어질 수 있습니다. 예를 들어 컨트롤러 수준에서는 그룹 액터로 플래그를 확인하고 서비스 수준에서는 사용자 액터로 확인하면, 같은 요청 안에서 기능이 어떤 지점에서는 활성화되고 다른 지점에서는 비활성화될 수 있습니다.

일관되지 않은 결과로 이어지지 않는다는 점이 확실한 상황이라면 액터 유형을 섞어도 안전합니다. 예를 들어 웹훅은 그룹이나 프로젝트 중 하나에 연결될 수 있으므로, 웹훅용 피처 플래그는 이를 활용해 같은 피처 플래그로 그룹 웹훅과 프로젝트 웹훅에 기능을 롤아웃할 수 있습니다.

서로 다른 액터 유형이 필요하고 해당 상황에서 안전하게 섞을 수 없다면 액터 유형마다 별도의 플래그를 사용합니다. 예를 들면 다음과 같습니다.

Feature.enabled?(:feature_flag_group, group)
Feature.enabled?(:feature_flag_user, user)

인스턴스 액터#

Warning

인스턴스 전체 피처 플래그는 기능이 인스턴스 전체와 연결된 경우에만 사용합니다. 항상 다른 액터를 먼저 고려합니다.

기능을 액터 기준이 아니라 인스턴스 전체에 대해 활성화하고 싶을 때가 있습니다. 대표적인 예가 Admin 설정으로, 그룹과 프로젝트가 모두 undefined이기 때문에 이를 기준으로는 피처 플래그를 활성화할 수 없습니다.

사용자 액터를 쓰면 관리자가 아닌 사용자에게는 피처 플래그가 활성화되고 관리자인 사용자에게는 비활성화될 수 있어 혼란을 줍니다.

대신 Feature.enabled?의 두 번째 인자로 :instance 심벌을 사용할 수 있으며, 이는 GitLab 인스턴스로 처리됩니다.

Feature.enabled?(:feature_flag, :instance)

현재 요청 액터#

시간 비율 롤아웃은 호출할 때마다 일관되지 않은 결과를 반환할 수 있으므로 권장하지 않습니다.

대신 현재 요청을 액터로 사용할 것을 권장합니다.

# Bad
Feature.enable_percentage_of_time(:feature_flag, 40)
Feature.enabled?(:feature_flag)

# Good
Feature.enable_percentage_of_actors(:feature_flag, 40)
Feature.enabled?(:feature_flag, Feature.current_request)

현재 요청을 액터로 사용하면 피처 플래그는 요청 컨텍스트 안에서 같은 값을 반환해야 합니다. 현재 요청 액터는 SafeRequestStore로 구현되어 있으므로 다음 범위에서는 피처 플래그 값이 일관됩니다.

  • Rack 요청
  • Sidekiq 워커 실행
  • ActionCable 워커 실행

기존 기능을 시간 비율에서 현재 요청 액터로 마이그레이션할 때는 새 피처 플래그를 만들 것을 권장합니다. 기존 percentage_of_time 값, 코드 변경 배포, percentage_of_actors로 전환하는 시점 사이의 타이밍을 제어하기 어렵기 때문입니다.

프로덕션 검증에 액터 사용#

Warning

프로덕션을 테스트 환경으로 사용하는 방식은 권장하지 않습니다. 프로덕션에 적용할 준비가 되지 않은 기능은 테스트 환경에서 테스트합니다.

스테이징 환경은 프로덕션과 비슷한 환경에서 기능을 테스트할 수 있게 해 주지만, 프로덕션 환경에 고유한 성능 지표를 적용 전후로 비교할 수는 없습니다. 개발용 피처 플래그를 활성화한 프로젝트를 프로덕션에 두면 Sitespeed 리포트 같은 도구로 피처 플래그 뒤에 있는 새 코드의 지표를 확인할 수 있어 유용합니다.

이미 Sitespeed에서 기존 코드베이스를 추적하고 있다면 피처 플래그 롤아웃 전후의 성능을 정확하게 비교할 수 있으므로 이 접근 방식이 더욱 유용합니다.

추가 객체를 액터로 사용하도록 설정#

액터 기반 피처 게이트를 사용하려면 모델이 flipper_id에 응답해야 합니다. 예를 들어 Foo 모델에 대해 활성화하려면 다음과 같이 작성합니다.

class Foo < ActiveRecord::Base
  include FeatureGate
end

include FeatureGate를 포함하거나 flipper_id 메서드를 노출하는 모델만 Feature.enabled?의 액터로 사용할 수 있습니다.

라이선스 기능을 위한 피처 플래그#

라이선스 기능 이름과 같은 이름의 피처 플래그는 이름 충돌을 일으키므로 사용할 수 없습니다. 이 방식은 폭넓게 논의된 끝에 제거되었습니다. 혼란을 주기 때문입니다.

라이선스 기능을 확인하려면 다른 이름으로 전용 피처 플래그를 추가하고 명시적으로 확인합니다. 예를 들면 다음과 같습니다.

Feature.enabled?(:licensed_feature_feature_flag, project) &&
  project.feature_available?(:licensed_feature)

피처 그룹#

피처 그룹은 lib/feature.rb의 .register_feature_groups 메서드에 정적으로 정의해야 하지만, 구현은 동적일 수 있습니다 (예: 데이터베이스 조회).

lib/feature.rb에 정의한 뒤에는 기능 API의 feature_group 파라미터로 특정 피처 그룹에 대해 기능을 활성화할 수 있습니다

사용할 수 있는 피처 그룹은 다음과 같습니다.

그룹 이름 범위 설명
gitlab_team_members 사용자 gitlab-com의 멤버인 사용자에게 기능을 활성화합니다

피처 그룹은 그룹 이름으로 활성화할 수 있습니다.

Feature.enable(:feature_flag_name, :gitlab_team_members)

로컬에서 피처 플래그 제어#

Rails 콘솔에서#

Rails 콘솔(rails c)에서 다음 명령을 입력해 피처 플래그를 활성화합니다.

Feature.enable(:feature_flag_name)

마찬가지로 다음 명령은 피처 플래그를 비활성화합니다.

Feature.disable(:feature_flag_name)

특정 게이트에 대해 피처 플래그를 활성화할 수도 있습니다.

Feature.enable(:feature_flag_name, Project.find_by_full_path("root/my-project"))

Rails 콘솔에서 피처 플래그를 수동으로 활성화하거나 비활성화하면 기본값이 덮어써집니다. 이 때문에 플래그의 default_enabled 속성을 변경할 때 혼란이 생길 수 있습니다.

피처 플래그를 기본 상태로 되돌리려면 다음을 실행합니다.

Feature.remove(:feature_flag_name)

모든 피처 플래그를 YAML 정의의 기본 상태로 되돌리려면 다음을 실행합니다.

Feature.all.each(&:remove)

브라우저에서#

http://gdk.test:3000/rails/features에 접속해 로컬에서 피처 플래그를 확인하고 관리합니다.

로깅#

코드에서 어떤 플래그에 대해 Feature.enabled?나 Feature.disabled?를 호출하면 GitLab은 요청당 한 번 확인된 상태를 기록합니다. 이 상태는 구조화 로그에 feature_flag_states 배열로 나타나며, 활성화는 my_feature_flag:1, 비활성화는 my_feature_flag:0 같은 항목으로 표시됩니다. 이 필드는 production_json.log, api_json.log, 예외 로그(exception.feature_flag_states), Sentry 오류 이벤트에서 확인할 수 있습니다.

GitLab은 다음 두 조건이 모두 충족될 때만 플래그의 상태를 기록합니다.

  • 해당 요청에 대해 feature_flag_state_logs ops 피처 플래그가 활성화되어 있습니다. 이 플래그는 기본적으로 비활성화되어 있으며, GitLab.com뿐 아니라 모든 GitLab 인스턴스에 적용됩니다.
  • 플래그 정의에 log_state_changes: true가 설정되어 있거나, 플래그의 milestone이 현재 GitLab 버전 이상입니다.

두 번째 조건 때문에 GitLab은 플래그의 마일스톤이 현재이거나 미래인 동안 플래그 상태를 자동으로 기록합니다. 마일스톤이 지난 뒤에도 로그가 필요하다면 플래그 정의 파일에 log_state_changes: true를 설정합니다. 예를 들어 롤아웃이 길어지는 동안이나, 플래그와 관련이 있다고 의심되는 문제를 디버깅하는 동안 설정합니다.

이 로그로 다음과 같은 내용을 확인할 수 있습니다.

  • 오류나 Sentry 이벤트를 발생시킨 요청에서 플래그가 활성화되어 있었는지 비활성화되어 있었는지.
  • 비율 롤아웃이 실제 트래픽에 도달하는지, 그리고 그 비율이 어느 정도인지.
  • 플래그가 활성화된 요청이 비활성화된 요청과 다르게 동작하는지, 예를 들어 요청 처리 시간에서 차이가 있는지.
  • 피처 플래그 변경이 데이터베이스 요청 수나 데이터베이스 호출 시간 같은 데이터베이스 지표에 미칠 수 있는 영향.
Note

GitLab.com에서는 요청의 20%에 대해 feature_flag_state_logs를 활성화합니다.

Kibana에서 피처 플래그 상태 검색#

피처 플래그의 상태가 기록되면 Kibana에서 "json.feature_flag_states": "feature_flag_name:1" 또는 "json.feature_flag_states": "feature_flag_name:0" 조건으로 찾을 수 있습니다. 저장된 시각화 모음은 Kibana의 FF Observability 컬렉션을 참고합니다.

이 검색은 log.gprd.gitlab.net에서 제공되는 GitLab.com 로그에서 동작합니다.

변경 로그#

최종 사용자가 직접(예: 기능을 사용할 수 있는 능력) 또는 간접적으로(예: 백그라운드 job의 이점, 성능 개선, 데이터베이스 마이그레이션 업데이트) 접근할 수 없는 기능에는 변경 로그를 도입하지 않습니다.

  • 데이터베이스 마이그레이션은 GitLab Self-Managed 고객이 업그레이드 전에 데이터베이스 변경을 알아야 하므로 항상 최종 사용자가 간접적으로 접근합니다. 따라서 변경 로그 항목이 있어야 합니다.

  • 기본적으로 비활성화된 피처 플래그 뒤의 변경에는 변경 로그 항목을 두지 않습니다.

  • 기본적으로 활성화된 피처 플래그 뒤의 변경에는 변경 로그 항목이 있어야 합니다.

  • 피처 플래그 자체의 변경(플래그 제거, 기본 활성화 설정)에는 변경 로그 항목이 있어야 합니다. 변경 로그 항목 유형은 플로차트로 판단합니다.

    Mermaid 다이어그램 (18줄)
    소스 코드 보기
    flowchart LR
      FDOFF(Flag is currently<br>'default: off')
      FDON(Flag is currently<br>'default: on')
      CDO{Change to<br>'default: on'}
      ACF(added / changed / fixed / '...')
      RF{Remove flag}
      RF2{Remove flag}
      RC(removed / changed)
      OTHER(other)
    

    FDOFF -->CDO-->ACF FDOFF -->RF RF-->|Keep new code?| ACF RF-->|Keep old code?| OTHER

    FDON -->RF2 RF2-->|Keep old code?| RC RF2-->|Keep new code?| OTHER

  • 피처 플래그의 변경 로그는 플래그가 아니라 기능을 설명해야 합니다. 다만 기본 활성화 피처 플래그를 제거하면서 새 코드를 유지하는 경우는 예외입니다(위 플로차트의 other).

  • 피처 플래그는 버그 수정이나 유지 보수 작업을 롤아웃하는 데도 사용할 수 있습니다. 이 경우 변경 로그는 그에 맞춰야 합니다. 예를 들면 fixed나 other입니다.

테스트에서의 피처 플래그#

코드베이스에 피처 플래그를 도입하면 테스트해야 할 코드 경로가 하나 더 생깁니다. 피처 플래그의 영향을 받는 모든 코드에 대해 활성화와 비활성화 두 경우 모두 자동화 테스트를 포함합니다. 다만 테스트에서는 플래그가 기본적으로 활성화되므로 활성화 상태가 기본 시나리오이며, 스텁이나 전용 컨텍스트가 필요하지 않습니다. 비활성화 상태는 플래그를 false로 스텁하는 독립된 별도 컨텍스트에서 다룹니다. 그러면 나중에 플래그를 제거할 때 한 번의 삭제로 끝납니다(아래 패턴 참고). 두 상태 모두에 대한 자동화 테스트를 포함하지 않는다면, 테스트되지 않은 코드 경로와 관련된 기능은 프로덕션에 배포하기 전에 수동으로 테스트해야 합니다.

테스트 환경에서는 모든 피처 플래그가 기본적으로 활성화됩니다. spec/spec_helper.rb 파일에서 플래그를 기본적으로 비활성화할 수 있습니다. 플래그를 비활성화해야 하는 이유는 인라인 주석으로 설명합니다. 가능하다면 참고용으로 이슈 URL도 함께 첨부할 수 있습니다.

Warning

이 내용은 피처 플래그를 기본적으로 활성화하지 않는 End-to-end(QA) 테스트에는 적용되지 않습니다. End-to-end 테스트에는 별도의 피처 플래그 사용 절차가 있습니다.

테스트에서 피처 플래그를 비활성화하려면 stub_feature_flags 헬퍼를 사용합니다. 예를 들어 테스트에서 ci_live_trace 피처 플래그를 전역으로 비활성화하려면 다음과 같이 작성합니다.

stub_feature_flags(ci_live_trace: false)

Feature.enabled?(:ci_live_trace) # => false

두 경로를 모두 테스트하는 일반적인 패턴은 다음과 같습니다.

it 'ci_live_trace works' do
  # tests assuming ci_live_trace is enabled in tests by default
  Feature.enabled?(:ci_live_trace) # => true
end

context 'when ci_live_trace is disabled' do
  before do
    stub_feature_flags(ci_live_trace: false)
  end

  it 'ci_live_trace does not work' do
    Feature.enabled?(:ci_live_trace) # => false
  end
end

일부 액터에만 피처 플래그가 활성화된 테스트를 구성하려면 헬퍼에 전달하는 옵션으로 지정할 수 있습니다. 예를 들어 특정 프로젝트에 대해 ci_live_trace 피처 플래그를 활성화하려면 다음과 같이 작성합니다.

project1, project2 = build_list(:project, 2)

# Feature will only be enabled for project1
stub_feature_flags(ci_live_trace: project1)

Feature.enabled?(:ci_live_trace) # => false
Feature.enabled?(:ci_live_trace, project1) # => true
Feature.enabled?(:ci_live_trace, project2) # => false

FlipperGate의 동작은 다음과 같습니다.

  1. 지정한 액터에 대해 활성화 재정의를 설정할 수 있습니다.
  2. 지정한 액터에 대한 재정의를 비활성화(제거)해 기본 상태로 되돌릴 수 있습니다.
  3. 지정한 액터를 명시적으로 비활성화했다고 표현할 방법은 없습니다.
Feature.enable(:my_feature)
Feature.disable(:my_feature, project1)
Feature.enabled?(:my_feature) # => true
Feature.enabled?(:my_feature, project1) # => true

Feature.disable(:my_feature2)
Feature.enable(:my_feature2, project1)
Feature.enabled?(:my_feature2) # => false
Feature.enabled?(:my_feature2, project1) # => true

have_pushed_frontend_feature_flags#

push_frontend_feature_flag이 HTML에 피처 플래그를 추가했는지 테스트하려면 have_pushed_frontend_feature_flags를 사용합니다.

예를 들면 다음과 같습니다.

stub_feature_flags(value_stream_analytics_path_navigation: false)

visit group_analytics_cycle_analytics_path(group)

expect(page).to have_pushed_frontend_feature_flags(valueStreamAnalyticsPathNavigation: false)

stub_feature_flags 대 Feature.enable*#

테스트 환경에서는 모든 피처 플래그가 기본적으로 활성화되므로, 보통은 stub_feature_flags로 플래그를 비활성화하거나 특정 액터에 대해서만 활성화합니다. 이 메서드는 이러한 사용 사례에 간단하고 명확한 인터페이스를 제공합니다.

# Good: disable the flag to test the disabled code path
stub_feature_flags(my_feature: false)

# Good: enable the flag only for specific actors, leaving it disabled elsewhere
stub_feature_flags(my_feature: project)
stub_feature_flags(my_feature: [project, project2])

# Redundant: the flag is already enabled by default in tests, so this has no
# effect unless the flag was disabled by default in spec/spec_helper.rb
stub_feature_flags(my_feature: true)

비율 롤아웃처럼 더 복잡한 동작에는 stub_feature_flags 대신 .enable_percentage_of_time이나 .enable_percentage_of_actors를 사용합니다.

# Bad: prefer stub_feature_flags for simple enable/disable
Feature.enable(:my_feature_2)

# Good: enable my_feature for 50% of time
Feature.enable_percentage_of_time(:my_feature_3, 50)

# Good: enable my_feature for 50% of actors/gates/things
Feature.enable_percentage_of_actors(:my_feature_4, 50)

상태가 정의된 각 피처 플래그는 테스트 실행 중에 영속화됩니다.

Feature.persisted_names.include?('my_feature') => true
Feature.persisted_names.include?('my_feature_2') => true
Feature.persisted_names.include?('my_feature_3') => true
Feature.persisted_names.include?('my_feature_4') => true

액터 스텁#

특정 액터에 대해서만 피처 플래그를 활성화하려면 그 액터의 표현을 스텁할 수 있습니다. Feature.enabled?와 Feature.disabled?에 인자로 전달하는 게이트는 FeatureGate를 포함한 객체여야 합니다.

스펙에서는 커스텀 액터를 빠르게 만들 수 있는 stub_feature_flag_gate 메서드를 사용할 수 있습니다.

gate = stub_feature_flag_gate('CustomActor')

stub_feature_flags(ci_live_trace: gate)

Feature.enabled?(:ci_live_trace) # => false
Feature.enabled?(:ci_live_trace, gate) # => true

테스트에서 피처 플래그 엔진 제어#

테스트 환경의 Flipper 엔진은 메모리 모드 Flipper::Adapters::Memory로 동작합니다. production과 development 모드는 Flipper::Adapters::ActiveRecord를 사용합니다.

Flipper::Adapters::Memory 모드를 쓸지 ActiveRecord 모드를 쓸지 제어할 수 있습니다.

stub_feature_flags: true(기본값이자 권장 방식)#

이 모드에서 Flipper는 Flipper::Adapters::Memory를 사용하도록 구성되며, 모든 피처 플래그를 기본 활성화로 표시하고 처음 사용할 때 영속화합니다.

피처 플래그 뒤의 동작이 특정되지 않은 컨텍스트에서 테스트되지 않은 채 남지 않도록 합니다.

stub_feature_flags: false#

이 설정은 메모리로 스텁된 flipper를 비활성화하고, production과 development에서 사용하는 Flipper::Adapters::ActiveRecord 모드를 사용합니다.

이 모드는 Flipper가 ActiveRecord와 상호작용하는 측면을 실제로 테스트해야 할 때만 사용합니다.

End-to-end(QA) 테스트#

End-to-end(QA) 테스트에서는 피처 플래그 토글이 다르게 동작합니다. End-to-end 테스트 프레임워크는 Rails나 데이터베이스에 직접 접근할 수 없어 Flipper를 사용할 수 없습니다. 대신 공개 API를 사용합니다. 각 End-to-end 테스트는 테스트 도중 피처 플래그를 활성화하거나 비활성화할 수 있습니다. 또는 GitLab 리포지터리의 qa 디렉터리에서 테스트를 실행하거나 GitLab QA로 테스트를 실행할 때 하나 이상의 테스트 전에 피처 플래그를 활성화하거나 비활성화할 수 있습니다.

앞에서 설명했듯이 End-to-end 테스트에서는 피처 플래그가 기본적으로 활성화되지 않습니다. 따라서 End-to-end 테스트는 피처 플래그를 명시적으로 활성화하거나 비활성화하도록 작성하지 않는 한, 소스 코드에 구현된 기본 상태의 피처 플래그나 테스트 대상 GitLab 인스턴스의 현재 상태로 실행됩니다.

스테이징이나 GitLab.com에서 피처 플래그가 변경되면 #e2e-run-staging 또는 #e2e-run-production 채널에 Slack 메시지가 게시되어 파이프라인 트리아지 DRI가 실패와 피처 플래그 변경의 관련성을 더 쉽게 판단할 수 있습니다. 다만 변경 작업을 진행 중이라면 피처 플래그를 활성화한 상태에서 End-to-end 테스트가 통과하는지 확인해 예기치 않은 실패를 줄일 수 있습니다.

피처 플래그로 Sidekiq 워커 동작 제어#

worker 유형 피처 플래그로 Sidekiq 워커의 동작을 제어할 수 있습니다.

Sidekiq job 지연#

run_sidekiq_jobs_{WorkerName} 형식의 피처 플래그를 비활성화하면 job을 나중 시각으로 예약해 워커 실행을 지연합니다. 이 피처 플래그는 모든 워커에 대해 기본적으로 활성화되어 있습니다. 워커 인스턴스의 경합 동작이 인프라 자원(데이터베이스와 데이터베이스 커넥션 풀 등)을 포화시키는 인시던트 상황에서 job 지연이 유용할 수 있습니다. 구현은 SkipJobs Sidekiq 서버 미들웨어에서 확인할 수 있습니다.

Note

피처 플래그가 비활성화되어 있는 동안 job은 무기한 지연됩니다. 워커가 계속 처리해도 안전하다고 판단되면 피처 플래그를 제거해야 합니다.

false로 설정하면 job의 100%가 지연됩니다. 처리를 재개하려면 시간 비율 롤아웃을 사용할 수 있습니다. 예를 들면 다음과 같습니다.

# not running any jobs, deferring all 100% of the jobs
/chatops gitlab run feature set run_sidekiq_jobs_SlowRunningWorker false

# only running 10% of the jobs, deferring 90% of the jobs
/chatops gitlab run feature set run_sidekiq_jobs_SlowRunningWorker 10

# running 50% of the jobs, deferring 50% of the jobs
/chatops gitlab run feature set run_sidekiq_jobs_SlowRunningWorker 50

# back to running all jobs normally
/chatops gitlab run feature delete run_sidekiq_jobs_SlowRunningWorker

Sidekiq job 삭제#

job을 지연하는 대신 drop_sidekiq_jobs_{WorkerName} 피처 플래그를 활성화해 job을 완전히 삭제할 수도 있습니다. 앞으로도 job을 처리할 필요가 없어 삭제해도 안전하다고 확신할 때 이 피처 플래그를 사용합니다.

# drop all the jobs
/chatops gitlab run feature set drop_sidekiq_jobs_SlowRunningWorker true

# process jobs normally
/chatops gitlab run feature delete drop_sidekiq_jobs_SlowRunningWorker
Note

삭제 피처 플래그(drop_sidekiq_jobs_{WorkerName})가 지연 피처 플래그(run_sidekiq_jobs_{WorkerName})보다 우선합니다. drop_sidekiq_jobs가 활성화되고 run_sidekiq_jobs가 비활성화되면 job은 완전히 삭제됩니다.

피처 플래그 이벤트#

Feature.enable이나 Feature.disable이 호출되면 Gitlab::FeatureFlags::FeatureFlagModifiedEvent가 발행됩니다.

[!NOTE] 이벤트는 Feature.enable이나 Feature.disable이 상태 변경이 일어났음을 나타내는 true를 반환할 때만 발행됩니다. 비율 기반 롤아웃 메서드(Feature.enable_percentage_of_actors, Feature.enable_percentage_of_time)는 이벤트를 발행하지 않습니다.

이벤트에는 다음 내용이 포함됩니다.

  • feature_key(String): 피처 플래그의 이름.
  • operation(String): 변경 유형. Feature::OPERATION_ENABLED_GLOBALLY, Feature::OPERATION_DISABLED_GLOBALLY, Feature::OPERATION_ENABLED_ACTOR, Feature::OPERATION_DISABLED_ACTOR 중 하나.
  • actor(String 또는 nil): 이 작업의 영향을 받는 특정 액터(예: "User:123", "Group:456"). 전역 작업에서는 nil.
  • state(String): 작업 후 피처 플래그의 현재 상태("on", "off", "conditional").

[!NOTE] actor 필드에는 현재 작업의 영향을 받는 액터 하나만 들어가며, 플래그에 활성화된 모든 액터가 들어가지는 않습니다. 액터 전체 목록을 얻으려면 Feature.get(:flag_name)으로 데이터베이스를 조회합니다.

특정 피처 플래그 구독#

워커는 조건부 필터로 특정 피처 플래그를 구독해야 합니다. 이렇게 하면 GitLab의 모든 피처 플래그 변경마다 워커가 큐에 쌓이는 일을 막을 수 있습니다. 또한 같은 작성자가 피처 플래그를 활성화하거나 비활성화하는 호출을 여러 번 하면 이벤트가 여러 개 발생할 수 있으므로 워커는 멱등해야 합니다.

피처 플래그 이벤트를 구독하려면 다음 절차를 따릅니다.

  1. Gitlab::EventStore::Subscriber를 포함하는 워커를 만듭니다.

    module MyFeature
      class MyFeatureFlagWorker
        include ApplicationWorker
        include Gitlab::EventStore::Subscriber
    
        data_consistency :always
        feature_category :your_category
        urgency :low
    
        idempotent!
    
        def handle_event(event)
          feature_key = event.data[:feature_key]
          operation = event.data[:operation]
          actor = event.data[:actor]
    
          case operation
          when Feature::OPERATION_ENABLED_ACTOR
            # Handle actor-specific enable
          when Feature::OPERATION_DISABLED_ACTOR
            # Handle actor-specific disable
          when Feature::OPERATION_ENABLED_GLOBALLY
            # Handle global enable
          when Feature::OPERATION_DISABLED_GLOBALLY
            # Handle global disable
          end
        end
      end
    end
    
  2. lib/gitlab/event_store/subscriptions/feature_subscriptions.rb에 구독을 등록합니다.

    def register
      # Subscribe to all changes for a specific feature flag
      store.subscribe ::MyFeature::MyFeatureFlagWorker,
        to: ::Gitlab::FeatureFlags::FeatureFlagModifiedEvent,
        if: ->(event) { event.data[:feature_key] == 'my_specific_flag' }
    
      # Subscribe to multiple feature flags
      store.subscribe ::MyFeature::MyFeatureFlagWorker,
        to: ::Gitlab::FeatureFlags::FeatureFlagModifiedEvent,
        if: ->(event) { %w[flag_one flag_two].include?(event.data[:feature_key]) }
    
      # Only trigger for actor-specific enables
      store.subscribe ::MyFeature::MyFeatureFlagWorker,
        to: ::Gitlab::FeatureFlags::FeatureFlagModifiedEvent,
        if: ->(event) do
          event.data[:feature_key] == 'my_specific_flag' &&
            event.data[:operation] == Feature::OPERATION_ENABLED_ACTOR
        end
    
      # Only trigger for global enables
      store.subscribe ::MyFeature::MyFeatureFlagWorker,
        to: ::Gitlab::FeatureFlags::FeatureFlagModifiedEvent,
        if: ->(event) do
          event.data[:feature_key] == 'my_specific_flag' &&
            event.data[:operation] == Feature::OPERATION_ENABLED_GLOBALLY
        end
    end
    

액터 형식#

actor 필드에는 작업의 영향을 받는 특정 액터의 Flipper ID 문자열이 들어갑니다. 전역 작업(OPERATION_ENABLED_GLOBALLY 또는 OPERATION_DISABLED_GLOBALLY)에서는 actor 필드가 nil입니다.

일반적인 액터 형식은 다음과 같습니다.

  • User:123 - ID가 123인 사용자
  • Project:456 - ID가 456인 프로젝트
  • Group:789 - ID가 789인 그룹
  • Namespace:101 - ID가 101인 네임스페이스
  • Ci::Runner:202 - ID가 202인 CI 러너
  • Organizations::Organization:303 - ID가 303인 조직

액터 ID를 파싱하려면 다음과 같이 작성합니다.

actor = event.data[:actor]
# => "Group:456"

return if actor.nil? # Global operation

actor_type, actor_id = actor.split(':', 2)

case actor_type
when 'User'
  user = User.find(actor_id)
  # Process user
when 'Group'
  group = Group.find(actor_id)
  # Process group
when 'Project'
  project = Project.find(actor_id)
  # Process project
end

GitLab 개발에서의 피처 플래그

GitLab v19.5
원문 보기

요약

이 페이지는 개발자가 피처 플래그를 통해 GitLab 제품의 개발과 운영에 기여하는 방법을 설명합니다. 새로 도입되는 피처 플래그는 모두 기본적으로 비활성화되어야 하며 액터와 함께 사용해야 합니다. 이 문서는 피처 플래그의 내부 사용 개선 에픽의 일환으로 계속 작업 중입니다.

이 페이지는 개발자가 피처 플래그를 통해 GitLab 제품의 개발과 운영에 기여하는 방법을 설명합니다. 자체 애플리케이션에서 기능을 표시하거나 숨기는 커스텀 피처 플래그를 생성하려면 피처 플래그 생성을 참고합니다. GitLab의 피처 플래그 전체 목록도 제공됩니다.

Warning

새로 도입되는 피처 플래그는 모두 기본적으로 비활성화되어야 하며 액터와 함께 사용해야 합니다.

설계 문서:

이 문서는 피처 플래그의 내부 사용 개선 에픽의 일환으로 계속 작업 중입니다. 제안 사항은 새 이슈로 등록해 해당 에픽에 연결합니다.

피처 플래그 라이프사이클 개요를 확인하거나 피처 플래그 사용 여부 판단에 도움이 필요하면 피처 플래그 라이프사이클 핸드북 페이지를 참고합니다.

피처 플래그를 사용해야 하는 경우#

핸드북의 "피처 플래그를 사용해야 하는 경우" 섹션으로 이동했습니다.

외부 API 소비자에서 피처 플래그를 사용하지 않기#

피처 플래그는 내부 구현 세부 사항이며 공개 API 계약의 일부가 아닙니다. IDE 확장, Duo CLI, CI 통합처럼 GraphQL metadata.featureFlags 필드나 더 이상 사용되지 않는 featureFlagEnabled 필드로 피처 플래그를 조회하는 외부 API 소비자는 특정한 위험에 노출됩니다. 플래그가 모노리스에서 제거될 때 외부 소비자는 아직 업데이트되지 않았을 수 있으며, 그 영향은 사소한 UI 문제부터 고객에게 영향을 주는 인시던트까지 다양합니다.

외부 API 소비자에서 피처 플래그를 다룰 때는 다음 지침을 따릅니다.

  • API 필드나 Application Settings를 우선합니다. 가능하면 외부 API 소비자에서 피처 플래그를 조회하지 않습니다. 대신 소비자가 조회할 수 있는 전용 API 필드나 Application Setting을 도입합니다. 이 값들은 플래그가 제거된 뒤에도 유지됩니다.
  • fail-open 동작을 구현합니다. 외부 API 소비자에서 피처 플래그를 반드시 사용해야 한다면 "fail-open" 메커니즘을 구현합니다. 롤아웃 마일스톤이 확정된 뒤에는 소비자가 플래그를 활성화된 것으로 간주하도록 기본값을 둡니다. 롤아웃 마일스톤이 확인되는 즉시 소비자를 업데이트합니다. GitLab Language Server의 예시를 참고합니다.
  • 제거 전에 사용자의 업그레이드 패턴을 고려합니다. 외부 API 소비자가 사용하는 플래그를 제거하기 전에 사용자가 클라이언트를 얼마나 빠르게 업데이트하는지 평가하고 가장 안전한 제거 시점을 결정합니다.

장기 설정에 피처 플래그를 사용하지 않기#

피처 플래그는 짧은 기간만 유지하도록 설계되었습니다. 오랜 기간에 걸쳐 사용자·그룹·프로젝트 단위로 무언가를 활성화하기 위해 피처 플래그를 추가하려는 경우라면 대신 Cascading Settings나 Application Settings 도입을 고려합니다. 설정은 고객이 GitLab.com 또는 GitLab Self-Managed에서 스스로 기능을 활성화하거나 비활성화할 수 있는 방법을 제공하며, 필요한 기간만큼 코드베이스에 남을 수 있습니다. 반면 사용자는 GitLab.com에서 스스로 피처 플래그를 활성화하거나 비활성화할 수 없고 GitLab Self-Managed 관리자만 피처 플래그를 변경할 수 있습니다. 또한 GitLab Dedicated에서는 피처 플래그가 지원되지 않으며 이 역시 피처 플래그를 설정의 대체재로 사용하면 안 되는 이유입니다.

GitLab 개발에서의 피처 플래그#

피처 플래그를 활용할지 결정할 때는 다음 사항을 고려합니다.

  • 피처 플래그는 기본적으로 비활성화되어야 합니다.
  • 피처 플래그 관리 부담을 줄이기 위해 피처 플래그는 가능한 한 짧은 기간만 코드베이스에 유지합니다.
  • 피처 플래그를 운영하는 담당자는 문서와 다른 이해관계자에게 피처 플래그 뒤에 있는 기능의 상태를 명확히 전달할 책임이 있습니다. 피처 플래그가 필요하다는 점이 분명해지는 즉시 이슈 설명에 피처 플래그 이름과 기본값이 활성화인지 비활성화인지를 업데이트합니다.
  • 피처 플래그를 도입하거나 상태를 변경하거나, 기능이 안정적이라고 판단되어 기존 피처 플래그를 제거하는 머지 리퀘스트에는 반드시 ~"feature flag" 레이블을 지정합니다.

기능 구현이 여러 머지 리퀘스트에 걸쳐 제공되는 경우:

  1. 플래그를 사용하는 첫 번째 머지 리퀘스트에서 기본적으로 비활성화된 새 피처 플래그를 생성합니다. 플래그를 별도로 추가해서는 안 됩니다.
  2. 하나 이상의 머지 리퀘스트로 점진적인 변경을 제출하며, 추가된 새 코드는 피처 플래그가 활성화된 경우에만 실행되도록 합니다. 개발 중에는 로컬 GDK에서 피처 플래그를 활성화한 상태로 둘 수 있습니다.
  3. 기능을 다른 팀원이 테스트할 준비가 되면 초기 문서를 작성합니다. 피처 플래그의 상태에 대한 세부 정보를 포함합니다.
  4. 특정 그룹·프로젝트·사용자에 대해 피처 플래그를 활성화하고 구현에 문제가 없는지 확인합니다. 문서가 없다면 gitlab-org/gitlab 같은 공개 프로젝트에서는 피처 플래그를 활성화하지 않습니다. 팀원과 기여자가 공개 프로젝트에서 활성화된 것을 보면 해당 기능의 사용 방법 문서를 찾으려 할 수 있습니다.
  5. GitLab Self-Managed 인스턴스를 포함한 프로덕션 사용에 기능이 준비되면 머지 리퀘스트 하나를 열어 다음을 수행합니다.
    • 최신 플래그 상태를 설명하도록 문서를 업데이트합니다.
    • 변경 로그 항목을 추가합니다.
    • 피처 플래그를 제거해 새 동작을 활성화하거나, 피처 플래그를 기본 활성화로 전환합니다(ops 및 beta 피처 플래그에만 해당).

피처 플래그 제거가 여러 머지 리퀘스트에 걸쳐 제공되는 경우:

  1. 피처 플래그의 값 변경은 머지 리퀘스트의 유일한 변경이어야 합니다. 피처 플래그가 코드베이스에 존재하는 동안에는 두 상태 모두 완전히 동작해야 합니다(기능이 켜졌을 때와 꺼졌을 때).
  2. 피처 플래그에 대한 모든 언급이 제거된 뒤에는 레거시 코드를 제거할 수 있습니다. 피처 플래그 롤아웃 이슈의 단계를 따라야 하며, 단계를 건너뛰어야 한다면 그 이유를 설명하는 댓글을 이슈에 추가합니다.

피처 플래그가 기능의 릴리스를 최소 한 달(= 한 번의 릴리스)만큼 미룬다고 생각하기 쉽습니다. 그렇지 않습니다. 피처 플래그를 특정 기간(예: 최소 한 번의 릴리스) 동안 유지해야 하는 것은 아닙니다. 대신 기능이 안정적이라고 판단될 때까지 유지합니다. 안정적이라는 것은 장애 같은 문제를 일으키지 않고 GitLab.com에서 동작한다는 의미입니다.

기본 브랜치 손상 위험#

피처 플래그는 반드시 그것을 도입하는 MR에서 사용해야 합니다. 그렇게 하지 않으면 기본 브랜치에서만 실행되는 rspec:feature-flags job 때문에 기본 브랜치 손상 상황이 발생합니다.

피처 플래그 유형#

예상되는 사용 방식에 맞는 피처 플래그 유형을 선택합니다.

gitlab_com_derisk 유형#

gitlab_com_derisk 피처 플래그는 GitLab.com 배포의 위험을 줄이는 데 사용하는 단기 피처 플래그입니다. GitLab에서 사용하는 대부분의 피처 플래그가 gitlab_com_derisk 유형입니다.

제약 조건#

  • default_enabled: true로 설정하면 안 됩니다. 이 종류의 피처 플래그는 GitLab.com의 위험을 낮추기 위한 것이므로, GitLab.com에서 활성화한 뒤에는 코드베이스에 플래그를 유지할 필요가 없습니다. 이 유형의 피처 플래그에서는 default_enabled: true가 아무런 효과를 내지 않습니다.
  • 최대 수명: 기본 브랜치에 병합된 후 2개월
  • 문서화: 이 유형의 피처 플래그는 수명이 짧고 배포와 관련된 것이므로 GitLab의 모든 피처 플래그 페이지에 문서화할 필요가 없습니다.
  • 롤아웃 이슈: Feature flag Roll Out 템플릿에서 생성한 롤아웃 이슈가 반드시 있어야 합니다.

사용법#

gitlab_com_derisk 피처 플래그의 형식은 Feature.<state>(:<dev_flag_name>)입니다.

GitLab Rails 콘솔에서 다음을 실행해 활성화하거나 비활성화합니다.

# To enable it for the instance:
Feature.enable(:<dev_flag_name>)

# To disable it for the instance:
Feature.disable(:<dev_flag_name>)

# To enable for a specific project:
Feature.enable(:<dev_flag_name>, Project.find(<project id>))

# To disable for a specific project:
Feature.disable(:<dev_flag_name>, Project.find(<project id>))

gitlab_com_derisk 피처 플래그의 상태를 확인하려면 다음을 실행합니다.

# Check if the feature flag is enabled
Feature.enabled?(:dev_flag_name)

# Check if the feature flag is disabled
Feature.disabled?(:dev_flag_name)

wip 유형#

일부 기능은 복잡해서 여러 MR에 걸쳐 구현해야 합니다. 완전히 구현되기 전까지는 누구에게도 보이지 않아야 합니다. 이 경우 wip("Work In Progress") 피처 플래그를 사용하면 기능을 아직 사용하지 않은 채로 모든 변경 사항을 main 브랜치에 병합할 수 있습니다.

기능이 완성되면 고객에게 기능을 어떻게 제시하고 문서화할지에 따라 피처 플래그 유형을 gitlab_com_derisk 또는 beta 유형으로 변경할 수 있습니다.

제약 조건#

  • default_enabled: true로 설정하면 안 됩니다. 필요하다면 기능이 완성된 뒤 이 유형을 beta로 변경할 수 있습니다.
  • 최대 수명: 기본 브랜치에 병합된 후 4개월
  • 문서화: 이 유형의 피처 플래그는 대부분 미완성 코드를 숨기는 용도이므로 GitLab의 모든 피처 플래그 페이지에 문서화할 필요가 없습니다.
  • 롤아웃 이슈: wip 피처 플래그는 활성화되기 전에 다른 유형으로 전환되어야 하므로 롤아웃 이슈가 필요하지 않을 가능성이 높습니다.

사용법#

# Check if feature flag is enabled
Feature.enabled?(:my_wip_flag, project)

# Check if feature flag is disabled
Feature.disabled?(:my_wip_flag, project)

# Push feature flag to Frontend
push_frontend_feature_flag(:my_wip_flag, project)

beta 유형#

설계된 모든 사용 사례에 대해 현재 형태의 기능을 확장·지원·유지할 수 있다고 확신하지 못할 때가 있습니다(예시). 또한 기능이 MVC로 보기에 충분히 완성되지 않은 경우도 있습니다. 이럴 때 플래그를 제공하면 엔지니어와 고객이 성능이 충분해질 때까지 새 기능을 비활성화할 수 있습니다.

제약 조건#

  • default_enabled: true로 설정할 수 있습니다. 그러면 확장성 문제가 생겼을 때 비활성화할 여지를 남긴 채 기능을 베타 상태로 모두에게 "릴리스"할 수 있습니다(이상적으로는 특정 온프레미스 설치에서 이 이유로만 비활성화합니다).
  • 최대 수명: 기본 브랜치에 병합된 후 6개월
  • 문서화: 이 유형의 피처 플래그는 반드시 GitLab의 모든 피처 플래그 페이지에 문서화해야 합니다. 해당 페이지는 YAML 정의 파일에서 문서 빌드 중에 자동 생성되므로, 수동으로 편집할 필요가 없습니다.
  • 롤아웃 이슈: 반드시 롤아웃 이슈가 있어야 하며, 이 이슈는 다음 템플릿에서 생성합니다. Feature flag Roll Out 템플릿

사용법#

# Check if feature flag is enabled
Feature.enabled?(:my_beta_flag, project)

# Check if feature flag is disabled
Feature.disabled?(:my_beta_flag, project)

# Push feature flag to Frontend
push_frontend_feature_flag(:my_beta_flag, project)

ops 유형#

ops 피처 플래그는 GitLab 제품 동작의 운영 측면을 제어하는 장기 피처 플래그입니다. 예를 들어 Sidekiq 워커 동작처럼 성능에 영향을 줄 수 있는 기능을 비활성화하는 피처 플래그가 여기에 해당합니다.

이 유형을 사용할 때는 인스턴스·그룹·프로젝트·사용자 설정을 도입하지 않기로 한 의식적인 결정이 선행되어야 합니다.

ops 유형 플래그의 수명에는 제한이 없지만, 12개월마다 여전히 필요한지 평가해야 합니다. 계속 필요하다면 ops 피처 플래그가 여전히 사용 중임을 확인하는 의미로 milestone 필드를 최신 마일스톤으로 업데이트합니다.

제약 조건#

  • default_enabled: 대부분의 경우 false로 두고, 일시적인 확장성 문제를 해결하거나 프로덕션 문제를 디버깅하는 데 도움이 될 때만 활성화합니다.
  • 최대 수명: 제한 없음. 단 12개월마다 평가해야 합니다.
  • 문서화: 이 유형의 피처 플래그는 반드시 GitLab의 모든 피처 플래그 페이지에 문서화해야 하며, 사용할 수 있는 상황을 설명하는 운영 런북과도 연결해야 합니다. 해당 페이지는 YAML 정의 파일에서 문서 빌드 중에 자동 생성되므로, 수동으로 편집할 필요가 없습니다
  • 롤아웃 이슈: 언제 활성화되거나 비활성화될지 예측하기 어렵기 때문에 롤아웃 이슈가 필요하지 않을 가능성이 높습니다.

사용법#

# Check if feature flag is enabled
Feature.enabled?(:my_ops_flag, project)

# Check if feature flag is disabled
Feature.disabled?(:my_ops_flag, project)

# Push feature flag to Frontend
push_frontend_feature_flag(:my_ops_flag, project)

experiment 유형#

experiment 피처 플래그는 GitLab.com에서 A/B 테스트에 사용합니다.

experiment 피처 플래그는 beta 피처 플래그와 같은 기준을 따라야 하지만, 인터페이스에는 차이가 있습니다. experiment 피처 플래그에는 Experiment tracking 템플릿으로 생성한 롤아웃 이슈가 있어야 합니다. 자세한 내용은 실험 가이드에서 확인할 수 있습니다.

제약 조건#

  • default_enabled: true로 설정하면 안 됩니다.
  • 최대 수명: 기본 브랜치에 병합된 후 6개월

worker 유형#

worker 피처 플래그는 Sidekiq job 지연처럼 Sidekiq 워커 동작을 제어하는 데 사용할 수 있는 특수한 ops 플래그입니다.

worker 피처 플래그는 워커 이름 자체로 이름을 동적으로 생성할 수 있어(예: run_sidekiq_jobs_AuthorizedProjectsWorker) YAML 정의가 없는 경우가 많습니다. worker 유형 피처 플래그의 사용 예시는 Sidekiq job 지연에서 확인할 수 있습니다.

markdown_cache 유형#

markdown_cache 피처 플래그는 Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION 상향의 단계적 롤아웃을 이끕니다. worker 플래그와 마찬가지로 YAML 정의가 없으며, 이름은 대상 캐시 버전에서 생성됩니다. 예를 들면 markdown_cache_stochastic_rollout_34 입니다. 상향할 때마다 각자의 플래그를 사용하므로 새 플래그는 비활성화 상태로 시작하고, 이전 상향의 플래그에 설정해 둔 비율은 다음 상향으로 이어지지 않습니다. 롤아웃 절차는 CACHE_COMMONMARK_VERSION 상향의 단계적 롤아웃을 참고합니다.

(사용 중단) development 유형#

development 유형은 gitlab_com_derisk, wip, beta 피처 플래그 유형으로 대체되어 사용이 중단되었습니다.

피처 플래그 정의 및 유효성 검사#

개발(RAILS_ENV=development) 또는 테스트(RAILS_ENV=test) 환경에서는 모든 피처 플래그 사용이 엄격하게 검증됩니다.

이 과정은 코드베이스에서 피처 플래그를 일관되게 사용하도록 보장하기 위한 것입니다. 모든 피처 플래그는 다음 조건을 충족해야 합니다.

  • 알려진 플래그여야 합니다. 명시적으로 정의된 피처 플래그만 사용합니다(experiment, worker, undefined 유형의 피처 플래그는 예외).
  • 두 번 정의하지 않습니다. FOSS 또는 EE 중 한쪽에만 정의해야 하며 양쪽 모두에 정의해서는 안 됩니다.
  • 정의 파일이 없는 피처 플래그는 모든 호출에서 유효하고 일관된 type:을 사용합니다.
  • 소유자가 있어야 합니다.

GitLab이 알고 있는 모든 피처 플래그는 다음 위치의 YAML 파일에 자체 문서화되어 있습니다.

각 피처 플래그는 여러 필드로 구성된 별도의 YAML 파일에 정의됩니다.

필드 필수 여부 설명
name 예 피처 플래그의 이름.
description 예 피처 플래그가 필요한 이유에 대한 짧은 설명.
type 예 피처 플래그의 유형.
default_enabled 예 피처 플래그의 기본 상태.
introduced_by_url 예 피처 플래그를 도입한 머지 리퀘스트의 URL.
milestone 예 피처 플래그를 생성한 마일스톤.
group 예 피처 플래그를 소유한 그룹.
feature_issue_url 아니요 원본 기능 이슈의 URL.
rollout_issue_url 아니요 피처 플래그 롤아웃을 다루는 이슈의 URL.
log_state_changes 아니요 피처 플래그의 상태를 로깅하려면 true로 설정합니다.
Note

RAILS_ENV=production으로 실행할 때는 모든 유효성 검사를 건너뜁니다.

새 피처 플래그 생성#

Note

GitLab Pages는 피처 플래그에 다른 절차를 사용합니다.

GitLab 코드베이스는 bin/feature-flag를 제공합니다. 새 피처 플래그 정의를 생성하는 전용 도구입니다. 이 도구는 새 피처 플래그에 대한 여러 질문을 한 다음 config/feature_flags 또는 ee/config/feature_flags에 YAML 정의를 생성합니다.

YAML 정의 파일이 있는 피처 플래그만 개발 또는 테스트 환경에서 사용할 수 있습니다.

도구가 롤아웃 이슈를 생성할지 물으면 생성된 템플릿을 클립보드에 복사하고, 브라우저에서 New issue 양식을 열고, 이슈 URL을 기다립니다. 도구는 양식을 제출하지 않습니다. 템플릿을 양식에 붙여넣고 제출한 뒤, 프롬프트에 새 이슈 URL을 입력합니다. 롤아웃 이슈는 한 번만 생성합니다. 양식이 열려 있는 동안 API나 다른 도구로 같은 템플릿에서 다른 이슈를 만들면 롤아웃 이슈가 중복되므로 만들지 않습니다.

$ bin/feature-flag my_feature_flag
>> Specify the feature flag type
?> beta
You picked the type 'beta'

>> Specify the group label to which the feature flag belongs, from the following list:
1. group::group1
2. group::group2
?> 2
You picked the group 'group::group2'

>> URL of the original feature issue (enter to skip):
?> https://gitlab.com/gitlab-org/gitlab/-/issues/435435

>> URL of the MR introducing the feature flag (enter to skip and let Danger provide a suggestion directly in the MR):
?> https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141023

>> Username of the feature flag DRI (enter to skip):
?> bob

>> Is this an EE only feature (enter to skip):
?> [Return]

>> Press any key and paste the issue content that we copied to your clipboard! 🚀
?> [Return opens the "New issue" page. Paste the issue content and submit the issue.]

>> URL of the rollout issue (enter to skip):
?> https://gitlab.com/gitlab-org/gitlab/-/issues/437162

create config/feature_flags/beta/my_feature_flag.yml
---
name: my_feature_flag
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/435435
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141023
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/437162
milestone: '16.9'
group: group::composition analysis
type: beta
default_enabled: false

새로 도입하는 피처 플래그는 모두 기본적으로 비활성화해야 합니다.

피처 플래그 뒤에서 개발하고 병합하는 기능에는 변경 로그 항목을 포함하지 않습니다. 해당 항목은 피처 플래그를 제거하는 머지 리퀘스트나 피처 플래그의 기본값을 활성화로 바꾸는 머지 리퀘스트에 추가합니다. 기능에 데이터베이스 마이그레이션이 포함된다면 데이터베이스 변경에 대한 변경 로그 항목을 포함해야 합니다.

Note

EE에서만 사용하는 피처 플래그를 생성하려면 --ee 플래그를 추가합니다: bin/feature-flag --ee

새 플래그 이름 짓기#

새 피처 플래그의 이름을 정할 때는 다음 지침을 고려합니다.

  • 피처 플래그가 담고 있는 기능을 설명합니다.

    • 짧지만 혼란스러운 이름보다 길더라도 설명적인 이름이 낫습니다.
  • _mvc, _alpha, _beta처럼 기능의 상태나 단계를 나타내는 이름은 피합니다.

  • 이름은 스네이크 케이스로 작성합니다(my_cool_feature_flag).

  • 이중 부정으로 생각하거나 문서화하지 않아도 되도록 이름에 disable을 쓰지 않습니다. hide_, remove_, disallow_로 시작하는 이름을 고려합니다.

    소프트웨어 공학에서 이 문제는 "불리언 변수의 부정형 이름"으로 알려져 있습니다. 다만 부정어를 완전히 금지할 수는 없습니다. 플래그를 기본적으로 비활성화된 상태로 도입하거나, 기능을 플래그 뒤로 옮겨 제거하거나, 액터별로 플래그를 선택적으로 비활성화하려면 부정어가 필요합니다.

master(main) 브랜치 손상 위험#

Warning

피처 플래그는 반드시 그것을 도입하는 MR에서 사용해야 합니다. 그렇게 하지 않으면 master 브랜치에서만 실행되는 rspec:feature-flags job 때문에 master 손상 상황이 발생합니다.

피처 플래그 자동 제거를 위한 .patch 파일 선택적 추가#

gitlab-housekeeper는 DeleteOldFeatureFlags keep을 사용해 피처 플래그 코드를 자동으로 제거할 수 있습니다. 이 도구는 주기적으로 실행되어 오래된 피처 플래그를 코드에서 자동으로 정리합니다.

이 도구가 코드에서 피처 플래그 사용처를 자동으로 제거하게 하려면 피처 플래그 YAML 파일 옆에 .patch 파일을 추가할 수 있습니다. 파일 이름은 확장자만 .yml 대신 .patch로 바꾸고 나머지는 완전히 같아야 합니다.

예를 들어 config/feature_flags/beta/my_feature_flag.yml의 패치 파일은 다음 단계로 생성할 수 있습니다.

  1. Git 작업 디렉터리가 깨끗한 상태인지 확인합니다.
  2. config/feature_flags/beta/my_feature_flag.yml을 삭제합니다.
  3. 피처 플래그가 이미 활성화되어 기능이 정식으로 제공되는 것처럼, 로컬에서 코드를 편집해 my_feature_flag 사용처를 모두 제거합니다.
  4. git diff > config/feature_flags/beta/my_feature_flag.patch를 실행합니다. 피처 플래그가 beta 플래그가 아니라면 패치 파일을 해당 피처 플래그를 정의한 YAML 파일과 같은 디렉터리에 둡니다.
  5. config/feature_flags/beta/my_feature_flag.yml 삭제를 되돌립니다.
  6. 피처 플래그 사용처를 제거하려고 편집한 파일의 변경 사항을 되돌립니다.
  7. 피처 플래그를 추가하는 브랜치에 패치 파일을 커밋합니다.

이후 gitlab-housekeeper가 이 패치를 적용해 피처 플래그를 자동으로 정리합니다.

모든 피처 플래그 나열#

ChatOps를 사용해 환경의 모든 피처 플래그를 Slack으로 출력하려면 run feature list 명령을 사용할 수 있습니다. 예를 들면 다음과 같습니다.

/chatops gitlab run feature list --dev
/chatops gitlab run feature list --staging

피처 플래그 토글#

피처 플래그 토글에 대한 자세한 내용은 변경 사항 롤아웃을 참고합니다.

피처 플래그 삭제#

피처 플래그 삭제에 대한 자세한 내용은 피처 플래그 정리를 참고합니다.

ops 피처 플래그를 애플리케이션 설정으로 마이그레이션#

Warning

애플리케이션 설정을 백필하는 변경과 코드에서 그 설정을 사용하는 변경은 같은 마일스톤에 병합해야 합니다.

ops 피처 플래그를 애플리케이션 설정으로 마이그레이션하려면 다음 절차를 따릅니다.

  1. 애플리케이션 설정에서 설정을 저장할 JSONB 칼럼을 새로 만들거나 기존 칼럼을 찾습니다.
  2. 애플리케이션 설정의 기본값은 피처 플래그 YAML 정의의 default_enabled:와 일치해야 합니다
  3. 칼럼을 백필하는 마이그레이션을 작성합니다. 이렇게 하면 기본 동작을 사용하지 않기로 한 인스턴스가 같은 상태를 유지할 수 있습니다. 마이그레이션에서는 Feature.enabled?나 Feature.disabled? 사용을 피합니다. Gitlab::Database::MigrationHelpers::FeatureFlagMigratorHelpers 마이그레이션 헬퍼를 사용합니다. 이 헬퍼는 명시적으로 true 또는 false로 설정된 피처 플래그만 마이그레이션합니다. 피처 플래그가 비율이나 특정 액터로 설정되어 있으면 기본값이 사용됩니다.
  4. Admin 영역에서 기능을 활성화하거나 비활성화하는 설정을 만듭니다.
  5. 모든 곳에서 피처 플래그를 애플리케이션 설정으로 교체합니다.
  6. 관련 문서 페이지를 모두 업데이트합니다. 프론트엔드 변경이 이후 마일스톤에 병합된다면 애플리케이션 설정 API나 Rails 콘솔로 설정을 업데이트하는 방법에 대한 문서를 추가합니다.

JSONB 칼럼의 마이그레이션 예시입니다.

# default_enabled copied from feature flag definition YAML before it is removed
DEFAULT_ENABLED = true

def up
  up_migrate_to_jsonb_setting(feature_flag_name: :my_flag_name,
    setting_name: :my_setting,
    jsonb_column_name: :settings,
    default_enabled: DEFAULT_ENABLED)
end

def down
  down_migrate_to_jsonb_setting(setting_name: :my_setting, jsonb_column_name: :settings)
end

불리언 칼럼의 마이그레이션 예시입니다.

# default_enabled copied from feature flag definition YAML before it is removed
DEFAULT_ENABLED = true

def up
  up_migrate_to_setting(feature_flag_name: :my_flag_name,
    setting_name: :my_setting,
    default_enabled: DEFAULT_ENABLED)
end

def down
  down_migrate_to_setting(setting_name: :my_setting, default_enabled: DEFAULT_ENABLED)
end

피처 플래그로 개발하기#

GitLab 코드베이스에서 피처 플래그를 사용하는 방법은 크게 두 가지입니다.

백엔드#

피처 플래그 인터페이스는 lib/feature.rb에 정의되어 있습니다. 이 인터페이스는 피처 플래그가 활성화되었는지 비활성화되었는지 확인하는 메서드를 제공합니다.

if Feature.enabled?(:my_feature_flag, project)
  # execute code if feature flag is enabled
else
  # execute code if feature flag is disabled
end

if Feature.disabled?(:my_feature_flag, project)
  # execute code if feature flag is disabled
end

설정되지 않은 피처 플래그의 기본 동작은 YAML 정의의 default_enabled:로 제어합니다.

피처 플래그에 YAML 정의가 없으면 개발 또는 테스트 환경에서는 오류가 발생하고, 프로덕션에서는 false를 반환합니다.

정의 파일이 없는 피처 플래그(experiment, worker, undefined 유형에만 허용)는 Feature.enabled?와 Feature.disabled?를 호출할 때 type:을 함께 전달해야 합니다.

if Feature.enabled?(:experiment_feature_flag, project, type: :experiment)
  # execute code if feature flag is enabled
end

if Feature.disabled?(:worker_feature_flag, project, type: :worker)
  # execute code if feature flag is disabled
end
Warning

애플리케이션 로드 시점에는 피처 플래그를 사용하지 않습니다. 예를 들어 config/initializers/*나 클래스 수준에서 Feature 클래스를 사용하면 예기치 않은 오류가 발생할 수 있습니다. 이 오류는 피처 플래그 어댑터가 의존할 수 있는 데이터베이스가 로드 시점에 존재하지 않기 때문에 발생합니다 (특히 새로 설치한 경우). 일부 어댑터는 데이터베이스가 전혀 필요하지 않으므로(예: HTTP 어댑터) 호출 지점에서 데이터베이스 존재 여부를 확인하는 방식은 권장하지 않습니다. 피처 플래그 설정 확인은 Feature 네임스페이스에서 추상화해야 합니다. 또한 이 방식은 피처 플래그가 바뀔 때마다 애플리케이션을 다시 로드해야 합니다. 따라서 프로덕션에서 Web/API/Sidekiq 플릿을 다시 로드해 달라고 SRE에게 요청해야 하며, 변경을 완전히 롤아웃하거나 롤백하는 데 시간이 걸립니다. 이런 이유로 환경 변수(예: ENV['YOUR_FEATURE_NAME'])나 gitlab.yml을 대신 사용합니다.

다음은 피해야 할 패턴의 예시입니다.

class MyClass
  if Feature.enabled?(:...)
    new_process
  else
    legacy_process
  end
end

재귀 감지#

피처 플래그가 많으면 어디에서 호출되는지 항상 분명하지는 않습니다. 한 피처 플래그를 평가할 때 다른 피처 플래그의 평가가 필요한 순환은 피합니다. 순환이 발생하면 순환이 끊기고 기본값이 반환됩니다.

이 재귀 감지가 올바르게 동작하려면 항상 Feature::enabled?로 피처 값에 접근하고 저수준의 Feature::get 사용은 피합니다. 순환이 발생하면 Feature::RecursionError 예외를 오류 추적기로 기록합니다.

프론트엔드#

UI 요소에 피처 플래그를 사용할 때는 해당하는 백엔드 코드가 있다면 그 코드에도 피처 플래그를 함께 사용합니다. 이렇게 하면 기능이 활성화되기 전까지는 어떤 방법으로도 사용할 수 없습니다.

ApplicationController를 상속하는 모든 컨트롤러에서 사용할 수 있는 push_frontend_feature_flag 메서드를 사용합니다. 이 메서드로 피처 플래그의 상태를 노출할 수 있습니다. 예를 들면 다음과 같습니다.

before_action do
  # Prefer to scope it per project or user, for example
  push_frontend_feature_flag(:vim_bindings, project)
end

def index
  # ...
end

def edit
  # ...
end

그런 다음 JavaScript에서 피처 플래그의 상태를 다음과 같이 확인할 수 있습니다.

if ( gon.features.vimBindings ) {
  // ...
}

JavaScript에서 피처 플래그 이름은 항상 camelCase이므로 gon.features.vim_bindings로 확인하면 동작하지 않습니다.

Vue 컴포넌트에서 피처 플래그에 접근하는 방법은 Vue 가이드를 참고합니다.

정의 파일이 없는 피처 플래그(experiment, worker, undefined 유형에만 허용)는 push_frontend_feature_flag를 호출할 때 type:을 함께 전달해야 합니다.

before_action do
  push_frontend_feature_flag(:vim_bindings, project, type: :experiment)
end

피처 액터#

피처 플래그에는 액터를 사용할 것을 강력히 권장합니다. 액터를 쓰면 특정 프로젝트, 그룹, 사용자에 대해서만 피처 플래그를 간단히 활성화할 수 있습니다. 액터를 기준으로 로그와 오류를 필터링할 수 있어 디버깅도 쉬워집니다. 또한 나머지 사용자에게 영향을 주지 않고 gitlab-org나 gitlab-com 그룹에서 먼저 기능을 활성화할 수 있습니다.

액터는 기능을 고정된 방식으로 비율 롤아웃하는 간편한 방법도 제공합니다. 1% 롤아웃에서 특정 액터에 기능이 활성화되었다면 그 액터는 10%, 50%, 100%에서도 계속 활성화된 상태를 유지합니다.

GitLab은 다음 피처 플래그 액터를 지원합니다.

  • User 모델
  • Project 모델
  • Group 모델
  • Ci::Runner 모델
  • 현재 요청

액터는 Feature.enabled? 호출의 두 번째 파라미터입니다. 예를 들면 다음과 같습니다.

Feature.enabled?(:feature_flag, project)

include FeatureGate를 포함한 모델에는 .actor_from_id 클래스 메서드가 있습니다. 모델의 ID가 있고 피처 플래그 상태 확인 외에는 모델이 필요하지 않다면, .actor_from_id를 사용해 모델을 조회하는 데이터베이스 쿼리 없이 피처 플래그 상태를 확인할 수 있습니다.

# Bad -- Unnecessary query is executed
Feature.enabled?(:feature_flag, Project.find(project_id))

# Good -- No query for projects
Feature.enabled?(:feature_flag, Project.actor_from_id(project_id))

# Good -- Project model is used after feature flag check
project = Project.find(project_id)
return unless Feature.enabled?(:feature_flag, project)
project.update!(column: value)

스테이징이나 프로덕션처럼 GitLab이 제공하는 환경에서 ChatOps로 피처 플래그를 선택적으로 활성화하거나 비활성화하는 방법은 ChatOps로 피처 플래그 활성화 및 비활성화를 참고합니다.

플래그 상태는 그룹에서 하위 그룹이나 프로젝트로 상속되지 않습니다. 그룹 계층 전체에서 플래그 상태를 일관되게 유지해야 한다면 최상위 그룹을 액터로 사용하는 방안을 고려합니다. 이 그룹은 어떤 그룹이나 프로젝트에서든 #root_ancestor를 호출해 찾을 수 있습니다.

Feature.enabled?(:feature_flag, group.root_ancestor)

액터 유형 혼용#

일반적으로 특정 피처 플래그의 모든 Feature.enabled? 호출에서는 한 가지 유형의 액터만 사용하고 서로 다른 액터 유형을 섞지 않습니다.

액터 유형을 섞으면 기능이 일관되지 않게 활성화되거나 비활성화되어 버그로 이어질 수 있습니다. 예를 들어 컨트롤러 수준에서는 그룹 액터로 플래그를 확인하고 서비스 수준에서는 사용자 액터로 확인하면, 같은 요청 안에서 기능이 어떤 지점에서는 활성화되고 다른 지점에서는 비활성화될 수 있습니다.

일관되지 않은 결과로 이어지지 않는다는 점이 확실한 상황이라면 액터 유형을 섞어도 안전합니다. 예를 들어 웹훅은 그룹이나 프로젝트 중 하나에 연결될 수 있으므로, 웹훅용 피처 플래그는 이를 활용해 같은 피처 플래그로 그룹 웹훅과 프로젝트 웹훅에 기능을 롤아웃할 수 있습니다.

서로 다른 액터 유형이 필요하고 해당 상황에서 안전하게 섞을 수 없다면 액터 유형마다 별도의 플래그를 사용합니다. 예를 들면 다음과 같습니다.

Feature.enabled?(:feature_flag_group, group)
Feature.enabled?(:feature_flag_user, user)

인스턴스 액터#

Warning

인스턴스 전체 피처 플래그는 기능이 인스턴스 전체와 연결된 경우에만 사용합니다. 항상 다른 액터를 먼저 고려합니다.

기능을 액터 기준이 아니라 인스턴스 전체에 대해 활성화하고 싶을 때가 있습니다. 대표적인 예가 Admin 설정으로, 그룹과 프로젝트가 모두 undefined이기 때문에 이를 기준으로는 피처 플래그를 활성화할 수 없습니다.

사용자 액터를 쓰면 관리자가 아닌 사용자에게는 피처 플래그가 활성화되고 관리자인 사용자에게는 비활성화될 수 있어 혼란을 줍니다.

대신 Feature.enabled?의 두 번째 인자로 :instance 심벌을 사용할 수 있으며, 이는 GitLab 인스턴스로 처리됩니다.

Feature.enabled?(:feature_flag, :instance)

현재 요청 액터#

시간 비율 롤아웃은 호출할 때마다 일관되지 않은 결과를 반환할 수 있으므로 권장하지 않습니다.

대신 현재 요청을 액터로 사용할 것을 권장합니다.

# Bad
Feature.enable_percentage_of_time(:feature_flag, 40)
Feature.enabled?(:feature_flag)

# Good
Feature.enable_percentage_of_actors(:feature_flag, 40)
Feature.enabled?(:feature_flag, Feature.current_request)

현재 요청을 액터로 사용하면 피처 플래그는 요청 컨텍스트 안에서 같은 값을 반환해야 합니다. 현재 요청 액터는 SafeRequestStore로 구현되어 있으므로 다음 범위에서는 피처 플래그 값이 일관됩니다.

  • Rack 요청
  • Sidekiq 워커 실행
  • ActionCable 워커 실행

기존 기능을 시간 비율에서 현재 요청 액터로 마이그레이션할 때는 새 피처 플래그를 만들 것을 권장합니다. 기존 percentage_of_time 값, 코드 변경 배포, percentage_of_actors로 전환하는 시점 사이의 타이밍을 제어하기 어렵기 때문입니다.

프로덕션 검증에 액터 사용#

Warning

프로덕션을 테스트 환경으로 사용하는 방식은 권장하지 않습니다. 프로덕션에 적용할 준비가 되지 않은 기능은 테스트 환경에서 테스트합니다.

스테이징 환경은 프로덕션과 비슷한 환경에서 기능을 테스트할 수 있게 해 주지만, 프로덕션 환경에 고유한 성능 지표를 적용 전후로 비교할 수는 없습니다. 개발용 피처 플래그를 활성화한 프로젝트를 프로덕션에 두면 Sitespeed 리포트 같은 도구로 피처 플래그 뒤에 있는 새 코드의 지표를 확인할 수 있어 유용합니다.

이미 Sitespeed에서 기존 코드베이스를 추적하고 있다면 피처 플래그 롤아웃 전후의 성능을 정확하게 비교할 수 있으므로 이 접근 방식이 더욱 유용합니다.

추가 객체를 액터로 사용하도록 설정#

액터 기반 피처 게이트를 사용하려면 모델이 flipper_id에 응답해야 합니다. 예를 들어 Foo 모델에 대해 활성화하려면 다음과 같이 작성합니다.

class Foo < ActiveRecord::Base
  include FeatureGate
end

include FeatureGate를 포함하거나 flipper_id 메서드를 노출하는 모델만 Feature.enabled?의 액터로 사용할 수 있습니다.

라이선스 기능을 위한 피처 플래그#

라이선스 기능 이름과 같은 이름의 피처 플래그는 이름 충돌을 일으키므로 사용할 수 없습니다. 이 방식은 폭넓게 논의된 끝에 제거되었습니다. 혼란을 주기 때문입니다.

라이선스 기능을 확인하려면 다른 이름으로 전용 피처 플래그를 추가하고 명시적으로 확인합니다. 예를 들면 다음과 같습니다.

Feature.enabled?(:licensed_feature_feature_flag, project) &&
  project.feature_available?(:licensed_feature)

피처 그룹#

피처 그룹은 lib/feature.rb의 .register_feature_groups 메서드에 정적으로 정의해야 하지만, 구현은 동적일 수 있습니다 (예: 데이터베이스 조회).

lib/feature.rb에 정의한 뒤에는 기능 API의 feature_group 파라미터로 특정 피처 그룹에 대해 기능을 활성화할 수 있습니다

사용할 수 있는 피처 그룹은 다음과 같습니다.

그룹 이름 범위 설명
gitlab_team_members 사용자 gitlab-com의 멤버인 사용자에게 기능을 활성화합니다

피처 그룹은 그룹 이름으로 활성화할 수 있습니다.

Feature.enable(:feature_flag_name, :gitlab_team_members)

로컬에서 피처 플래그 제어#

Rails 콘솔에서#

Rails 콘솔(rails c)에서 다음 명령을 입력해 피처 플래그를 활성화합니다.

Feature.enable(:feature_flag_name)

마찬가지로 다음 명령은 피처 플래그를 비활성화합니다.

Feature.disable(:feature_flag_name)

특정 게이트에 대해 피처 플래그를 활성화할 수도 있습니다.

Feature.enable(:feature_flag_name, Project.find_by_full_path("root/my-project"))

Rails 콘솔에서 피처 플래그를 수동으로 활성화하거나 비활성화하면 기본값이 덮어써집니다. 이 때문에 플래그의 default_enabled 속성을 변경할 때 혼란이 생길 수 있습니다.

피처 플래그를 기본 상태로 되돌리려면 다음을 실행합니다.

Feature.remove(:feature_flag_name)

모든 피처 플래그를 YAML 정의의 기본 상태로 되돌리려면 다음을 실행합니다.

Feature.all.each(&:remove)

브라우저에서#

http://gdk.test:3000/rails/features에 접속해 로컬에서 피처 플래그를 확인하고 관리합니다.

로깅#

코드에서 어떤 플래그에 대해 Feature.enabled?나 Feature.disabled?를 호출하면 GitLab은 요청당 한 번 확인된 상태를 기록합니다. 이 상태는 구조화 로그에 feature_flag_states 배열로 나타나며, 활성화는 my_feature_flag:1, 비활성화는 my_feature_flag:0 같은 항목으로 표시됩니다. 이 필드는 production_json.log, api_json.log, 예외 로그(exception.feature_flag_states), Sentry 오류 이벤트에서 확인할 수 있습니다.

GitLab은 다음 두 조건이 모두 충족될 때만 플래그의 상태를 기록합니다.

  • 해당 요청에 대해 feature_flag_state_logs ops 피처 플래그가 활성화되어 있습니다. 이 플래그는 기본적으로 비활성화되어 있으며, GitLab.com뿐 아니라 모든 GitLab 인스턴스에 적용됩니다.
  • 플래그 정의에 log_state_changes: true가 설정되어 있거나, 플래그의 milestone이 현재 GitLab 버전 이상입니다.

두 번째 조건 때문에 GitLab은 플래그의 마일스톤이 현재이거나 미래인 동안 플래그 상태를 자동으로 기록합니다. 마일스톤이 지난 뒤에도 로그가 필요하다면 플래그 정의 파일에 log_state_changes: true를 설정합니다. 예를 들어 롤아웃이 길어지는 동안이나, 플래그와 관련이 있다고 의심되는 문제를 디버깅하는 동안 설정합니다.

이 로그로 다음과 같은 내용을 확인할 수 있습니다.

  • 오류나 Sentry 이벤트를 발생시킨 요청에서 플래그가 활성화되어 있었는지 비활성화되어 있었는지.
  • 비율 롤아웃이 실제 트래픽에 도달하는지, 그리고 그 비율이 어느 정도인지.
  • 플래그가 활성화된 요청이 비활성화된 요청과 다르게 동작하는지, 예를 들어 요청 처리 시간에서 차이가 있는지.
  • 피처 플래그 변경이 데이터베이스 요청 수나 데이터베이스 호출 시간 같은 데이터베이스 지표에 미칠 수 있는 영향.
Note

GitLab.com에서는 요청의 20%에 대해 feature_flag_state_logs를 활성화합니다.

Kibana에서 피처 플래그 상태 검색#

피처 플래그의 상태가 기록되면 Kibana에서 "json.feature_flag_states": "feature_flag_name:1" 또는 "json.feature_flag_states": "feature_flag_name:0" 조건으로 찾을 수 있습니다. 저장된 시각화 모음은 Kibana의 FF Observability 컬렉션을 참고합니다.

이 검색은 log.gprd.gitlab.net에서 제공되는 GitLab.com 로그에서 동작합니다.

변경 로그#

최종 사용자가 직접(예: 기능을 사용할 수 있는 능력) 또는 간접적으로(예: 백그라운드 job의 이점, 성능 개선, 데이터베이스 마이그레이션 업데이트) 접근할 수 없는 기능에는 변경 로그를 도입하지 않습니다.

  • 데이터베이스 마이그레이션은 GitLab Self-Managed 고객이 업그레이드 전에 데이터베이스 변경을 알아야 하므로 항상 최종 사용자가 간접적으로 접근합니다. 따라서 변경 로그 항목이 있어야 합니다.

  • 기본적으로 비활성화된 피처 플래그 뒤의 변경에는 변경 로그 항목을 두지 않습니다.

  • 기본적으로 활성화된 피처 플래그 뒤의 변경에는 변경 로그 항목이 있어야 합니다.

  • 피처 플래그 자체의 변경(플래그 제거, 기본 활성화 설정)에는 변경 로그 항목이 있어야 합니다. 변경 로그 항목 유형은 플로차트로 판단합니다.

    Mermaid 다이어그램 (18줄)
    소스 코드 보기
    flowchart LR
      FDOFF(Flag is currently<br>'default: off')
      FDON(Flag is currently<br>'default: on')
      CDO{Change to<br>'default: on'}
      ACF(added / changed / fixed / '...')
      RF{Remove flag}
      RF2{Remove flag}
      RC(removed / changed)
      OTHER(other)
    

    FDOFF -->CDO-->ACF FDOFF -->RF RF-->|Keep new code?| ACF RF-->|Keep old code?| OTHER

    FDON -->RF2 RF2-->|Keep old code?| RC RF2-->|Keep new code?| OTHER

  • 피처 플래그의 변경 로그는 플래그가 아니라 기능을 설명해야 합니다. 다만 기본 활성화 피처 플래그를 제거하면서 새 코드를 유지하는 경우는 예외입니다(위 플로차트의 other).

  • 피처 플래그는 버그 수정이나 유지 보수 작업을 롤아웃하는 데도 사용할 수 있습니다. 이 경우 변경 로그는 그에 맞춰야 합니다. 예를 들면 fixed나 other입니다.

테스트에서의 피처 플래그#

코드베이스에 피처 플래그를 도입하면 테스트해야 할 코드 경로가 하나 더 생깁니다. 피처 플래그의 영향을 받는 모든 코드에 대해 활성화와 비활성화 두 경우 모두 자동화 테스트를 포함합니다. 다만 테스트에서는 플래그가 기본적으로 활성화되므로 활성화 상태가 기본 시나리오이며, 스텁이나 전용 컨텍스트가 필요하지 않습니다. 비활성화 상태는 플래그를 false로 스텁하는 독립된 별도 컨텍스트에서 다룹니다. 그러면 나중에 플래그를 제거할 때 한 번의 삭제로 끝납니다(아래 패턴 참고). 두 상태 모두에 대한 자동화 테스트를 포함하지 않는다면, 테스트되지 않은 코드 경로와 관련된 기능은 프로덕션에 배포하기 전에 수동으로 테스트해야 합니다.

테스트 환경에서는 모든 피처 플래그가 기본적으로 활성화됩니다. spec/spec_helper.rb 파일에서 플래그를 기본적으로 비활성화할 수 있습니다. 플래그를 비활성화해야 하는 이유는 인라인 주석으로 설명합니다. 가능하다면 참고용으로 이슈 URL도 함께 첨부할 수 있습니다.

Warning

이 내용은 피처 플래그를 기본적으로 활성화하지 않는 End-to-end(QA) 테스트에는 적용되지 않습니다. End-to-end 테스트에는 별도의 피처 플래그 사용 절차가 있습니다.

테스트에서 피처 플래그를 비활성화하려면 stub_feature_flags 헬퍼를 사용합니다. 예를 들어 테스트에서 ci_live_trace 피처 플래그를 전역으로 비활성화하려면 다음과 같이 작성합니다.

stub_feature_flags(ci_live_trace: false)

Feature.enabled?(:ci_live_trace) # => false

두 경로를 모두 테스트하는 일반적인 패턴은 다음과 같습니다.

it 'ci_live_trace works' do
  # tests assuming ci_live_trace is enabled in tests by default
  Feature.enabled?(:ci_live_trace) # => true
end

context 'when ci_live_trace is disabled' do
  before do
    stub_feature_flags(ci_live_trace: false)
  end

  it 'ci_live_trace does not work' do
    Feature.enabled?(:ci_live_trace) # => false
  end
end

일부 액터에만 피처 플래그가 활성화된 테스트를 구성하려면 헬퍼에 전달하는 옵션으로 지정할 수 있습니다. 예를 들어 특정 프로젝트에 대해 ci_live_trace 피처 플래그를 활성화하려면 다음과 같이 작성합니다.

project1, project2 = build_list(:project, 2)

# Feature will only be enabled for project1
stub_feature_flags(ci_live_trace: project1)

Feature.enabled?(:ci_live_trace) # => false
Feature.enabled?(:ci_live_trace, project1) # => true
Feature.enabled?(:ci_live_trace, project2) # => false

FlipperGate의 동작은 다음과 같습니다.

  1. 지정한 액터에 대해 활성화 재정의를 설정할 수 있습니다.
  2. 지정한 액터에 대한 재정의를 비활성화(제거)해 기본 상태로 되돌릴 수 있습니다.
  3. 지정한 액터를 명시적으로 비활성화했다고 표현할 방법은 없습니다.
Feature.enable(:my_feature)
Feature.disable(:my_feature, project1)
Feature.enabled?(:my_feature) # => true
Feature.enabled?(:my_feature, project1) # => true

Feature.disable(:my_feature2)
Feature.enable(:my_feature2, project1)
Feature.enabled?(:my_feature2) # => false
Feature.enabled?(:my_feature2, project1) # => true

have_pushed_frontend_feature_flags#

push_frontend_feature_flag이 HTML에 피처 플래그를 추가했는지 테스트하려면 have_pushed_frontend_feature_flags를 사용합니다.

예를 들면 다음과 같습니다.

stub_feature_flags(value_stream_analytics_path_navigation: false)

visit group_analytics_cycle_analytics_path(group)

expect(page).to have_pushed_frontend_feature_flags(valueStreamAnalyticsPathNavigation: false)

stub_feature_flags 대 Feature.enable*#

테스트 환경에서는 모든 피처 플래그가 기본적으로 활성화되므로, 보통은 stub_feature_flags로 플래그를 비활성화하거나 특정 액터에 대해서만 활성화합니다. 이 메서드는 이러한 사용 사례에 간단하고 명확한 인터페이스를 제공합니다.

# Good: disable the flag to test the disabled code path
stub_feature_flags(my_feature: false)

# Good: enable the flag only for specific actors, leaving it disabled elsewhere
stub_feature_flags(my_feature: project)
stub_feature_flags(my_feature: [project, project2])

# Redundant: the flag is already enabled by default in tests, so this has no
# effect unless the flag was disabled by default in spec/spec_helper.rb
stub_feature_flags(my_feature: true)

비율 롤아웃처럼 더 복잡한 동작에는 stub_feature_flags 대신 .enable_percentage_of_time이나 .enable_percentage_of_actors를 사용합니다.

# Bad: prefer stub_feature_flags for simple enable/disable
Feature.enable(:my_feature_2)

# Good: enable my_feature for 50% of time
Feature.enable_percentage_of_time(:my_feature_3, 50)

# Good: enable my_feature for 50% of actors/gates/things
Feature.enable_percentage_of_actors(:my_feature_4, 50)

상태가 정의된 각 피처 플래그는 테스트 실행 중에 영속화됩니다.

Feature.persisted_names.include?('my_feature') => true
Feature.persisted_names.include?('my_feature_2') => true
Feature.persisted_names.include?('my_feature_3') => true
Feature.persisted_names.include?('my_feature_4') => true

액터 스텁#

특정 액터에 대해서만 피처 플래그를 활성화하려면 그 액터의 표현을 스텁할 수 있습니다. Feature.enabled?와 Feature.disabled?에 인자로 전달하는 게이트는 FeatureGate를 포함한 객체여야 합니다.

스펙에서는 커스텀 액터를 빠르게 만들 수 있는 stub_feature_flag_gate 메서드를 사용할 수 있습니다.

gate = stub_feature_flag_gate('CustomActor')

stub_feature_flags(ci_live_trace: gate)

Feature.enabled?(:ci_live_trace) # => false
Feature.enabled?(:ci_live_trace, gate) # => true

테스트에서 피처 플래그 엔진 제어#

테스트 환경의 Flipper 엔진은 메모리 모드 Flipper::Adapters::Memory로 동작합니다. production과 development 모드는 Flipper::Adapters::ActiveRecord를 사용합니다.

Flipper::Adapters::Memory 모드를 쓸지 ActiveRecord 모드를 쓸지 제어할 수 있습니다.

stub_feature_flags: true(기본값이자 권장 방식)#

이 모드에서 Flipper는 Flipper::Adapters::Memory를 사용하도록 구성되며, 모든 피처 플래그를 기본 활성화로 표시하고 처음 사용할 때 영속화합니다.

피처 플래그 뒤의 동작이 특정되지 않은 컨텍스트에서 테스트되지 않은 채 남지 않도록 합니다.

stub_feature_flags: false#

이 설정은 메모리로 스텁된 flipper를 비활성화하고, production과 development에서 사용하는 Flipper::Adapters::ActiveRecord 모드를 사용합니다.

이 모드는 Flipper가 ActiveRecord와 상호작용하는 측면을 실제로 테스트해야 할 때만 사용합니다.

End-to-end(QA) 테스트#

End-to-end(QA) 테스트에서는 피처 플래그 토글이 다르게 동작합니다. End-to-end 테스트 프레임워크는 Rails나 데이터베이스에 직접 접근할 수 없어 Flipper를 사용할 수 없습니다. 대신 공개 API를 사용합니다. 각 End-to-end 테스트는 테스트 도중 피처 플래그를 활성화하거나 비활성화할 수 있습니다. 또는 GitLab 리포지터리의 qa 디렉터리에서 테스트를 실행하거나 GitLab QA로 테스트를 실행할 때 하나 이상의 테스트 전에 피처 플래그를 활성화하거나 비활성화할 수 있습니다.

앞에서 설명했듯이 End-to-end 테스트에서는 피처 플래그가 기본적으로 활성화되지 않습니다. 따라서 End-to-end 테스트는 피처 플래그를 명시적으로 활성화하거나 비활성화하도록 작성하지 않는 한, 소스 코드에 구현된 기본 상태의 피처 플래그나 테스트 대상 GitLab 인스턴스의 현재 상태로 실행됩니다.

스테이징이나 GitLab.com에서 피처 플래그가 변경되면 #e2e-run-staging 또는 #e2e-run-production 채널에 Slack 메시지가 게시되어 파이프라인 트리아지 DRI가 실패와 피처 플래그 변경의 관련성을 더 쉽게 판단할 수 있습니다. 다만 변경 작업을 진행 중이라면 피처 플래그를 활성화한 상태에서 End-to-end 테스트가 통과하는지 확인해 예기치 않은 실패를 줄일 수 있습니다.

피처 플래그로 Sidekiq 워커 동작 제어#

worker 유형 피처 플래그로 Sidekiq 워커의 동작을 제어할 수 있습니다.

Sidekiq job 지연#

run_sidekiq_jobs_{WorkerName} 형식의 피처 플래그를 비활성화하면 job을 나중 시각으로 예약해 워커 실행을 지연합니다. 이 피처 플래그는 모든 워커에 대해 기본적으로 활성화되어 있습니다. 워커 인스턴스의 경합 동작이 인프라 자원(데이터베이스와 데이터베이스 커넥션 풀 등)을 포화시키는 인시던트 상황에서 job 지연이 유용할 수 있습니다. 구현은 SkipJobs Sidekiq 서버 미들웨어에서 확인할 수 있습니다.

Note

피처 플래그가 비활성화되어 있는 동안 job은 무기한 지연됩니다. 워커가 계속 처리해도 안전하다고 판단되면 피처 플래그를 제거해야 합니다.

false로 설정하면 job의 100%가 지연됩니다. 처리를 재개하려면 시간 비율 롤아웃을 사용할 수 있습니다. 예를 들면 다음과 같습니다.

# not running any jobs, deferring all 100% of the jobs
/chatops gitlab run feature set run_sidekiq_jobs_SlowRunningWorker false

# only running 10% of the jobs, deferring 90% of the jobs
/chatops gitlab run feature set run_sidekiq_jobs_SlowRunningWorker 10

# running 50% of the jobs, deferring 50% of the jobs
/chatops gitlab run feature set run_sidekiq_jobs_SlowRunningWorker 50

# back to running all jobs normally
/chatops gitlab run feature delete run_sidekiq_jobs_SlowRunningWorker

Sidekiq job 삭제#

job을 지연하는 대신 drop_sidekiq_jobs_{WorkerName} 피처 플래그를 활성화해 job을 완전히 삭제할 수도 있습니다. 앞으로도 job을 처리할 필요가 없어 삭제해도 안전하다고 확신할 때 이 피처 플래그를 사용합니다.

# drop all the jobs
/chatops gitlab run feature set drop_sidekiq_jobs_SlowRunningWorker true

# process jobs normally
/chatops gitlab run feature delete drop_sidekiq_jobs_SlowRunningWorker
Note

삭제 피처 플래그(drop_sidekiq_jobs_{WorkerName})가 지연 피처 플래그(run_sidekiq_jobs_{WorkerName})보다 우선합니다. drop_sidekiq_jobs가 활성화되고 run_sidekiq_jobs가 비활성화되면 job은 완전히 삭제됩니다.

피처 플래그 이벤트#

Feature.enable이나 Feature.disable이 호출되면 Gitlab::FeatureFlags::FeatureFlagModifiedEvent가 발행됩니다.

[!NOTE] 이벤트는 Feature.enable이나 Feature.disable이 상태 변경이 일어났음을 나타내는 true를 반환할 때만 발행됩니다. 비율 기반 롤아웃 메서드(Feature.enable_percentage_of_actors, Feature.enable_percentage_of_time)는 이벤트를 발행하지 않습니다.

이벤트에는 다음 내용이 포함됩니다.

  • feature_key(String): 피처 플래그의 이름.
  • operation(String): 변경 유형. Feature::OPERATION_ENABLED_GLOBALLY, Feature::OPERATION_DISABLED_GLOBALLY, Feature::OPERATION_ENABLED_ACTOR, Feature::OPERATION_DISABLED_ACTOR 중 하나.
  • actor(String 또는 nil): 이 작업의 영향을 받는 특정 액터(예: "User:123", "Group:456"). 전역 작업에서는 nil.
  • state(String): 작업 후 피처 플래그의 현재 상태("on", "off", "conditional").

[!NOTE] actor 필드에는 현재 작업의 영향을 받는 액터 하나만 들어가며, 플래그에 활성화된 모든 액터가 들어가지는 않습니다. 액터 전체 목록을 얻으려면 Feature.get(:flag_name)으로 데이터베이스를 조회합니다.

특정 피처 플래그 구독#

워커는 조건부 필터로 특정 피처 플래그를 구독해야 합니다. 이렇게 하면 GitLab의 모든 피처 플래그 변경마다 워커가 큐에 쌓이는 일을 막을 수 있습니다. 또한 같은 작성자가 피처 플래그를 활성화하거나 비활성화하는 호출을 여러 번 하면 이벤트가 여러 개 발생할 수 있으므로 워커는 멱등해야 합니다.

피처 플래그 이벤트를 구독하려면 다음 절차를 따릅니다.

  1. Gitlab::EventStore::Subscriber를 포함하는 워커를 만듭니다.

    module MyFeature
      class MyFeatureFlagWorker
        include ApplicationWorker
        include Gitlab::EventStore::Subscriber
    
        data_consistency :always
        feature_category :your_category
        urgency :low
    
        idempotent!
    
        def handle_event(event)
          feature_key = event.data[:feature_key]
          operation = event.data[:operation]
          actor = event.data[:actor]
    
          case operation
          when Feature::OPERATION_ENABLED_ACTOR
            # Handle actor-specific enable
          when Feature::OPERATION_DISABLED_ACTOR
            # Handle actor-specific disable
          when Feature::OPERATION_ENABLED_GLOBALLY
            # Handle global enable
          when Feature::OPERATION_DISABLED_GLOBALLY
            # Handle global disable
          end
        end
      end
    end
    
  2. lib/gitlab/event_store/subscriptions/feature_subscriptions.rb에 구독을 등록합니다.

    def register
      # Subscribe to all changes for a specific feature flag
      store.subscribe ::MyFeature::MyFeatureFlagWorker,
        to: ::Gitlab::FeatureFlags::FeatureFlagModifiedEvent,
        if: ->(event) { event.data[:feature_key] == 'my_specific_flag' }
    
      # Subscribe to multiple feature flags
      store.subscribe ::MyFeature::MyFeatureFlagWorker,
        to: ::Gitlab::FeatureFlags::FeatureFlagModifiedEvent,
        if: ->(event) { %w[flag_one flag_two].include?(event.data[:feature_key]) }
    
      # Only trigger for actor-specific enables
      store.subscribe ::MyFeature::MyFeatureFlagWorker,
        to: ::Gitlab::FeatureFlags::FeatureFlagModifiedEvent,
        if: ->(event) do
          event.data[:feature_key] == 'my_specific_flag' &&
            event.data[:operation] == Feature::OPERATION_ENABLED_ACTOR
        end
    
      # Only trigger for global enables
      store.subscribe ::MyFeature::MyFeatureFlagWorker,
        to: ::Gitlab::FeatureFlags::FeatureFlagModifiedEvent,
        if: ->(event) do
          event.data[:feature_key] == 'my_specific_flag' &&
            event.data[:operation] == Feature::OPERATION_ENABLED_GLOBALLY
        end
    end
    

액터 형식#

actor 필드에는 작업의 영향을 받는 특정 액터의 Flipper ID 문자열이 들어갑니다. 전역 작업(OPERATION_ENABLED_GLOBALLY 또는 OPERATION_DISABLED_GLOBALLY)에서는 actor 필드가 nil입니다.

일반적인 액터 형식은 다음과 같습니다.

  • User:123 - ID가 123인 사용자
  • Project:456 - ID가 456인 프로젝트
  • Group:789 - ID가 789인 그룹
  • Namespace:101 - ID가 101인 네임스페이스
  • Ci::Runner:202 - ID가 202인 CI 러너
  • Organizations::Organization:303 - ID가 303인 조직

액터 ID를 파싱하려면 다음과 같이 작성합니다.

actor = event.data[:actor]
# => "Group:456"

return if actor.nil? # Global operation

actor_type, actor_id = actor.split(':', 2)

case actor_type
when 'User'
  user = User.find(actor_id)
  # Process user
when 'Group'
  group = Group.find(actor_id)
  # Process group
when 'Project'
  project = Project.find(actor_id)
  # Process project
end