InfoGrab Docs

셀(Cell)에 속성 클레임하기

요약

이 기능이 적용되려면 cells와 피처 플래그 Feature.enabled?(:cells_unique_claims) 모두 활성화되어야 합니다. 또한 개별 모델 클레임은 모델별 피처 플래그로 제어됩니다. 일부 속성은 전체 클러스터에서 전역적으로 고유해야 합니다.

Feature flag

이 기능이 적용되려면 cells와 피처 플래그 Feature.enabled?(:cells_unique_claims) 모두 활성화되어야 합니다.

또한 개별 모델 클레임은 모델별 피처 플래그로 제어됩니다. 전체 목록은 피처 플래그를 참조하세요.

속성 클레임이 필요한 이유#

일부 속성은 전체 클러스터에서 전역적으로 고유해야 합니다. 예를 들어, 라우팅 목적으로 특정 URL 또는 식별자가 최대 하나의 셀에 속하도록 보장하여 해당 셀로 라우팅할 수 있어야 합니다.

각 셀은 자체 데이터베이스를 가지고 있으며, 다른 데이터베이스에 걸쳐 고유 제약을 강제할 수 없습니다. 따라서, 이러한 속성이 고유하도록 클러스터 전체 데이터베이스가 필요합니다.

이러한 속성에 대해, 속성이 특정 셀에 속한다는 것을 클레임하기 위해 토폴로지 서비스와 통신합니다. 한번 클레임되면 다른 셀은 동일한 속성을 클레임할 수 없습니다.

클레임할 속성 결정#

속성이 다음에 해당하는지 고려하세요:

  • 라우팅에 사용되는가?
    • URL에 사용되는가?
    • REST API에 사용되는가?
    • GraphQL API에 사용되는가?
  • 로그인에 사용되는가?

롤아웃 라이프사이클#

새 속성을 클레임하려면 두 단계가 필요합니다. 각 단계에는 자체 피처 플래그가 있으며 각각 고유한 목적을 제공합니다.

1단계: 라이브 요청 클레임#

모델에 Cells::Claimable 컨선(concern)을 추가하고 모델별 피처 플래그를 생성합니다. 활성화되면 Rails after_savebefore_destroy 콜백이 모든 생성, 업데이트, 삭제에 대해 토폴로지 서비스에서 속성을 클레임하고 해제합니다.

이 단계는 새 쓰기만 처리합니다. 데이터베이스의 기존 레코드는 2단계까지 클레임되지 않습니다.

모델 구성 방법에 대한 자세한 내용은 속성 클레임 방법을 참조하세요.

Note

Cells::Claimable 컨선은 ActiveRecord 콜백에 의존합니다. delete_all, insert_all, upsert_all 또는 원시 SQL을 사용하는 코드 경로는 이러한 콜백을 우회합니다. 이러한 코드 경로의 경우 데이터베이스 트랜잭션 외부에서 클레임을 처리하기 위해 Cells::BulkClaimsWorker를 사용합니다. 자세한 내용과 기존 패턴은 ActiveRecord를 우회하는 코드 경로에 대한 벌크 클레임을 참조하세요.

2단계: 백필 및 검증#

검증 워커 피처 플래그(cells_claims_verification_worker_<model_name>)를 활성화하여 검증 서비스를 시작합니다. 첫 실행 시 서비스는 모델의 모든 로컬 레코드를 스캔하고, 토폴로지 서비스에서 일치하는 클레임을 찾지 못하면 클레임을 생성합니다. 이것이 기존 데이터의 백필 역할을 합니다.

백필이 완료된 후 검증 서비스는 크론 스케줄에서 계속 실행됩니다. 로컬 레코드와 토폴로지 서비스 클레임을 조정하여 누락된 클레임, 고아 클레임 또는 변경된 값과 같은 드리프트를 감지하고 수정합니다.

검증에 대한 자세한 내용은 검증 및 백필을 참조하세요.

롤아웃 소유권#

기능 소유 팀이 두 단계의 롤아웃을 소유합니다. 여기에는 피처 플래그 생성, 활성화, 활성화 후 클레임이 올바르게 작동하는지 모니터링이 포함됩니다.

셀 인프라 팀은 지원을 제공할 수 있지만, 롤아웃의 소유권과 정확성 보장은 기능 소유 팀에 속합니다.

피처 플래그#

클레임 시스템은 세밀한 제어를 위한 계층적 피처 플래그 구조를 사용합니다:

전역 피처 플래그#

피처 플래그 설명
cells_unique_claims 전체 클레임 시스템의 기본 스위치. 클레임이 작동하려면 활성화되어 있어야 합니다.

모델별 피처 플래그#

각 클레임 가능한 모델 유형은 독립적인 롤아웃을 허용하는 자체 피처 플래그를 가지고 있습니다:

피처 플래그 모델 설명
cells_claims_users User 사용자 ID 및 사용자 이름 클레임 제어
cells_claims_emails Email 이메일 주소 클레임 제어
cells_claims_organizations Organization 조직 경로 클레임 제어
cells_claims_namespaces Namespace, Group, UserNamespace 네임스페이스/그룹 ID 클레임 제어
cells_claims_projects Project 프로젝트 ID 클레임 제어
cells_claims_routes Route, RedirectRoute 라우트 및 리다이렉트 라우트 경로 클레임 제어
cells_claims_keys Key, GpgKey, DeployKey SSH, GPG 및 Deploy 키 클레임 제어
cells_claims_service_desk_settings ServiceDeskSetting Service Desk 커스텀 이메일 클레임 제어

검증 워커 피처 플래그#

각 모델에는 검증 워커를 위한 별도의 피처 플래그가 있습니다:

피처 플래그 설명
cells_claims_verification_worker_<model_name> 특정 모델에 대해 검증 워커를 실행할지 여부를 제어합니다. <model_name>을 param_key로 교체하세요. 예: cells_claims_verification_worker_user

클레임 활성화#

특정 모델에 대한 클레임을 활성화하려면 전역 플래그와 모델별 플래그 모두 활성화되어 있어야 합니다:

# Rails 콘솔에서

# 1. 전역 클레임 시스템 활성화
Feature.enable(:cells_unique_claims)

