InfoGrab DocsInfoGrab Docs

샤딩 가이드라인

요약

샤딩 이니셔티브는 대부분의 GitLab 데이터베이스 테이블이 직접 또는 간접적으로 Organization에 연결될 수 있도록 보장하는 장기 프로젝트입니다. 다음 gitlab_schema를 가진 모든 테이블은 organization 레벨로 간주됩니다:

샤딩 이니셔티브는 대부분의 GitLab 데이터베이스 테이블이 직접 또는 간접적으로 Organization에 연결될 수 있도록 보장하는 장기 프로젝트입니다. 이 작업에는 테이블에 organization_id, namespace_id 또는 project_id 칼럼을 추가하고, NOT NULL 폴백 데이터를 백필(backfill)하는 것이 포함됩니다. 이 작업은 Cells와 Organizations 제공을 위해 중요합니다. 자세한 내용은 Organizations의 설계 목표를 참조하세요.

샤딩 원칙#

다음 gitlab_schema를 가진 모든 테이블은 organization 레벨로 간주됩니다:

  • gitlab_main_org

  • gitlab_ci

  • gitlab_sec

  • gitlab_main_user

새로 생성된 organization 레벨 테이블은 모두 해당 테이블의 db/docs/ 파일에 sharding_key가 정의되어 있어야 합니다.

샤딩 키의 목적은 Organization isolation blueprint에 문서화되어 있지만, 간략히 말하면 이 칼럼은 데이터베이스의 특정 행이 어느 Organization에 속하는지 표준적인 방법으로 결정하는 데 사용됩니다.

올바른 샤딩 키 선택#

모든 행은 정확히 1개의 샤딩 키를 가져야 하며, 가능한 한 구체적이어야 합니다. 대형 테이블에서는 예외를 둘 수 없습니다.

외래 키의 실제 이름은 무엇이든 될 수 있지만 projects 또는 namespaces의 행을 참조해야 합니다.

다음은 유효한 샤딩 키의 예시입니다:

테이블 항목이 특정 프로젝트에만 속하는 경우:

sharding_key:
  project_id: projects

테이블 항목이 프로젝트에 속하고 외래 키가 target_project_id인 경우:

sharding_key:
  target_project_id: projects

테이블 항목이 네임스페이스/그룹에만 속하는 경우:

sharding_key:
  namespace_id: namespaces

테이블 항목이 네임스페이스/그룹에만 속하고 외래 키가 group_id인 경우:

sharding_key:
  group_id: namespaces

(gitlab_main_user에만 해당) 테이블 항목이 사용자에만 속하는 경우:

sharding_key:
  user_id: users

샤딩 키는 null이 아니어야 합니다#

선택한 sharding_key는 null이 아니어야 합니다. 각 행을 organization에 일관되게 귀속시킬 수 있어야 하며, organization을 다른 cell로 마이그레이션한 후에도 데이터를 잃지 않아야 합니다.

또한 Org Mover를 위한 행 필터링을 설정하려면 샤딩 키 칼럼을 포함하는 REPLICA IDENTITY가 필요합니다. 이 REPLICA IDENTITYnull이 아닌 칼럼만 포함해야 합니다.

복수 샤딩 키 칼럼#

테이블이 여러 다른 부모 엔티티(예: 프로젝트와 네임스페이스 모두)에 속할 수 있는 드문 경우에는 sharding_key를 여러 칼럼으로 정의할 수 있습니다. 이는 테이블에 체크 제약 조건이 있어 테이블의 각 행에 대해 샤딩 키 칼럼 중 정확히 하나만 null이 아니도록 올바르게 보장하는 경우에만 허용됩니다. 이러한 제약 조건을 생성하는 방법은 복수 칼럼에 대한 NOT NULL 제약 조건을 참조하세요.

테이블 항목이 네임스페이스 또는 프로젝트에 속하는 경우:

sharding_key:
  project_id: projects
  namespace_id: namespaces

예시:

  • organization을 cell 간에 이동하려면 모든 테이블의 모든 행에 organization_id가 필요합니다.

  • 그러나 실제로 최상위 그룹(또는 그 하위 그룹이나 프로젝트)이 소유한 행에 organization_id를 추가하면 최상위 그룹 이전이 비효율적(organization_id 재작성으로 인해)이 되어 실용적이지 않게 됩니다.

  • 타협안: 모든 테이블의 모든 행에 organization_id 또는 namespace_id를 추가합니다.

  • 그러나 실제로 프로젝트가 소유한 테이블의 행에 namespace_id를 추가하면 프로젝트 이전(및 특정 하위 그룹 이전)이 비효율적(namespace_id 재작성으로 인해)이 되어 실용적이지 않게 됩니다.

  • 타협안: 모든 테이블의 모든 행에 organization_id, namespace_id 또는 project_id 중 가장 구체적인 것을 추가합니다.

    복수 칼럼 샤딩 키를 가진 테이블은 향후 cell 간 효율적인 데이터 마이그레이션 및 격리를 지원하기 위해 별도의 테이블로 분리되어야 할 수 있습니다. 꼭 필요한 경우가 아니라면 복수 칼럼 샤딩 키를 가진 새 테이블을 설계하지 마세요.

샤딩 키는 변경 불가능해야 합니다#

sharding_key의 선택은 항상 변경 불가능해야 합니다. 이는 샤딩 키 칼럼이 계획된 Org Mover의 인덱스로 사용되고, organization 데이터의 격리 적용에도 사용되기 때문입니다. sharding_key를 변경하면 읽혀지는 데이터가 일관성을 잃을 수 있습니다.

따라서 기능에서 데이터를 프로젝트나 그룹/네임스페이스 간에 이동할 수 있는 사용자 경험이 필요한 경우, 이동 기능을 새 행을 생성하도록 재설계해야 할 수 있습니다. 이에 대한 예시는 이슈 이동 기능에서 확인할 수 있습니다. 이 기능은 실제로 기존 issues 행의 project_id 칼럼을 변경하지 않고, 대신 새 issues 행을 생성하고 원래 issues 행에서 데이터베이스에 링크를 만듭니다. 데이터 이동을 허용해야 하는 특히 까다로운 기존 기능이 있다면 Tenant Scale 팀에 일찍 연락하여 샤딩 키 관리 방법에 대한 옵션을 논의해야 합니다.

namespace_id를 샤딩 키로 사용하기#

namespaces 테이블에는 Group, ProjectNamespace 또는 UserNamespace를 참조하는 행이 있습니다. UserNamespace 유형은 개인 네임스페이스라고도 합니다.

namespace_id를 샤딩 키로 사용하는 것은 좋은 옵션이지만, namespace_idUserNamespace를 참조할 때는 예외입니다. 사용자가 반드시 관련 namespace 레코드를 가지고 있는 것은 아니기 때문에 이 샤딩 키는 NULL일 수 있습니다. 샤딩 키는 NULL 값을 가져서는 안 됩니다.

프로젝트와 네임스페이스에 동일한 샤딩 키 사용하기#

개발자는 그룹과 프로젝트 통합 blueprint를 따라 기능이 개발되고 있는 경우, 프로젝트에 속할 수 있는 테이블에 namespace_id만 사용하도록 선택할 수도 있습니다. 이 경우 namespace_id는 네임스페이스가 속한 그룹의 ID가 아닌 ProjectNamespace의 ID여야 합니다.

organization_id를 샤딩 키로 사용하기#

일반적으로 project_id 또는 namespace_id가 가장 일반적인 샤딩 키입니다. 그러나 테이블이 프로젝트나 네임스페이스에 속하지 않는 경우도 있습니다.

이러한 경우 아래 가이드라인을 따르는 조건 하에 organization_id가 샤딩 키 옵션이 될 수 있습니다:

  • sharding_key 칼럼은 여전히 변경 불가능해야 합니다.

  • 루트 레벨 모델(예: namespaces)에만 organization_id를 추가하고, 리프 레벨 모델(예: issues)에는 추가하지 마세요.

  • 이러한 테이블에 그룹이나 프로젝트와 관련된 데이터(또는 그룹이나 프로젝트에 속한 레코드)가 포함되지 않도록 하세요. 대신 project_id 또는 namespace_id를 사용하세요.

  • 행이 많은 테이블은 엔티티를 다른 organization으로 이동할 경우 모든 행을 다시 작성해야 할 수 있어 비용이 많이 들 수 있으므로 좋은 후보가 아닙니다.

  • 이 테이블을 참조하는 다른 테이블이 있을 때, 참조하는 테이블 레코드가 다른 organization으로 이동해도 애플리케이션이 계속 작동해야 합니다.

organization_id가 샤딩 키로 가장 좋은 옵션이라고 생각되면 Tenant Scale 그룹의 승인을 구하세요. 이는 데이터 마이그레이션에 영향을 미치며 샤딩 키 선택을 재고해야 할 수 있으므로 중요합니다.

예시로, 기존 테이블에 organization_id를 샤딩 키로 추가한 이 이슈를 참조하세요.

organization_id로 테이블 샤딩하기#

organization_id로 샤딩되도록 새 테이블을 추가하거나 기존 테이블을 수정할 때 다음을 수행해야 합니다:

  • 전송 서비스 지원 추가: 그룹이나 사용자가 새 organization으로 이전할 때 레코드의 organization_id를 업데이트합니다.

  • 팩토리에서 공통 organization 사용: RSpec 팩토리가 공통 organization과 자동으로 연결되도록 합니다. Namespaces 팩토리의 after build 블록을 참조하세요.

크로스 스키마 참조#

Cells 아키텍처는 organization 데이터를 cell 간에 안전하게 마이그레이션할 수 있어야 합니다. 일반적으로 크로스 스키마 참조는 허용되지 않습니다.

핵심 원칙: Organization 데이터는 마이그레이션 가능해야 합니다#

organization이 다른 cell로 이동할 때, organization 레벨 테이블에 저장된 모든 데이터가 전송되어야 합니다. 이는 다음을 의미합니다:

  • Organization 데이터는 cell 로컬 데이터에 의존할 수 없습니다. 단, 그 의존성이 일관적이거나 자가 복구(self-healing)되는 경우는 예외입니다.

  • Cell 로컬 데이터는 organization 데이터를 참조할 수 있습니다. organization 데이터는 안정적이고 organization과 함께 이동하기 때문입니다.

허용되는 패턴#

다음 크로스 스키마 참조는 허용됩니다:

From To Why
gitlab_main_cell_local gitlab_main_org Cell 로컬 데이터는 org 데이터를 안전하게 참조할 수 있습니다. org 데이터는 organization과 함께 이동합니다.
gitlab_ci_cell_local gitlab_ci CI/CD cell 로컬 데이터는 CI/CD org 데이터를 참조할 수 있습니다.

구현 가이드#

