Geo 동기화 및 검증 오류 문제 해결
GitLab v19.4Offering: GitLab Self-Managed
요약
Admin > Geo > Sites 또는 Sync status Rake 태스크에서 복제 또는 검증 실패가 확인되면 다음 일반 절차로 실패를 해결해 볼 수 있습니다. 수동 재시도를 시도하기 전에 다음 향상된 진단 절차를 사용하면 동기화 문제의 범위와 성격을 더 잘 파악할 수 있습니다.
Admin > Geo > Sites 또는 Sync status Rake 태스크에서 복제 또는 검증 실패가 확인되면 다음 일반 절차로 실패를 해결해 볼 수 있습니다.
- Geo는 실패를 자동으로 재시도합니다. 실패가 새로 발생했고 수가 적거나 근본 원인이 이미 해결되었다고 판단되면, 실패가 사라지는지 기다려 볼 수 있습니다.
- 실패가 오래 지속되었다면 이미 많은 재시도가 이루어졌으며, 실패 유형에 따라 자동 재시도 간격이 최대 4시간까지 늘어난 상태입니다. 근본 원인이 이미 해결되었다고 판단되면 복제 또는 검증을 수동으로 재시도하여 대기 시간을 줄일 수 있습니다.
- 실패가 계속되면 다음 섹션을 사용해 해결을 시도합니다.
진단 절차#
수동 재시도를 시도하기 전에 다음 향상된 진단 절차를 사용하면 동기화 문제의 범위와 성격을 더 잘 파악할 수 있습니다.
모델 상태 확인#
이 절차는 모든 Geo 데이터 유형 모델 클래스에 대한 상세 상태 정보를 제공하며 체크섬 실패를 식별하는 데 도움이 됩니다. 이 실패는 복제 가능한 객체의 체크섬을 계산하지 못했을 때 발생합니다. 「기본 사이트 검증 실패」라고도 부릅니다.
체크섬 실패는 UI 또는 Rails 콘솔에서 확인할 수 있습니다.
기본 사이트에서 Data Management 페이지를 사용합니다.
다음 스크립트를 사용하면 각 모델 유형에 대해 다음을 포함한 상세 정보를 출력할 수 있습니다.
- 레코드 총 개수
- 실패, 검증됨, 대기 중인 레코드 수
- 조사용 실패 레코드 샘플
ModelMapper 클래스는 GitLab 18.3에서 추가되었습니다.
이전 버전에서는 Geo 데이터 유형 모델 클래스 목록을 직접 지정해야 합니다.
-
기본 사이트에서 Rails 콘솔 세션을 시작합니다.
-
다음 스크립트를 실행하여 전체 개요를 확인합니다.
def output_geo_verification_failures model_classes = ::Gitlab::Geo::ModelMapper.available_models model_classes.each do |klass| total = klass.count state_klass = klass.verification_state_table_class failed_examples = [] puts "\n=== #{klass.name} ===" puts "Total: #{total}" ::Geo::VerificationState::VERIFICATION_STATE_VALUES.each do |key, value| records = state_klass.where(verification_state: value) failed_examples = records if key == 'verification_failed' puts "#{key.gsub('verification_', '').camelize}: #{records.size}" end if failed_examples.any? puts "\nSample failed records:" failed_examples.limit(3).each { |record| puts " ID: #{record.id}, Checksum: #{record.verification_checksum || 'nil'}, Error: #{record.verification_failure}" } end end nil end output_geo_verification_failures
레지스트리 상태 확인#
이 절차는 모든 Geo 레지스트리 유형에 대한 상세 상태 정보를 제공하며 실패의 패턴을 식별하는 데 도움이 됩니다.
-
보조 사이트에서 Rails 콘솔 세션을 시작합니다.
-
다음 스크립트를 실행하여 전체 개요를 확인합니다.
def output_geo_failures() registry_classes = [ Geo::UploadRegistry, Geo::JobArtifactRegistry, Geo::PackageFileRegistry, Geo::PagesDeploymentRegistry, Geo::ProjectRepositoryRegistry, Geo::TerraformStateVersionRegistry, Geo::MergeRequestDiffRegistry, Geo::LfsObjectRegistry, Geo::PipelineArtifactRegistry, Geo::CiSecureFileRegistry, Geo::ContainerRepositoryRegistry ] registry_classes.each do |klass| puts "\n=== #{klass.name} ===" puts "Total: #{klass.count}" puts "Failed: #{klass.failed.count}" puts "Synced: #{klass.synced.count}" puts "Pending: #{klass.pending.count}" puts "Started: #{klass.with_state(:started).count}" if klass.failed.count > 0 puts "\nSample failed records:" klass.failed.limit(3).each { |record| puts " ID: #{record.id}, Error: #{record.last_sync_failure}" } end end nil end output_geo_failures() -
이 스크립트는 각 레지스트리 유형에 대해 다음을 포함한 상세 정보를 출력합니다.
- 레코드 총 개수
- 실패, 동기화됨, 대기 중인 레코드 수
- 조사용 실패 레코드 샘플
복제 또는 검증 수동 재시도#
보조 Geo 사이트의 Rails 콘솔에서 다음을 수행할 수 있습니다.
개별 구성 요소 다시 동기화 및 다시 검증#
보조 사이트에서 Admin > Geo > Replication으로 이동하면 개별 항목을 강제로 다시 동기화하거나 다시 검증할 수 있습니다.
이 방법이 동작하지 않으면 Rails 콘솔을 사용해 같은 작업을 수행할 수 있습니다. 다음 섹션에서는 Rails 콘솔에서 내부 애플리케이션 명령을 사용해 개별 레코드의 복제 또는 검증을 동기 또는 비동기로 실행하는 방법을 설명합니다.
Replicator 인스턴스 가져오기#
데이터를 변경하는 명령은 올바르지 않게 또는 적절한 조건이 아닌 상태에서 실행하면 피해를 줄 수 있습니다. 항상 테스트 환경에서 먼저 명령을 실행하고, 복원할 수 있는 백업 인스턴스를 준비해 둡니다.
동기화 또는 검증 작업을 수행하려면 먼저 Replicator 인스턴스를 가져와야 합니다.
먼저 수행하려는 작업에 따라 기본 사이트 또는 보조 사이트에서 Rails 콘솔 세션을 시작합니다.
기본 사이트:
- 리소스의 체크섬을 계산할 수 있습니다.
보조 사이트:
- 리소스를 동기화할 수 있습니다.
- 리소스의 체크섬을 계산하고 그 체크섬을 기본 사이트의 체크섬과 대조하여 검증할 수 있습니다.
다음으로 아래 스니펫 중 하나를 실행하여 Replicator 인스턴스를 가져옵니다.
모델 레코드의 ID가 주어진 경우#
123을 실제 ID로 바꿉니다.Packages::PackageFile을 Geo 데이터 유형 모델 클래스 중 하나로 바꿉니다.
model_record = Packages::PackageFile.find_by(id: 123)
replicator = model_record.replicator
레지스트리 레코드의 ID가 주어진 경우#
432를 실제 ID로 바꿉니다. 레지스트리 레코드는 추적하는 모델 레코드와 ID 값이 같을 수도 있고 다를 수도 있습니다.Geo::PackageFileRegistry를 Geo 레지스트리 클래스 중 하나로 바꿉니다.
보조 Geo 사이트에서 실행합니다.
registry_record = Geo::PackageFileRegistry.find_by(id: 432)
replicator = registry_record.replicator
레지스트리 레코드의 last_sync_failure에 오류 메시지가 있는 경우#
Geo::PackageFileRegistry를 Geo 레지스트리 클래스 중 하나로 바꿉니다.error message here를 실제 오류 메시지로 바꿉니다.
registry = Geo::PackageFileRegistry.find_by("last_sync_failure LIKE '%error message here%'")
replicator = registry.replicator
레지스트리 레코드의 verification_failure에 오류 메시지가 있는 경우#
Geo::PackageFileRegistry를 Geo 레지스트리 클래스 중 하나로 바꿉니다.error message here를 실제 오류 메시지로 바꿉니다.
registry = Geo::PackageFileRegistry.find_by("verification_failure LIKE '%error message here%'")
replicator = registry.replicator
Replicator 인스턴스로 작업 수행#
replicator 변수에 Replicator 인스턴스를 저장하고 나면 다음과 같은 여러
작업을 수행할 수 있습니다.
콘솔에서 동기화#
이 스니펫은 보조 사이트에서만 동작합니다.
동기화 코드를 콘솔에서 동기적으로 실행하므로, 리소스를 동기화하는 데 걸리는 시간을 확인하거나 전체 오류 백트레이스를 볼 수 있습니다.
replicator.sync
필요하면 콘솔의 로그 수준을 설정된 로그 수준보다 더 상세하게 지정한 다음 동기화를 수행합니다.
Rails.logger.level = :debug
콘솔에서 체크섬 계산 또는 검증#
이 스니펫은 모든 기본 사이트 또는 보조 사이트에서 동작합니다.
기본 사이트에서는 리소스의 체크섬을 계산하고 그 결과를 GitLab 메인 데이터베이스에 저장합니다. 보조 사이트에서는 리소스의 체크섬을 계산하고, GitLab 메인 데이터베이스에 있는 체크섬(기본 사이트가 생성한 값)과 비교한 뒤 그 결과를 Geo 추적 데이터베이스에 저장합니다.
체크섬 계산과 검증 코드를 콘솔에서 동기적으로 실행하므로, 걸리는 시간을 확인하거나 전체 오류 백트레이스를 볼 수 있습니다.
replicator.verify
Sidekiq job에서 동기화#
이 스니펫은 보조 사이트에서만 동작합니다.
Sidekiq이 리소스의 동기화를 수행하도록 job을 큐에 넣습니다.
replicator.enqueue_sync
Sidekiq job에서 검증#
이 스니펫은 모든 기본 사이트 또는 보조 사이트에서 동작합니다.
Sidekiq이 리소스의 체크섬 계산 또는 검증을 수행하도록 job을 큐에 넣습니다.
replicator.verify_async
모델 레코드 가져오기#
이 스니펫은 모든 기본 사이트 또는 보조 사이트에서 동작합니다.
replicator.model_record
레지스트리 레코드 가져오기#
레지스트리 테이블은 Geo 추적 DB에 저장되므로 이 스니펫은 보조 사이트에서만 동작합니다.
replicator.registry
Geo 데이터 유형 모델 클래스#
Geo 데이터 유형은 하나 이상의 GitLab 기능이 관련 데이터를 저장하기 위해 필요로 하는 특정 데이터 클래스이며, Geo가 보조 사이트로 복제합니다.
- Blob 유형:
Ci::JobArtifactCi::PipelineArtifactCi::SecureFileLfsObjectMergeRequestDiffPackages::PackageFilePagesDeploymentTerraform::StateVersionUploadDependencyProxy::ManifestDependencyProxy::Blob
- Git 리포지터리 유형:
DesignManagement::RepositoryProjectRepositoryProjectWikiRepositorySnippetRepositoryGroupWikiRepository
- 기타 유형:
ContainerRepository
주요 클래스 종류는 Registry, Model, Replicator 입니다. 이 클래스 중 하나의 인스턴스가 있으면 나머지도 가져올 수 있습니다. Registry와 Model은 주로 PostgreSQL DB 상태를 관리합니다. Replicator는 PostgreSQL이 아닌 데이터(파일, Git 리포지터리, 컨테이너 리포지터리)를 복제하거나 검증하는 방법을 알고 있습니다.
Geo 레지스트리 클래스#
GitLab Geo에서 레지스트리 레코드는 Geo 추적 데이터베이스의 레지스트리 테이블을 가리킵니다. 각 레코드는 GitLab 메인 데이터베이스에 있는 LFS 파일이나 프로젝트 Git 리포지터리 같은 복제 가능 항목 하나를 추적합니다. 쿼리할 수 있는 Geo 레지스트리 테이블에 대응하는 Rails 모델은 다음과 같습니다.
- Blob 유형:
Geo::CiSecureFileRegistryGeo::DependencyProxyBlobRegistryGeo::DependencyProxyManifestRegistryGeo::JobArtifactRegistryGeo::LfsObjectRegistryGeo::MergeRequestDiffRegistryGeo::PackageFileRegistryGeo::PagesDeploymentRegistryGeo::PipelineArtifactRegistryGeo::ProjectWikiRepositoryRegistryGeo::SnippetRepositoryRegistryGeo::TerraformStateVersionRegistryGeo::UploadRegistry
- Git 리포지터리 유형:
Geo::DesignManagementRepositoryRegistryGeo::ProjectRepositoryRegistryGeo::ProjectWikiRepositoryRegistryGeo::SnippetRepositoryRegistryGeo::GroupWikiRepositoryRegistry
- 기타 유형:
Geo::ContainerRepositoryRegistry
여러 구성 요소 다시 동기화 및 다시 검증#
구성 요소 리소스의 동기화 또는 검증이 실패하면 일괄 작업을 트리거하여 복제 큐를 다시 가동할 수 있습니다. 이 작업은 재시도 횟수와 예약 시간을 0으로 되돌려, 시스템이 실패한 리소스를 최대 1시간까지 기다리지 않고 더 빨리 처리하게 합니다.
이 작업은 리소스를 즉시 처리하지 않습니다. 대신 동기화와 검증을 담당하는 백그라운드 job을 다시 큐에 넣습니다. 실제 복제 작업은 표준 Geo 복제 프로세스를 통해 비동기로 수행됩니다.
다시 동기화와 다시 검증의 동작 방식#
다시 동기화 또는 다시 검증 작업을 트리거하면 시스템이 일치하는 레코드를 pending으로 표시합니다. Geo 다시 동기화 및
다시 검증 백그라운드 워커가 이 레코드를 가져가 일반 큐 우선순위에 따라 처리합니다.
이 방식으로 작업이 끝날 때까지 막히지 않고도 실패한 리소스의 처리를 앞당길 수 있습니다.
성공적으로 동기화되지 않은 레코드는 다시 검증할 수 없습니다. 동기화된 레코드만 검증할 수 있습니다.
일괄 작업은 UI 또는 Rails 콘솔에서 트리거할 수 있습니다.
UI에서#
UI에서 한 구성 요소의 모든 리소스에 대해 전체 다시 동기화를 예약할 수 있습니다.
- 오른쪽 위 모서리에서 Admin을 선택합니다.
- 왼쪽 사이드바에서 Geo > Sites를 선택합니다.
- Replication details 아래에서 원하는 구성 요소를 선택합니다.
선택한 구성 요소의 리소스 다시 동기화#
- Resync all을 선택합니다. 선택한 리소스의 모든 레코드 상태를 이미 동기화되었는지 여부와 관계없이 초기화합니다.
- Resync all failed를 선택합니다. 동기화가 실패한 모든 레코드를 초기화합니다.
선택한 구성 요소의 리소스 다시 검증#
- Reverify all을 선택합니다. 선택한 리소스의 모든 레코드 상태를 이미 검증되었는지 여부와 관계없이 초기화합니다.
- Reverify all failed를 선택합니다. 검증이 실패했지만 동기화는 성공한 모든 레코드를 초기화합니다.
모든 사이트에서 한 구성 요소 다시 검증#
기본 사이트의 체크섬이 의심스러우면 기본 사이트가 체크섬을 다시 계산하게 해야 합니다.
그러면 「전체 재검증」이 이루어집니다. 기본 사이트에서 체크섬이 다시 계산될 때마다 이벤트가 생성되어
모든 보조 사이트로 전파되고, 보조 사이트가 각자 체크섬을 다시 계산해 값을 비교하기 때문입니다.
불일치가 있으면 레지스트리를 sync failed로 표시하며, 그에 따라 동기화 재시도가 예약됩니다.
UI에서 기본 사이트의 체크섬을 다시 계산할 수 있습니다.
- 오른쪽 위 모서리에서 Admin을 선택합니다.
- 왼쪽 사이드바에서 Monitoring > Data management를 선택합니다.
- 드롭다운 목록에서 원하는 구성 요소를 선택합니다.
- Checksum all을 선택합니다.
Resync all, Reverify all, Checksum all은 이미 동기화 또는 검증되었는지 여부와 관계없이 모든 리소스의 업데이트를 트리거합니다. 인스턴스에 특정 객체 유형(예: CI Job Artifacts)이 수천 개 있을 때는 실행하지 않아야 합니다.
Rails 콘솔에서#
데이터를 변경하는 명령은 올바르지 않게 또는 적절한 조건이 아닌 상태에서 실행하면 피해를 줄 수 있습니다. 항상 테스트 환경에서 먼저 명령을 실행하고, 복원할 수 있는 백업 인스턴스를 준비해 둡니다.
다음 섹션에서는 Rails 콘솔에서 내부 애플리케이션 명령을 사용해 일괄 복제 또는 검증을 수행하는 방법을 설명합니다.
동기화에 실패한 한 구성 요소의 모든 리소스 동기화#
다음 스크립트는 다음을 수행합니다.
- 실패한 모든 리포지터리를 순회합니다.
- 마지막 실패 원인을 포함한 Geo 동기화 및 검증 메타데이터를 표시합니다.
- 리포지터리의 다시 동기화를 시도합니다.
- 실패가 발생하면 그 사실과 이유를 보고합니다.
- 완료하는 데 시간이 걸릴 수 있습니다. 각 리포지터리 검사가 끝나야
결과를 보고합니다. 세션이 시간 초과되면
프로세스가 계속 실행되도록
screen세션을 시작하거나 Rails runner와nohup을 사용해 실행하는 등의 조치를 합니다.
이 스크립트는 보조 Geo 사이트에서 실행합니다.
Geo::ProjectRepositoryRegistry.failed.find_each do |registry|
begin
puts "ID: #{registry.id}, Project ID: #{registry.project_id}, Last Sync Failure: '#{registry.last_sync_failure}'"
registry.replicator.sync
puts "Sync initiated for registry ID: #{registry.id}"
rescue => e
puts "ID: #{registry.id}, Project ID: #{registry.project_id}, Failed: '#{e}'", e.backtrace.join("\n")
end
end; nil
기본 사이트에서 체크섬 계산에 실패한 모든 리소스 다시 검증#
시스템은 기본 사이트에서 체크섬 계산에 실패한 모든 리소스를 자동으로 다시 검증하지만, 과도한 양의 실패를 피하려고 점진적 백오프 방식을 사용합니다.
예를 들어 시도한 조치를 마친 경우 등에는 선택적으로 재검증을 더 빨리 수동으로 트리거할 수 있습니다.
-
기본 사이트의 GitLab Rails 노드에 SSH로 접속합니다.
-
Rails 콘솔을 엽니다.
-
Upload를 Geo 데이터 유형 모델 클래스 중 하나로 바꾸어, 모든 리소스를pending verification으로 표시합니다.Upload.verification_state_table_class.where(verification_state: 3).each_batch do |relation| relation.update_all(verification_state: 0) end
오류#
메시지: The file is missing on the Geo primary site#
동기화 실패 The file is missing on the Geo primary site는 보조 Geo 사이트를
처음 설정할 때 흔히 발생하며, 기본 사이트의 데이터
불일치가 원인입니다.
데이터 불일치와 누락된 파일은 GitLab을 운영하는 중 시스템 오류나 사람의 실수로 발생할 수 있습니다. 예를 들어 인스턴스 관리자가 로컬 파일 시스템의 아티팩트 여러 개를 수동으로 삭제한 경우입니다. 이러한 변경은 데이터베이스에 제대로 전파되지 않아 불일치가 생깁니다. 이 불일치는 그대로 남아 마찰을 일으킬 수 있습니다. Geo 보조 사이트는 데이터베이스에 여전히 참조되지만 더 이상 존재하지 않는 그 파일들을 계속 복제하려고 시도할 수 있습니다.
최근 로컬에서 오브젝트 스토리지로 마이그레이션한 경우에는 전용 오브젝트 스토리지 문제 해결 섹션을 참고합니다.
불일치 식별#
누락된 파일이나 불일치가 있으면 geo.log에서 다음과 같은 항목이 나타날 수 있습니다. "primary_missing_file" : true 필드를 확인합니다.
{
"bytes_downloaded" : 0,
"class" : "Geo::BlobDownloadService",
"correlation_id" : "01JT69C1ECRBEMZHA60E5SAX8E",
"download_success" : false,
"download_time_s" : 0.196,
"gitlab_host" : "gitlab.example.com",
"mark_as_synced" : false,
"message" : "Blob download",
"model_record_id" : 55,
"primary_missing_file" : true,
"reason" : "Not Found",
"replicable_name" : "upload",
"severity" : "WARN",
"status_code" : 404,
"time" : "2025-05-01T16:02:44.836Z",
"url" : "http://gitlab.example.com/api/v4/geo/retrieve/upload/55"
}
특정 복제 가능 항목의 동기화 상태를 검토할 때 Admin > Geo > Sites의 UI에서도 같은 오류를 확인할 수 있습니다. 이 시나리오에서는 특정 업로드가 누락되었습니다.