# 2. 특정 모델에 대한 클레임 활성화
Feature.enable(:cells_claims_users)
Feature.enable(:cells_claims_emails)
Feature.enable(:cells_claims_organizations)

# 3. 백필 및 지속적 일관성을 위해 검증 워커 활성화
Feature.enable(:cells_claims_verification_worker_user)
Feature.enable(:cells_claims_verification_worker_email)

# 모든 cells claims 피처 플래그 확인
Feature.all.select { |f| f.name.start_with?('cells_claims') }

속성 클레임 방법#

각 속성에 대해 세 가지를 클레임합니다:

  • 속성의 값 (typefeature_flag 매개변수가 있는 cells_claims_attribute로 정의됨)
  • 레코드의 주체 (cells_claims_metadata로 정의됨)
  • 레코드의 소스 (cells_claims_metadata로 정의됨)

[!note] 모든 cells_claims_attributetype(버킷 유형)과 feature_flag(모델별 제어 플래그)을 모두 지정해야 합니다.

Rails#

User를 예로 사용합니다:

class User < ApplicationRecord
  include Cells::Claimable

  cells_claims_attribute :id, type: CLAIMS_BUCKET_TYPE::USER_IDS, feature_flag: :cells_claims_users
  cells_claims_attribute :username, type: CLAIMS_BUCKET_TYPE::USERNAMES, feature_flag: :cells_claims_users

  cells_claims_metadata subject_type: CLAIMS_SUBJECT_TYPE::USER, subject_key: :id
end

먼저, 모델에 Cells::Claimable을 포함합니다.

여기서 idusername 두 속성을 클레임합니다. 각 속성에는 다음이 필요합니다:

  • type(버킷 유형) - 토폴로지 서비스에 정의됩니다(아래 설명)
  • 이 클레임이 활성화될 때를 제어하는 feature_flag (네이밍 규칙: cells_claims_<model>s)

다음으로, cells_claims_metadata로 메타데이터를 정의합니다. 일반적으로 subject_typesubject_key만 설정하면 됩니다; source_type과 소스 값은 자동으로 추론됩니다. 이것들도 토폴로지 서비스에 정의되어 있어야 합니다.

subject_typesubject_key는 클레임된 속성을 소유하는 레코드를 식별합니다. 이것은 종종 샤딩 키와 일치하지만 항상 그런 것은 아닙니다. 샤딩 키가 적용되지 않을 때는 판단을 사용하세요.

Note

연관 관계에 대한 변경도 저장 시 동일한 트랜잭션에서 자동으로 클레임됩니다.

새 클레임 가능한 모델 추가#

새 모델에 클레임을 추가할 때:

  1. 모델에 대한 피처 플래그 생성 (없는 경우):

    # config/feature_flags/beta/cells_claims_<model>s.yml
    ---
    name: cells_claims_<model>s
    feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/XXX
    introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/XXX
    rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/tenant-scale/cells-infrastructure/team/-/issues/XXX
    milestone: 'XX.X'
    group: group::cells infrastructure
    type: beta
    default_enabled: false
    
  2. 검증 워커에 대한 피처 플래그 생성:

    # config/feature_flags/beta/cells_claims_verification_worker_<model_name>.yml
    ---
    name: cells_claims_verification_worker_<model_name>
    feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/XXX
    introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/XXX
    rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/tenant-scale/cells-infrastructure/team/-/issues/XXX
    milestone: 'XX.X'
    group: group::cells infrastructure
    type: beta
    default_enabled: false
    
  3. 모델에 클레임 구성 추가:

    class YourModel < ApplicationRecord
      include Cells::Claimable
    
      cells_claims_attribute :id, type: CLAIMS_BUCKET_TYPE::YOUR_MODEL_IDS, feature_flag: :cells_claims_your_model
      cells_claims_attribute :unique_attr, type: CLAIMS_BUCKET_TYPE::YOUR_MODEL_ATTRS, feature_flag: :cells_claims_your_model
    
      cells_claims_metadata subject_type: CLAIMS_SUBJECT_TYPE::YOUR_MODEL, subject_key: :id
    end
    
  4. 토폴로지 서비스에 유형 추가 (토폴로지 서비스 섹션 참조)

  5. ActiveRecord를 우회하는 코드 경로 감사 (ActiveRecord를 우회하는 코드 경로에 대한 벌크 클레임 참조)

  6. 테스트 추가 (테스트 섹션 참조)

특정 값에 대한 클레임 건너뛰기#

일부 모델은 모든 속성 값을 클레임하면 안 됩니다. 예를 들어:

  • Route는 서브 경로(gitlab/project)가 아닌 최상위 경로(gitlab)만 클레임해야 합니다.

  • ServiceDeskSettingcustom_email 컬럼의 nil 값을 클레임하면 안 됩니다.

cells_claims_attributeif: 옵션을 사용하여 어떤 값이 클레임되는지 제어합니다. if: 옵션은 레코드를 받아 불리언을 반환하는 람다를 허용합니다. if:false를 반환하면 생성 및 삭제 시 값이 Topology Service로 전송되지 않습니다.

class Route < ApplicationRecord
  include Cells::Claimable

  cells_claims_attribute :path, type: CLAIMS_BUCKET_TYPE::ROUTES,
    feature_flag: :cells_claims_routes,
    if: ->(record) { record.path.exclude?('/') }
end

이 예시에서 경로에 /가 없는 경로만 클레임됩니다.

if:의 동작#
  • 저장 (생성): if:true를 반환할 때만 새 클레임이 생성됩니다.

  • 저장 (업데이트): 이전 값이 저장될 때 if:false를 반환했더라도 이전 값은 항상 삭제됩니다. 새 값은 if:true를 반환할 때만 생성됩니다.

  • 레코드 삭제: 삭제 요청은 if:가 true를 반환할 때만 전송됩니다.

  • 검증: cells_claims_metadataif:false를 반환하는 항목을 제외하므로, 검증 서비스는 클레임할 수 없는 값에 대한 클레임을 생성하지 않습니다.

cells_claims_scope를 사용한 범위 필터링#

검증 서비스가 로컬 레코드와 Topology Service를 조정할 때, 기본적으로 모델의 모든 레코드를 쿼리합니다. 쿼리 수준에서 행을 제외하려면 블록과 함께 cells_claims_scope DSL을 사용합니다.