organization 데이터가 cell 로컬 데이터를 참조해야 하는 경우:

마이그레이션 차단을 방지하려면 Loose Foreign Key를 사용하세요:

# config/gitlab_loose_foreign_keys.yml
org_table:
  - table: cell_local_table
    column: cell_local_id
    on_delete: async_delete

그러나 마이그레이션 후 참조를 자가 복구하거나 재생성하는 애플리케이션 로직도 구현해야 합니다. 참조된 cell 로컬 데이터는 대상 cell에 존재하지 않거나 다른 ID를 가질 수 있습니다. 애플리케이션은 다음 중 하나를 수행해야 합니다:

  • 진실 공급원에서 데이터를 재계산하거나 다시 가져와 참조를 재생성합니다

  • 누락되거나 오래된 참조를 정상적으로 처리합니다

  • 마이그레이션 프로세스의 일부로 참조를 검증하고 복구합니다

cell 로컬 데이터가 organization 데이터를 참조해야 하는 경우:

일반 외래 키를 사용하세요.

class AddForeignKeyToCellLocalTable < Gitlab::Database::Migration[2.2]
  disable_ddl_transaction!

  def up
    add_foreign_key :cell_local_table, :org_table,
      column: :org_table_id,
      on_delete: :cascade
  end

  def down
    remove_foreign_key :cell_local_table, :org_table
  end
end

검증#

테스트 spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb가 이러한 원칙을 시행합니다. 크로스 데이터베이스 외래 키 오류로 실패하면 다음 중 하나를 수행하세요:

  • Loose Foreign Key를 사용합니다 (org → cell 로컬 참조에 권장).

  • 이슈 번호와 함께 allowed_cross_database_foreign_keys에 추가합니다 (기존 FK만 해당).

교훈: organization 데이터에서 불안정한 식별자 사용 피하기#

organization 데이터가 외부 소스(예: Gitaly)를 참조할 때, cell 간에 일관성이 없을 수 있는 식별자는 지속하지 마세요.

예시: programming_languages 테이블은 Gitaly에서 데이터를 받습니다. 한 cell에서 생성된 ID는 다른 cell과 다를 수 있습니다. 그 이유는:

  • 각 cell은 초기 언어 세트가 다릅니다

  • 새 언어는 런타임에 발견됩니다

  • cell 간에 삽입 순서가 다릅니다

organization 데이터(예: repository_languages)가 이러한 불안정한 ID를 지속하면, 데이터가 cell 간에 일관성을 잃게 되어 신뢰할 수 있게 마이그레이션할 수 없습니다.

해결책:

  • 전역적으로 안정적인 식별자 사용: organization 데이터가 외부 데이터를 참조해야 하는 경우, 모든 cell에서 일관된 식별자를 사용합니다(예: 사전 정의된 목록에서).

  • 계산된 데이터를 지속하지 않기: 데이터가 즉시 계산되고 organization과 함께 이동할 필요가 없다면, organization 테이블에 ID를 지속하지 마세요.

  • 참조를 자가 복구 가능하게 만들기: cell 로컬 데이터에 대한 참조를 지속해야 하는 경우, 마이그레이션 후 재생성하거나 검증할 수 있도록 하세요.

  • Loose Foreign Key 사용: organization 데이터에서 cell 로컬 데이터로의 참조에는 마이그레이션이 차단되지 않도록 Loose Foreign Key를 사용하세요.

자세한 사례 연구는 이슈 519895를 참조하세요.

샤딩 키 구현#

테이블에 샤딩 키를 추가하려면 다음 단계를 따르세요. 샤딩 키가 없는 수백 개의 테이블에 sharding_key를 백필해야 합니다. 반복적인 작업을 최소화하기 위해 gitlab-housekeeper를 사용하여 sharding_key를 백필하는 방법을 선언적으로 설명하는 방식을 도입했으며, 이를 통해 수동으로 처리하는 대신 원하는 변경 사항이 담긴 머지 리퀘스트를 생성할 수 있습니다.

애플리케이션 레벨에서 샤딩 키 존재 보장#

샤딩 키를 정의할 때 애플리케이션 레벨에서 채워지는지 확인해야 합니다. 모든 ApplicationRecord 모델에는 샤딩 키 로직을 정의하는 편리한 방법을 제공하는 헬퍼 populate_sharding_key가 포함되어 있으며, 샤딩 키 로직을 테스트하는 데 해당하는 매처(matcher)도 있습니다. 예시:

# in model.rb
populate_sharding_key :project_id, source: :merge_request, field: :target_project_id

# in model_spec.rb
it { is_expected.to populate_sharding_key(:project_id).from(:merge_request, :target_project_id) }

더 많은 헬퍼 예시RSpec 매처 예시를 참조하세요.

desired_sharding_key 구성 정의#

백필 프로세스를 자동화하려면 테이블의 YAML 구성에 desired_sharding_key를 정의하세요. 이 머지 리퀘스트에 예시가 추가되었습니다:

--- # db/docs/security_findings.yml
table_name: security_findings
classes:
- Security::Finding

# ...

desired_sharding_key:
  project_id:
    references: projects
    backfill_via:
      parent:
        foreign_key: scanner_id
        table: vulnerability_scanners
        table_primary_key: id # Optional. Defaults to 'id'
        sharding_key: project_id
        belongs_to: scanner

YAML은 배치 백그라운드 마이그레이션에서 백필할 부모 테이블과 해당 sharding_key를 지정합니다. 또한 before_savesharding_key를 채우기 위해 모델에 추가될 belongs_to 관계를 지정합니다.

부모 테이블도 desired_sharding_key를 가진 경우

부모 테이블도 desired_sharding_key 구성을 가지고 있고 자체적으로 백필 대기 중인 경우 awaiting_backfill_on_parent 필드를 포함하세요:

desired_sharding_key:
  project_id:
    references: projects
    backfill_via:
      parent:
        foreign_key: package_file_id
        table: packages_package_files
        table_primary_key: id # Optional. Defaults to 'id'
        sharding_key: project_id
        belongs_to: package_file
    awaiting_backfill_on_parent: true

desired_sharding_key 구조가 sharding_key 백필에 적합하지 않은 엣지 케이스가 있습니다. 이러한 경우 테이블을 소유한 팀이 필요한 머지 리퀘스트를 직접 생성해야 합니다.

칼럼, 트리거, 인덱스 및 외래 키 추가#

이 단계에서는 샤딩 키 칼럼, 인덱스, 외래 키 및 필요한 트리거를 추가합니다.

1. 설정 단계:

Housekeeper에 필요한 키 설정: export HOUSEKEEPER_TARGET_PROJECT_ID=278964 (GitLab 프로젝트의 프로젝트 ID).

본인을 위한 새 PAT 생성: export HOUSEKEEPER_GITLAB_API_TOKEN=<your-pat>.

로컬 master 브랜치 업데이트: git checkout master && git pull origin master --rebase.

다음 명령으로 sharding-key-backfill-keeps 브랜치로 전환:

git checkout sharding-key-backfill-keeps

머지 리퀘스트에서 브랜치를 찾을 수도 있습니다.

이 브랜치를 master 위에 리베이스하고 변경 사항을 origin으로 다시 푸시합니다. 이렇게 하면 이 브랜치가 master의 변경 사항을 인식합니다:

git pull origin master --rebase

다음 명령을 실행하여 리베이스된 변경 사항을 브랜치로 다시 푸시하고 LEFTHOOK=0을 생략합니다 (그렇지 않으면 RuboCop이 실패합니다):

LEFTHOOK=0 git push --force-with-lease -o ci.skip
  • bundle install과 마이그레이션을 실행합니다.

이 브랜치에 변경 사항을 푸시하지 말고 계속 리베이스만 하세요.

2. 자동화된 머지 리퀘스트 생성 단계:

keeps 디렉터리 내에 소형 테이블과 대형 테이블을 위한 샤딩 키 keeps가 저장되어 있습니다. 파일 이름은 backfill_desired_sharding_key_*.rb로 시작합니다.

소형 테이블 keep을 이해해 봅시다:

[

](/19.1/development/organization/sharding/img/small-table-keep_v18_6.png)

keep 파일에는 다음을 수행하는 코드가 포함되어 있습니다:

  • 소형 테이블에 원하는 샤딩 키를 백필하기 위한 Housekeeper::Keep 클래스 정의

  • ::Keeps::DesiredShardingKey::CHANGE_TYPES를 포함하도록 변경 유형 설정

  • database_yaml_entries의 항목 반복

  • 각 항목에 대해 필요한 경우 인덱스, 트리거 및 외래 키를 포함한 샤딩 키 칼럼 추가

keep 파일을 열고 next unless entry.table_name == 'table name'을 추가합니다. 여기서 테이블 이름은 마이그레이션을 생성하려는 테이블의 이름입니다.

테이블의 desired_sharding_key 구성을 기반으로 빠른 확인을 수행합니다. 구성이 올바른가요? 부모 테이블의 샤딩 키에 예상대로 NOT NULL 제약 조건이 있나요? 없다면 해당 테이블을 건너뛰고 다른 테이블로 이동합니다. 테이블이 괜찮으면 진행할 수 있습니다.

테이블의 기본 키를 확인합시다. 터미널에서 다음 명령을 실행하세요:

gdk psql

  • \d <table_name>

위 명령을 실행하면 인덱스, pk 등 테이블에 대한 유용한 정보를 얻을 수 있습니다. 예를 들어 security_scans 테이블은 다음과 같습니다:

[

](/19.1/development/organization/sharding/img/security-scans-table_v18_6.png)

출력 내용:

테이블: public.security_scans

  • 칼럼: id (bigint, not null), created_at (timestamp), updated_at (timestamp), build_id (bigint, not null), scan_type (smallint, not null) 및 기타 필드

  • 기본 키: security_scans_pkey (id에 대한 btree)

  • 인덱스: index_security_scans_on_build_idindex_security_scans_on_project_id 포함

  • 외래 키: ci_buildsprojects에 대한 참조 포함

이것이 중요한 이유는 기본 키가 복합적이거나, 고유하지 않은 경우 등 keep에 일부 수동 변경이 필요한 경우가 많기 때문입니다.

생성된 변경 사항을 보여주고 아무것도 커밋하지 않는 드라이 런을 실행합니다:

bundle exec gitlab-housekeeper -k Keeps::BackfillDesiredShardingKeySmallTable -d

드라이 런 결과가 좋으면 -d 플래그 없이 동일한 명령을 실행합니다:

bundle exec gitlab-housekeeper -k Keeps::BackfillDesiredShardingKeySmallTable

위 명령을 실행하면 변경 사항이 포함된 머지 리퀘스트가 생성됩니다 🎉

대형 테이블에도 동일한 방법을 따르고 대형 테이블 keep을 사용합니다. 유일한 차이점은 성능상의 이유로 테이블에 FK가 없다는 것입니다.

