Geo 저장소 동기화
GitLab v19.5요약
리포지터리 복제(동기화 또는 sync 라고도 합니다)는 기본 사이트의 Git 리포지터리를 보조 사이트로 복제하는 Geo의 핵심 기능입니다. Geo::ProjectRepositoryRegistry 클래스는 보조 사이트에서 리포지터리 복제 상태를 추적하는 데 사용하는 모델을 정의합니다.
리포지터리 복제(동기화 또는 sync 라고도 합니다)는 기본 사이트의 Git 리포지터리를 보조 사이트로 복제하는 Geo의 핵심 기능입니다. 이를 통해 보조 사이트가 모든 리포지터리의 최신 사본을 유지하므로, 재해 복구와 Git 데이터의 지리적 분산이 가능해집니다.
Geo::ProjectRepositoryRegistry 클래스는 보조 사이트에서 리포지터리 복제 상태를 추적하는 데 사용하는 모델을
정의합니다. 메인(기본) 데이터베이스의 각 프로젝트마다 추적 데이터베이스에 레코드가 하나씩 유지됩니다.
리포지터리에 대해 다음을 기록합니다.
- 마지막으로 동기화된 시각
- 마지막으로 동기화에 성공한 시각
- 재동기화가 필요한지 여부
- 재시도를 시도해야 하는 시점
- 재시도 횟수
- 검증 여부와 검증 시각
동기화 트리거#
리포지터리 동기화는 여러 메커니즘으로 트리거될 수 있습니다.
소스 코드 보기
flowchart TD
A(["Start"])
A --> B{"What triggers the repository sync?"}
B --> C["Resync from the admin interface"]
B --> D["An update event on the repository (for example, git push)"]
B --> E["Cron worker periodic sync"]
B --> F["Rails console sync: \n 'replicator.sync'"]
C --> G["Geo::EventWorker"]
D --> G
E --> H["Geo::SyncWorker"]
F --> I["Geo::FrameworkRepositorySyncService.execute"]
H --> I
G --> I이 트리거들은 백그라운드 동기화와 이벤트 기반 동기화라는 두 범주로 정리됩니다.
백그라운드 동기화#
모든 리포지터리가 동기화되도록 보장하기 위해, 보조 사이트에서는 Geo::RepositoryRegistrySyncWorker가
백그라운드에서 주기적으로 실행됩니다. 구체적으로 이 워커는 다음을 수행합니다.
- 동기화가 필요한 레지스트리를 추적 데이터베이스에서 조회합니다. 다음과 같은 경우가 있습니다.
- 미동기화: 보조 사이트에서 한 번도 동기화된 적이 없어 아직 존재하지 않는 프로젝트입니다.
- 최근 업데이트됨:
Geo::ProjectRepositoryRegistry모델의last_repository_successful_sync_at타임스탬프보다last_repository_updated_at타임스탬프가 더 최근인 프로젝트입니다. - 수동 재동기화: 관리자가 Geo Admin area에서 리포지터리를 재동기화 대상으로 직접 표시할 수 있습니다.
- 업데이트가 필요한 각 리포지터리에 대해
Geo::SyncWorkerjob을 큐에 넣습니다. - 모든 리포지터리에 대한 기준선 동기화를 제공합니다.
이벤트 기반 동기화#
이벤트 기반 동기화는 기본 사이트의 리포지터리 업데이트로 트리거됩니다. 리포지터리가 업데이트되면 해당 리포지터리
업데이트에 대한 새 레코드가 Geo 이벤트 테이블에 생성됩니다. 보조 사이트에서 실행되는 Geo Log Cursor 데몬이
이 이벤트를 가져와 Geo::EventWorker job을 예약하고, 이 job 이 Geo::EventService를 실행합니다.
이 서비스는 다음을 수행합니다.
- 리포지터리 replicator를 찾아
consume(event)메서드를 실행합니다. - 경합 상태를 피하기 위해 동기화를 시도하기 전에 레지스트리 상태를
pending으로 변경합니다 - 최신 변경 사항이 보조 사이트로 신속하게 복제되도록 보장합니다
시퀀스 다이어그램#
다음 다이어그램은 리포지터리 동기화가 시스템에서 어떻게 흐르는지 보여 줍니다. 두 경로는 마지막이 동일하므로 같은 다이어그램에 함께 표현했습니다.
소스 코드 보기
sequenceDiagram
participant Worker as Geo::EventWorker
participant Service as Geo::EventService
participant Scheduler as Geo::RepositoryRegistrySyncWorker
participant SyncWorker as Geo::SyncWorker
participant SyncService as Geo::SyncService
participant Replicator as Geo::ProjectRepositoryReplicator
participant Registry as Geo::ProjectRepositoryRegistry
participant FRSS as Geo::FrameworkRepositorySyncService
participant Primary as Primary Site
Note over Worker,Replicator: Event-driven sync path
Worker->>Service: new(replicable_name, event_name, payload)
Service->>Replicator: new(model_record_id: payload[:model_record_id])
Replicator-->>Service: Geo:ProjectRepositoryReplicator
Service->>Replicator: consume(event_name, **payload)
Replicator->>Registry: find_or_initialize_by(model_foreign_key => model_record_id)
Registry-->>Replicator: Geo::ProjectRepositoryRegistry
Replicator->>Registry: pending!
Registry-->>Replicator: true
Note over Scheduler,Replicator: Background sync path
Scheduler->>SyncWorker: enqueues
SyncWorker->>SyncService: new(replicable_name, model_record_id)
SyncService->>Replicator: new(model_record_id: payload[:model_record_id])
Replicator-->>SyncService: Geo:ProjectRepositoryReplicator
SyncService->>Replicator: sync
Note over Replicator, Primary: Common path
Replicator->>FRSS: execute
FRSS->>Replicator: registry
Replicator->>Registry: find_or_initialize_by(model_foreign_key => model_record_id)
Registry-->>Replicator: Geo::ProjectRepositoryRegistry
Replicator-->>FRSS: Geo::ProjectRepositoryRegistry
FRSS->>Registry: started!
FRSS->>Primary: Fetch repository
Primary->>FRSS: Repository data
FRSS->>Registry: synced!</code></pre></details></div>
배타적 임대 메커니즘#
배타적 임대 메커니즘은 동일한 리포지터리에 대해 동기화 작업이 동시에 실행되는 것을 막는 핵심 구성 요소입니다.
임대는 Geo::FrameworkRepositorySyncService가 실행될 때 획득합니다.
목적은 다음과 같습니다.
- 여러 동기화 job 이 동시에 같은 리포지터리를 업데이트하려 할 때 발생하는 경합 상태를 방지합니다.
- 리포지터리별로 동기화 작업을 직렬화해 데이터 일관성을 보장합니다.
- 중복된 이벤트 워커와
pending 상태에 멈춘 동기화에서 오는 불필요한 부하를 막습니다.
임대의 타임아웃은 3시간입니다. 임대 키 형식은 geo_sync_ssf_service:{replicable_name}:{model_record_id} 입니다.
동기화 작업 중 동작#
동기화 작업이 시작되면 다음과 같이 진행됩니다.
- 서비스가 해당 리포지터리에 대한 배타적 임대를 획득하려고 시도합니다
- 다른 프로세스가 이미 임대를 보유하고 있는 경우
- "Cannot obtain an exclusive lease" 로그 메시지를 남기고 동기화가 조기에 종료됩니다.
- 동기화 작업은 수행되지 않습니다.
- 해당 job은 나중에 재시도됩니다.
- 임대를 획득한 경우
- 동기화 작업이 진행됩니다.
- 동기화가 끝난 뒤
ensure 블록에서 임대가 해제됩니다.
Mermaid 다이어그램 (18줄)소스 코드 보기
sequenceDiagram
participant FRSS as FrameworkRepositorySyncService
participant Redis as Redis (Lease Store)
participant Primary as Primary Site
FRSS->>Redis: Try obtain lease<br/>(geo_sync_ssf_service:type:id, TTL=8h)
alt Lease obtained
Redis-->>FRSS: Lease granted
FRSS->>FRSS: Change state to started
FRSS->>Primary: Fetch repository
Primary-->>FRSS: Repository data
FRSS->>FRSS: Change state to synced
FRSS->>Redis: Cancel lease
else Lease taken
Redis-->>FRSS: Lease denied
Note over FRSS: Sync skipped
end</code></pre></details></div>
고아 임대 키#
고아 임대 키는 동기화 프로세스가 정상적으로 종료되지 않을 때(예: SIGKILL 또는 프로세스 충돌) 발생할 수 있습니다.
ensure 블록이 임대를 해제하기 전에 프로세스가 종료되기 때문입니다.
임대 키가 고아 상태가 되면 해당 리포지터리에 대한 이후 동기화 시도가 최대 3시간 동안(임대가 만료될 때까지)
차단됩니다.
이 문제를 완화하려면 다음 선택지가 있습니다.
- 자동 만료: 고아 임대는 3시간이 지나면 자동으로 만료됩니다.
- 모니터링:
geo.log에서 "Cannot obtain an exclusive lease" 메시지가 증가하는지 확인하고,
lease_key 값이 geo_sync_ssf_service:{replicable_name}:{model_record_id} 인 항목으로 영향을 받았을 수 있는 리포지터리를 식별합니다.
- 수동 해제: 최후의 수단으로 Rails 콘솔에서 고아 임대를 직접 해제할 수 있습니다(문제 해결 참고)
관련 주제#
- Geo 셀프 서비스 프레임워크 - Geo 복제 프레임워크 개요
- 동기화 및 검증 문제 해결 - 고아 임대 키 해결을 포함한 문제 해결 가이드
- 이슈 #552408 - 고아 임대 키 문제를 최초로 문서화한 이슈