class Route < ApplicationRecord
  include Cells::Claimable

  cells_claims_scope do
    where("strpos(path, '/') = 0")
  end

  cells_claims_attribute :path, type: CLAIMS_BUCKET_TYPE::ROUTES,
    feature_flag: :cells_claims_routes,
    if: ->(record) { record.path.exclude?('/') }
end

블록은 ActiveRecord::Relation을 반환해야 합니다. 블록이 제공되지 않으면 기본 범위는 all입니다. 데이터베이스 수준에서 검증의 행을 제외해야 할 때만 블록을 정의합니다.

다음 경우에 if:cells_claims_scope를 함께 사용합니다:

  • if: — 저장 콜백 중 레코드별 클레임을 제어합니다.

  • cells_claims_scope — 검증 서비스가 스캔하는 레코드를 제어합니다.

인스턴스 수준에서만 필터링이 필요한 경우(예: nil 값 건너뛰기), cells_claims_scope를 정의하지 않고 if:만 사용합니다:

class ServiceDeskSetting < ApplicationRecord
  include Cells::Claimable

  cells_claims_attribute :custom_email,
    type: CLAIMS_BUCKET_TYPE::SERVICE_DESK_CUSTOM_EMAILS,
    feature_flag: :cells_claims_service_desk_settings,
    if: ->(record) { record.custom_email.present? }
end

ActiveRecord를 우회하는 코드 경로에 대한 벌크 클레임#

Cells::Claimable 컨선은 ActiveRecord 콜백에 의존합니다. delete_all, insert_all, upsert_all 또는 원시 SQL을 사용하는 코드 경로는 이러한 콜백을 우회하므로 클레임이 자동으로 생성되거나 삭제되지 않습니다.

모델에서 이러한 코드 경로를 감사하세요. 존재하는 경우 Cells::BulkClaimsWorker를 사용하여 클레임을 처리합니다. run_after_commit을 사용하여 워커를 스케줄링하여 클레임 작업을 데이터베이스 트랜잭션 외부에 유지합니다.

워커는 두 가지 페이로드 키를 허용합니다:

  • destroy_metadata: 클레임을 해제할 레코드에 대한 사전 빌드된 메타데이터. 레코드가 아직 존재하는 동안 메타데이터를 캡처해야 하므로 레코드를 삭제하기 전에 build_destroy_metadata_for_worker로 빌드합니다.

  • create_record_ids: 레코드 ID 배열. 워커가 데이터베이스에서 레코드를 로드하고 클레임 메타데이터를 빌드합니다.

# ActiveRecord 외부에서 삭제된 레코드의 클레임 삭제
destroy_metadata = records.filter_map do |record|
  record.build_destroy_metadata_for_worker(:attribute_name)
end

# ActiveRecord 외부에서 삽입된 레코드의 클레임 생성
create_record_ids = [record1.id, record2.id]

# 트랜잭션 외부에서 스케줄링
run_after_commit do
  destroy_metadata.each_slice(Cells::Claimable::BULK_CLAIMS_BATCH_SIZE) do |batch|
    Cells::BulkClaimsWorker.perform_async(
      YourModel.name, 'attribute_name', { 'destroy_metadata' => batch }
    )
  end

  create_record_ids.each_slice(Cells::Claimable::BULK_CLAIMS_BATCH_SIZE) do |batch|
    Cells::BulkClaimsWorker.perform_async(
      YourModel.name, 'attribute_name', { 'create_record_ids' => batch }
    )
  end
end
  • run_after_commit을 사용하여 데이터베이스 트랜잭션 외부에서 Sidekiq 작업을 스케줄링합니다.

  • 워커를 스케줄링하기 전에 cells_claims_enabled_for_attribute?를 확인합니다.

전체 구현 예시는 라우트 및 이메일에 대한 벌크 클레임을 추가한 MR !230849를 참조하세요.

테스트#

새로운 클레임을 추가할 때 테스트를 추가해야 합니다. 정의가 올바른 값을 생성하는지 확인하는 테스트와 예상대로 작동하는지 확인하는 테스트, 두 가지를 추가합니다.

동일한 사용자 예를 사용하여 모델 테스트에 다음을 추가합니다:

it_behaves_like 'cells claimable model',
  subject_type: Cells::Claimable::CLAIMS_SUBJECT_TYPE::USER,
  subject_key: :id,
  source_type: Cells::Claimable::CLAIMS_SOURCE_TYPE::RAILS_TABLE_USERS,
  claiming_attributes: [:id, :username]

source_typeCells::Claimable::CLAIMS_SOURCE_TYPE::RAILS_TABLE_USERS로 추론되는 것을 확인할 수 있습니다.

다음으로 spec/cells/claims/user_spec.rb에 새 테스트 파일을 추가합니다:

# frozen_string_literal: true

require 'spec_helper'

RSpec.describe 'Claim for User', feature_category: :cell do
  subject! { build(:user, email: email.email, emails: [email]) }

  let(:email) { build(:email) }

  shared_context 'with claims records for User' do
    def claims_records(only: {})
      claims_records_for(subject, only: only) +
        claims_records_for(email, only: only)
    end
  end

  it_behaves_like 'creating new claims' do
    include_context 'with claims records for User'
  end

  it_behaves_like 'deleting existing claims' do
    include_context 'with claims records for User'
  end

  it_behaves_like 'updating existing claims' do
    let(:transform_attributes) { { username: subject.username.reverse } }

    include_context 'with claims records for User'
  end
end

까다로운 부분은 사용자 모델에서 정의하지 않더라도 email을 정의해야 한다는 것입니다. 이는 클레임 속성이 있는 연관 관계(이메일 등)도 클레임되기 때문입니다.

그래서 claims_records를 재정의합니다. 기본적으로는 주체 자체에 대한 클레임만 생성하지만, 여기서는 이메일도 함께 클레임해야 합니다.

세 가지 공유 예시가 있습니다:

  • creating new claims (새 클레임 생성)
  • deleting existing claims (기존 클레임 삭제)
  • updating existing claims (기존 클레임 업데이트)

세 가지 모두 claims_records 재정의가 필요합니다. 기존 클레임 업데이트의 경우, 업데이트하려는 클레임에 대해 transform_attributes도 정의해야 합니다. 여기서는 사용자 이름을 반전시키고, 테스트는 이전 클레임이 삭제되고 새 클레임이 생성되는지 확인합니다.