유용한 팁:

1. :id가 아닌 기본 키를 포함하는 테이블

첫 번째 diff, 두 번째 diff세 번째 diff에서 :id를 비-ID 기본 키로 교체합니다.

이 diff에서 이 줄에 주석을 답니다.

이 파일let(:batch_column) 줄을 추가합니다. 이 예시처럼 합니다.

예시 머지 리퀘스트: !165940 (merged) - 테이블이 gitlab_ci db를 사용하는 경우 마이그레이션 파일에 migration: :gitlab_ci를 지정해야 합니다.

2. 복합 기본 키를 포함하는 테이블 (두 개 이상)

테이블 정보를 확인하고 기본 키 칼럼 중 하나에 UNIQUE 인덱스가 정의되어 있는지 확인합니다. 있다면 위에서 한 것처럼 기본 키와 배치 칼럼으로 사용합니다.

고유한 칼럼이 없다면 수동으로 변경 사항을 추가해야 합니다.

deployment_merge_requests 테이블을 예로 들어 보겠습니다. 이 테이블은 고유하지 않은 복합 기본 키 "deployment_merge_requests_pkey" PRIMARY KEY, btree (deployment_id, merge_request_id)를 가진 테이블입니다.

cursor 기반 배칭을 사용했습니다.

먼저 keep을 사용하여 변경 사항을 생성한 다음 변경 사항을 편집합니다.

queue backfill post migrate 파일을 열고 모든 변경 사항을 제거한 후 새로운 것을 추가합니다. 예시:

[

](/19.1/development/organization/sharding/img/deployment-merge-req-backfill_v18_6.png)

예시 머지 리퀘스트: !183738 (merged)

위의 변경 사항 스타일은 이러한 사양을 가진 다른 테이블에도 사용할 수 있습니다.

lib/gitlab/background_migration/backfill_*.rb를 열고 keep이 생성한 모든 변경 사항을 제거한 후 다음을 추가합니다:

[

](/19.1/development/organization/sharding/img/batched-migration-job_v18_6.png)

!183738을 참조하세요.

테이블이 대형인 경우 schema_spec.rb:ignored_fk_columns_map에 샤딩 키를 무시된 FK 목록에 추가합니다.

스펙도 업데이트했는지 확인합니다.

더 많은 예시: !183047 (merged), !176714 (merged).

3. 다른 데이터베이스에 있는 테이블

테이블이 ci db에 있고 샤딩 키가 main db에 있는 경우일 수 있습니다.

예를 들어, dast_site_profiles_buildssec db에 있고 샤딩 키 테이블 projects는 main db에 있습니다.

이 경우 LFK(loose foreign key)를 추가해야 할 수 있습니다. 예시: housekeep이 생성한 머지 리퀘스트 및 이후에 추가한 LFK.

backfill 스펙queue 스펙migration: :gitlab_sec를 추가했는지 확인합니다.

일반 FK는 서로 다른 db에 있으므로 작동하지 않습니다.

부모 테이블 dast_site_profiles에 대해서는 projects에 대한 LFK가 있습니다.

테이블 dast_site_profiles_builds가 부모 테이블 dast_site_profiles에 CASCADE 삭제로 FK 관계를 가지고 있다면 관련 dast_site_profiles 레코드가 삭제될 때 레코드가 삭제됩니다.

그러나 dast_site_profiles_builds에 대한 LFK 항목도 추가하는 것이 좋습니다.

 dast_site_profiles_builds:
   - table: projects
     column: project_id
     on_delete: async_delete

파이널라이제이션 마이그레이션#

칼럼이 추가되고 백필이 완료되면 마이그레이션을 완료해야 합니다. #chat-ops-test Slack 채널에서 대기 중인 마이그레이션 상태를 확인할 수 있습니다.

/chatops gitlab run batched_background_migrations list --job-class-name=<desired_sharding_key_migration_job_name> - 특정 job의 상태를 확인합니다

출력은 다음과 같습니다:

[

](/19.1/development/organization/sharding/img/chatops_v18_6.png)

ChatOps 출력에는 다음이 표시됩니다:

Job 클래스 이름

  • 테이블 이름

  • 상태 (예: finished, active, paused)

  • 진행률(%)

100%가 되면 master에서 새 브랜치를 만들고 다음을 실행합니다: bundle exec rails g post_deployment_migration finalize_<table><sharding_key>

이렇게 하면 post deployment 마이그레이션 파일이 생성됩니다. 이를 편집합니다. 예를 들어 테이블 subscription_user_add_on_assignments의 경우 다음과 같습니다:

class FinalizeBackfillSubscriptionUserAddOnAssignmentsOrganizationId < Gitlab::Database::Migration[2.2]
  milestone '17.6'
  disable_ddl_transaction!

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  def up
    ensure_batched_background_migration_is_finished(
      job_class_name: 'BackfillSubscriptionUserAddOnAssignmentsOrganizationId',
      table_name: :subscription_user_add_on_assignments,
      column_name: :id,
      job_arguments: [:organization_id, :subscription_add_on_purchases, :organization_id, :add_on_purchase_id],
      finalize: true
    )
  end

  def down; end
end

다른 모든 테이블에서도 job_class_name, table_name, column_name, job_arguments를 제외하고는 유사합니다. job 인수가 올바른지 확인하세요. 샤딩 키 추가 & 백필 머지 리퀘스트에서 job 인수를 확인할 수 있습니다.

완료되면 bin/rails db:migrate를 실행하고 db/docs에서 키 finalized_by를 업데이트합니다. 예시 머지 리퀘스트: !169834.

  • 완료되었습니다. Git 커밋하고 머지 리퀘스트를 생성하세요 🎉

NOT NULL 제약 조건 추가#

마지막 단계는 샤딩 키에 NOT NULL 제약 조건이 있는지 확인하는 것입니다.

소형 테이블

bundle exec rails g post_deployment_migration <table_name>_not_null을 사용하여 post deployment 마이그레이션을 생성합니다

예를 들어, 테이블 subscription_user_add_on_assignments:

class AddSubscriptionUserAddOnAssignmentsOrganizationIdNotNull < Gitlab::Database::Migration[2.2]
  milestone '17.6'
  disable_ddl_transaction!

  def up
    add_not_null_constraint :subscription_user_add_on_assignments, :organization_id
    end

  def down
    remove_not_null_constraint :subscription_user_add_on_assignments, :organization_id
  end
end

bin/rails db:migrate를 실행합니다.

해당 db/docs.*.yml 파일(이 경우 db/docs/subscription_user_add_on_assignments.yml)을 열고 desired_sharding_keydesired_sharding_key_migration_job_name 구성을 제거한 후 sharding_key를 추가합니다.

sharding_key:
  organization_id: organizations

대형 테이블 또는 런타임을 초과하는 테이블

이 경우 샤딩 키를 추가하기 전에 비동기 검증을 추가해야 합니다. 2개의 머지 리퀘스트 프로세스가 됩니다. 테이블 packages_package_files를 예로 들어 보겠습니다.

Step 1 (MR 1): packages_package_files에 샤딩 키에 대한 NOT NULL 추가:

validate: false로 not null 제약 조건을 추가하는 post deployment 마이그레이션을 생성합니다.

[

](/19.1/development/organization/sharding/img/packages-package-files_v18_6.png)

class AddPackagesPackageFilesProjectIdNotNull < Gitlab::Database::Migration[2.2]
  milestone '17.11'
  disable_ddl_transaction!

  def up
    add_not_null_constraint :packages_package_files, :project_id, validate: false
  end

  def down
    remove_not_null_constraint :packages_package_files, :project_id
  end
end

비동기 제약 조건 검증을 준비하는 또 다른 post deployment 마이그레이션을 생성합니다.

class PreparePackagesPackageFilesProjectIdNotNullValidation < Gitlab::Database::Migration[2.2]
  disable_ddl_transaction!
  milestone '17.11'

  CONSTRAINT_NAME = :check_43773f06dc

  def up
    prepare_async_check_constraint_validation :packages_package_files, name: CONSTRAINT_NAME
  end

  def down
    unprepare_async_check_constraint_validation :packages_package_files, name: CONSTRAINT_NAME
  end
end

bin/rails db:migrate를 실행하고 변경 사항으로 머지 리퀘스트를 생성합니다.

Step 2 (MR 2): packages_package_filesproject_id NOT NULL 검증:

Step 1의 머지 리퀘스트가 병합되면 몇 일을 기다려 준비합니다. https://console.postgres.ai/에서 상태를 확인할 수 있습니다. joe instance 봇에게 테이블 정보를 물어보세요. Check constraints를 찾으세요.

샤딩 키 project_id가 NOT VALID로 표시됩니다.

Check constraints:
  "check_43773f06dc" CHECK (project_id IS NOT NULL) NOT VALID

표시되면 not null 제약 조건을 검증하는 새 post deployment 마이그레이션을 생성할 수 있습니다. down 마이그레이션은 no-op이 됩니다.

bin/rails db:migrate를 실행하고 structure.sql에서 다음 add constraint를 제거한 후 테이블 정의에 추가합니다:

제거:

ALTER TABLE packages_package_files
     ADD CONSTRAINT check_43773f06dc CHECK ((project_id IS NOT NULL)) NOT VALID;
  • 추가:
CREATE TABLE packages_package_files (
    .
    .
    CONSTRAINT check_43773f06dc CHECK ((project_id IS NOT NULL)),
);

해당 db/docs.*.yml 파일(이 경우 db/docs/packages_package_files.yml)을 열고 desired_sharding_keydesired_sharding_key_migration_job_name 구성을 제거한 후 sharding_key를 추가합니다.

이 마이그레이션을 되돌리는 것이 #no-op으로 의도되었으므로 pipeline:skip-check-migrations 라벨을 붙여 머지 리퀘스트를 생성합니다.

파이프라인이 누락된 FK에 대해 불평할 수 있습니다. [sharding_key_spec.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/lib/gitlab/organizations/sharding_key_spec.rb)의 `allowed_to_be_missing_foreign_key`에 FK를 추가해야 합니다.

FK를 생략해도 되는 경우에 대한 지침은 샤딩 키 칼럼에서 외래 키를 생략하는 경우를 참조하세요.

샤딩 키 칼럼에서 외래 키를 생략하는 경우#

샤딩 키 칼럼은 부모 테이블(projects, namespaces 또는 organizations)을 외래 키 제약 조건으로 참조해야 합니다. 시간 기반 파티션 관리(partitioned_byretain_for로 구성)를 통해 고정 보존 기간 후 자동으로 데이터를 삭제하는 테이블의 경우 외래 키를 생략할 수 있습니다. 이전 파티션은 전체적으로 삭제되므로 참조 무결성은 cascade FK 제약 조건이 아닌 보존 정책에 의해 유지됩니다. 파티션이 분리되어 모든 행이 오래된 것으로 간주되면 삭제되는 sliding_list 파티셔닝 전략을 사용하는 테이블에도 동일하게 적용됩니다.

