CI 미러링된 테이블

문제 설명

분해 작업의 일환으로, GitLab이 사용 중인 단일 데이터베이스를 두 개의 데이터베이스, mainci로 분할하는 것이 목표였습니다. 이 과정에서 mainci 테이블 간의 모든 조인을 제거하는 것이 큰 과제가 되었습니다. PostgreSQL은 서로 다른 데이터베이스에 속하는 테이블 간의 조인을 지원하지 않기 때문입니다. 그러나 main 데이터베이스의 일부 핵심 애플리케이션 모델은 CI 측에서 매우 자주 쿼리됩니다. 예를 들어:

  • namespaces 테이블의 Namespace
  • projects 테이블의 Project

이러한 테이블에서 조인을 수행할 수 없는 것은 큰 도전입니다. 팀은 main 데이터베이스의 해당 테이블을 CI 데이터베이스로 논리적으로 복제하기로 결정했습니다. 새로운 테이블은 다음과 같습니다:

  • namespaces 테이블의 미러인 ci_namespace_mirrors.
  • projects 테이블의 미러인 ci_project_mirrors.

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

  1. main 데이터베이스 테이블은 namespacesprojects 테이블과 조인하여 쿼리될 수 있습니다.
  2. ci 데이터베이스 테이블은 ci_namespace_mirrorsci_project_mirrors 테이블과 조인하여 쿼리될 수 있습니다.
graph LR subgraph "Main database (tables)" A[namespaces] -->|updates| B[namespaces_sync_events] A -->|deletes| C[loose_foreign_keys_deleted_records] D[projects] -->|deletes| C D -->|updates| E[projects_sync_events] end B --> F C --> G E --> H subgraph "Sidekiq worker jobs" F[Namespaces::ProcessSyncEventsWorker] G[LooseForeignKeys::CleanupWorker] H[Projects::ProcessSyncEventsWorker] end F -->|do update| I G -->|delete records| I G -->|delete records| J H -->|do update| J subgraph "CI database (tables)" I[ci_namespace_mirrors] J[ci_project_mirrors] end

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

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

소스 테이블과 대상 테이블 간에 CI 미러링된 테이블 동기화 유지

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

  1. 새로운 네임스페이스 또는 프로젝트의 생성.
  2. 네임스페이스 또는 프로젝트의 업데이트.
  3. 네임스페이스/프로젝트의 삭제.
graph LR subgraph CI["CI 테이블"] E[다른 CI 테이블] F{조인이 허용된 쿼리} G[ci_project_mirrors] H[ci_namespace_mirrors] E---F F---G F---H end Main["Main 테이블"]---L["⛔ ← 조인이 허용되지 않음 → ⛔"] L---CI subgraph Main["Main 테이블"] A[다른 main 테이블] 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 테이블의 대상 레코드는 loose foreign keys (LFK) 메커니즘을 사용하여 삭제됩니다.

config/gitlab_loose_foreign_keys.yml에 이러한 항목이 있어서 LFK 메커니즘이 예상대로 작동했습니다. CI 미러링된 테이블에서 삭제된 main 데이터베이스의 namespaces 또는 projects에 매핑된 레코드를 삭제했습니다.

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. 메인 데이터베이스의 소스 테이블을 커서를 사용하여 스캔합니다.
  2. ci 데이터베이스의 대상 테이블과 namespacesprojects의 항목을 비교합니다.
  3. 동기화되지 않은 항목을 Kibana 및 Prometheus에 보고합니다.
  4. 어떠한 불일치 사항도 수정합니다.