이 레코드가 절대 업데이트되지 않는 경우, updating existing claims 테스트는 생략할 수 있습니다.

피처 플래그 동작 테스트#

클레임이 피처 플래그를 준수하는지 테스트하려면:

RSpec.describe 'Claim for YourModel', feature_category: :cell do
  context 'when cells_claims_your_model feature flag is enabled' do
    it_behaves_like 'creating new claims'
    it_behaves_like 'deleting existing claims'
  end

  context 'when cells_claims_your_model feature flag is disabled' do
    before do
      stub_feature_flags(cells_claims_your_model: false)
    end

    it_behaves_like 'not creating claims'
    it_behaves_like 'not deleting claims'
  end
end

토폴로지 서비스#

사용 중인 유형은 토폴로지 서비스에 다음 위치에 정의되어 있습니다: proto/claims/v1/messages.proto

각 새 클레임에 대해 다음 위치에 새 유형을 추가합니다:

새 유형을 Rails에서 사용 가능하게 만드는 워크플로우:

  • 토폴로지 서비스proto/claims/v1/messages.proto에 새 유형을 추가하는 MR을 생성합니다
  • validation.go 파일에 새 버킷 유형에 대한 유효성 검사 규칙을 추가하여 잘못된 사용을 방지합니다(유효성 검사 문서 참조)
  • 검토 및 머지된 후, GitLab에 MR 브랜치에서 scripts/update-topology-service-gem.sh를 실행하여 토폴로지 서비스 클라이언트를 업데이트하는 MR을 생성합니다
  • 검토 및 머지된 후, GitLab 기본 브랜치에서 사용 가능합니다

검증 및 백필#

검증 서비스(Cells::Claims::VerificationService)는 로컬 데이터베이스 레코드와 토폴로지 서비스에 저장된 클레임을 조정합니다. 두 가지 목적을 제공합니다:

  • 백필: 모델에 대해 처음 활성화될 때 서비스는 토폴로지 서비스에 해당 클레임이 없는 모든 로컬 레코드를 스캔하고 클레임을 생성합니다.

  • 지속적 일관성: 백필 후 서비스는 크론 스케줄에서 계속 실행하여 드리프트를 감지하고 수정합니다.

검증 작동 방식#

ScheduleClaimsVerificationWorker 크론 작업은 클레임 가능한 각 모델에 대해 ClaimsVerificationWorker를 10분 간격으로 스케줄링합니다.

각 워커 실행:

  • 동일한 모델에 대한 동시 실행을 방지하기 위해 배타적 임대(5분 TTL)를 획득합니다.

  • 기본 키 순서로 1000개씩 배치로 로컬 레코드를 스캔합니다.

  • 각 배치 범위에 대해 토폴로지 서비스에서 해당 클레임을 가져옵니다.

  • 로컬 레코드와 토폴로지 서비스 클레임을 비교합니다:

    • 일치하는 클레임이 없는 로컬 레코드: 클레임을 생성합니다.
    • 일치하는 로컬 레코드가 없는 토폴로지 서비스 클레임: 클레임을 삭제합니다.
    • 클레임 메타데이터가 다른 레코드: 이전 클레임을 삭제하고 수정된 클레임을 생성합니다.
  • 진행 중인 저장과의 충돌을 피하기 위해 최근 1시간 내에 업데이트된 레코드는 건너뜁니다.

  • 각 배치 후 진행 상황(마지막으로 처리된 ID)을 Redis에 저장합니다. 워커가 시간 제한(4.5분)이 초과되면 멈춘 곳부터 계속하도록 자신을 재스케줄링합니다.

검증 워커 활성화#

검증 워커에 대한 피처 플래그를 생성하고 모델별 클레임 플래그가 활성화된 후 활성화합니다:

# 모델 클레임 플래그가 이미 활성화된 후 활성화
Feature.enable(:cells_claims_verification_worker_user)

검증 워커 플래그는 네이밍 규칙 cells_claims_verification_worker_<model_name>을 따르며, 여기서 <model_name>은 매개변수화된 모델 이름입니다(예: user, email, route).

유효성 검사#

클레임 속성을 정의한 후, Rails는 레코드를 생성, 업데이트 또는 삭제할 때 자동으로 속성을 클레임합니다. 이 클레임은 토폴로지 서비스로 전송되어 데이터베이스에 저장됩니다. GDK에서 토폴로지 서비스는 기본적으로 로컬 PostgreSQL 데이터베이스를 사용합니다. gdk psql -d topology_service를 실행하여 psql 콘솔에 접근할 수 있습니다. 예를 들어, 이 명령을 사용하여 모든 클레임을 나열할 수 있습니다:

gdk psql -d topology_service -c "SELECT * FROM claims;"

웹 UI를 사용하여 레코드를 생성, 업데이트 및 삭제해보고, 이 명령을 수시로 실행하여 예상대로 작동하는지 확인할 수 있습니다.

문제 해결#

클레임이 생성되지 않음#

  1. 전역 피처 플래그 확인:

    Feature.enabled?(:cells_unique_claims)
    
  2. 모델별 피처 플래그 확인:

    Feature.enabled?(:cells_claims_users)  # 모델의 플래그로 대체
    
  3. 토폴로지 서비스 실행 확인:

    gdk status gitlab-topology-service
    
  4. 토폴로지 서비스 로그 확인:

    gdk tail gitlab-topology-service
    

백필이 진행되지 않음#

  1. 검증 워커 피처 플래그 확인:

    Feature.enabled?(:cells_claims_verification_worker_user)  # 모델로 교체
    
  2. 검증 워커 로그 확인 (배치 진행 상황). Cells::Claims::VerificationService batch processed 로그 항목에서 createddestroyed 카운트를 확인합니다.

  3. 진행 상태를 위해 Redis 확인. 워커가 마지막으로 처리된 ID를 저장합니다. 워커가 ID 0에서 계속 재시작되면 Redis 키가 존재하는지 확인합니다:

    Gitlab::Redis::SharedState.with do |redis|
      redis.get("cells:claims:verification_service:last_processed_id:User")  # User를 모델 이름으로 교체
    end
    