예시 1: retain_for를 사용한 고정 보존

web_hook_logs_daily는 일별로 파티셔닝되고 14일보다 오래된 파티션을 삭제합니다:

partitioned_by :created_at, strategy: :daily, retain_for: 14.days

이전 파티션은 자동으로 삭제되므로 projects를 참조하는 web_hook_logs_daily.project_id의 외래 키는 필요하지 않습니다.

예시 2: sliding list 파티셔닝

security_findingssliding_list 전략을 사용하고 모든 findings가 정리된 스캔과 연관되어 security_scan_stale_after_days(구성 가능, 기본값 3개월)보다 오래된 경우 파티션을 분리합니다:

partitioned_by :partition_number,
  strategy: :sliding_list,
  next_partition_if: ->(partition) { partition_full?(partition) || oldest_record_stale?(partition) },
  detach_partition_if: ->(partition) { detach_partition?(partition.value) }

오래된 파티션은 분리되어 결국 삭제되므로 projects를 참조하는 security_findings.project_id의 외래 키는 필요하지 않습니다.

이러한 이유로 외래 키를 생략할 때는 보존 동작을 설명하는 주석과 함께 spec/lib/gitlab/organizations/sharding_key_spec.rballowed_to_be_missing_foreign_key에 칼럼을 추가하세요:

# No LFK needed: daily partitions are dropped after 14 days via retain_for
'web_hook_logs_daily.project_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/524820

# No LFK needed: sliding_list partitions are detached once findings are stale and purged
'security_findings.project_id', # https://gitlab.com/gitlab-org/gitlab/-/work_items/588191

전송 서비스 지원 추가#

테이블이 organization_id로 샤딩된 경우, organization 이전(사용자나 그룹이 organization 간에 이동하는 경우) 중에 테이블이 처리되는지 여부를 추적하기 위해 organization_transfer_support도 추가해야 합니다.

다음 이전 서비스 중 하나에 이전 로직을 구현한 경우 supported로 설정합니다:

app/services/organizations/transfer/groups_service.rb

  • app/services/organizations/transfer/users_service.rb

  • ee/app/services/ee/organizations/transfer/groups_service.rb

  • 테이블에 이전 지원이 필요하지만 아직 없는 경우 todo로 설정합니다 (기존 테이블만 해당 - 새 테이블은 반드시 supported여야 합니다)

  sharding_key:
    organization_id: organizations
  organization_transfer_support: supported

update_organization_id_for 헬퍼를 사용하여 해당 이전 서비스에 테이블을 추가합니다:

# app/services/organizations/transfer/users_service.rb
def update_associated_organization_ids(user_ids)
  update_organization_id_for(PersonalAccessToken) { |relation| relation.for_users(user_ids) }
  update_organization_id_for(YourModel) { |relation| relation.where(user_id: user_ids) }
end

팩토리에서 공통 organization 사용#

organization_id로 샤딩된 모델의 RSpec 팩토리는 공통 organization과 자동으로 연결되어야 합니다. 이렇게 하면 명시적인 organization 설정 없이도 테스트가 올바르게 작동합니다.

# spec/factories/your_models.rb
factory :your_model do
  organization { association(:common_organization) }
  # or derive from a related model
  organization { user&.organization || association(:common_organization) }
end

실패 디버그#

Kibana 사용

백필 job을 대기열에 추가한 후 실패 알림을 받는 경우가 있습니다. 한 가지 방법은 Kibana 로그를 사용하는 것입니다.

참고: Kibana 로그는 7일간만 저장됩니다

최근 BackfillPushEventPayloadsProjectId BBM 실패를 예시로 살펴보겠습니다.

실패는 백필된 원본 머지 리퀘스트에 댓글로 보고됩니다. 예시: 머지 리퀘스트 !183123

[

](/19.1/development/organization/sharding/img/bbm-failure_v18_6.png)

#chat-ops-test Slack 채널에서 /chatops gitlab run batched_background_migrations list --job-class-name=<desired_sharding_key_migration_job_name>을 사용하여 job 상태를 확인할 수도 있습니다.

[

](/19.1/development/organization/sharding/img/backfill-chatops-failed_v18_6.png)

Kibana 대시보드를 사용하여 실패 원인을 파악해 봅시다.

데이터 뷰가 pubsub-sidekiq-inf-gprd*로 설정되어 있는지 확인합니다.

[

](/19.1/development/organization/sharding/img/pubsub_v18_6.png)

왼쪽에서 사용 가능한 모든 필드를 볼 수 있습니다. json.job_class_name(원하는 샤딩 키 마이그레이션 job 이름)과 json.new_state: failed만 필요합니다.

[

](/19.1/development/organization/sharding/img/pubsub-fields_v18_6.png)

원하는 로그를 얻기 위해 해당 필터를 추가합시다.

[

](/19.1/development/organization/sharding/img/pubsub-filters_v18_6.png)

이 경우 json.job_class_nameBackfillPushEventPayloadsProjectId로, json.new_statefailed로 설정하고 필터를 적용합니다.

[

](/19.1/development/organization/sharding/img/pubsub-filters-add_v18_6.png)

올바른 타임라인을 선택했는지 확인합니다. 이 마이그레이션이 며칠 전에 실패로 보고되었으므로 마지막 7일만 표시하도록 필터링합니다.

[

](/19.1/development/organization/sharding/img/kibana-dashboard-timeline_v18_6.png)

그 후 추가된 필터와 함께 원하는 로그가 표시됩니다.

[

](/19.1/development/organization/sharding/img/kibana-logs_v18_6.png)

로그를 확장하고 json.exception_message를 찾습니다.

[

](/19.1/development/organization/sharding/img/kibana-logs-message_v18_6.png)

  • 보시다시피 이 BBM은 Sidekiq::Shutdown 😡으로 인해 실패했습니다.

  • 수정하려면 마이그레이션을 다시 대기열에 추가하면 됩니다.

Grafana 사용

Kibana에서 아무것도 찾을 수 없는 경우가 있습니다. 로그를 7일까지만 저장하기 때문입니다. 이를 위해 Grafana 대시보드를 사용할 수 있습니다.

최근 BackfillApprovalMergeRequestRulesUsersProjectId BBM 실패를 예시로 살펴보겠습니다.

원본 머지 리퀘스트에서 태그됩니다.

[

](/19.1/development/organization/sharding/img/bbm-mr-message_v18_6.png)

#chat-ops-test Slack 채널에서 /chatops gitlab run batched_background_migrations list --job-class-name=<desired_sharding_key_migration_job_name>을 사용하여 job 상태를 확인할 수도 있습니다.

[

](/19.1/development/organization/sharding/img/chatops-failed-status_v18_6.png)

Kibana 대시보드를 확인합시다. 이 job에 대한 로그가 없습니다.

Grafana 대시보드로 이동합시다.

[

](/19.1/development/organization/sharding/img/grafana_v18_6.png)

Explore를 클릭하고 새 쿼리를 추가합니다.

[

](/19.1/development/organization/sharding/img/grafana-explore_v18_6.png)

샤딩 키 실패를 디버그하는 가장 쉬운 방법은 테이블 크기 이상을 확인하는 것입니다.

[

](/19.1/development/organization/sharding/img/grafana-metrics_v18_6.png)

  • 지표: gitlab_component_utilization:pg_table_size_bytes:1h.

  • 라벨 필터:

env: gprd.

  • type: patroni.

  • relname: approval_merge_request_rules_users.

타임라인: job 생성일 며칠 전부터 선택합니다. 머지 리퀘스트에서 실패가 2025-03-31에 보고되었고 job은 2025-03-11에 생성되었음을 볼 수 있습니다. 2025-03-01부터 2025-04-02까지 시간 범위를 선택했습니다. 적절히 조정할 수 있습니다.

쿼리를 실행하면 선택된 시간 프레임 내에 그래프가 생성됩니다.

[

](/19.1/development/organization/sharding/img/grafana-output-1_v18_6.png)

이 그래프를 이해해 봅시다. 백필 job이 2025-03-11에 시작되었으며, 이 날짜부터 테이블 크기가 약간 증가하는 것을 볼 수 있습니다.

[

](/19.1/development/organization/sharding/img/grafana-output-2_v18_6.png)

이는 매우 정상적인 현상입니다.

post 마이그레이션에서 추가한 변경 사항을 살펴봅시다. 먼저 prepare_async 인덱스를 추가했습니다. postgres.ai에서 크기를 확인하면 10GB입니다. 그래프의 급등에서 볼 수 있듯이 2025-03-15 00:00에 생성되었습니다.

[

](/19.1/development/organization/sharding/img/grafana-output-3_v18_6.png)

인덱스가 생성되면 백필이 시작됩니다.

[

](/19.1/development/organization/sharding/img/grafana-output-4_v18_6.png)

BBM이 2025-03-29에 실패합니다. 그래프에서 이 시점에 테이블 크기가 감소한 것을 볼 수 있습니다.

[

](/19.1/development/organization/sharding/img/grafana-output-5_v18_6.png)

  • 인덱스 + 칼럼 백필로 인해 백필 전에 비해 테이블 크기가 약 ~20GB 증가했으며, 약 ~22% 테이블 크기 증가(~90GB에서 ~110GB로) 🫨.

  • 모든 테이블을 100GB 미만으로 유지하는 것이 목표입니다.

미해결, 종료된 이슈 업데이트#

데이터베이스 YAML 문서에 연결된 일부 이슈가 새 이슈를 위해 종료되었지만 YAML 파일은 여전히 원래 URL을 가리키고 있습니다. 진행 상황을 정확하게 측정하려면 올바른 항목을 가리키도록 이를 업데이트해야 합니다.

샤딩 이슈에 더 많은 정보 추가#

모든 샤딩 이슈에는 담당자, 관련 마일스톤이 있어야 하고, 해당하는 경우 블로커에 연결되어야 합니다. 이를 통해 작업을 계획하고 완료 날짜를 추정할 수 있습니다. 또한 문제나 우려 사항이 있을 경우 연락할 담당자가 각 이슈에 지정됩니다. 블로커 이슈를 강조 표시하여 해결할 수 있도록 프로젝트 작업을 시각화하는 데도 도움이 됩니다.

블로커는 의존성이 될 수 있습니다. 예를 들어, notes 테이블은 다른 테이블이 진행되기 전에 완전히 마이그레이션되어야 합니다. 다운스트림 이슈는 이러한 관계를 이해하는 데 도움이 되도록 관련 항목을 블로커로 표시해야 합니다.

샤딩 가이드라인

