CI 미러 테이블

문제 진술

데이터베이스 분해 작업의 일환으로,

GitLab이 사용하는 단일 데이터베이스를 mainci 두 개의 데이터베이스로 분할하는 목표가 있었으며,

이와 관련하여 mainci 테이블 간의 모든 조인을 제거하는 큰 도전이 있었습니다.

이는 PostgreSQL이 서로 다른 데이터베이스에 속하는 테이블 간의 조인을 지원하지 않기 때문입니다.

하지만 메인 데이터베이스의 일부 핵심 애플리케이션 모델은 CI 측에 의해 매우 자주 쿼리됩니다. 예를 들어:

  • Namespace, namespaces 테이블에서.
  • Project, projects 테이블에서.

이 테이블들에서 joins를 수행할 수 없다는 것은 큰 도전 과제가 됩니다. 팀은 이러한 테이블들의 논리적 복제를

메인 데이터베이스에서 CI 데이터베이스로 새 테이블에 수행하기로 선택했습니다:

  • ci_namespace_mirrors, namespaces 테이블의 미러로
  • ci_project_mirrors, projects 테이블의 미러로

이 논리적 복제는 두 가지 의미를 가집니다:

  1. main 데이터베이스 테이블을 쿼리하고 namespacesprojects 테이블에 조인할 수 있습니다.

  2. ci 데이터베이스 테이블은 ci_namespace_mirrorsci_project_mirrors 테이블과 조인할 수 있습니다.

graph LR subgraph "메인 데이터베이스 (테이블)" A[namespaces] -->|업데이트| B[namespaces_sync_events] A -->|삭제| C[loose_foreign_keys_deleted_records] D[projects] -->|삭제| C D -->|업데이트| E[projects_sync_events] end B --> F C --> G E --> H subgraph "사이드킥 작업" F[Namespaces::ProcessSyncEventsWorker] G[LooseForeignKeys::CleanupWorker] H[Projects::ProcessSyncEventsWorker] end F -->|업데이트 수행| I G -->|레코드 삭제| I G -->|레코드 삭제| J H -->|업데이트 수행| J subgraph "CI 데이터베이스 (테이블)" I[ci_namespace_mirrors] J[ci_project_mirrors] end

이 복제는 각 모델에서 필요한 몇 가지 속성으로 제한되었습니다:

  • Namespace에서 traversal_ids를 복제합니다.
  • Project에서는 프로젝트가 속한 그룹을 나타내는 namespace_id만 복제합니다.

CI 미러 테이블을 소스 테이블과 동기화 유지하기

우리는 소스와 대상 테이블을 동기화하기 위해 두 가지 유형의 이벤트에 주의해야 합니다:

  1. 새로운 namespaces 또는 projects의 생성.
  2. namespaces 또는 projects의 업데이트.
  3. namespaces/projects의 삭제.
graph LR subgraph CI["CI 테이블"] E[다른 CI 테이블] F{조인 허용 쿼리} G[ci_project_mirrors] H[ci_namespace_mirrors] E---F F---G F---H end Main["메인 테이블"]---L["⛔ ← 조인이 허용되지 않음 → ⛔"] L---CI subgraph Main["메인 테이블"] A[다른 메인 테이블] B{조인 허용 쿼리} C[projects] D[namespaces] A---B B---C B---D end

생성 및 업데이트

새로 생성되거나 업데이트된 네임스페이스 또는 프로젝트의 데이터 동기화는 다음과 같은 순서로 진행됩니다:

  1. main 데이터베이스에서: namespaces 또는 projects 테이블에서의 모든 INSERT 또는 UPDATEnamespaces_sync_eventsprojects_sync_events 테이블에 항목을 추가합니다. 이 테이블들은 main 데이터베이스에도 존재합니다. 이러한 항목은 두 테이블의 트리거에 의해 추가됩니다.

  2. 모델 수준에서: Namespace 또는 Project 소스 모델 중 하나에 커밋이 발생하면, 해당하는 Sidekiq 작업 Namespaces::ProcessSyncEventsWorker 또는 Projects::ProcessSyncEventsWorker가 실행되도록 예약됩니다.

  3. 이러한 작업자들은 다음을 수행합니다:

    1. main 데이터베이스의 (namespaces/project)_sync_events 테이블에서 항목을 읽어, 동기화할 네임스페이스 또는 프로젝트를 확인합니다.
    2. 업데이트된 레코드의 데이터를 타겟 테이블 ci_namespace_mirrors, ci_project_mirrors로 복사합니다.

삭제

namespaces 또는 projects가 삭제되면, 미러링된 CI 테이블의 타겟 레코드는 느슨한 외래 키 (LFK) 메커니즘을 사용하여 삭제됩니다.

config/gitlab_loose_foreign_keys.yml에 이러한 항목이 있기 때문에, LFK 메커니즘은 이미 기대한 대로 작동하고 있었습니다. 이는 main 데이터베이스에서 삭제된 namespaces 또는 projects에 매핑된 CI 미러링 테이블의 모든 레코드를 삭제했습니다.

ci_namespace_mirrors:
  - table: namespaces
    column: namespace_id
    on_delete: async_delete
ci_project_mirrors:
  - table: projects
    column: project_id
    on_delete: async_delete

일관성 검사

두 동기화 메커니즘이 정상적으로 작동하도록 하기 위해, 우리는 몇 분마다 크론 작업에 의해 트리거되는 두 개의 추가 작업자를 배포합니다:

  1. Database::CiNamespaceMirrorsConsistencyCheckWorker
  2. Database::CiProjectMirrorsConsistencyCheckWorker

이 작업들은:

  1. 커서를 사용하여 main 데이터베이스의 두 소스 테이블을 스캔합니다.
  2. namespacesprojects의 항목을 ci 데이터베이스의 타겟 테이블과 비교합니다.
  3. 동기화되지 않은 항목을 Kibana 및 Prometheus에 보고합니다.
  4. 모든 불일치를 수정합니다.