셀(Cell)에 속성 클레임하기

원문 보기
요약

이 기능이 적용되려면 cells와 피처 플래그 Feature.enabled?(:cells_unique_claims) 모두 활성화되어야 합니다. 또한 개별 모델 클레임은 모델별 피처 플래그로 제어됩니다. 일부 속성은 전체 클러스터에서 전역적으로 고유해야 합니다.

Feature flag

이 기능이 적용되려면 cells와 피처 플래그 Feature.enabled?(:cells_unique_claims) 모두 활성화되어야 합니다.

또한 개별 모델 클레임은 모델별 피처 플래그로 제어됩니다. 전체 목록은 피처 플래그를 참조하세요.

속성 클레임이 필요한 이유#

일부 속성은 전체 클러스터에서 전역적으로 고유해야 합니다. 예를 들어, 라우팅 목적으로 특정 URL 또는 식별자가 최대 하나의 셀에 속하도록 보장하여 해당 셀로 라우팅할 수 있어야 합니다.

각 셀은 자체 데이터베이스를 가지고 있으며, 다른 데이터베이스에 걸쳐 고유 제약을 강제할 수 없습니다. 따라서, 이러한 속성이 고유하도록 클러스터 전체 데이터베이스가 필요합니다.

이러한 속성에 대해, 속성이 특정 셀에 속한다는 것을 클레임하기 위해 토폴로지 서비스와 통신합니다. 한번 클레임되면 다른 셀은 동일한 속성을 클레임할 수 없습니다.

클레임할 속성 결정#

속성이 다음에 해당하는지 고려하세요:

  • 라우팅에 사용되는가?
    • URL에 사용되는가?
    • REST API에 사용되는가?
    • GraphQL API에 사용되는가?
  • 로그인에 사용되는가?

롤아웃 라이프사이클#

새 속성을 클레임하려면 두 단계가 필요합니다. 각 단계에는 자체 피처 플래그가 있으며 각각 고유한 목적을 제공합니다.

1단계: 라이브 요청 클레임#

모델에 Cells::Claimable 컨선(concern)을 추가하고 모델별 피처 플래그를 생성합니다. 활성화되면 Rails after_savebefore_destroy 콜백이 모든 생성, 업데이트, 삭제에 대해 토폴로지 서비스에서 속성을 클레임하고 해제합니다.

이 단계는 새 쓰기만 처리합니다. 데이터베이스의 기존 레코드는 2단계까지 클레임되지 않습니다.

모델 구성 방법에 대한 자세한 내용은 속성 클레임 방법을 참조하세요.

Note

Cells::Claimable 컨선은 ActiveRecord 콜백에 의존합니다. delete_all, insert_all, upsert_all 또는 원시 SQL을 사용하는 코드 경로는 이러한 콜백을 우회합니다. 이러한 코드 경로의 경우 데이터베이스 트랜잭션 외부에서 클레임을 처리하기 위해 Cells::BulkClaimsWorker를 사용합니다. 자세한 내용과 기존 패턴은 ActiveRecord를 우회하는 코드 경로에 대한 벌크 클레임을 참조하세요.

2단계: 백필 및 검증#

검증 워커 피처 플래그(cells_claims_verification_worker_<model_name>)를 활성화하여 검증 서비스를 시작합니다. 첫 실행 시 서비스는 모델의 모든 로컬 레코드를 스캔하고, 토폴로지 서비스에서 일치하는 클레임을 찾지 못하면 클레임을 생성합니다. 이것이 기존 데이터의 백필 역할을 합니다.

백필이 완료된 후 검증 서비스는 크론 스케줄에서 계속 실행됩니다. 로컬 레코드와 토폴로지 서비스 클레임을 조정하여 누락된 클레임, 고아 클레임 또는 변경된 값과 같은 드리프트를 감지하고 수정합니다.

검증에 대한 자세한 내용은 검증 및 백필을 참조하세요.

롤아웃 소유권#

기능 소유 팀이 두 단계의 롤아웃을 소유합니다. 여기에는 피처 플래그 생성, 활성화, 활성화 후 클레임이 올바르게 작동하는지 모니터링이 포함됩니다.

셀 인프라 팀은 지원을 제공할 수 있지만, 롤아웃의 소유권과 정확성 보장은 기능 소유 팀에 속합니다.

피처 플래그#

클레임 시스템은 세밀한 제어를 위한 계층적 피처 플래그 구조를 사용합니다:

전역 피처 플래그#

피처 플래그 설명
cells_unique_claims 전체 클레임 시스템의 기본 스위치. 클레임이 작동하려면 활성화되어 있어야 합니다.

모델별 피처 플래그#

각 클레임 가능한 모델 유형은 독립적인 롤아웃을 허용하는 자체 피처 플래그를 가지고 있습니다:

피처 플래그 모델 설명
cells_claims_users User 사용자 ID 및 사용자 이름 클레임 제어
cells_claims_emails Email 이메일 주소 클레임 제어
cells_claims_organizations Organization 조직 경로 클레임 제어
cells_claims_namespaces Namespace, Group, UserNamespace 네임스페이스/그룹 ID 클레임 제어
cells_claims_projects Project 프로젝트 ID 클레임 제어
cells_claims_routes Route, RedirectRoute 라우트 및 리다이렉트 라우트 경로 클레임 제어
cells_claims_keys Key, GpgKey, DeployKey SSH, GPG 및 Deploy 키 클레임 제어
cells_claims_service_desk_settings ServiceDeskSetting Service Desk 커스텀 이메일 클레임 제어

검증 워커 피처 플래그#

각 모델에는 검증 워커를 위한 별도의 피처 플래그가 있습니다:

피처 플래그 설명
cells_claims_verification_worker_<model_name> 특정 모델에 대해 검증 워커를 실행할지 여부를 제어합니다. <model_name>을 param_key로 교체하세요. 예: cells_claims_verification_worker_user

클레임 활성화#

특정 모델에 대한 클레임을 활성화하려면 전역 플래그와 모델별 플래그 모두 활성화되어 있어야 합니다:

# Rails 콘솔에서

# 1. 전역 클레임 시스템 활성화
Feature.enable(:cells_unique_claims)