GitLab v19.1
원문 보기
요약

샤딩 이니셔티브는 대부분의 GitLab 데이터베이스 테이블이 직접 또는 간접적으로 Organization에 연결될 수 있도록 보장하는 장기 프로젝트입니다. 다음 gitlab_schema를 가진 모든 테이블은 organization 레벨로 간주됩니다:

샤딩 이니셔티브는 대부분의 GitLab 데이터베이스 테이블이 직접 또는 간접적으로 Organization에 연결될 수 있도록 보장하는 장기 프로젝트입니다. 이 작업에는 테이블에 organization_id, namespace_id 또는 project_id 칼럼을 추가하고, NOT NULL 폴백 데이터를 백필(backfill)하는 것이 포함됩니다. 이 작업은 Cells와 Organizations 제공을 위해 중요합니다. 자세한 내용은 Organizations의 설계 목표를 참조하세요.

샤딩 원칙#

다음 gitlab_schema를 가진 모든 테이블은 organization 레벨로 간주됩니다:

  • gitlab_main_org

  • gitlab_ci

  • gitlab_sec

  • gitlab_main_user

새로 생성된 organization 레벨 테이블은 모두 해당 테이블의 db/docs/ 파일에 sharding_key가 정의되어 있어야 합니다.

샤딩 키의 목적은 Organization isolation blueprint에 문서화되어 있지만, 간략히 말하면 이 칼럼은 데이터베이스의 특정 행이 어느 Organization에 속하는지 표준적인 방법으로 결정하는 데 사용됩니다.

올바른 샤딩 키 선택#

모든 행은 정확히 1개의 샤딩 키를 가져야 하며, 가능한 한 구체적이어야 합니다. 대형 테이블에서는 예외를 둘 수 없습니다.

외래 키의 실제 이름은 무엇이든 될 수 있지만 projects 또는 namespaces의 행을 참조해야 합니다.

다음은 유효한 샤딩 키의 예시입니다:

테이블 항목이 특정 프로젝트에만 속하는 경우:

sharding_key:
  project_id: projects

테이블 항목이 프로젝트에 속하고 외래 키가 target_project_id인 경우:

sharding_key:
  target_project_id: projects

테이블 항목이 네임스페이스/그룹에만 속하는 경우:

sharding_key:
  namespace_id: namespaces

테이블 항목이 네임스페이스/그룹에만 속하고 외래 키가 group_id인 경우:

sharding_key:
  group_id: namespaces

(gitlab_main_user에만 해당) 테이블 항목이 사용자에만 속하는 경우:

sharding_key:
  user_id: users

샤딩 키는 null이 아니어야 합니다#

선택한 sharding_key는 null이 아니어야 합니다. 각 행을 organization에 일관되게 귀속시킬 수 있어야 하며, organization을 다른 cell로 마이그레이션한 후에도 데이터를 잃지 않아야 합니다.

또한 Org Mover를 위한 행 필터링을 설정하려면 샤딩 키 칼럼을 포함하는 REPLICA IDENTITY가 필요합니다. 이 REPLICA IDENTITYnull이 아닌 칼럼만 포함해야 합니다.

복수 샤딩 키 칼럼#

테이블이 여러 다른 부모 엔티티(예: 프로젝트와 네임스페이스 모두)에 속할 수 있는 드문 경우에는 sharding_key를 여러 칼럼으로 정의할 수 있습니다. 이는 테이블에 체크 제약 조건이 있어 테이블의 각 행에 대해 샤딩 키 칼럼 중 정확히 하나만 null이 아니도록 올바르게 보장하는 경우에만 허용됩니다. 이러한 제약 조건을 생성하는 방법은 복수 칼럼에 대한 NOT NULL 제약 조건을 참조하세요.

테이블 항목이 네임스페이스 또는 프로젝트에 속하는 경우:

sharding_key:
  project_id: projects
  namespace_id: namespaces

예시:

  • organization을 cell 간에 이동하려면 모든 테이블의 모든 행에 organization_id가 필요합니다.

  • 그러나 실제로 최상위 그룹(또는 그 하위 그룹이나 프로젝트)이 소유한 행에 organization_id를 추가하면 최상위 그룹 이전이 비효율적(organization_id 재작성으로 인해)이 되어 실용적이지 않게 됩니다.

  • 타협안: 모든 테이블의 모든 행에 organization_id 또는 namespace_id를 추가합니다.

  • 그러나 실제로 프로젝트가 소유한 테이블의 행에 namespace_id를 추가하면 프로젝트 이전(및 특정 하위 그룹 이전)이 비효율적(namespace_id 재작성으로 인해)이 되어 실용적이지 않게 됩니다.

  • 타협안: 모든 테이블의 모든 행에 organization_id, namespace_id 또는 project_id 중 가장 구체적인 것을 추가합니다.

    복수 칼럼 샤딩 키를 가진 테이블은 향후 cell 간 효율적인 데이터 마이그레이션 및 격리를 지원하기 위해 별도의 테이블로 분리되어야 할 수 있습니다. 꼭 필요한 경우가 아니라면 복수 칼럼 샤딩 키를 가진 새 테이블을 설계하지 마세요.

샤딩 키는 변경 불가능해야 합니다#

sharding_key의 선택은 항상 변경 불가능해야 합니다. 이는 샤딩 키 칼럼이 계획된 Org Mover의 인덱스로 사용되고, organization 데이터의 격리 적용에도 사용되기 때문입니다. sharding_key를 변경하면 읽혀지는 데이터가 일관성을 잃을 수 있습니다.

따라서 기능에서 데이터를 프로젝트나 그룹/네임스페이스 간에 이동할 수 있는 사용자 경험이 필요한 경우, 이동 기능을 새 행을 생성하도록 재설계해야 할 수 있습니다. 이에 대한 예시는 이슈 이동 기능에서 확인할 수 있습니다. 이 기능은 실제로 기존 issues 행의 project_id 칼럼을 변경하지 않고, 대신 새 issues 행을 생성하고 원래 issues 행에서 데이터베이스에 링크를 만듭니다. 데이터 이동을 허용해야 하는 특히 까다로운 기존 기능이 있다면 Tenant Scale 팀에 일찍 연락하여 샤딩 키 관리 방법에 대한 옵션을 논의해야 합니다.

namespace_id를 샤딩 키로 사용하기#

namespaces 테이블에는 Group, ProjectNamespace 또는 UserNamespace를 참조하는 행이 있습니다. UserNamespace 유형은 개인 네임스페이스라고도 합니다.

namespace_id를 샤딩 키로 사용하는 것은 좋은 옵션이지만, namespace_idUserNamespace를 참조할 때는 예외입니다. 사용자가 반드시 관련 namespace 레코드를 가지고 있는 것은 아니기 때문에 이 샤딩 키는 NULL일 수 있습니다. 샤딩 키는 NULL 값을 가져서는 안 됩니다.

프로젝트와 네임스페이스에 동일한 샤딩 키 사용하기#

개발자는 그룹과 프로젝트 통합 blueprint를 따라 기능이 개발되고 있는 경우, 프로젝트에 속할 수 있는 테이블에 namespace_id만 사용하도록 선택할 수도 있습니다. 이 경우 namespace_id는 네임스페이스가 속한 그룹의 ID가 아닌 ProjectNamespace의 ID여야 합니다.

organization_id를 샤딩 키로 사용하기#

일반적으로 project_id 또는 namespace_id가 가장 일반적인 샤딩 키입니다. 그러나 테이블이 프로젝트나 네임스페이스에 속하지 않는 경우도 있습니다.

이러한 경우 아래 가이드라인을 따르는 조건 하에 organization_id가 샤딩 키 옵션이 될 수 있습니다:

  • sharding_key 칼럼은 여전히 변경 불가능해야 합니다.

  • 루트 레벨 모델(예: namespaces)에만 organization_id를 추가하고, 리프 레벨 모델(예: issues)에는 추가하지 마세요.

  • 이러한 테이블에 그룹이나 프로젝트와 관련된 데이터(또는 그룹이나 프로젝트에 속한 레코드)가 포함되지 않도록 하세요. 대신 project_id 또는 namespace_id를 사용하세요.

  • 행이 많은 테이블은 엔티티를 다른 organization으로 이동할 경우 모든 행을 다시 작성해야 할 수 있어 비용이 많이 들 수 있으므로 좋은 후보가 아닙니다.

  • 이 테이블을 참조하는 다른 테이블이 있을 때, 참조하는 테이블 레코드가 다른 organization으로 이동해도 애플리케이션이 계속 작동해야 합니다.

organization_id가 샤딩 키로 가장 좋은 옵션이라고 생각되면 Tenant Scale 그룹의 승인을 구하세요. 이는 데이터 마이그레이션에 영향을 미치며 샤딩 키 선택을 재고해야 할 수 있으므로 중요합니다.

예시로, 기존 테이블에 organization_id를 샤딩 키로 추가한 이 이슈를 참조하세요.

organization_id로 테이블 샤딩하기#

organization_id로 샤딩되도록 새 테이블을 추가하거나 기존 테이블을 수정할 때 다음을 수행해야 합니다:

  • 전송 서비스 지원 추가: 그룹이나 사용자가 새 organization으로 이전할 때 레코드의 organization_id를 업데이트합니다.

  • 팩토리에서 공통 organization 사용: RSpec 팩토리가 공통 organization과 자동으로 연결되도록 합니다. Namespaces 팩토리의 after build 블록을 참조하세요.

크로스 스키마 참조#

Cells 아키텍처는 organization 데이터를 cell 간에 안전하게 마이그레이션할 수 있어야 합니다. 일반적으로 크로스 스키마 참조는 허용되지 않습니다.

핵심 원칙: Organization 데이터는 마이그레이션 가능해야 합니다#

organization이 다른 cell로 이동할 때, organization 레벨 테이블에 저장된 모든 데이터가 전송되어야 합니다. 이는 다음을 의미합니다:

  • Organization 데이터는 cell 로컬 데이터에 의존할 수 없습니다. 단, 그 의존성이 일관적이거나 자가 복구(self-healing)되는 경우는 예외입니다.

  • Cell 로컬 데이터는 organization 데이터를 참조할 수 있습니다. organization 데이터는 안정적이고 organization과 함께 이동하기 때문입니다.

허용되는 패턴#

다음 크로스 스키마 참조는 허용됩니다:

From To Why
gitlab_main_cell_local gitlab_main_org Cell 로컬 데이터는 org 데이터를 안전하게 참조할 수 있습니다. org 데이터는 organization과 함께 이동합니다.
gitlab_ci_cell_local gitlab_ci CI/CD cell 로컬 데이터는 CI/CD org 데이터를 참조할 수 있습니다.

구현 가이드#

organization 데이터가 cell 로컬 데이터를 참조해야 하는 경우:

