CI 미러 테이블
GitLab v19.1데이터베이스 분리 작업의 일환으로, GitLab이 사용하는 단일 데이터베이스를 main과 ci라는 두 개의 데이터베이스로 분리하는 것이 목표였으며, 이 과정에서 main 테이블과 ci 테이블 간의 조인을 모두 제거해야 하는 큰 과제가 생겼습니다.
문제 정의#
데이터베이스 분리 작업의 일환으로,
GitLab이 사용하는 단일 데이터베이스를 main과 ci라는 두 개의 데이터베이스로 분리하는 것이 목표였으며,
이 과정에서 main 테이블과 ci 테이블 간의 조인을 모두 제거해야 하는 큰 과제가 생겼습니다.
이는 PostgreSQL이 서로 다른 데이터베이스에 속한 테이블 간의 조인을 지원하지 않기 때문입니다.
그러나 main 데이터베이스의 일부 핵심 애플리케이션 모델은 CI 측에서 매우 자주 조회됩니다.
예를 들어:
-
namespaces테이블의Namespace. -
projects테이블의Project.
이러한 테이블에 대해 조인을 수행할 수 없다는 점은 큰 과제입니다. 팀은 이 테이블들을 main 데이터베이스에서 CI 데이터베이스로 논리적으로 복제하여 다음과 같은 새 테이블을 만들기로 결정했습니다:
-
namespaces테이블의 미러인ci_namespace_mirrors -
projects테이블의 미러인ci_project_mirrors
이 논리적 복제는 두 가지를 의미합니다:
-
main데이터베이스 테이블은namespaces및projects테이블과 조인하여 조회할 수 있습니다. -
ci데이터베이스 테이블은ci_namespace_mirrors및ci_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 미러 테이블을 소스 테이블과 동기화 유지#
소스 테이블과 타깃 테이블을 동기화 상태로 유지하려면 다음과 같은 세 가지 유형의 이벤트를 처리해야 합니다:
-
새로운 네임스페이스 또는 프로젝트 생성.
-
네임스페이스 또는 프로젝트 업데이트.
-
네임스페이스/프로젝트 삭제.
graph LR subgraph CI["CI Tables"] E[other CI tables] F{queries with joins allowed} G[ci_project_mirrors] H[ci_namespace_mirrors]
E---F
F---G
F---H
end
Main["Main Tables"]---L["⛔ ← Joins are not allowed → ⛔"]
L---CI
subgraph Main["Main Tables"]
A[other main tables]
B{queries with joins allowed}
C[projects]
D[namespaces]
A---B
B---C
B---D
end
생성 및 업데이트#
새로 생성되거나 업데이트된 네임스페이스 또는 프로젝트의 데이터 동기화는 다음 순서로 이루어집니다:
-
main데이터베이스에서:namespaces또는projects테이블에 대한INSERT또는UPDATE가 발생하면namespaces_sync_events및projects_sync_events테이블에 항목이 추가됩니다. 이 테이블들도main데이터베이스에 존재합니다. 이 항목들은 두 테이블 모두에 설정된 트리거에 의해 추가됩니다. -
모델 수준에서: 소스 모델
Namespace또는Project에서 커밋이 발생하면, 해당하는 Sidekiq job인Namespaces::ProcessSyncEventsWorker또는Projects::ProcessSyncEventsWorker가 실행되도록 예약됩니다. -
이 워커들은 다음을 수행합니다:
main 데이터베이스에서 (namespaces/project)_sync_events 테이블의 항목을 읽어,
어떤 네임스페이스 또는 프로젝트를 동기화할지 확인합니다.
- 업데이트된 레코드의 데이터를 타깃 테이블인
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
일관성 검사#
두 동기화 메커니즘이 예상대로 작동하는지 확인하기 위해, cron job으로 몇 분마다 트리거되는 두 개의 추가 워커 job을 배포합니다:
-
Database::CiNamespaceMirrorsConsistencyCheckWorker -
Database::CiProjectMirrorsConsistencyCheckWorker
이 job들은 다음을 수행합니다:
-
커서를 사용하여
main데이터베이스의 소스 테이블 두 개를 스캔합니다. -
ci데이터베이스의 타깃 테이블과namespaces및projects항목을 비교합니다. -
동기화되지 않은 항목을 Kibana 및 Prometheus에 보고합니다.
-
불일치를 수정합니다.