# 2. 특정 모델에 대한 클레임 활성화
Feature.enable(:cells_claims_users)
Feature.enable(:cells_claims_emails)
Feature.enable(:cells_claims_organizations)

# 3. 백필 및 지속적 일관성을 위해 검증 워커 활성화
Feature.enable(:cells_claims_verification_worker_user)
Feature.enable(:cells_claims_verification_worker_email)

# 모든 cells claims 피처 플래그 확인
Feature.all.select { |f| f.name.start_with?('cells_claims') }

속성 클레임 방법#

각 속성에 대해 세 가지를 클레임합니다:

  • 속성의 값 (typefeature_flag 매개변수가 있는 cells_claims_attribute로 정의됨)
  • 레코드의 주체 (cells_claims_metadata로 정의됨)
  • 레코드의 소스 (cells_claims_metadata로 정의됨)

[!note] 모든 cells_claims_attributetype(버킷 유형)과 feature_flag(모델별 제어 플래그)을 모두 지정해야 합니다.

Rails#

User를 예로 사용합니다:

class User < ApplicationRecord
  include Cells::Claimable

  cells_claims_attribute :id, type: CLAIMS_BUCKET_TYPE::USER_IDS, feature_flag: :cells_claims_users
  cells_claims_attribute :username, type: CLAIMS_BUCKET_TYPE::USERNAMES, feature_flag: :cells_claims_users

  cells_claims_metadata subject_type: CLAIMS_SUBJECT_TYPE::USER, subject_key: :id
end

먼저, 모델에 Cells::Claimable을 포함합니다.

여기서 idusername 두 속성을 클레임합니다. 각 속성에는 다음이 필요합니다:

  • type(버킷 유형) - 토폴로지 서비스에 정의됩니다(아래 설명)
  • 이 클레임이 활성화될 때를 제어하는 feature_flag (네이밍 규칙: cells_claims_<model>s)

다음으로, cells_claims_metadata로 메타데이터를 정의합니다. 일반적으로 subject_typesubject_key만 설정하면 됩니다; source_type과 소스 값은 자동으로 추론됩니다. 이것들도 토폴로지 서비스에 정의되어 있어야 합니다.

subject_typesubject_key는 클레임된 속성을 소유하는 레코드를 식별합니다. 이것은 종종 샤딩 키와 일치하지만 항상 그런 것은 아닙니다. 샤딩 키가 적용되지 않을 때는 판단을 사용하세요.

Note

연관 관계에 대한 변경도 저장 시 동일한 트랜잭션에서 자동으로 클레임됩니다.

새 클레임 가능한 모델 추가#

새 모델에 클레임을 추가할 때:

  1. 모델에 대한 피처 플래그 생성 (없는 경우):

    # config/feature_flags/beta/cells_claims_<model>s.yml
    ---
    name: cells_claims_<model>s
    feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/XXX
    introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/XXX
    rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/tenant-scale/cells-infrastructure/team/-/issues/XXX
    milestone: 'XX.X'
    group: group::cells infrastructure
    type: beta
    default_enabled: false
    
  2. 검증 워커에 대한 피처 플래그 생성:

    # config/feature_flags/beta/cells_claims_verification_worker_<model_name>.yml
    ---
    name: cells_claims_verification_worker_<model_name>
    feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/XXX
    introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/XXX
    rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/tenant-scale/cells-infrastructure/team/-/issues/XXX
    milestone: 'XX.X'
    group: group::cells infrastructure
    type: beta
    default_enabled: false
    
  3. 모델에 클레임 구성 추가:

    class YourModel < ApplicationRecord
      include Cells::Claimable
    
      cells_claims_attribute :id, type: CLAIMS_BUCKET_TYPE::YOUR_MODEL_IDS, feature_flag: :cells_claims_your_model
      cells_claims_attribute :unique_attr, type: CLAIMS_BUCKET_TYPE::YOUR_MODEL_ATTRS, feature_flag: :cells_claims_your_model
    
      cells_claims_metadata subject_type: CLAIMS_SUBJECT_TYPE::YOUR_MODEL, subject_key: :id
    end
    
  4. 토폴로지 서비스에 유형 추가 (토폴로지 서비스 섹션 참조)

  5. ActiveRecord를 우회하는 코드 경로 감사 (ActiveRecord를 우회하는 코드 경로에 대한 벌크 클레임 참조)

  6. 테스트 추가 (테스트 섹션 참조)

특정 값에 대한 클레임 건너뛰기#

일부 모델은 모든 속성 값을 클레임하면 안 됩니다. 예를 들어:

  • Route는 서브 경로(gitlab/project)가 아닌 최상위 경로(gitlab)만 클레임해야 합니다.

  • ServiceDeskSettingcustom_email 컬럼의 nil 값을 클레임하면 안 됩니다.

cells_claims_attributeif: 옵션을 사용하여 어떤 값이 클레임되는지 제어합니다. if: 옵션은 레코드를 받아 불리언을 반환하는 람다를 허용합니다. if:false를 반환하면 생성 및 삭제 시 값이 Topology Service로 전송되지 않습니다.

class Route < ApplicationRecord
  include Cells::Claimable

  cells_claims_attribute :path, type: CLAIMS_BUCKET_TYPE::ROUTES,
    feature_flag: :cells_claims_routes,
    if: ->(record) { record.path.exclude?('/') }
end

이 예시에서 경로에 /가 없는 경로만 클레임됩니다.

if:의 동작#
  • 저장 (생성): if:true를 반환할 때만 새 클레임이 생성됩니다.

  • 저장 (업데이트): 이전 값이 저장될 때 if:false를 반환했더라도 이전 값은 항상 삭제됩니다. 새 값은 if:true를 반환할 때만 생성됩니다.

  • 레코드 삭제: 삭제 요청은 if:가 true를 반환할 때만 전송됩니다.

  • 검증: cells_claims_metadataif:false를 반환하는 항목을 제외하므로, 검증 서비스는 클레임할 수 없는 값에 대한 클레임을 생성하지 않습니다.

cells_claims_scope를 사용한 범위 필터링#

검증 서비스가 로컬 레코드와 Topology Service를 조정할 때, 기본적으로 모델의 모든 레코드를 쿼리합니다. 쿼리 수준에서 행을 제외하려면 블록과 함께 cells_claims_scope DSL을 사용합니다.