마이그레이션 차단을 방지하려면 Loose Foreign Key를 사용하세요:

# config/gitlab_loose_foreign_keys.yml
org_table:
  - table: cell_local_table
    column: cell_local_id
    on_delete: async_delete

그러나 마이그레이션 후 참조를 자가 복구하거나 재생성하는 애플리케이션 로직도 구현해야 합니다. 참조된 cell 로컬 데이터는 대상 cell에 존재하지 않거나 다른 ID를 가질 수 있습니다. 애플리케이션은 다음 중 하나를 수행해야 합니다:

  • 진실 공급원에서 데이터를 재계산하거나 다시 가져와 참조를 재생성합니다

  • 누락되거나 오래된 참조를 정상적으로 처리합니다

  • 마이그레이션 프로세스의 일부로 참조를 검증하고 복구합니다

cell 로컬 데이터가 organization 데이터를 참조해야 하는 경우:

일반 외래 키를 사용하세요.

class AddForeignKeyToCellLocalTable < Gitlab::Database::Migration[2.2]
  disable_ddl_transaction!

  def up
    add_foreign_key :cell_local_table, :org_table,
      column: :org_table_id,
      on_delete: :cascade
  end

  def down
    remove_foreign_key :cell_local_table, :org_table
  end
end

검증#

테스트 spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb가 이러한 원칙을 시행합니다. 크로스 데이터베이스 외래 키 오류로 실패하면 다음 중 하나를 수행하세요:

  • Loose Foreign Key를 사용합니다 (org → cell 로컬 참조에 권장).

  • 이슈 번호와 함께 allowed_cross_database_foreign_keys에 추가합니다 (기존 FK만 해당).

교훈: organization 데이터에서 불안정한 식별자 사용 피하기#

organization 데이터가 외부 소스(예: Gitaly)를 참조할 때, cell 간에 일관성이 없을 수 있는 식별자는 지속하지 마세요.

예시: programming_languages 테이블은 Gitaly에서 데이터를 받습니다. 한 cell에서 생성된 ID는 다른 cell과 다를 수 있습니다. 그 이유는:

  • 각 cell은 초기 언어 세트가 다릅니다

  • 새 언어는 런타임에 발견됩니다

  • cell 간에 삽입 순서가 다릅니다

organization 데이터(예: repository_languages)가 이러한 불안정한 ID를 지속하면, 데이터가 cell 간에 일관성을 잃게 되어 신뢰할 수 있게 마이그레이션할 수 없습니다.

해결책:

  • 전역적으로 안정적인 식별자 사용: organization 데이터가 외부 데이터를 참조해야 하는 경우, 모든 cell에서 일관된 식별자를 사용합니다(예: 사전 정의된 목록에서).

  • 계산된 데이터를 지속하지 않기: 데이터가 즉시 계산되고 organization과 함께 이동할 필요가 없다면, organization 테이블에 ID를 지속하지 마세요.

  • 참조를 자가 복구 가능하게 만들기: cell 로컬 데이터에 대한 참조를 지속해야 하는 경우, 마이그레이션 후 재생성하거나 검증할 수 있도록 하세요.

  • Loose Foreign Key 사용: organization 데이터에서 cell 로컬 데이터로의 참조에는 마이그레이션이 차단되지 않도록 Loose Foreign Key를 사용하세요.

자세한 사례 연구는 이슈 519895를 참조하세요.

샤딩 키 구현#

테이블에 샤딩 키를 추가하려면 다음 단계를 따르세요. 샤딩 키가 없는 수백 개의 테이블에 sharding_key를 백필해야 합니다. 반복적인 작업을 최소화하기 위해 gitlab-housekeeper를 사용하여 sharding_key를 백필하는 방법을 선언적으로 설명하는 방식을 도입했으며, 이를 통해 수동으로 처리하는 대신 원하는 변경 사항이 담긴 머지 리퀘스트를 생성할 수 있습니다.

애플리케이션 레벨에서 샤딩 키 존재 보장#

샤딩 키를 정의할 때 애플리케이션 레벨에서 채워지는지 확인해야 합니다. 모든 ApplicationRecord 모델에는 샤딩 키 로직을 정의하는 편리한 방법을 제공하는 헬퍼 populate_sharding_key가 포함되어 있으며, 샤딩 키 로직을 테스트하는 데 해당하는 매처(matcher)도 있습니다. 예시:

# in model.rb
populate_sharding_key :project_id, source: :merge_request, field: :target_project_id

# in model_spec.rb
it { is_expected.to populate_sharding_key(:project_id).from(:merge_request, :target_project_id) }

더 많은 헬퍼 예시RSpec 매처 예시를 참조하세요.

desired_sharding_key 구성 정의#

백필 프로세스를 자동화하려면 테이블의 YAML 구성에 desired_sharding_key를 정의하세요. 이 머지 리퀘스트에 예시가 추가되었습니다:

--- # db/docs/security_findings.yml
table_name: security_findings
classes:
- Security::Finding

# ...

desired_sharding_key:
  project_id:
    references: projects
    backfill_via:
      parent:
        foreign_key: scanner_id
        table: vulnerability_scanners
        table_primary_key: id # Optional. Defaults to 'id'
        sharding_key: project_id
        belongs_to: scanner

YAML은 배치 백그라운드 마이그레이션에서 백필할 부모 테이블과 해당 sharding_key를 지정합니다. 또한 before_savesharding_key를 채우기 위해 모델에 추가될 belongs_to 관계를 지정합니다.

부모 테이블도 desired_sharding_key를 가진 경우

부모 테이블도 desired_sharding_key 구성을 가지고 있고 자체적으로 백필 대기 중인 경우 awaiting_backfill_on_parent 필드를 포함하세요:

desired_sharding_key:
  project_id:
    references: projects
    backfill_via:
      parent:
        foreign_key: package_file_id
        table: packages_package_files
        table_primary_key: id # Optional. Defaults to 'id'
        sharding_key: project_id
        belongs_to: package_file
    awaiting_backfill_on_parent: true

desired_sharding_key 구조가 sharding_key 백필에 적합하지 않은 엣지 케이스가 있습니다. 이러한 경우 테이블을 소유한 팀이 필요한 머지 리퀘스트를 직접 생성해야 합니다.

칼럼, 트리거, 인덱스 및 외래 키 추가#

이 단계에서는 샤딩 키 칼럼, 인덱스, 외래 키 및 필요한 트리거를 추가합니다.

1. 설정 단계:

Housekeeper에 필요한 키 설정: export HOUSEKEEPER_TARGET_PROJECT_ID=278964 (GitLab 프로젝트의 프로젝트 ID).

본인을 위한 새 PAT 생성: export HOUSEKEEPER_GITLAB_API_TOKEN=<your-pat>.

로컬 master 브랜치 업데이트: git checkout master && git pull origin master --rebase.

다음 명령으로 sharding-key-backfill-keeps 브랜치로 전환:

git checkout sharding-key-backfill-keeps

머지 리퀘스트에서 브랜치를 찾을 수도 있습니다.

이 브랜치를 master 위에 리베이스하고 변경 사항을 origin으로 다시 푸시합니다. 이렇게 하면 이 브랜치가 master의 변경 사항을 인식합니다:

git pull origin master --rebase

다음 명령을 실행하여 리베이스된 변경 사항을 브랜치로 다시 푸시하고 LEFTHOOK=0을 생략합니다 (그렇지 않으면 RuboCop이 실패합니다):

LEFTHOOK=0 git push --force-with-lease -o ci.skip
  • bundle install과 마이그레이션을 실행합니다.

이 브랜치에 변경 사항을 푸시하지 말고 계속 리베이스만 하세요.

2. 자동화된 머지 리퀘스트 생성 단계:

keeps 디렉터리 내에 소형 테이블과 대형 테이블을 위한 샤딩 키 keeps가 저장되어 있습니다. 파일 이름은 backfill_desired_sharding_key_*.rb로 시작합니다.

소형 테이블 keep을 이해해 봅시다:

[

](/19.1/development/organization/sharding/img/small-table-keep_v18_6.png)

keep 파일에는 다음을 수행하는 코드가 포함되어 있습니다:

  • 소형 테이블에 원하는 샤딩 키를 백필하기 위한 Housekeeper::Keep 클래스 정의

  • ::Keeps::DesiredShardingKey::CHANGE_TYPES를 포함하도록 변경 유형 설정

  • database_yaml_entries의 항목 반복

  • 각 항목에 대해 필요한 경우 인덱스, 트리거 및 외래 키를 포함한 샤딩 키 칼럼 추가

keep 파일을 열고 next unless entry.table_name == 'table name'을 추가합니다. 여기서 테이블 이름은 마이그레이션을 생성하려는 테이블의 이름입니다.

테이블의 desired_sharding_key 구성을 기반으로 빠른 확인을 수행합니다. 구성이 올바른가요? 부모 테이블의 샤딩 키에 예상대로 NOT NULL 제약 조건이 있나요? 없다면 해당 테이블을 건너뛰고 다른 테이블로 이동합니다. 테이블이 괜찮으면 진행할 수 있습니다.

테이블의 기본 키를 확인합시다. 터미널에서 다음 명령을 실행하세요:

gdk psql

  • \d <table_name>

위 명령을 실행하면 인덱스, pk 등 테이블에 대한 유용한 정보를 얻을 수 있습니다. 예를 들어 security_scans 테이블은 다음과 같습니다:

[

](/19.1/development/organization/sharding/img/security-scans-table_v18_6.png)

출력 내용:

테이블: public.security_scans

  • 칼럼: id (bigint, not null), created_at (timestamp), updated_at (timestamp), build_id (bigint, not null), scan_type (smallint, not null) 및 기타 필드

  • 기본 키: security_scans_pkey (id에 대한 btree)

  • 인덱스: index_security_scans_on_build_idindex_security_scans_on_project_id 포함

  • 외래 키: ci_buildsprojects에 대한 참조 포함

이것이 중요한 이유는 기본 키가 복합적이거나, 고유하지 않은 경우 등 keep에 일부 수동 변경이 필요한 경우가 많기 때문입니다.

생성된 변경 사항을 보여주고 아무것도 커밋하지 않는 드라이 런을 실행합니다:

bundle exec gitlab-housekeeper -k Keeps::BackfillDesiredShardingKeySmallTable -d

드라이 런 결과가 좋으면 -d 플래그 없이 동일한 명령을 실행합니다:

bundle exec gitlab-housekeeper -k Keeps::BackfillDesiredShardingKeySmallTable

위 명령을 실행하면 변경 사항이 포함된 머지 리퀘스트가 생성됩니다 🎉

대형 테이블에도 동일한 방법을 따르고 대형 테이블 keep을 사용합니다. 유일한 차이점은 성능상의 이유로 테이블에 FK가 없다는 것입니다.