불일치 정리#
삭제 명령을 실행하기 전에 최근의 정상 작동하는 백업이 있는지 확인합니다.
레코드를 삭제하기 전에 파일이 실제로 누락되었는지 확인합니다.
검증에서 제외된 레코드는
파일이 존재하는데도 같은 File is not checksummable 오류로 실패합니다.
영향을 받은 레코드 중 일부를 표본 점검합니다. 예를 들어 업로드는 record.retrieve_uploader.exists?,
다른 blob 유형은 record.file.exists?를 사용합니다.
이 오류를 제거하려면 먼저 영향을 받는 특정 리소스를 식별합니다. 그런 다음 적절한 destroy 명령을 실행하여 삭제가 모든 Geo 사이트와 그 데이터베이스에 전파되도록 합니다. 앞의 시나리오에서는 업로드 하나가 이 오류를 일으키므로 아래 예시에서 그 업로드를 사용합니다.
-
식별한 불일치를 해당하는 Geo 모델 클래스 이름에 대응시킵니다. 이 클래스 이름은 다음 단계에서 필요합니다. 이 시나리오에서 업로드는
Upload에 해당합니다. -
Geo 기본 사이트에서 Rails 콘솔을 시작합니다.
-
이전 단계의 Geo 모델 클래스를 기준으로, 파일 누락 때문에 검증에 실패한 모든 리소스를 쿼리합니다. 더 많은 결과를 표시하려면
limit(20)을 조정하거나 제거합니다. 나열된 리소스가 UI에 표시된 실패 항목과 일치하는지 확인합니다.Upload.verification_failed.where("verification_failure like '%File is not checksummable%'").limit(20) => # -
선택적으로, 영향을 받는 리소스의
id를 사용해 여전히 필요한 리소스인지 확인합니다.Upload.find(55) => #- 영향을 받는 리소스를 복구해야 한다고 판단되면 다음 옵션(전부는 아님)으로 복구를 시도할 수 있습니다.
- 보조 사이트에 해당 객체가 있는지 확인하고, 있으면 기본 사이트로 수동 복사합니다.
- 이전 백업을 살펴보고 해당 객체를 기본 사이트로 수동 복사합니다.
- 일부를 표본 점검하여 레코드를 삭제해도 문제가 없을 것으로 보이는지 판단합니다. 예를 들어 모두 매우 오래된 아티팩트라면 중요한 데이터가 아닐 수 있습니다.
- 영향을 받는 리소스를 복구해야 한다고 판단되면 다음 옵션(전부는 아님)으로 복구를 시도할 수 있습니다.
-
식별한 리소스의
id를 사용해destroy로 개별 또는 일괄 삭제합니다. 적절한 Geo 모델 클래스 이름을 사용해야 합니다.-
개별 리소스 삭제:
Upload.find(55).destroy -
영향을 받는 모든 리소스 삭제:
def destroy_uploads_not_checksummable uploads = Upload.verification_failed.where("verification_failure like '%File is not checksummable%'");1 puts "Found #{uploads.count} resources that failed verification with 'File is not checksummable'." puts "Enter 'y' to continue: " prompt = STDIN.gets.chomp if prompt != 'y' puts "Exiting without action..." return end puts "Destroying all..." uploads.destroy_all end destroy_uploads_not_checksummable
-
영향을 받는 모든 리소스와 Geo 데이터 유형에 대해 이 단계를 반복합니다.
메시지: "Error during verification","error":"File is not checksummable"#
"Error during verification","error":"File is not checksummable" 오류는 기본 사이트의 불일치 때문에 발생합니다. GitLab 18.9부터 오류 메시지에 원인에 관한 추가 세부 정보가 포함됩니다.
File is not checksummable - file does not exist at: <path>: 스토리지에서 파일이 누락되었습니다. 표시된 경로로 누락된 파일을 식별할 수 있습니다. 이 오류를 해결하려면 The file is missing on the Geo primary site의 지침을 따릅니다.File is not checksummable - is excluded from verification: 레코드가 더 이상 복제 범위에 속하지 않으므로 Geo가 검증할 수 없습니다. 이 동작은 기본 사이트가 레코드를 삭제하지 않고 복제 범위에서 제거할 때 예상되는 동작입니다. 예를 들어 GitLab은 스토리지 최적화 중에 오래된MergeRequestDiff레코드를without_files상태로 옮깁니다. 레지스트리 일관성 워커가 시간이 지나면서 이러한 레지스트리 항목을 자동으로 제거합니다. 오브젝트 스토리지에 저장된 레코드도 파일이 존재하는데,geo_object_storage_verification기능 플래그가 비활성화되어 있으면 검증에서 제외됩니다.
GitLab 18.8 이전 버전에서는 오류 메시지에 원인이 포함되지 않으므로, 검증에서 제외된 레코드와 파일이 누락된 레코드를 구별할 수 없습니다. 파일이 누락되었다고 가정하기 전에 기본 사이트에서 Rails 콘솔 세션을 시작하고 오브젝트 스토리지 검증이 활성화되어 있는지 확인합니다.
Feature.enabled?(:geo_object_storage_verification)
이 명령이 false를 반환하면 오브젝트 스토리지의 레코드는 검증에서 제외되며
파일은 온전할 가능성이 높습니다. 이 레코드는 삭제하지 않아야 합니다.
영향을 받는 MergeRequestDiff 레지스트리 항목을 즉시 제거하려면 보조 사이트의
Rails 콘솔에서 다음 명령을 실행합니다.
Geo::MergeRequestDiffRegistry.where("verification_failure LIKE '%excluded from verification%'").find_each(&:destroy)
기본 Geo 사이트에서 업로드 검증 실패#
기본 Geo 사이트에서 일부 업로드의 검증이 verification_checksum = nil로 실패하고 verification_failure에 Error during verification: undefined method `underscore' for NilClass:Class 또는 The model which owns this upload is missing.이 포함되어 있다면, 고아 상태가 된 업로드 때문입니다. 업로드를 소유한 부모 레코드(업로드의 「모델」)가 어떤 이유로 삭제되었지만 업로드 레코드는 여전히 존재하는 경우입니다. 이는 보통 「모델」을 일괄 삭제하도록 구현하면서 연관된 업로드 레코드를 함께 일괄 삭제하는 것을 빠뜨린 애플리케이션 버그 때문입니다. 따라서 이러한 검증 실패는 검증 자체의 실패가 아니라 Postgres의 잘못된 데이터에서 비롯된 오류입니다.
이러한 오류는 기본 Geo 사이트의 geo.log 파일에서 확인할 수 있습니다.
모델 레코드가 누락되었는지 확인하려면 기본 Geo 사이트에서 Rake 태스크를 실행할 수 있습니다.
sudo gitlab-rake gitlab:uploads:check
Rails 콘솔에서 다음 스크립트를 실행하여 기본 Geo 사이트의 이러한 업로드 레코드를 삭제하면 이 실패를 없앨 수 있습니다.
def delete_orphaned_uploads(dry_run: true)
if dry_run
p "This is a dry run. Upload rows will only be printed."
else
p "This is NOT A DRY RUN! Upload rows will be deleted from the DB!"
end
subquery = Geo::UploadState.where("(verification_failure LIKE 'Error during verification: The model which owns this upload is missing.%' OR verification_failure = 'Error during verification: undefined method `underscore'' for NilClass:Class') AND verification_checksum IS NULL")
uploads = Upload.where(upload_state: subquery)
p "Found #{uploads.count} uploads with a model that does not exist"
uploads_deleted = 0
begin
uploads.each do |upload|
if dry_run
p upload
else
uploads_deleted=uploads_deleted + 1
p upload.destroy!
end
rescue => e
puts "checking upload #{upload.id} failed with #{e.message}"
end
end
p "#{uploads_deleted} remote objects were destroyed." unless dry_run
end
앞의 스크립트는 delete_orphaned_uploads라는 메서드를 정의하며, 다음과 같이 호출하여 드라이 런을 실행할 수 있습니다.
delete_orphaned_uploads(dry_run: true)
고아 상태의 업로드 행을 실제로 삭제하려면 다음과 같이 호출합니다.
delete_orphaned_uploads(dry_run: false)
고아 상태의 exclusive lease 키가 리포지터리 동기화를 차단하는 경우#
exclusive lease 키가 고아 상태가 되면 리포지터리 동기화가 차단되어, 최대 8시간 동안 동기화 작업을 수행하지 못할 수 있습니다.
증상:
- 리포지터리 동기화 차단: 영향을 받는 리포지터리의 복제 상태가
pending과failed사이를 오갑니다. geo.log에서 「Cannot obtain an exclusive lease」 메시지가 있는 로그 줄 수가 증가합니다.- 영향을 받는 리포지터리에 대해 실행 중인 활성 동기화 job이 없습니다.
- 리포지터리 하나에 영향을 주며, lease가 만료될 때까지 최대 8시간 지속됩니다.
진단:
-
Geo 관리 인터페이스에서 해당 리포지터리가 활발히 동기화 중이 아닌지 확인합니다.
-
geo.log에서 「Cannot obtain an exclusive lease」 메시지가 늘어났는지 확인합니다.grep "Cannot obtain an exclusive lease" /var/log/gitlab/geo/geo.log -
이 로그 줄이 모두
lease_key필드를 포함하고 그 값이geo_sync_ssf_service:project_repository:<repository id>인지 확인합니다. 여기서<repository id>는 영향을 받는 리포지터리의 고유 ID 입니다. -
Sidekiq에서 영향을 받는 리포지터리에 대해 실행 중인 활성 동기화 job이 없는지 확인합니다.
해결 방법:
8시간의 lease 만료를 기다리는 방법을 권장합니다. 수동 lease 해제는 즉시 동기화가 반드시 필요하고 동기화 job이 실행 중이 아님을 확인한 경우에만 사용해야 합니다.
고아 상태의 lease 키를 수동으로 해제하려면 다음을 수행합니다.
-
보조 사이트에서 Rails 콘솔 세션을 시작합니다.
-
영향을 받는 리포지터리의 프로젝트 ID를 찾습니다(
<project-path>를 실제 프로젝트 경로로 바꿉니다).project = Project.find_by_full_path('<project-path>') project_id = project.id -
같은 세션에서 고아 상태의 lease를 해제합니다.
replicator = Geo::ProjectRepositoryRegistry.find_by(project_id: project_id).replicator sync_service = Geo::FrameworkRepositorySyncService.new(replicator) uuid = Gitlab::ExclusiveLease.get_uuid(sync_service.lease_key) if uuid Gitlab::ExclusiveLease.cancel(sync_service.lease_key, uuid) puts "Lease released for project ID #{project_id}" else puts "No active lease found for project ID #{project_id}" end -
lease가 해제되었는지 확인하고 새 동기화를 트리거합니다.
replicator.sync
lease를 해제하면 리포지터리 동기화는 일반 Geo 동기화 일정에 따라 재시도되며, 위에 나온 것처럼 동기화를 수동으로 트리거할 수도 있습니다.
오류: Error syncing repository: 13:fatal: could not read Username#
last_sync_failure 오류
Error syncing repository: 13:fatal: could not read Username for 'https://gitlab.example.com': terminal prompts disabled는
Geo 클론 또는 fetch 요청 중에 JWT 인증이 실패하고 있음을 나타냅니다.
먼저 시스템 시계가 동기화되어 있는지 확인합니다. Health check Rake 태스크를 실행하거나,
보조 사이트의 모든 Sidekiq 노드와 기본 사이트의 모든 Puma 노드에서 date가
동일한지 수동으로 확인합니다.
시스템 시계가 동기화되어 있다면, Git fetch가 두 개의 별도 HTTP 요청 사이에서 계산을 수행하는 동안 JWT 토큰이 만료되고 있을 수 있습니다. 이슈 464101을 참고합니다. 이 이슈는 GitLab 17.1.0, 17.0.5, 16.11.7에서 수정되기 전까지 모든 GitLab 버전에 존재했습니다.
이 문제를 겪고 있는지 확인하려면 다음을 수행합니다.
-
Rails 콘솔에서 코드를 몽키 패치하여 토큰 유효 기간을 1분에서 10분으로 늘립니다. 보조 사이트의 Rails 콘솔에서 다음을 실행합니다.
module Gitlab; module Geo; class BaseRequest private def geo_auth_token(message) signed_data = Gitlab::Geo::SignedData.new(geo_node: requesting_node, validity_period: 10.minutes).sign_and_encode_data(message) "#{GITLAB_GEO_AUTH_TOKEN_TYPE} #{signed_data}" end end;end;end -
같은 Rails 콘솔에서 영향을 받은 프로젝트를 다시 동기화합니다.
Project.find_by_full_path('<mygroup/mysubgroup/myproject>').replicator.resync -
동기화 상태를 확인합니다.
Project.find_by_full_path('<mygroup/mysubgroup/myproject>').replicator.registry -
last_sync_failure에 더 이상fatal: could not read Username오류가 포함되지 않으면 이 문제의 영향을 받고 있는 것입니다. 이제 상태가2여야 하며, 이는 동기화되었음을 의미합니다. 그렇다면 수정이 포함된 GitLab 버전으로 업그레이드해야 합니다. 이 문제의 심각도를 낮출 수 있었던 이슈 466681에 추천을 누르거나 댓글을 남길 수도 있습니다.
이 문제를 우회하려면 보조 사이트의 모든 Sidekiq 노드에 핫패치를 적용하여 JWT 만료 시간을 늘려야 합니다.
-
/opt/gitlab/embedded/service/gitlab-rails/ee/lib/gitlab/geo/signed_data.rb를 편집합니다. -
Gitlab::Geo::SignedData.new(geo_node: requesting_node)를 찾아, validity_period: 10.minutes를 추가합니다.- Gitlab::Geo::SignedData.new(geo_node: requesting_node) + Gitlab::Geo::SignedData.new(geo_node: requesting_node, validity_period: 10.minutes) -
Sidekiq을 다시 시작합니다.
sudo gitlab-ctl restart sidekiq -
수정이 포함된 버전으로 업그레이드하지 않는 한, GitLab을 업그레이드할 때마다 이 우회 방법을 반복해야 합니다.
오류: Error syncing repository: 13:creating repository: cloning repository: exit status 128#
동기화에 성공하지 못하는 프로젝트에서 이 오류가 나타날 수 있습니다.
리포지터리 생성 중 종료 코드 128은 클론하는 동안 Git에 치명적인 오류가 발생했음을 의미합니다. 리포지터리 손상, 네트워크 문제, 인증 문제, 리소스 제한, 또는 프로젝트에 연결된 Git 리포지터리가 없다는 점이 원인일 수 있습니다. 이러한 실패의 구체적인 원인에 대한 자세한 내용은 Gitaly 로그에서 확인할 수 있습니다.
어디서부터 시작해야 할지 확신이 없다면, 기본 사이트의 소스 리포지터리에서 명령줄에서 git fsck 명령을 수동으로 실행하여 무결성 검사를 실행합니다.
로드 밸런서의 HTTP 504로 인한 종료 상태 128#
대용량 리포지터리에서는 보조 사이트의 Gitaly 로그에 다음과 같이 표시될 수 있습니다.
error: RPC failed; HTTP 504 curl 22 The requested URL returned error: 504
fatal: expected 'packfile'
이 오류는 기본 사이트 앞단의 로드 밸런서나 프록시가 Git 클론 packfile 전송 중에 연결을 종료할 때 발생합니다. 기본 유휴 타임아웃이 60초인 AWS Application Load Balancer(ALB)에서 흔히 발생합니다. Gitaly가 데이터 전송을 시작하기 전에 packfile을 준비하는 데 시간이 걸리는 대용량 리포지터리에서는, ALB가 데이터가 전송되기 전에 연결을 끊어 이 오류를 일으킬 수 있습니다.
이 문제를 해결하려면 다음을 수행합니다.
-
대용량 리포지터리 클론을 수용할 수 있도록 기본 사이트 앞단의 로드 밸런서에서 유휴 타임아웃을 늘립니다. AWS ALB의 경우 AWS Management Console의 로드 밸런서 속성에서 유휴 타임아웃 설정을 업데이트합니다.
-
실패한 레지스트리를 초기화합니다.
-
Rails 콘솔 세션을 시작합니다 (보조 사이트에서 실행).
-
영향을 받는 리포지터리를 식별하고 초기화합니다.
project_ids = Geo::ProjectRepositoryRegistry.failed .where("last_sync_failure LIKE '%exit status 128%'") .pluck(:project_id) puts "Found #{project_ids.count} repositories failing with exit status 128" # state: 0 sets the registry back to pending so Geo retries the sync Geo::ProjectRepositoryRegistry.where(project_id: project_ids).update_all( state: 0, retry_count: 0, retry_at: nil, last_sync_failure: nil ) puts "Reset #{project_ids.count} registries to pending"
-
-
Geo가 동기화를 자동으로 재시도할 때까지 기다리거나 복제를 수동으로 재시도합니다.
오류: gitmodulesUrl: disallowed submodule url#
일부 프로젝트 리포지터리가 Error syncing repository: 13:creating repository: cloning repository: exit status 128
오류와 함께 계속 동기화에 실패합니다. 그런데
일부 리포지터리에서는 Gitaly 로그의 구체적인 오류 메시지가 gitmodulesUrl: disallowed submodule url로 다릅니다.
이 실패는 리포지터리의 .gitmodules 파일에 잘못된 서브모듈 URL이 있을 때 발생합니다.
근본 원인: 이 문제는 Git 리포지터리의 과거 커밋에 형식이 잘못된 URL을 가진 .gitmodules 파일이 있어서 발생합니다. Geo가 기본 사이트에서 보조 사이트로 리포지터리를 클론하려 할 때 실행되는 Git의 일관성 검사(git fsck) 중에 문제가 나타납니다.
문제는 리포지터리의 커밋 기록에 있습니다. .gitmodules 파일의 서브모듈 URL 이
경로에서 / 대신 :를 쓰는 잘못된 형식을 가지고 있습니다.
- 잘못된 형식:
https://example.gitlab.com:group/project.git - 올바른 형식:
https://example.gitlab.com/group/project.git
이것이 Geo 동기화를 깨뜨리는 이유:
- Git의 엄격한 검증: GitLab 17.0 이후와 최신 Git 버전부터 Git은 클론 작업 중에 더 엄격한
fsck검사를 수행합니다. - 과거 데이터의 잔존: 현재
.gitmodules파일이 올바르더라도 Git은 과거의 모든 버전을 리포지터리에 「blob」으로 저장합니다. - 클론 시점의 실패: Geo가 리포지터리를 클론하려 할 때 Git의
fsck는 과거의 객체를 포함한 모든 객체를 검사하고, 형식이 잘못된 URL을 발견하면 실패합니다. - 전체 동기화 실패: 클론 작업 전체가 실패하여 리포지터리가 보조 사이트에 도달하지 못합니다.
중요: 문제가 되는 데이터가 현재 파일 버전만이 아니라 리포지터리의 Git 기록에 존재하므로, 현재 .gitmodules 파일을 편집해도 이 문제는 해결되지 않습니다.
이 문제는 GitLab 17.0 이상에서 알려져 있으며, 더 엄격해진 리포지터리 일관성 검사의 결과입니다. 이 새로운 동작은 이 검사가 추가된 Git 자체의 변경에서 비롯됩니다. GitLab Geo 나 Gitaly에 국한된 문제가 아닙니다. 자세한 내용은 이슈 468560을 참고합니다.
해결 방법#
-
프로젝트 백업
진행하기 전에 프로젝트 내보내기 옵션을 사용해 프로젝트를 미리 백업합니다.
-
문제가 되는 blob ID 식별
영향을 받는 각 프로젝트에 대해 다음 방법 중 하나로 문제가 되는 blob ID를 식별합니다.
-
git fsck사용: 리포지터리를 클론한 다음git fsck를 실행하여 문제를 확인합니다.git clone https://example.gitlab.com/group/project.git cd project git fsck출력에 문제가 되는 blob이 표시됩니다.
Checking object directories: 100% (256/256), done. error in blob : gitmodulesUrl: disallowed submodule url: https://example.gitlab.com:group/project.git Checking objects: 100% (12/12), done. -
Gitaly 로그를 확인합니다.
gitmodulesUrl이 포함된 오류 메시지를 찾아 해당 blob SHA를 확인합니다.
-
-
blob 제거
영향을 받는 각 프로젝트에서 이전 단계에서 식별한 문제가 되는 blob ID를 제거합니다.
중요한 제한 사항: 이 리포지터리 중 일부가 포크 네트워크에 속해 있다면 blob 제거 방법이 동작하지 않을 수 있습니다(오브젝트 풀에 포함된 blob은 이 방법으로 제거할 수 없습니다).
-
필요한 경우 .gitmodules의 잘못된 URL 수정
영향을 받는 각 리포지터리에서
.gitmodules파일의 상태를 확인합니다..gitmodules에https://example.gitlab.com/foo/bar.git대신https://example.gitlab.com:foo/bar.git같은 잘못된 URL이 여전히 있다면 고객은 다음을 해야 합니다..gitmodules파일의 URL 수정- 올바른 URL이 포함된 커밋 푸시
수정 후에는 영향을 받는 프로젝트에서 작업하는 모든 개발자가 현재 로컬 사본을 제거하고 리포지터리를 새로 클론해야 합니다. 그렇지 않으면 변경 사항을 푸시할 때 문제가 되는 blob을 다시 유입시킬 수 있습니다.
오류: 정확히 3시간 시점의 fetch remote: signal: terminated: context deadline exceeded#
Git 리포지터리를 동기화하는 중 Git fetch가 정확히 3시간 시점에 실패하는 경우 다음을 수행합니다.
-
/etc/gitlab/gitlab.rb를 편집하여 Git 타임아웃을 기본값인 10800초에서 늘립니다.# Git timeout in seconds gitlab_rails['gitlab_shell_git_timeout'] = 21600 -
GitLab을 재구성합니다.
sudo gitlab-ctl reconfigure
레지스트리 복제를 구성할 때 보조 사이트에서 발생하는 Failed to open TCP connection to localhost:5000 오류#
보조 사이트에서 컨테이너 레지스트리 복제를 구성할 때 다음 오류가 발생할 수 있습니다.
Failed to open TCP connection to localhost:5000 (Connection refused - connect(2) for \"localhost\" port 5000)"
이 오류는 보조 사이트에서 컨테이너 레지스트리가 활성화되지 않았을 때 발생합니다. 해결하려면 컨테이너 레지스트리가 보조 사이트에서 활성화되어 있는지 확인합니다. Let's Encrypt 통합이 비활성화되어 있으면 컨테이너 레지스트리도 비활성화되므로 수동으로 구성해야 합니다.
오류: Verification timed out after 28800#
가능한 근본 원인: 여러 레지스트리 유형에서 중복된 레지스트리 레코드가 검증 충돌을 일으킵니다.
진단:
-
보조 사이트에서 Rails 콘솔 세션을 시작합니다.
-
여러 유형에 걸쳐 중복된 레지스트리가 있는지 확인합니다.
# Check for duplicate upload registries upload_ids = Geo::UploadRegistry.group(:file_id).having('COUNT(*) > 1').pluck(:file_id) puts "Duplicate upload IDs count: #{upload_ids.size}" puts 'Duplicate Upload IDs:', upload_ids # Check for duplicate job artifact registries artifact_ids = Geo::JobArtifactRegistry.group(:artifact_id).having('COUNT(*) > 1').pluck(:artifact_id) puts "Duplicate artifact IDs count: #{artifact_ids.size}" puts 'Duplicate Artifact IDs:', artifact_ids # Check for duplicate package file registries package_file_ids = Geo::PackageFileRegistry.group(:package_file_id).having('COUNT(*) > 1').pluck(:package_file_id) puts "Duplicate package file IDs count: #{package_file_ids.size}" puts 'Duplicate Package File IDs:', package_file_ids # Check for duplicate LFS object registries lfs_object_ids = Geo::LfsObjectRegistry.group(:lfs_object_id).having('COUNT(*) > 1').pluck(:lfs_object_id) puts "Duplicate LFS object IDs count: #{lfs_object_ids.size}" puts 'Duplicate LFS Object IDs:', lfs_object_ids # Check for duplicate pages deployment registries pages_deployment_ids = Geo::PagesDeploymentRegistry.group(:pages_deployment_id).having('COUNT(*) > 1').pluck(:pages_deployment_id) puts "Duplicate pages deployment IDs count: #{pages_deployment_ids.size}" puts 'Duplicate Pages Deployment IDs:', pages_deployment_ids # Check for duplicate terraform state version registries terraform_state_ids = Geo::TerraformStateVersionRegistry.group(:terraform_state_version_id).having('COUNT(*) > 1').pluck(:terraform_state_version_id) puts "Duplicate terraform state version IDs count: #{terraform_state_ids.size}" puts 'Duplicate Terraform State Version IDs:', terraform_state_ids
해결:
-
보조 사이트에서 Rails 콘솔 세션을 시작합니다.
-
영향을 받는 각 유형의 중복 레지스트리 항목을 제거합니다.
# Remove duplicate upload registries upload_ids = Geo::UploadRegistry.group(:file_id).having('COUNT(*) > 1').pluck(:file_id) if upload_ids.any? Geo::UploadRegistry.where(file_id: upload_ids).delete_all puts "Removed #{upload_ids.size} duplicate upload registry entries" end # Remove duplicate job artifact registries artifact_ids = Geo::JobArtifactRegistry.group(:artifact_id).having('COUNT(*) > 1').pluck(:artifact_id) if artifact_ids.any? Geo::JobArtifactRegistry.where(artifact_id: artifact_ids).delete_all puts "Removed #{artifact_ids.size} duplicate job artifact registry entries" end # Remove duplicate package file registries package_file_ids = Geo::PackageFileRegistry.group(:package_file_id).having('COUNT(*) > 1').pluck(:package_file_id) if package_file_ids.any? Geo::PackageFileRegistry.where(package_file_id: package_file_ids).delete_all puts "Removed #{package_file_ids.size} duplicate package file registry entries" end # Remove duplicate LFS object registries lfs_object_ids = Geo::LfsObjectRegistry.group(:lfs_object_id).having('COUNT(*) > 1').pluck(:lfs_object_id) if lfs_object_ids.any? Geo::LfsObjectRegistry.where(lfs_object_id: lfs_object_ids).delete_all puts "Removed #{lfs_object_ids.size} duplicate LFS object registry entries" end # Remove duplicate pages deployment registries pages_deployment_ids = Geo::PagesDeploymentRegistry.group(:pages_deployment_id).having('COUNT(*) > 1').pluck(:pages_deployment_id) if pages_deployment_ids.any? Geo::PagesDeploymentRegistry.where(pages_deployment_id: pages_deployment_ids).delete_all puts "Removed #{pages_deployment_ids.size} duplicate pages deployment registry entries" end # Remove duplicate terraform state version registries terraform_state_ids = Geo::TerraformStateVersionRegistry.group(:terraform_state_version_id).having('COUNT(*) > 1').pluck(:terraform_state_version_id) if terraform_state_ids.any? Geo::TerraformStateVersionRegistry.where(terraform_state_version_id: terraform_state_ids).delete_all puts "Removed #{terraform_state_ids.size} duplicate terraform state version registry entries" end -
모든 레지스트리 유형에서 정리가 완료되었는지 확인합니다.
# Verify no remaining duplicates upload_duplicates = Geo::UploadRegistry.group(:file_id).having('COUNT(*) > 1').count artifact_duplicates = Geo::JobArtifactRegistry.group(:artifact_id).having('COUNT(*) > 1').count package_duplicates = Geo::PackageFileRegistry.group(:package_file_id).having('COUNT(*) > 1').count lfs_duplicates = Geo::LfsObjectRegistry.group(:lfs_object_id).having('COUNT(*) > 1').count pages_duplicates = Geo::PagesDeploymentRegistry.group(:pages_deployment_id).having('COUNT(*) > 1').count terraform_duplicates = Geo::TerraformStateVersionRegistry.group(:terraform_state_version_id).having('COUNT(*) > 1').count puts "Remaining duplicates:" puts " Uploads: #{upload_duplicates.size}" puts " Job Artifacts: #{artifact_duplicates.size}" puts " Package Files: #{package_duplicates.size}" puts " LFS Objects: #{lfs_duplicates.size}" puts " Pages Deployments: #{pages_duplicates.size}" puts " Terraform State Versions: #{terraform_duplicates.size}"
오류: Checksum does not match the primary checksum#
가능한 근본 원인: 리포지터리 또는 컨테이너 레지스트리의 검증 간격 변경이 체크섬 불일치를 일으킵니다.
진단:
-
보조 사이트에서 Rails 콘솔 세션을 시작합니다.
-
실패한 리포지터리 또는 컨테이너 레지스트리를 확인합니다.
failed_repos = Geo::ProjectRepositoryRegistry.failed.limit(100) failed_repos.each do |repo| puts "Project ID: #{repo.project_id}" puts "Primary checksum: #{repo.verification_checksum_mismatched}" puts "Secondary checksum: #{repo.verification_checksum}" puts "Error: #{repo.last_sync_failure}" puts "---" endfailed_container_repos = Geo::ContainerRepositoryRegistry.failed.limit(100) failed_container_repos.each do |repo| puts "Container Repo Id: #{repo.model_record_id}" puts "Primary checksum: #{repo.verification_checksum_mismatched}" puts "Secondary checksum: #{repo.verification_checksum}" puts "Error: #{repo.last_sync_failure}" puts "---" end
해결:
-
기본 사이트에서 Rails 콘솔 세션을 시작합니다.
-
특정 프로젝트 또는 컨테이너 레지스트리에 대해 재검증을 강제로 실행합니다.
project_ids = [1, 2, 3] # Replace with actual failing project IDs project_ids.each do |project_id| project = Project.find(project_id) puts "Reverifying project: #{project.full_path}" project_state = project.project_state project_state.update!(verification_state: 0) puts "Project #{project_id} marked for reverification" endcontainer_repo_ids = [1, 2, 3] container_repo_ids.each do |repo_id| container_repo = ContainerRepository.find(repo_id) puts "Reverifying container repository: #{container_repo.path}" state = container_repo.container_repository_state state.update!(verification_state: 0) puts "Container Repo #{repo_id} marked for reverification" end
Error during verification: File is not checksummable에 대한 객체 유형별 문제 해결#
Geo 데이터 유형마다 고유한 특성과 흔한 실패 패턴이 있습니다. 이 섹션에서는 특정 객체 유형에 맞춘 문제 해결 방법을 설명합니다.
다음 destroy 스크립트를 실행하기 전에 파일이 실제로 누락되었는지 확인합니다.
검증에서 제외된 레코드는
파일이 존재하는데도 같은 오류로 실패하며, 다음 스크립트는
그 레코드도 삭제합니다.
영향을 받은 레코드 중 일부를 표본 점검합니다. 예를 들어 record.file.exists?(업로드는 record.retrieve_uploader.exists?)를 사용합니다.
업로드#
진단:
-
기본 사이트에서 Rails 콘솔 세션을 시작합니다.
-
파일이 누락된 업로드를 식별합니다. 더 많은 결과를 보려면
limit(5)를 필요에 맞게 조정합니다.checksummable_failures = Upload.verification_failed .where("verification_failure LIKE '%File is not checksummable%'") puts "Found #{checksummable_failures.count} uploads with missing files" checksummable_failures.limit(5).each_with_index do |record, index| puts "Record #{index + 1}:" puts " ID: #{record.id}" puts " Path: #{record.path}" puts " Model: #{record.model_type} (ID: #{record.model_id})" puts " Created: #{record.created_at}" puts "---" end
해결:
이 실패를 해결하려면 기본 Geo 사이트에서 업로드 검증 실패의 단계를 따릅니다.
Pages 배포#
진단:
-
기본 사이트에서 Rails 콘솔 세션을 시작합니다.
-
문제가 있는 Pages 배포를 확인합니다.
checksummable_failures = PagesDeployment.verification_failed .where("verification_failure LIKE '%File is not checksummable%'") checksummable_failures.each_with_index do |record, index| puts "Record #{index + 1}:" puts " ID: #{record.id}" puts " Project: #{record.project.full_path}" puts " Created: #{record.created_at}" puts " File exists: #{record.file.exists?}" puts "---" end
해결:
Pages 배포 레코드를 삭제하기 전에 최근의 정상 작동하는 백업이 있는지 확인합니다. 팀과 협의하여 이 배포를 제거해도 안전한지 확인합니다.
-
기본 사이트에서 Rails 콘솔 세션을 시작합니다.
-
팀과 협의하여 배포를 제거해도 안전함을 확인한 후 다음을 실행합니다.
def destroy_pages_deployments_not_checksummable(dry_run: true) deployments = PagesDeployment.verification_failed.where("verification_failure LIKE '%File is not checksummable%'") puts "Found #{deployments.count} pages deployments that failed verification with 'File is not checksummable'." if dry_run puts "DRY RUN - No changes made" deployments.each { |d| puts "Would remove: ID #{d.id}, Project: #{d.project.full_path}" } return end puts "Enter 'y' to continue: " prompt = STDIN.gets.chomp if prompt != 'y' puts "Exiting without action..." return end puts "Destroying all..." deployments.destroy_all puts "Done!" end # Run in dry run mode first destroy_pages_deployments_not_checksummable(dry_run: true)
LFS 객체#
진단:
-
기본 사이트에서 Rails 콘솔 세션을 시작합니다.
-
문제가 있는 LFS 객체를 확인합니다.
checksummable_failures = LfsObject.verification_failed .where("verification_failure LIKE '%File is not checksummable%'") checksummable_failures.each_with_index do |record, index| puts "Record #{index + 1}:" puts " OID: #{record.oid}" puts " Size: #{record.size} bytes" puts " File Store: #{record.file_store}" puts " Created: #{record.created_at}" # Show associated projects associations = record.lfs_objects_projects.includes(:project) puts " Associated projects (#{associations.count}):" associations.each do |assoc| project = assoc.project if project puts " - #{project.full_path}" else puts " - Project ID: #{assoc.project_id} (not found)" end end puts "---" end
해결:
LFS 객체를 제거하면 해당 객체를 참조하는 모든 프로젝트에 영향을 줍니다. 삭제하기 전에 백업이 있는지 확인하고 프로젝트 Maintainer와 협의합니다.
-
기본 사이트에서 Rails 콘솔 세션을 시작합니다.
-
파일이 누락된 LFS 객체를 제거합니다.
def destroy_lfs_not_checksummable(dry_run: true) lfs_objects = LfsObject.verification_failed.where("verification_failure like '%File is not checksummable%'") puts "Found #{lfs_objects.count} LFS objects that failed verification with 'File is not checksummable'." if dry_run puts "DRY RUN - No changes made" lfs_objects.each { |obj| puts "Would remove: OID #{obj.oid}, Size: #{obj.size}" } return end puts "Enter 'y' to continue with deletion: " prompt = STDIN.gets.chomp if prompt != 'y' puts "Exiting without action..." return end puts "Destroying all..." lfs_objects.each do |lfs_object| lfs_object.lfs_objects_projects.destroy_all lfs_object.destroy! end puts "Done!" end # Run in dry run mode first destroy_lfs_not_checksummable(dry_run: true)
Job 아티팩트#
진단:
-
기본 사이트에서 Rails 콘솔 세션을 시작합니다.
-
파일이 누락된 아티팩트가 있는지 확인합니다.
failed_artifacts = Ci::JobArtifact.verification_failed.where("verification_failure LIKE '%File is not checksummable%'") failed_artifacts.each do |registry| artifact = Ci::JobArtifact.find_by(id: registry.id) if artifact puts "Artifact ID: #{artifact.id}" puts "Job ID: #{artifact.job_id}" puts "Project ID: #{artifact.project_id}" puts "File exists: #{artifact.file.exists?}" puts "File path: #{artifact.file.path}" else puts "Artifact ID #{artifact.id} not found in database" end puts "---" end
해결:
Job 아티팩트 레코드를 삭제하기 전에 최근의 정상 작동하는 백업이 있는지 확인합니다. 팀과 협의하여 이 아티팩트를 제거해도 안전한지 확인합니다.
-
기본 사이트에서 Rails 콘솔 세션을 시작합니다.
-
파일이 누락된 아티팩트를 정리합니다.
def cleanup_missing_artifacts(dry_run: true) missing_file_artifacts = [] Ci::JobArtifact.find_each do |artifact| unless artifact.file.exists? missing_file_artifacts << artifact.id puts "Missing file for artifact #{artifact.id}" if dry_run end end puts "Found #{missing_file_artifacts.size} artifacts with missing files" unless dry_run Ci::JobArtifact.where(id: missing_file_artifacts).destroy_all puts "Removed #{missing_file_artifacts.size} artifacts with missing files" end end # Run in dry run mode first cleanup_missing_artifacts(dry_run: true)
패키지 파일#
이 오류는 기본 사이트의 스토리지에서 패키지 파일이 누락되었을 때 발생합니다.
영향을 받는 패키지 파일을 식별하려면 다음을 수행합니다.
-
Rails 콘솔 세션을 시작합니다 (기본 사이트에서 실행).
-
영향을 받는 레코드를 쿼리합니다. 더 많은 결과를 보려면
limit(5)를 필요에 맞게 조정합니다.checksummable_failures = Packages::PackageFile.verification_failed .where("verification_failure LIKE '%File is not checksummable%'") puts "Found #{checksummable_failures.count} package files with missing files" checksummable_failures.limit(5).each_with_index do |record, index| puts "Record #{index + 1}:" puts " ID: #{record.id}" puts " File Name: #{record.file_name}" puts " Package ID: #{record.package_id}" puts " Created: #{record.created_at}" puts "---" end
패키지 파일 레코드를 삭제하기 전에 최근의 정상 작동하는 백업이 있는지 확인합니다. 팀과 협의하여 이 패키지 파일을 제거해도 안전한지 확인합니다.
영향을 받는 패키지 파일을 제거하려면 다음을 수행합니다.
-
Rails 콘솔 세션을 시작합니다 (기본 사이트에서 실행).
-
영향을 받는 레코드를 삭제합니다.
def destroy_packages_not_checksummable(dry_run: true) packages = Packages::PackageFile.verification_failed .where("packages_package_file_states.verification_failure LIKE '%File is not checksummable%'") puts "Found #{packages.count} packages that failed verification with 'File is not checksummable'." if dry_run puts "DRY RUN - No changes made" packages.each { |p| puts "Would remove: ID #{p.id}, File: #{p.file_name}" } return end puts "Enter 'y' to continue: " prompt = STDIN.gets.chomp if prompt != 'y' puts "Exiting without action..." return end puts "Destroying all..." packages.destroy_all puts "Done!" end # Run in dry run mode first destroy_packages_not_checksummable(dry_run: true)
파이프라인 아티팩트#
진단:
-
기본 사이트에서 Rails 콘솔 세션을 시작합니다.
-
파일이 누락된 아티팩트가 있는지 확인합니다.
failed_pipeline_artifacts = Ci::PipelineArtifact.verification_failed.where("verification_failure LIKE '%checksummable%'") failed_pipeline_artifacts.each do |registry| artifact = Ci::PipelineArtifact.find_by(id: registry.id) if artifact puts "Artifact ID: #{artifact.id}" puts "Pipeline ID: #{artifact.pipeline_id}" puts "Project ID: #{artifact.project_id}" puts "File exists: #{artifact.file.exists?}" puts "File path: #{artifact.file.path}" else puts "Artifact ID #{artifact.id} not found in database" end puts "---" end
해결:
파이프라인 아티팩트 레코드를 삭제하기 전에 최근의 정상 작동하는 백업이 있는지 확인합니다. 팀과 협의하여 이 아티팩트를 제거해도 안전한지 확인합니다.
-
기본 사이트에서 Rails 콘솔 세션을 시작합니다.
-
파일이 누락된 파이프라인 아티팩트를 제거합니다.
def destroy_pipeline_artifacts_not_checksummable artifacts = Ci::PipelineArtifact.verification_failed.where("verification_failure like '%File is not checksummable%'") puts "Found #{artifacts.count} pipeline artifacts that failed verification with 'File is not checksummable'." puts "Enter 'y' to continue: " prompt = STDIN.gets.chomp if prompt != 'y' puts "Exiting without action..." return end puts "Destroying all..." artifacts.destroy_all puts "Done!" end destroy_pipeline_artifacts_not_checksummable
타임아웃으로 인한 blob 동기화 불일치#
큰 파일이 기본 8시간의 blob 다운로드 타임아웃을 초과하면 blob(예: LFS 객체, job 아티팩트, 패키지 파일)이
Sync timed out after 28800 오류와 함께 동기화에 실패할 수 있습니다.
이 문제를 해결하려면 지원되는 옵션을 다음 순서로 먼저 사용합니다.
- blob 다운로드 타임아웃을 늘리고 Geo가 재시도하게 하면 프레임워크가 다운로드, 검증, 동기화 상태를 처리합니다.
- blob이 여전히 동기화에 실패하면 영향을 받는 blob을 식별하고 검증한 다음 기본 사이트에서 파일을 복사합니다.
- 최후의 수단으로 Rails 콘솔에서 blob을 다시 동기화합니다.
blob 다운로드 타임아웃 늘리기#
GitLab 18.10 이상에서는 blob 다운로드 타임아웃을 Geo 사이트별로 구성할 수 있습니다.
blob 다운로드 타임아웃을 늘리려면 <secondary_id>를 보조 사이트 ID로,
<token>을 관리자 API 토큰으로 바꿉니다.
curl --header "PRIVATE-TOKEN: <token>" \
--request PUT \
--data '{"blob_download_timeout": 43200}' \
"https://gitlab.example.com/api/v4/geo_nodes/<secondary_id>"
타임아웃을 늘린 후에는 Geo가 자동으로 재시도할 때까지 기다리거나 복제를 수동으로 재시도합니다.
타임아웃된 blob 식별 및 검증#
타임아웃을 늘린 후에도 blob이 계속 실패하면 영향을 받는 객체를 식별하고 기본 사이트에 파일이 있는지 확인합니다. 다음 예시는 LFS 객체를 사용합니다. 다른 blob 유형에는 일치하는 Geo 레지스트리 클래스와 모델을 사용합니다.
-
보조 사이트에서 영향을 받는 객체를 식별합니다.
registries = Geo::LfsObjectRegistry.failed.where("last_sync_failure LIKE '%timed out%'") puts "Found #{registries.count} LFS objects that failed with a timeout" registries.each do |registry| lfs_object = LfsObject.find_by(id: registry.lfs_object_id) size_gb = lfs_object ? (lfs_object.size / 1024.0 / 1024.0 / 1024.0).round(2) : 'unknown' puts " Registry ID: #{registry.id}, LFS Object ID: #{registry.lfs_object_id}, Size: #{size_gb} GB, Failure: #{registry.last_sync_failure}, Retries: #{registry.retry_count}" end -
이전 단계의
lfs_object_id값을 사용해 기본 사이트에 파일이 있는지 확인합니다.[lfs_object_id1, lfs_object_id2, lfs_object_id3].each do |id| lfs_object = LfsObject.find_by(id: id) if lfs_object.nil? puts "LFS Object ID: #{id} not found" next end puts "LFS Object ID: #{id}, Size: #{(lfs_object.size / 1024.0 / 1024.0 / 1024.0).round(2)} GB, File exists?: #{lfs_object.file.exists?}, Path: #{lfs_object.file.path}" end
기본 사이트에서 보조 사이트로 파일 복사#
기본 사이트에는 파일이 있지만 보조 사이트에는 없다면, 이전 단계에서 확인한 경로를 사용해 파일을 찾습니다.
- 오브젝트 스토리지: 경로는 구성된 LFS 버킷 내의 객체 키입니다. 기본 버킷에서 파일을 찾아 다운로드한 다음 보조 버킷의 같은 키로 업로드합니다.
- 로컬 스토리지: 경로는 기본 사이트의
/var/opt/gitlab/gitlab-rails/shared/lfs-objects/기준 상대 경로입니다. 파일을 보조 사이트의 같은 상대 경로로 복사합니다.
blob을 동기화됨으로 표시#
보조 사이트에 파일이 있는 상태가 되면 해당 blob을 동기화됨으로 표시하고 검증을 트리거합니다. 다음 예시는 LFS 객체를 사용합니다. 다른 blob 유형에는 일치하는 Geo 레지스트리 클래스를 사용합니다.
[lfs_object_id1, lfs_object_id2, lfs_object_id3].each do |lfs_object_id|
begin
registry = Geo::LfsObjectRegistry.find_by(lfs_object_id: lfs_object_id)
if registry.nil?
puts "Registry not found for LFS Object #{lfs_object_id}"
next
end
registry.update!(
state: 2,
success: true,
last_synced_at: Time.current,
last_sync_failure: nil,
retry_count: 0,
retry_at: nil
)
registry.replicator.verify
puts "LFS Object #{lfs_object_id}: marked as synced and verification triggered"
rescue => e
puts "Error processing LFS Object #{lfs_object_id}: #{e.message}"
end
end
Rails 콘솔에서 타임아웃된 blob 자동 재동기화#
이 절차는 지원되는 옵션 (blob 다운로드 타임아웃 늘리기, API, 관리자 영역의 Geo 복제 세부 정보)로 실패가 해결되지 않은 뒤 최후의 수단으로만 사용합니다. 이 절차는 Geo 프레임워크 밖에서 동기화를 실행하므로 가능하면 지원되는 옵션을 우선합니다.
다음 헬퍼는 기본 사이트에서 blob을 긴 읽기 타임아웃으로 직접 스트리밍하고 (동기화 job의 고정 타임아웃을 피함), 기본 사이트를 기준으로 크기와 콘텐츠 체크섬을 검증한 뒤 프레임워크의 업로더를 통해 저장하고 레지스트리를 동기화됨으로 표시하며 검증을 다시 트리거합니다.
이 방식은 blob 유형의 모든 복제 가능 항목에서 동작합니다. Ci::JobArtifact,
Ci::PipelineArtifact, Ci::SecureFile, LfsObject, Packages::PackageFile,
PagesDeployment, Terraform::StateVersion, Upload입니다. Git 리포지터리와
컨테이너 리포지터리는 동기화 경로가 달라 다루지 않습니다.
데이터를 변경하는 명령은 올바르지 않게 또는 적절한 조건이 아닌 상태에서 실행하면 피해를 줄 수 있습니다. 항상 테스트 환경에서 먼저 명령을 실행하고, 복원할 수 있는 백업 인스턴스를 준비해 둡니다.
-
Rails 콘솔 세션을 시작합니다 (보조 사이트에서 실행).
-
헬퍼를 정의합니다.
require 'net/http' require 'digest' require 'tempfile' # Content-hash attribute for each blob model. Types that are not listed, or # whose hash attribute is nil, fall back to size-only verification. GEO_BLOB_VERIFICATION = { 'Ci::JobArtifact' => :file_sha256, 'Ci::PipelineArtifact' => :file_sha256, 'Packages::PackageFile' => :file_sha256, 'PagesDeployment' => :file_sha256, 'Upload' => :checksum, 'Ci::SecureFile' => :checksum, 'LfsObject' => :oid } # Streams an HTTP GET to the block, following redirects. The Geo # authentication header is sent only on the first request. On a redirect to a # pre-signed object storage URL (when proxy_download is disabled) it is # dropped, because the pre-signed URL is already authenticated. def geo_stream_get(uri, headers, limit = 5, &block) raise 'too many redirects' if limit < 0 Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', open_timeout: 60, read_timeout: 86_400, write_timeout: 86_400) do |http| request = Net::HTTP::Get.new(uri) headers.each { |key, value| request[key] = value } http.request(request) do |response| case response.code.to_i when 200 response.read_body { |chunk| yield chunk } when 301, 302, 303, 307, 308 raise 'redirect with no Location' unless response['location'] return geo_stream_get(URI(response['location']), {}, limit - 1, &block) else raise "HTTP #{response.code}: #{response.message}" end end end end def manual_geo_blob_sync(registry_class, registry_id) registry = registry_class.find_by(id: registry_id) return "no #{registry_class.name} ##{registry_id}" unless registry replicator = registry.replicator model = replicator.model_record return 'missing model record (gone on primary?)' unless model uploader = replicator.carrierwave_uploader downloader = Gitlab::Geo::Replication::BlobDownloader.new(replicator: replicator) uri = URI(downloader.resource_url) # request_headers is a private BlobDownloader method, accessed here with # send. It returns a short-lived Geo JWT. This relies on an internal API # and might need updating after a GitLab upgrade. auth = downloader.send(:request_headers) sha_attr = GEO_BLOB_VERIFICATION[model.class.name] want_size = model.respond_to?(:size) ? model.size : nil want_sha = sha_attr && model.respond_to?(sha_attr) ? model.public_send(sha_attr) : nil tmp = Tempfile.new(['geo-blob', '.bin'], '/tmp') tmp.binmode begin geo_stream_get(uri, auth) { |chunk| tmp.write(chunk) } tmp.flush raise "size mismatch (#{tmp.size}/#{want_size})" if want_size && tmp.size != want_size if want_sha && Digest::SHA256.file(tmp.path).hexdigest != want_sha raise 'checksum mismatch - not marking as synced' end # Store the blob through the same uploader method the Geo framework # uses (BlobDownloader#download_file), so local and object storage are # both handled the same way. uploader.replace_file_without_saving!(CarrierWave::SanitizedFile.new(tmp)) registry.update!(state: 2, last_synced_at: Time.current, retry_at: nil, retry_count: 0, last_sync_failure: nil) registry.update!(bytes: tmp.size) if registry.respond_to?(:bytes) # The raw update! above bypasses the after_synced state-machine # callback, so re-trigger verification explicitly to reconcile a # previously verification_failed registry. replicator.verify "OK #{registry_class.name}##{registry_id} (#{tmp.size} bytes)" ensure tmp.close! end end -
영향을 받는 레지스트리 레코드에 대해 헬퍼를 실행합니다. 레지스트리 클래스를 Geo 레지스트리 클래스 중 하나로,
123을 실제 레지스트리 ID로 바꿉니다.manual_geo_blob_sync(Geo::LfsObjectRegistry, 123) -
선택 사항입니다. 이 오류로 실패한 한 유형의 모든 blob을 다시 동기화하려면 다음을 실행합니다.
Geo::LfsObjectRegistry .where("last_sync_failure LIKE '%Sync timed out after%'") .pluck(:id) .each { |id| puts manual_geo_blob_sync(Geo::LfsObjectRegistry, id) }; nil
헬퍼가 여전히 타임아웃되거나 실패하면 기본 사이트에서 해당 객체가 누락되었거나 읽을 수 없는 상태일 수 있습니다. 자세한 내용은 The file is missing on the Geo primary site를 참고합니다.
오류: Projects - Error during verification: Repository does not exist#
근본 원인: Git 리포지터리가 없는 프로젝트가 검증 실패를 일으킵니다.
증상:
- 프로젝트가 검증 중에 「Repository does not exist」 오류를 표시합니다.
- 리포지터리가 정당하게 없는 프로젝트에 대해 Geo UI가 잘못된 오류를 보고합니다.
- 존재하지 않는 리포지터리에 대한 동기화 시도가 낭비됩니다.
해결 방법:
기본 사이트에 프로젝트 리포지터리가 없으면 생성합니다.
failed_projects = Project.verification_failed.where("verification_failure LIKE '%Repository does not exist%'")
puts "Found #{failed_projects.count} project repos with 'Repository does not exist' verification failure"
failed_projects.find_each do |p|
puts "#{p.full_path} #{p.ensure_repository.inspect}"
end
오류: Expected(200) <=> Actual(403 Forbidden)#
근본 원인: ListBucket 권한이 없어 S3 API가 404 대신 403을 반환합니다.
증상:
- S3 엔드포인트에서 로그에 403 오류가 나타납니다.
- S3 버킷으로의 HEAD 요청이 실패합니다.
- 오브젝트 스토리지 기반 데이터 유형의 동기화가 실패합니다.
해결:
인프라 팀이 개입하여 GitLab이 사용하는 S3 IAM 정책에 ListBucket 권한을 추가해야 합니다.
메시지: Synchronization failed - Error syncing repository#
이 문제의 영향을 받는 대용량 리포지터리가 있으면 다시 동기화하는 데 오랜 시간이 걸릴 수 있으며, Geo 사이트와 스토리지 및 네트워크 시스템에 상당한 부하를 줄 수 있습니다.
다음 오류 메시지는 리포지터리를 동기화할 때 일관성 검사 오류가 발생했음을 나타냅니다.
Synchronization failed - Error syncing repository [..] fatal: fsck error in packed object
여러 문제가 이 오류를 일으킬 수 있습니다. 예를 들어 이메일 주소 문제가 있습니다.
Error syncing repository: 13:fetch remote: "error: object : badEmail: invalid author/committer line - bad email
fatal: fsck error in packed object
fatal: fetch-pack: invalid index-pack output
이 오류를 일으킬 수 있는 다른 문제로 object : hasDotgit: contains '.git'가 있습니다. 모든 리포지터리에 걸쳐 문제가 둘 이상일 수 있으므로
구체적인 오류를 확인합니다.
두 번째 동기화 오류는 리포지터리 검사 문제로 인해서도 발생할 수 있습니다.
Error syncing repository: 13:Received RST_STREAM with error code 2.
이러한 오류는 실패한 모든 리포지터리를 즉시 동기화하여 확인할 수 있습니다.
일관성 오류를 일으키는 잘못된 객체를 제거하려면 리포지터리 기록을 다시 써야 하며, 이는 보통 선택할 수 없는 방법입니다.
이러한 일관성 검사를 무시하려면 보조 Geo 사이트에서 Gitaly를 재구성하여 이러한 git fsck 문제를 무시하도록 합니다.
다음 구성 예시는 아래와 같습니다.
- 업데이트된 구성 구조를 사용합니다.
- 흔한 검사 실패 다섯 가지를 무시합니다.
Gitaly 문서에 더 자세한 내용이 있습니다. 다른 Git 검사 실패와 이전 GitLab 버전에 관한 내용을 다룹니다.
gitaly['configuration'] = {
git: {
config: [
{ key: "fsck.duplicateEntries", value: "ignore" },
{ key: "fsck.badFilemode", value: "ignore" },
{ key: "fsck.missingEmail", value: "ignore" },
{ key: "fsck.badEmail", value: "ignore" },
{ key: "fsck.hasDotgit", value: "ignore" },
{ key: "fetch.fsck.duplicateEntries", value: "ignore" },
{ key: "fetch.fsck.badFilemode", value: "ignore" },
{ key: "fetch.fsck.missingEmail", value: "ignore" },
{ key: "fetch.fsck.badEmail", value: "ignore" },
{ key: "fetch.fsck.hasDotgit", value: "ignore" },
{ key: "receive.fsck.duplicateEntries", value: "ignore" },
{ key: "receive.fsck.badFilemode", value: "ignore" },
{ key: "receive.fsck.missingEmail", value: "ignore" },
{ key: "receive.fsck.badEmail", value: "ignore" },
{ key: "receive.fsck.hasDotgit", value: "ignore" },
],
},
}
fsck 오류의 전체 목록은 Git 문서에서 확인할 수 있습니다.
GitLab은 이러한 문제 중 일부를 해결할 수 있는 개선 사항을 포함하고 있습니다.
Gitaly 이슈 5625는 원본 리포지터리에 문제가 있는 커밋이 포함되어 있더라도 Geo가 리포지터리를 복제하도록 보장하는 방안을 제안합니다.
관련 오류 does not appear to be a git repository#
다음 로그 메시지와 함께 Synchronization failed - Error syncing repository 오류 메시지가 나타날 수도 있습니다.
이 오류는 보조 Geo 사이트의 파일 시스템에 있는 리포지터리의 .git/config 파일에
예상되는 Geo 원격이 없음을 나타냅니다.
{
"created": "@1603481145.084348757",
"description": "Error received from peer unix:/var/opt/gitlab/gitaly/gitaly.socket",
…
"grpc_message": "exit status 128",
"grpc_status": 13
}
{ …
"grpc.request.fullMethod": "/gitaly.RemoteService/FindRemoteRootRef",
"grpc.request.glProjectPath": "<namespace>/<project>",
…
"level": "error",
"msg": "fatal: 'geo' does not appear to be a git repository
fatal: Could not read from remote repository. …",
}
이 문제를 해결하려면 다음을 수행합니다.
-
보조 Geo 사이트의 웹 인터페이스에 로그인합니다.
-
.git폴더를 백업합니다. -
선택 사항입니다. 이 ID 중 일부를 표본 점검하여 실제로 Geo 복제 실패가 알려진 프로젝트에 해당하는지 확인합니다.
grep검색어로fatal: 'geo'를 사용하고 다음 API 호출을 사용합니다.curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/<first_failed_geo_sync_ID>" -
Rails 콘솔을 열고 다음을 실행합니다.
failed_project_registries = Geo::ProjectRepositoryRegistry.failed if failed_project_registries.any? puts "Found #{failed_project_registries.count} failed project repository registry entries:" failed_project_registries.each do |registry| puts "ID: #{registry.id}, Project ID: #{registry.project_id}, Last Sync Failure: '#{registry.last_sync_failure}'" end else puts "No failed project repository registry entries found." end -
다음 명령을 실행하여 각 프로젝트에 대해 새 동기화를 실행합니다.
failed_project_registries.each do |registry| registry.replicator.sync puts "Sync initiated for registry ID: #{registry.id}, Project ID: #{registry.project_id}" end
백필 중 실패#
백필 중에 실패한 항목은 백필 큐의 끝에서 재시도되도록 예약되므로, 이러한 실패는 백필이 완료된 후에야 해소됩니다.
메시지: unexpected disconnect while reading sideband packet#
불안정한 네트워크 상태 때문에 Gitaly가 기본 사이트에서 대용량 리포지터리 데이터를 가져오지 못할 수 있습니다. 이러한 상태에서는 다음 오류가 발생할 수 있습니다.
curl 18 transfer closed with outstanding read data remaining & fetch-pack:
unexpected disconnect while reading sideband packet
이 오류는 사이트 간에 리포지터리를 처음부터 복제해야 할 때 발생할 가능성이 더 높습니다.
Geo는 여러 번 재시도하지만, 네트워크 문제로 전송이 계속 중단되는 경우
rsync 같은 대체 방법을 사용하면 git 을 우회하여 Geo가 복제하지 못하는 리포지터리의
초기 사본을 만들 수 있습니다.
실패한 리포지터리를 하나씩 전송하고 전송할 때마다 일관성을 확인하기를 권장합니다.
rsync 를 사용해 다른 서버로 이동하는 방법에 따라
영향을 받는 각 리포지터리를 기본 사이트에서 보조 사이트로 전송합니다.
Geo 보조 사이트에서 리포지터리 검사 실패 찾기#
모든 리포지터리 데이터 유형은 GitLab 16.3에서 Geo Self-Service Framework로 마이그레이션되었습니다. 이 기능을 Geo Self-Service Framework에 다시 구현하기 위한 이슈가 있습니다.
GitLab 16.2 이하:
모든 프로젝트에 활성화하면 리포지터리 검사가 Geo 보조 사이트에서도 수행됩니다. 메타데이터는 Geo 추적 데이터베이스에 저장됩니다.
Geo 보조 사이트의 리포지터리 검사 실패가 반드시 복제 문제를 의미하지는 않습니다. 다음은 이러한 실패를 해결하기 위한 일반적인 접근 방법입니다.
- 아래에 설명된 대로 영향을 받는 리포지터리와 그 기록된 오류를 찾습니다.
- 구체적인
git fsck오류를 진단해 봅니다. 가능한 오류의 범위가 넓으므로 검색 엔진에 검색해 봅니다. - 영향을 받는 리포지터리의 일반적인 기능을 테스트합니다. 보조 사이트에서 풀하고 파일을 확인합니다.
- 기본 사이트의 리포지터리 사본에 동일한
git fsck오류가 있는지 확인합니다. 장애 조치를 계획 중이라면 보조 사이트가 기본 사이트와 같은 정보를 가지도록 우선순위를 두는 것을 고려합니다. 기본 사이트의 백업을 확보하고 계획된 장애 조치 지침을 따릅니다. - 기본 사이트에 푸시하고 변경 사항이 보조 사이트로 복제되는지 확인합니다.
- 복제가 자동으로 동작하지 않으면 리포지터리를 수동으로 동기화해 봅니다.
Rails 콘솔 세션을 시작하여 다음의 기본적인 문제 해결 단계를 수행합니다.
데이터를 변경하는 명령은 올바르지 않게 또는 적절한 조건이 아닌 상태에서 실행하면 피해를 줄 수 있습니다. 항상 테스트 환경에서 먼저 명령을 실행하고, 복원할 수 있는 백업 인스턴스를 준비해 둡니다.
리포지터리 검사에 실패한 리포지터리 수 확인#
Geo::ProjectRegistry.where(last_repository_check_failed: true).count
리포지터리 검사에 실패한 리포지터리 찾기#
Geo::ProjectRegistry.where(last_repository_check_failed: true)
Gitaly Cluster에서 리포지터리 완전 삭제 후 다시 동기화#
이 절차는 위험하고 강압적입니다. 다른 문제 해결 방법이 모두 실패했을 때에만 최후의 수단으로 사용합니다. 이 절차를 수행하면 리포지터리가 다시 동기화될 때까지 일시적인 데이터 손실이 발생합니다.
이 절차는 보조 사이트의 Gitaly 클러스터에서 리포지터리를 삭제하고 다시 동기화합니다. 위험을 이해하고 있으며 다음 조건이 모두 참인 경우에만 사용을 고려해야 합니다.
- 기본 사이트의 리포지터리에서
git clone이 동작합니다. p.replicator.sync_repository(p는 프로젝트 모델 인스턴스)가 보조 사이트에서 Gitaly 오류를 기록합니다.- 표준 문제 해결로 문제가 해결되지 않았습니다.
사전 요구 사항:
- 보조 사이트의 Rails 콘솔과 Praefect 노드에 모두 관리자 권한으로 접근할 수 있어야 합니다.
- 기본 사이트에서 리포지터리에 접근할 수 있고 올바르게 동작하는지 확인합니다.
- 이 절차를 되돌려야 할 경우를 대비한 백업 계획을 마련합니다.
이 절차를 수행하려면 다음을 실행합니다.
-
보조 사이트의 Rails 콘솔에 로그인합니다.
-
다음 옵션 중 하나로 프로젝트 모델을 인스턴스화하고 변수
p에 저장합니다.-
영향을 받는 프로젝트 ID를 아는 경우(예:
60087):p = Project.find(60087) -
GitLab에서 영향을 받는 프로젝트 경로를 아는 경우(예:
my-group/my-project):p = Project.find_by_full_path('my-group/my-project')
-
-
프로젝트 Git 리포지터리의 가상 스토리지를 출력하고 나중을 위해 기록해 둡니다.
p.repository.storage출력 예시:
irb(main):002:0> p.repository.storage => "default" -
프로젝트 Git 리포지터리의 상대 경로를 출력하고 나중을 위해 기록해 둡니다.
p.repository.disk_path + '.git'출력 예시:
irb(main):003:0> p.repository.disk_path + '.git' => "@hashed/66/b2/66b2fc8562b3432399acc2d0108fcd2782b32bd31d59226c7a03a20b32c76ee8.git" -
보조 사이트의 Praefect 노드에 SSH로 접속합니다.
-
이전 단계에서 기록해 둔 가상 스토리지와 상대 경로를 사용해 Gitaly Cluster에서 리포지터리 수동 제거 절차를 따릅니다.
이제 보조 사이트의 Git 리포지터리가 삭제되었습니다.
-
Rails 콘솔에서 다시 동기화하기 전에 correlation ID를 설정합니다. 이 ID를 사용하면 이 세션에서 실행한 명령과 관련된 모든 로그를 검색할 수 있습니다.
Gitlab::ApplicationContext.push({})출력 예시:
[2] pry(main)> Gitlab::ApplicationContext.push({}) => #"53da64ae800bd4794a2b61ab1c80b028"}> -
프로젝트 Git 리포지터리를 동기화합니다.
p.replicator.sync_repository
이제 Git 리포지터리가 기본 사이트에서 보조 사이트로 다시 동기화됩니다. Geo 관리 인터페이스를 통하거나 Rails 콘솔에서 리포지터리의 동기화 상태를 확인하여 동기화 프로세스를 모니터링합니다.
인프라 및 성능 고려 사항#
일부 동기화 문제는 인프라 수준의 문제나 성능 제약 때문에 발생합니다.
높은 동시성 문제#
과도한 Geo 검증 동시성은 데이터베이스에 과부하를 주어 동기화 실패를 일으킬 수 있습니다.
증상:
- 데이터베이스 연결 타임아웃
- 데이터베이스 서버의 높은 CPU 사용량
- 인프라가 정상인데도 동기화 진행이 느림
진단 및 해결:
UI를 통해 기본 사이트의 동시성 설정을 줄입니다.
수동 동기화 상태 업데이트#
경우에 따라 근본적인 문제를 해결한 후 특정 객체 유형을 수동으로 동기화됨으로 표시해야 할 수 있습니다. 이 시나리오는 보조 사이트의 오브젝트 버킷에 파일을 수동으로 업로드해야만 문제를 해결할 수 있을 때 발생합니다. 일반적으로 이 작업은 필요하지 않지만 버전 버그 때문에 발생할 수 있습니다. 다음은 수동으로 업로드한 객체 유형(여기서는 업로드)을 동기화됨으로 표시하는 방법입니다.
파일이 보조 사이트에 실제로 있고 접근 가능함을 확인한 경우에만 객체를 동기화됨으로 표시합니다.
def mark_upload_synced(upload_id)
upload = Upload.find(upload_id)
registry = upload.replicator.registry
registry.start
registry.synced!
puts "Marked upload #{upload_id} as synced"
end
# Mark specific uploads as synced
upload_ids = [107221, 107320] # Replace with actual IDs
upload_ids.each { |id| mark_upload_synced(id) }
Geo 보조 사이트 복제 초기화#
보조 사이트가 손상된 상태가 되어 복제 상태를 초기화하고 처음부터 다시 시작하려면 도움이 되는 몇 가지 단계가 있습니다.
-
Sidekiq과 Geo Log Cursor를 중지합니다.
Sidekiq을 정상적으로 중지할 수 있습니다. 새 job을 가져오지 않도록 하고 현재 job의 처리가 끝날 때까지 기다리는 방식입니다.
첫 단계에서는 SIGTSTP kill 시그널을 보내고, 모든 job이 끝나면 SIGTERM을 보내야 합니다. 그렇지 않으면
gitlab-ctl stop명령을 사용합니다.gitlab-ctl status sidekiq # run: sidekiq: (pid 10180) <- this is the PID you will use kill -TSTP 10180 # change to the correct PID gitlab-ctl stop sidekiq gitlab-ctl stop geo-logcursorSidekiq 로그를 보면 Sidekiq job 처리가 언제 끝났는지 알 수 있습니다.
gitlab-ctl tail sidekiq -
Gitaly와 Gitaly Cluster(Praefect) 데이터를 삭제합니다.
mv /var/opt/gitlab/git-data/repositories /var/opt/gitlab/git-data/repositories.old sudo gitlab-ctl reconfigure- 선택 사항입니다. Praefect 내부 로드 밸런서를 비활성화합니다.
-
각 Praefect 서버에서 Praefect를 중지합니다.
sudo gitlab-ctl stop praefect -
Praefect 데이터베이스를 초기화합니다.
sudo /opt/gitlab/embedded/bin/psql -U praefect -d template1 -h localhost -c "DROP DATABASE praefect_production WITH (FORCE);" sudo /opt/gitlab/embedded/bin/psql -U praefect -d template1 -h localhost -c "CREATE DATABASE praefect_production WITH OWNER=praefect ENCODING=UTF8;" -
각 Gitaly 노드에서 리포지터리 데이터의 이름을 바꾸거나 삭제합니다.
sudo mv /var/opt/gitlab/git-data/repositories /var/opt/gitlab/git-data/repositories.old sudo gitlab-ctl reconfigure -
Praefect 배포 노드에서 reconfigure를 실행하여 데이터베이스를 설정합니다.
sudo gitlab-ctl reconfigure -
각 Praefect 서버에서 Praefect를 시작합니다.
sudo gitlab-ctl start praefect -
선택 사항입니다. 비활성화했다면 Praefect 내부 로드 밸런서를 다시 활성화합니다.
-
[!note]
/var/opt/gitlab/git-data/repositories.old는 더 이상 필요하지 않음을 확인하는 즉시 디스크 공간을 절약하기 위해 나중에 삭제하는 것이 좋습니다.-
선택 사항입니다. 다른 데이터 폴더의 이름을 바꾸고 새로 만듭니다.
[!warning] 기본 사이트에서는 제거되었지만 그 제거가 반영되지 않은 파일이 보조 사이트에 여전히 남아 있을 수 있습니다. 이 단계를 건너뛰면 해당 파일은 Geo 보조 사이트에서 제거되지 않습니다.
업로드된 콘텐츠(파일 첨부, 아바타, LFS 객체 등)는 다음 경로 중 하나의 하위 폴더에 저장됩니다.
/var/opt/gitlab/gitlab-rails/shared/var/opt/gitlab/gitlab-rails/uploads
모두 이름을 바꾸려면 다음을 실행합니다.
gitlab-ctl stop mv /var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-rails/shared.old mkdir -p /var/opt/gitlab/gitlab-rails/shared mv /var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/uploads.old mkdir -p /var/opt/gitlab/gitlab-rails/uploads gitlab-ctl start postgresql gitlab-ctl start geo-postgresql폴더를 다시 만들고 권한과 소유권이 올바른지 확인하려면 reconfigure를 실행합니다.
gitlab-ctl reconfigure -
추적 데이터베이스를 초기화합니다.
[!warning] 선택 단계 3을 건너뛰었다면
geo-postgresql과postgresql서비스가 모두 실행 중인지 확인합니다.gitlab-rake db:drop:geo DISABLE_DATABASE_ENVIRONMENT_CHECK=1 # on a secondary app node gitlab-ctl reconfigure # on the tracking database node gitlab-rake db:migrate:geo # on a secondary app node -
이전에 중지한 서비스를 다시 시작합니다.
gitlab-ctl start
- 선택 사항입니다. Praefect 내부 로드 밸런서를 비활성화합니다.