class Route < ApplicationRecord
  include Cells::Claimable

  cells_claims_scope do
    where("strpos(path, '/') = 0")
  end

  cells_claims_attribute :path, type: CLAIMS_BUCKET_TYPE::ROUTES,
    feature_flag: :cells_claims_routes,
    if: ->(record) { record.path.exclude?('/') }
end

블록은 ActiveRecord::Relation을 반환해야 합니다. 블록이 제공되지 않으면 기본 범위는 all입니다. 데이터베이스 수준에서 검증의 행을 제외해야 할 때만 블록을 정의합니다.

다음 경우에 if:cells_claims_scope를 함께 사용합니다:

  • if: — 저장 콜백 중 레코드별 클레임을 제어합니다.

  • cells_claims_scope — 검증 서비스가 스캔하는 레코드를 제어합니다.

인스턴스 수준에서만 필터링이 필요한 경우(예: nil 값 건너뛰기), cells_claims_scope를 정의하지 않고 if:만 사용합니다:

class ServiceDeskSetting < ApplicationRecord
  include Cells::Claimable

  cells_claims_attribute :custom_email,
    type: CLAIMS_BUCKET_TYPE::SERVICE_DESK_CUSTOM_EMAILS,
    feature_flag: :cells_claims_service_desk_settings,
    if: ->(record) { record.custom_email.present? }
end

ActiveRecord를 우회하는 코드 경로에 대한 벌크 클레임#

Cells::Claimable 컨선은 ActiveRecord 콜백에 의존합니다. delete_all, insert_all, upsert_all 또는 원시 SQL을 사용하는 코드 경로는 이러한 콜백을 우회하므로 클레임이 자동으로 생성되거나 삭제되지 않습니다.

모델에서 이러한 코드 경로를 감사하세요. 존재하는 경우 Cells::BulkClaimsWorker를 사용하여 클레임을 처리합니다. run_after_commit을 사용하여 워커를 스케줄링하여 클레임 작업을 데이터베이스 트랜잭션 외부에 유지합니다.

워커는 두 가지 페이로드 키를 허용합니다:

  • destroy_metadata: 클레임을 해제할 레코드에 대한 사전 빌드된 메타데이터. 레코드가 아직 존재하는 동안 메타데이터를 캡처해야 하므로 레코드를 삭제하기 전에 build_destroy_metadata_for_worker로 빌드합니다.

  • create_record_ids: 레코드 ID 배열. 워커가 데이터베이스에서 레코드를 로드하고 클레임 메타데이터를 빌드합니다.

# ActiveRecord 외부에서 삭제된 레코드의 클레임 삭제
destroy_metadata = records.filter_map do |record|
  record.build_destroy_metadata_for_worker(:attribute_name)
end

# ActiveRecord 외부에서 삽입된 레코드의 클레임 생성
create_record_ids = [record1.id, record2.id]

# 트랜잭션 외부에서 스케줄링
run_after_commit do
  destroy_metadata.each_slice(Cells::Claimable::BULK_CLAIMS_BATCH_SIZE) do |batch|
    Cells::BulkClaimsWorker.perform_async(
      YourModel.name, 'attribute_name', { 'destroy_metadata' => batch }
    )
  end

  create_record_ids.each_slice(Cells::Claimable::BULK_CLAIMS_BATCH_SIZE) do |batch|
    Cells::BulkClaimsWorker.perform_async(
      YourModel.name, 'attribute_name', { 'create_record_ids' => batch }
    )
  end
end
  • run_after_commit을 사용하여 데이터베이스 트랜잭션 외부에서 Sidekiq 작업을 스케줄링합니다.

  • 워커를 스케줄링하기 전에 cells_claims_enabled_for_attribute?를 확인합니다.

전체 구현 예시는 라우트 및 이메일에 대한 벌크 클레임을 추가한 MR !230849를 참조하세요.

테스트#

새로운 클레임을 추가할 때 테스트를 추가해야 합니다. 정의가 올바른 값을 생성하는지 확인하는 테스트와 예상대로 작동하는지 확인하는 테스트, 두 가지를 추가합니다.

동일한 사용자 예를 사용하여 모델 테스트에 다음을 추가합니다:

it_behaves_like 'cells claimable model',
  subject_type: Cells::Claimable::CLAIMS_SUBJECT_TYPE::USER,
  subject_key: :id,
  source_type: Cells::Claimable::CLAIMS_SOURCE_TYPE::RAILS_TABLE_USERS,
  claiming_attributes: [:id, :username]

source_typeCells::Claimable::CLAIMS_SOURCE_TYPE::RAILS_TABLE_USERS로 추론되는 것을 확인할 수 있습니다.

다음으로 spec/cells/claims/user_spec.rb에 새 테스트 파일을 추가합니다:

# frozen_string_literal: true

require 'spec_helper'

RSpec.describe 'Claim for User', feature_category: :cell do
  subject! { build(:user, email: email.email, emails: [email]) }

  let(:email) { build(:email) }

  shared_context 'with claims records for User' do
    def claims_records(only: {})
      claims_records_for(subject, only: only) +
        claims_records_for(email, only: only)
    end
  end

  it_behaves_like 'creating new claims' do
    include_context 'with claims records for User'
  end

  it_behaves_like 'deleting existing claims' do
    include_context 'with claims records for User'
  end

  it_behaves_like 'updating existing claims' do
    let(:transform_attributes) { { username: subject.username.reverse } }

    include_context 'with claims records for User'
  end
end

까다로운 부분은 사용자 모델에서 정의하지 않더라도 email을 정의해야 한다는 것입니다. 이는 클레임 속성이 있는 연관 관계(이메일 등)도 클레임되기 때문입니다.

그래서 claims_records를 재정의합니다. 기본적으로는 주체 자체에 대한 클레임만 생성하지만, 여기서는 이메일도 함께 클레임해야 합니다.

세 가지 공유 예시가 있습니다:

  • creating new claims (새 클레임 생성)
  • deleting existing claims (기존 클레임 삭제)
  • updating existing claims (기존 클레임 업데이트)

세 가지 모두 claims_records 재정의가 필요합니다. 기존 클레임 업데이트의 경우, 업데이트하려는 클레임에 대해 transform_attributes도 정의해야 합니다. 여기서는 사용자 이름을 반전시키고, 테스트는 이전 클레임이 삭제되고 새 클레임이 생성되는지 확인합니다.