유용한 팁:

1. :id가 아닌 기본 키를 포함하는 테이블

첫 번째 diff, 두 번째 diff세 번째 diff에서 :id를 비-ID 기본 키로 교체합니다.

이 diff에서 이 줄에 주석을 답니다.

이 파일let(:batch_column) 줄을 추가합니다. 이 예시처럼 합니다.

예시 머지 리퀘스트: !165940 (merged) - 테이블이 gitlab_ci db를 사용하는 경우 마이그레이션 파일에 migration: :gitlab_ci를 지정해야 합니다.

2. 복합 기본 키를 포함하는 테이블 (두 개 이상)

테이블 정보를 확인하고 기본 키 칼럼 중 하나에 UNIQUE 인덱스가 정의되어 있는지 확인합니다. 있다면 위에서 한 것처럼 기본 키와 배치 칼럼으로 사용합니다.

고유한 칼럼이 없다면 수동으로 변경 사항을 추가해야 합니다.

deployment_merge_requests 테이블을 예로 들어 보겠습니다. 이 테이블은 고유하지 않은 복합 기본 키 "deployment_merge_requests_pkey" PRIMARY KEY, btree (deployment_id, merge_request_id)를 가진 테이블입니다.

cursor 기반 배칭을 사용했습니다.

먼저 keep을 사용하여 변경 사항을 생성한 다음 변경 사항을 편집합니다.

queue backfill post migrate 파일을 열고 모든 변경 사항을 제거한 후 새로운 것을 추가합니다. 예시:

[

](/19.1/development/organization/sharding/img/deployment-merge-req-backfill_v18_6.png)

예시 머지 리퀘스트: !183738 (merged)

위의 변경 사항 스타일은 이러한 사양을 가진 다른 테이블에도 사용할 수 있습니다.

lib/gitlab/background_migration/backfill_*.rb를 열고 keep이 생성한 모든 변경 사항을 제거한 후 다음을 추가합니다:

[

](/19.1/development/organization/sharding/img/batched-migration-job_v18_6.png)

!183738을 참조하세요.

테이블이 대형인 경우 schema_spec.rb:ignored_fk_columns_map에 샤딩 키를 무시된 FK 목록에 추가합니다.

스펙도 업데이트했는지 확인합니다.

더 많은 예시: !183047 (merged), !176714 (merged).

3. 다른 데이터베이스에 있는 테이블

테이블이 ci db에 있고 샤딩 키가 main db에 있는 경우일 수 있습니다.

예를 들어, dast_site_profiles_buildssec db에 있고 샤딩 키 테이블 projects는 main db에 있습니다.

이 경우 LFK(loose foreign key)를 추가해야 할 수 있습니다. 예시: housekeep이 생성한 머지 리퀘스트 및 이후에 추가한 LFK.

backfill 스펙queue 스펙migration: :gitlab_sec를 추가했는지 확인합니다.

일반 FK는 서로 다른 db에 있으므로 작동하지 않습니다.

부모 테이블 dast_site_profiles에 대해서는 projects에 대한 LFK가 있습니다.

테이블 dast_site_profiles_builds가 부모 테이블 dast_site_profiles에 CASCADE 삭제로 FK 관계를 가지고 있다면 관련 dast_site_profiles 레코드가 삭제될 때 레코드가 삭제됩니다.

그러나 dast_site_profiles_builds에 대한 LFK 항목도 추가하는 것이 좋습니다.

 dast_site_profiles_builds:
   - table: projects
     column: project_id
     on_delete: async_delete

파이널라이제이션 마이그레이션#

칼럼이 추가되고 백필이 완료되면 마이그레이션을 완료해야 합니다. #chat-ops-test Slack 채널에서 대기 중인 마이그레이션 상태를 확인할 수 있습니다.

/chatops gitlab run batched_background_migrations list --job-class-name=<desired_sharding_key_migration_job_name> - 특정 job의 상태를 확인합니다

출력은 다음과 같습니다:

[

](/19.1/development/organization/sharding/img/chatops_v18_6.png)

ChatOps 출력에는 다음이 표시됩니다:

Job 클래스 이름

  • 테이블 이름

  • 상태 (예: finished, active, paused)

  • 진행률(%)

100%가 되면 master에서 새 브랜치를 만들고 다음을 실행합니다: bundle exec rails g post_deployment_migration finalize_<table><sharding_key>

이렇게 하면 post deployment 마이그레이션 파일이 생성됩니다. 이를 편집합니다. 예를 들어 테이블 subscription_user_add_on_assignments의 경우 다음과 같습니다:

class FinalizeBackfillSubscriptionUserAddOnAssignmentsOrganizationId < Gitlab::Database::Migration[2.2]
  milestone '17.6'
  disable_ddl_transaction!

  restrict_gitlab_migration gitlab_schema: :gitlab_main_org

  def up
    ensure_batched_background_migration_is_finished(
      job_class_name: 'BackfillSubscriptionUserAddOnAssignmentsOrganizationId',
      table_name: :subscription_user_add_on_assignments,
      column_name: :id,
      job_arguments: [:organization_id, :subscription_add_on_purchases, :organization_id, :add_on_purchase_id],
      finalize: true
    )
  end

  def down; end
end

다른 모든 테이블에서도 job_class_name, table_name, column_name, job_arguments를 제외하고는 유사합니다. job 인수가 올바른지 확인하세요. 샤딩 키 추가 & 백필 머지 리퀘스트에서 job 인수를 확인할 수 있습니다.

완료되면 bin/rails db:migrate를 실행하고 db/docs에서 키 finalized_by를 업데이트합니다. 예시 머지 리퀘스트: !169834.

  • 완료되었습니다. Git 커밋하고 머지 리퀘스트를 생성하세요 🎉

NOT NULL 제약 조건 추가#

마지막 단계는 샤딩 키에 NOT NULL 제약 조건이 있는지 확인하는 것입니다.

소형 테이블

bundle exec rails g post_deployment_migration <table_name>_not_null을 사용하여 post deployment 마이그레이션을 생성합니다

예를 들어, 테이블 subscription_user_add_on_assignments:

class AddSubscriptionUserAddOnAssignmentsOrganizationIdNotNull < Gitlab::Database::Migration[2.2]
  milestone '17.6'
  disable_ddl_transaction!

  def up
    add_not_null_constraint :subscription_user_add_on_assignments, :organization_id
    end

  def down
    remove_not_null_constraint :subscription_user_add_on_assignments, :organization_id
  end
end

bin/rails db:migrate를 실행합니다.

해당 db/docs.*.yml 파일(이 경우 db/docs/subscription_user_add_on_assignments.yml)을 열고 desired_sharding_keydesired_sharding_key_migration_job_name 구성을 제거한 후 sharding_key를 추가합니다.

sharding_key:
  organization_id: organizations

대형 테이블 또는 런타임을 초과하는 테이블

이 경우 샤딩 키를 추가하기 전에 비동기 검증을 추가해야 합니다. 2개의 머지 리퀘스트 프로세스가 됩니다. 테이블 packages_package_files를 예로 들어 보겠습니다.

Step 1 (MR 1): packages_package_files에 샤딩 키에 대한 NOT NULL 추가:

validate: false로 not null 제약 조건을 추가하는 post deployment 마이그레이션을 생성합니다.

[

](/19.1/development/organization/sharding/img/packages-package-files_v18_6.png)

class AddPackagesPackageFilesProjectIdNotNull < Gitlab::Database::Migration[2.2]
  milestone '17.11'
  disable_ddl_transaction!

  def up
    add_not_null_constraint :packages_package_files, :project_id, validate: false
  end

  def down
    remove_not_null_constraint :packages_package_files, :project_id
  end
end

비동기 제약 조건 검증을 준비하는 또 다른 post deployment 마이그레이션을 생성합니다.

class PreparePackagesPackageFilesProjectIdNotNullValidation < Gitlab::Database::Migration[2.2]
  disable_ddl_transaction!
  milestone '17.11'

  CONSTRAINT_NAME = :check_43773f06dc

  def up
    prepare_async_check_constraint_validation :packages_package_files, name: CONSTRAINT_NAME
  end

  def down
    unprepare_async_check_constraint_validation :packages_package_files, name: CONSTRAINT_NAME
  end
end

bin/rails db:migrate를 실행하고 변경 사항으로 머지 리퀘스트를 생성합니다.

Step 2 (MR 2): packages_package_filesproject_id NOT NULL 검증:

Step 1의 머지 리퀘스트가 병합되면 몇 일을 기다려 준비합니다. https://console.postgres.ai/에서 상태를 확인할 수 있습니다. joe instance 봇에게 테이블 정보를 물어보세요. Check constraints를 찾으세요.

샤딩 키 project_id가 NOT VALID로 표시됩니다.

Check constraints:
  "check_43773f06dc" CHECK (project_id IS NOT NULL) NOT VALID

표시되면 not null 제약 조건을 검증하는 새 post deployment 마이그레이션을 생성할 수 있습니다. down 마이그레이션은 no-op이 됩니다.

bin/rails db:migrate를 실행하고 structure.sql에서 다음 add constraint를 제거한 후 테이블 정의에 추가합니다:

제거:

ALTER TABLE packages_package_files
     ADD CONSTRAINT check_43773f06dc CHECK ((project_id IS NOT NULL)) NOT VALID;
  • 추가:
CREATE TABLE packages_package_files (
    .
    .
    CONSTRAINT check_43773f06dc CHECK ((project_id IS NOT NULL)),
);

해당 db/docs.*.yml 파일(이 경우 db/docs/packages_package_files.yml)을 열고 desired_sharding_keydesired_sharding_key_migration_job_name 구성을 제거한 후 sharding_key를 추가합니다.

이 마이그레이션을 되돌리는 것이 #no-op으로 의도되었으므로 pipeline:skip-check-migrations 라벨을 붙여 머지 리퀘스트를 생성합니다.

파이프라인이 누락된 FK에 대해 불평할 수 있습니다. [sharding_key_spec.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/lib/gitlab/organizations/sharding_key_spec.rb)의 `allowed_to_be_missing_foreign_key`에 FK를 추가해야 합니다.

FK를 생략해도 되는 경우에 대한 지침은 샤딩 키 칼럼에서 외래 키를 생략하는 경우를 참조하세요.

샤딩 키 칼럼에서 외래 키를 생략하는 경우#

샤딩 키 칼럼은 부모 테이블(projects, namespaces 또는 organizations)을 외래 키 제약 조건으로 참조해야 합니다. 시간 기반 파티션 관리(partitioned_byretain_for로 구성)를 통해 고정 보존 기간 후 자동으로 데이터를 삭제하는 테이블의 경우 외래 키를 생략할 수 있습니다. 이전 파티션은 전체적으로 삭제되므로 참조 무결성은 cascade FK 제약 조건이 아닌 보존 정책에 의해 유지됩니다. 파티션이 분리되어 모든 행이 오래된 것으로 간주되면 삭제되는 sliding_list 파티셔닝 전략을 사용하는 테이블에도 동일하게 적용됩니다.

