셀(Cell)에 속성 클레임하기
이 기능이 적용되려면 cells와 피처 플래그 Feature.enabled?(:cells_unique_claims) 모두 활성화되어야 합니다. 또한 개별 모델 클레임은 모델별 피처 플래그로 제어됩니다. 일부 속성은 전체 클러스터에서 전역적으로 고유해야 합니다.
이 기능이 적용되려면 cells와
피처 플래그 Feature.enabled?(:cells_unique_claims) 모두 활성화되어야 합니다.
또한 개별 모델 클레임은 모델별 피처 플래그로 제어됩니다. 전체 목록은 피처 플래그를 참조하세요.
속성 클레임이 필요한 이유#
일부 속성은 전체 클러스터에서 전역적으로 고유해야 합니다. 예를 들어, 라우팅 목적으로 특정 URL 또는 식별자가 최대 하나의 셀에 속하도록 보장하여 해당 셀로 라우팅할 수 있어야 합니다.
각 셀은 자체 데이터베이스를 가지고 있으며, 다른 데이터베이스에 걸쳐 고유 제약을 강제할 수 없습니다. 따라서, 이러한 속성이 고유하도록 클러스터 전체 데이터베이스가 필요합니다.
이러한 속성에 대해, 속성이 특정 셀에 속한다는 것을 클레임하기 위해 토폴로지 서비스와 통신합니다. 한번 클레임되면 다른 셀은 동일한 속성을 클레임할 수 없습니다.
클레임할 속성 결정#
속성이 다음에 해당하는지 고려하세요:
- 라우팅에 사용되는가?
- URL에 사용되는가?
- REST API에 사용되는가?
- GraphQL API에 사용되는가?
- 로그인에 사용되는가?
롤아웃 라이프사이클#
새 속성을 클레임하려면 두 단계가 필요합니다. 각 단계에는 자체 피처 플래그가 있으며 각각 고유한 목적을 제공합니다.
1단계: 라이브 요청 클레임#
모델에 Cells::Claimable 컨선(concern)을 추가하고 모델별 피처 플래그를 생성합니다. 활성화되면 Rails after_save 및 before_destroy 콜백이 모든 생성, 업데이트, 삭제에 대해 토폴로지 서비스에서 속성을 클레임하고 해제합니다.
이 단계는 새 쓰기만 처리합니다. 데이터베이스의 기존 레코드는 2단계까지 클레임되지 않습니다.
모델 구성 방법에 대한 자세한 내용은 속성 클레임 방법을 참조하세요.
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') }
속성 클레임 방법#
각 속성에 대해 세 가지를 클레임합니다:
- 속성의 값 (
type및feature_flag매개변수가 있는cells_claims_attribute로 정의됨) - 레코드의 주체 (
cells_claims_metadata로 정의됨) - 레코드의 소스 (
cells_claims_metadata로 정의됨)
[!note] 모든
cells_claims_attribute는type(버킷 유형)과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을 포함합니다.
여기서 id와 username 두 속성을 클레임합니다. 각 속성에는 다음이 필요합니다:
type(버킷 유형) - 토폴로지 서비스에 정의됩니다(아래 설명)- 이 클레임이 활성화될 때를 제어하는
feature_flag(네이밍 규칙:cells_claims_<model>s)
다음으로, cells_claims_metadata로 메타데이터를 정의합니다. 일반적으로
subject_type과 subject_key만 설정하면 됩니다; source_type과 소스 값은 자동으로 추론됩니다.
이것들도 토폴로지 서비스에 정의되어 있어야 합니다.
subject_type과 subject_key는 클레임된 속성을 소유하는 레코드를 식별합니다. 이것은 종종
샤딩 키와 일치하지만 항상 그런 것은 아닙니다. 샤딩 키가 적용되지 않을 때는 판단을 사용하세요.
연관 관계에 대한 변경도 저장 시 동일한 트랜잭션에서 자동으로 클레임됩니다.
새 클레임 가능한 모델 추가#
새 모델에 클레임을 추가할 때:
-
모델에 대한 피처 플래그 생성 (없는 경우):
# 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 -
검증 워커에 대한 피처 플래그 생성:
# 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 -
모델에 클레임 구성 추가:
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 -
토폴로지 서비스에 유형 추가 (토폴로지 서비스 섹션 참조)
-
ActiveRecord를 우회하는 코드 경로 감사 (ActiveRecord를 우회하는 코드 경로에 대한 벌크 클레임 참조)
-
테스트 추가 (테스트 섹션 참조)
특정 값에 대한 클레임 건너뛰기#
일부 모델은 모든 속성 값을 클레임하면 안 됩니다. 예를 들어:
-
Route는 서브 경로(gitlab/project)가 아닌 최상위 경로(gitlab)만 클레임해야 합니다. -
ServiceDeskSetting은custom_email컬럼의nil값을 클레임하면 안 됩니다.
cells_claims_attribute의 if: 옵션을 사용하여 어떤 값이 클레임되는지 제어합니다.
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_metadata는if:가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_type이 Cells::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
각 새 클레임에 대해 다음 위치에 새 유형을 추가합니다:
- Bucket::Type
- Subject::Type (이미 존재할 수 있음)
- Source::Type
새 유형을 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를 사용하여 레코드를 생성, 업데이트 및 삭제해보고, 이 명령을 수시로 실행하여 예상대로 작동하는지 확인할 수 있습니다.
문제 해결#
클레임이 생성되지 않음#
-
전역 피처 플래그 확인:
Feature.enabled?(:cells_unique_claims) -
모델별 피처 플래그 확인:
Feature.enabled?(:cells_claims_users) # 모델의 플래그로 대체 -
토폴로지 서비스 실행 확인:
gdk status gitlab-topology-service -
토폴로지 서비스 로그 확인:
gdk tail gitlab-topology-service
백필이 진행되지 않음#
-
검증 워커 피처 플래그 확인:
Feature.enabled?(:cells_claims_verification_worker_user) # 모델로 교체 -
검증 워커 로그 확인 (배치 진행 상황).
Cells::Claims::VerificationService batch processed로그 항목에서created및destroyed카운트를 확인합니다. -
진행 상태를 위해 Redis 확인. 워커가 마지막으로 처리된 ID를 저장합니다. 워커가 ID 0에서 계속 재시작되면 Redis 키가 존재하는지 확인합니다:
Gitlab::Redis::SharedState.with do |redis| redis.get("cells:claims:verification_service:last_processed_id:User") # User를 모델 이름으로 교체 end