이 레코드가 절대 업데이트되지 않는 경우, updating existing claims 테스트는 생략할 수 있습니다.

피처 플래그 동작 테스트#

클레임이 피처 플래그를 준수하는지 테스트하려면:

RSpec.describe 'Claim for YourModel', feature_category: :cell do
  context 'when cells_claims_your_model feature flag is enabled' do
    it_behaves_like 'creating new claims'
    it_behaves_like 'deleting existing claims'
  end

  context 'when cells_claims_your_model feature flag is disabled' do
    before do
      stub_feature_flags(cells_claims_your_model: false)
    end

    it_behaves_like 'not creating claims'
    it_behaves_like 'not deleting claims'
  end
end

토폴로지 서비스#

사용 중인 유형은 토폴로지 서비스에 다음 위치에 정의되어 있습니다: proto/claims/v1/messages.proto

각 새 클레임에 대해 다음 위치에 새 유형을 추가합니다:

새 유형을 Rails에서 사용 가능하게 만드는 워크플로우:

  • 토폴로지 서비스proto/claims/v1/messages.proto에 새 유형을 추가하는 MR을 생성합니다
  • validation.go 파일에 새 버킷 유형에 대한 유효성 검사 규칙을 추가하여 잘못된 사용을 방지합니다(유효성 검사 문서 참조)
  • 검토 및 머지된 후, GitLab에 MR 브랜치에서 scripts/update-topology-service-gem.sh를 실행하여 토폴로지 서비스 클라이언트를 업데이트하는 MR을 생성합니다
  • 검토 및 머지된 후, GitLab 기본 브랜치에서 사용 가능합니다

검증 및 백필#

검증 서비스(Cells::Claims::VerificationService)는 로컬 데이터베이스 레코드와 토폴로지 서비스에 저장된 클레임을 조정합니다. 두 가지 목적을 제공합니다:

  • 백필: 모델에 대해 처음 활성화될 때 서비스는 토폴로지 서비스에 해당 클레임이 없는 모든 로컬 레코드를 스캔하고 클레임을 생성합니다.

  • 지속적 일관성: 백필 후 서비스는 크론 스케줄에서 계속 실행하여 드리프트를 감지하고 수정합니다.

검증 작동 방식#

ScheduleClaimsVerificationWorker 크론 작업은 클레임 가능한 각 모델에 대해 ClaimsVerificationWorker를 10분 간격으로 스케줄링합니다.

각 워커 실행:

  • 동일한 모델에 대한 동시 실행을 방지하기 위해 배타적 임대(5분 TTL)를 획득합니다.

  • 기본 키 순서로 1000개씩 배치로 로컬 레코드를 스캔합니다.

  • 각 배치 범위에 대해 토폴로지 서비스에서 해당 클레임을 가져옵니다.

  • 로컬 레코드와 토폴로지 서비스 클레임을 비교합니다:

    • 일치하는 클레임이 없는 로컬 레코드: 클레임을 생성합니다.
    • 일치하는 로컬 레코드가 없는 토폴로지 서비스 클레임: 클레임을 삭제합니다.
    • 클레임 메타데이터가 다른 레코드: 이전 클레임을 삭제하고 수정된 클레임을 생성합니다.
  • 진행 중인 저장과의 충돌을 피하기 위해 최근 1시간 내에 업데이트된 레코드는 건너뜁니다.

  • 각 배치 후 진행 상황(마지막으로 처리된 ID)을 Redis에 저장합니다. 워커가 시간 제한(4.5분)이 초과되면 멈춘 곳부터 계속하도록 자신을 재스케줄링합니다.

검증 워커 활성화#

검증 워커에 대한 피처 플래그를 생성하고 모델별 클레임 플래그가 활성화된 후 활성화합니다:

# 모델 클레임 플래그가 이미 활성화된 후 활성화
Feature.enable(:cells_claims_verification_worker_user)

검증 워커 플래그는 네이밍 규칙 cells_claims_verification_worker_<model_name>을 따르며, 여기서 <model_name>은 매개변수화된 모델 이름입니다(예: user, email, route).

유효성 검사#

클레임 속성을 정의한 후, Rails는 레코드를 생성, 업데이트 또는 삭제할 때 자동으로 속성을 클레임합니다. 이 클레임은 토폴로지 서비스로 전송되어 데이터베이스에 저장됩니다. GDK에서 토폴로지 서비스는 기본적으로 로컬 PostgreSQL 데이터베이스를 사용합니다. gdk psql -d topology_service를 실행하여 psql 콘솔에 접근할 수 있습니다. 예를 들어, 이 명령을 사용하여 모든 클레임을 나열할 수 있습니다:

gdk psql -d topology_service -c "SELECT * FROM claims;"

웹 UI를 사용하여 레코드를 생성, 업데이트 및 삭제해보고, 이 명령을 수시로 실행하여 예상대로 작동하는지 확인할 수 있습니다.

문제 해결#

클레임이 생성되지 않음#

  1. 전역 피처 플래그 확인:

    Feature.enabled?(:cells_unique_claims)
    
  2. 모델별 피처 플래그 확인:

    Feature.enabled?(:cells_claims_users)  # 모델의 플래그로 대체
    
  3. 토폴로지 서비스 실행 확인:

    gdk status gitlab-topology-service
    
  4. 토폴로지 서비스 로그 확인:

    gdk tail gitlab-topology-service
    

백필이 진행되지 않음#

  1. 검증 워커 피처 플래그 확인:

    Feature.enabled?(:cells_claims_verification_worker_user)  # 모델로 교체
    
  2. 검증 워커 로그 확인 (배치 진행 상황). Cells::Claims::VerificationService batch processed 로그 항목에서 createddestroyed 카운트를 확인합니다.

  3. 진행 상태를 위해 Redis 확인. 워커가 마지막으로 처리된 ID를 저장합니다. 워커가 ID 0에서 계속 재시작되면 Redis 키가 존재하는지 확인합니다:

    Gitlab::Redis::SharedState.with do |redis|
      redis.get("cells:claims:verification_service:last_processed_id:User")  # User를 모델 이름으로 교체
    end