예시 1: retain_for를 사용한 고정 보존

web_hook_logs_daily는 일별로 파티셔닝되고 14일보다 오래된 파티션을 삭제합니다:

partitioned_by :created_at, strategy: :daily, retain_for: 14.days

이전 파티션은 자동으로 삭제되므로 projects를 참조하는 web_hook_logs_daily.project_id의 외래 키는 필요하지 않습니다.

예시 2: sliding list 파티셔닝

security_findingssliding_list 전략을 사용하고 모든 findings가 정리된 스캔과 연관되어 security_scan_stale_after_days(구성 가능, 기본값 3개월)보다 오래된 경우 파티션을 분리합니다:

partitioned_by :partition_number,
  strategy: :sliding_list,
  next_partition_if: ->(partition) { partition_full?(partition) || oldest_record_stale?(partition) },
  detach_partition_if: ->(partition) { detach_partition?(partition.value) }

오래된 파티션은 분리되어 결국 삭제되므로 projects를 참조하는 security_findings.project_id의 외래 키는 필요하지 않습니다.

이러한 이유로 외래 키를 생략할 때는 보존 동작을 설명하는 주석과 함께 spec/lib/gitlab/organizations/sharding_key_spec.rballowed_to_be_missing_foreign_key에 칼럼을 추가하세요:

# No LFK needed: daily partitions are dropped after 14 days via retain_for
'web_hook_logs_daily.project_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/524820

# No LFK needed: sliding_list partitions are detached once findings are stale and purged
'security_findings.project_id', # https://gitlab.com/gitlab-org/gitlab/-/work_items/588191

전송 서비스 지원 추가#

테이블이 organization_id로 샤딩된 경우, organization 이전(사용자나 그룹이 organization 간에 이동하는 경우) 중에 테이블이 처리되는지 여부를 추적하기 위해 organization_transfer_support도 추가해야 합니다.

다음 이전 서비스 중 하나에 이전 로직을 구현한 경우 supported로 설정합니다:

app/services/organizations/transfer/groups_service.rb

  • app/services/organizations/transfer/users_service.rb

  • ee/app/services/ee/organizations/transfer/groups_service.rb

  • 테이블에 이전 지원이 필요하지만 아직 없는 경우 todo로 설정합니다 (기존 테이블만 해당 - 새 테이블은 반드시 supported여야 합니다)

  sharding_key:
    organization_id: organizations
  organization_transfer_support: supported

update_organization_id_for 헬퍼를 사용하여 해당 이전 서비스에 테이블을 추가합니다:

# app/services/organizations/transfer/users_service.rb
def update_associated_organization_ids(user_ids)
  update_organization_id_for(PersonalAccessToken) { |relation| relation.for_users(user_ids) }
  update_organization_id_for(YourModel) { |relation| relation.where(user_id: user_ids) }
end

팩토리에서 공통 organization 사용#

organization_id로 샤딩된 모델의 RSpec 팩토리는 공통 organization과 자동으로 연결되어야 합니다. 이렇게 하면 명시적인 organization 설정 없이도 테스트가 올바르게 작동합니다.

# spec/factories/your_models.rb
factory :your_model do
  organization { association(:common_organization) }
  # or derive from a related model
  organization { user&.organization || association(:common_organization) }
end

실패 디버그#

Kibana 사용

백필 job을 대기열에 추가한 후 실패 알림을 받는 경우가 있습니다. 한 가지 방법은 Kibana 로그를 사용하는 것입니다.

참고: Kibana 로그는 7일간만 저장됩니다

최근 BackfillPushEventPayloadsProjectId BBM 실패를 예시로 살펴보겠습니다.

실패는 백필된 원본 머지 리퀘스트에 댓글로 보고됩니다. 예시: 머지 리퀘스트 !183123

[

](/19.1/development/organization/sharding/img/bbm-failure_v18_6.png)

#chat-ops-test Slack 채널에서 /chatops gitlab run batched_background_migrations list --job-class-name=<desired_sharding_key_migration_job_name>을 사용하여 job 상태를 확인할 수도 있습니다.

[

](/19.1/development/organization/sharding/img/backfill-chatops-failed_v18_6.png)

Kibana 대시보드를 사용하여 실패 원인을 파악해 봅시다.

데이터 뷰가 pubsub-sidekiq-inf-gprd*로 설정되어 있는지 확인합니다.

[

](/19.1/development/organization/sharding/img/pubsub_v18_6.png)

왼쪽에서 사용 가능한 모든 필드를 볼 수 있습니다. json.job_class_name(원하는 샤딩 키 마이그레이션 job 이름)과 json.new_state: failed만 필요합니다.

[

](/19.1/development/organization/sharding/img/pubsub-fields_v18_6.png)

원하는 로그를 얻기 위해 해당 필터를 추가합시다.

[

](/19.1/development/organization/sharding/img/pubsub-filters_v18_6.png)

이 경우 json.job_class_nameBackfillPushEventPayloadsProjectId로, json.new_statefailed로 설정하고 필터를 적용합니다.

[

](/19.1/development/organization/sharding/img/pubsub-filters-add_v18_6.png)

올바른 타임라인을 선택했는지 확인합니다. 이 마이그레이션이 며칠 전에 실패로 보고되었으므로 마지막 7일만 표시하도록 필터링합니다.

[

](/19.1/development/organization/sharding/img/kibana-dashboard-timeline_v18_6.png)

그 후 추가된 필터와 함께 원하는 로그가 표시됩니다.

[

](/19.1/development/organization/sharding/img/kibana-logs_v18_6.png)

로그를 확장하고 json.exception_message를 찾습니다.

[

](/19.1/development/organization/sharding/img/kibana-logs-message_v18_6.png)

  • 보시다시피 이 BBM은 Sidekiq::Shutdown 😡으로 인해 실패했습니다.

  • 수정하려면 마이그레이션을 다시 대기열에 추가하면 됩니다.

Grafana 사용

Kibana에서 아무것도 찾을 수 없는 경우가 있습니다. 로그를 7일까지만 저장하기 때문입니다. 이를 위해 Grafana 대시보드를 사용할 수 있습니다.

최근 BackfillApprovalMergeRequestRulesUsersProjectId BBM 실패를 예시로 살펴보겠습니다.

원본 머지 리퀘스트에서 태그됩니다.

[

](/19.1/development/organization/sharding/img/bbm-mr-message_v18_6.png)

#chat-ops-test Slack 채널에서 /chatops gitlab run batched_background_migrations list --job-class-name=<desired_sharding_key_migration_job_name>을 사용하여 job 상태를 확인할 수도 있습니다.

[

](/19.1/development/organization/sharding/img/chatops-failed-status_v18_6.png)

Kibana 대시보드를 확인합시다. 이 job에 대한 로그가 없습니다.

Grafana 대시보드로 이동합시다.

[

](/19.1/development/organization/sharding/img/grafana_v18_6.png)

Explore를 클릭하고 새 쿼리를 추가합니다.

[

](/19.1/development/organization/sharding/img/grafana-explore_v18_6.png)

샤딩 키 실패를 디버그하는 가장 쉬운 방법은 테이블 크기 이상을 확인하는 것입니다.

[

](/19.1/development/organization/sharding/img/grafana-metrics_v18_6.png)

  • 지표: gitlab_component_utilization:pg_table_size_bytes:1h.

  • 라벨 필터:

env: gprd.

  • type: patroni.

  • relname: approval_merge_request_rules_users.

타임라인: job 생성일 며칠 전부터 선택합니다. 머지 리퀘스트에서 실패가 2025-03-31에 보고되었고 job은 2025-03-11에 생성되었음을 볼 수 있습니다. 2025-03-01부터 2025-04-02까지 시간 범위를 선택했습니다. 적절히 조정할 수 있습니다.

쿼리를 실행하면 선택된 시간 프레임 내에 그래프가 생성됩니다.

[

](/19.1/development/organization/sharding/img/grafana-output-1_v18_6.png)

이 그래프를 이해해 봅시다. 백필 job이 2025-03-11에 시작되었으며, 이 날짜부터 테이블 크기가 약간 증가하는 것을 볼 수 있습니다.

[

](/19.1/development/organization/sharding/img/grafana-output-2_v18_6.png)

이는 매우 정상적인 현상입니다.

post 마이그레이션에서 추가한 변경 사항을 살펴봅시다. 먼저 prepare_async 인덱스를 추가했습니다. postgres.ai에서 크기를 확인하면 10GB입니다. 그래프의 급등에서 볼 수 있듯이 2025-03-15 00:00에 생성되었습니다.

[

](/19.1/development/organization/sharding/img/grafana-output-3_v18_6.png)

인덱스가 생성되면 백필이 시작됩니다.

[

](/19.1/development/organization/sharding/img/grafana-output-4_v18_6.png)

BBM이 2025-03-29에 실패합니다. 그래프에서 이 시점에 테이블 크기가 감소한 것을 볼 수 있습니다.

[

](/19.1/development/organization/sharding/img/grafana-output-5_v18_6.png)

  • 인덱스 + 칼럼 백필로 인해 백필 전에 비해 테이블 크기가 약 ~20GB 증가했으며, 약 ~22% 테이블 크기 증가(~90GB에서 ~110GB로) 🫨.

  • 모든 테이블을 100GB 미만으로 유지하는 것이 목표입니다.

미해결, 종료된 이슈 업데이트#

데이터베이스 YAML 문서에 연결된 일부 이슈가 새 이슈를 위해 종료되었지만 YAML 파일은 여전히 원래 URL을 가리키고 있습니다. 진행 상황을 정확하게 측정하려면 올바른 항목을 가리키도록 이를 업데이트해야 합니다.

샤딩 이슈에 더 많은 정보 추가#

모든 샤딩 이슈에는 담당자, 관련 마일스톤이 있어야 하고, 해당하는 경우 블로커에 연결되어야 합니다. 이를 통해 작업을 계획하고 완료 날짜를 추정할 수 있습니다. 또한 문제나 우려 사항이 있을 경우 연락할 담당자가 각 이슈에 지정됩니다. 블로커 이슈를 강조 표시하여 해결할 수 있도록 프로젝트 작업을 시각화하는 데도 도움이 됩니다.

블로커는 의존성이 될 수 있습니다. 예를 들어, notes 테이블은 다른 테이블이 진행되기 전에 완전히 마이그레이션되어야 합니다. 다운스트림 이슈는 이러한 관계를 이해하는 데 도움이 되도록 관련 항목을 블로커로 표시해야 합니다.