오프라인 전송
GitLab v19.3요약
오프라인 전송은 아직 진행 중인 작업입니다. 오프라인 전송을 사용하면 소스 GitLab 인스턴스와 대상 GitLab 인스턴스가 서로 직접 통신하지 않고도 그룹 또는 프로젝트를 오브젝트 스토리지 버킷으로 내보낸 뒤 나중에 그 버킷에서 가져올 수 있습니다.
오프라인 전송은 아직 진행 중인 작업입니다. offline_transfer_exports, offline_transfer_imports,
offline_transfer_ui 기능 플래그로 게이트되어 있으며, 모두 기본적으로 비활성화되어 있습니다.
오프라인 전송을 사용하면 소스 GitLab 인스턴스와 대상 GitLab 인스턴스가 서로 직접 통신하지 않고도 그룹 또는 프로젝트를 오브젝트 스토리지 버킷으로 내보낸 뒤 나중에 그 버킷에서 가져올 수 있습니다. 이는 두 인스턴스가 네트워크를 통해 서로 연결할 수 없거나, 내보내기와 가져오기가 서로 다른 시점에 이루어져야 할 때 유용합니다.
오프라인 전송은 직접 전송 아키텍처를 재사용합니다. 동일한 BulkImport, BulkImports::Entity,
BulkImports::Tracker 레코드, 동일한 ETL(추출, 변환, 적재) 파이프라인 concern, 동일한 NDJSON(줄바꿈으로 구분된 JSON) 관계 형식을 사용합니다. 이 페이지는
오프라인 전송에만 고유한 부분만 설명합니다. 공유되는 개념(용어, 파이프라인 설계, NDJSON 파이프라인, 멱등성, 예외 처리)은
먼저 직접 전송을 읽어 보세요. 이 개념들은 여기에서도 그대로 적용됩니다.
직접 전송과 다른 점#
직접 전송에서는 대상 인스턴스가 마이그레이션 전체를 구동합니다. 대상 인스턴스가 소스 인스턴스의 API에 관계를 내보내 달라고 요청하고, 준비될 때까지 폴링한 다음, HTTP로 내려받습니다. 오프라인 전송에는 요청할 소스 인스턴스가 없습니다. 내보내기와 가져오기가 서로 다른 시점에, 서로 다른 네트워크에서 일어날 수 있고, 가져오기가 실행될 때 소스 인스턴스에 도달할 수 있다는 보장이 전혀 없기 때문입니다. 대신 내보내기와 가져오기는 각각 오브젝트 스토리지를 상대로 독립적으로 실행됩니다:
-
내보내기는 모든 관계 파일을 사용자가 구성한 버킷에 직접 기록한 다음, 모든 관계가 완료되면
metadata.json.gz매니페스트를 기록합니다. -
가져오기는 나중에 같은 버킷에서 그 매니페스트를 다시 읽어 어떤 엔티티가 내보내졌고 그것들이 오브젝트 키에 어떻게 매핑되는지 파악한 다음, 직접 전송이 사용하는 것과 동일한 파이프라인으로 가져옵니다.
호출할 소스 API가 없으므로, 오프라인 BulkImport(bulk_import.offline_export?가 true)에서는 직접 전송에만 해당하는
일부 단계가 건너뛰어집니다:
-
BulkImports::ProcessService#import_entity는 내보내기 시작을 요청할 소스 인스턴스가 없으므로BulkImports::ExportRequestWorker대신BulkImports::EntityWorker를 직접 큐에 넣습니다. -
같은 이유로
ProcessService는 소스 고스트 사용자 ID 캐싱을 건너뜁니다. -
BulkImports::Entity#pipelines는 다른 스테이지 목록을 사용합니다.BulkImports::Groups::Stage또는BulkImports::Projects::Stage대신Import::Offline::Imports::Groups::Stage또는Import::Offline::Imports::Projects::Stage를 사용합니다. 대부분의 파이프라인은 직접 전송에서 수정 없이 재사용되지만(예:LabelsPipeline,MilestonesPipeline,BoardsPipeline,UploadsPipeline), 일부는Import::Offline::Groups::Pipelines,Import::Offline::Projects::Pipelines,Import::Offline::Common::Pipelines네임스페이스 아래에 있는 오프라인 전송 전용 파이프라인입니다. 예를 들어Import::Offline::Groups::Pipelines::GroupPipeline과Import::Offline::Common::Pipelines::UserContributionsPipeline이 있습니다. -
관계 파일은 HTTP가 아니라 오브젝트 스토리지에서 내려받습니다. Fog 어댑터와 오브젝트 스토리지를 참조하세요.
핵심 모델#
-
BulkImport#source_type은BulkImport를 오프라인 전송으로 표시하는 enum(gitlab또는offline_export)입니다.BulkImport#offline?는offline_export?의 별칭입니다. -
Import::Offline::Export는 하나의 내보내기 요청을 추적하며, 자체 상태 머신(created/started/finished/failed)과 완료 이메일을 가집니다. 직접 전송이 사용하는 관계별 내보내기 레코드와 동일한BulkImports::Export에는belongs_to :offline_export가 추가되어 관계 내보내기들을 하나의Import::Offline::Export아래로 묶을 수 있습니다. -
Import::Offline::Configuration은 오브젝트 스토리지의provider,bucket, 암호화된 자격 증명, 이번 내보내기의export_prefix, 그리고entity_prefix_mapping(소스 전체 경로에서 스토리지 엔티티 접두사로의 매핑)을 저장합니다. 이 모델은 폴리모픽입니다. 내보내기가 시작될 때 한 행이 생성되고, 가져오기가 시작될 때 또 한 행이 생성되며, 각각 자신의 버킷과 자격 증명을 가리킵니다.
Sidekiq job 실행 계층 구조#
내보내기#
소스 코드 보기
flowchart TD
accTitle: Export Sidekiq job hierarchy
accDescr: ExportWorker enqueues itself and calls ExportService, which enqueues RelationExportWorker. When all relations finish, ExportWorker calls WriteMetadataService.
subgraph s1["Export"]
Import::Offline::ExportWorker -- Enqueue itself --> Import::Offline::ExportWorker
Import::Offline::ExportWorker --> BulkImports::ExportService
BulkImports::ExportService --> BulkImports::RelationExportWorker
Import::Offline::ExportWorker -- All relations finished --> Import::Offline::Exports::WriteMetadataService
endImport::Offline::Exports::CreateService는
Import::Offline::ExportWorker를 큐에 넣고,
이 워커가 Import::Offline::Exports::ProcessService를 구동합니다:
-
첫 실행에서는 내보내려는 엔티티의 모든 하위 그룹 또는 프로젝트에 대해
self관계의BulkImports::Export를 생성합니다. -
대기 중인 각 관계 내보내기에 대해, 직접 전송의 소스 인스턴스가 사용하는 것과 동일한
BulkImports::ExportService를offline_export_id와 함께 호출합니다. 이는BulkImports::RelationExportWorker를 큐에 넣고, 거기서부터 직접 전송의 Sidekiq job 실행 계층 구조에 문서화된 것과 동일한RelationBatchExportWorker/FinishBatchedRelationExportWorker/UserContributionsExportWorker체인으로 이어집니다. -
동시에 실행되는 관계 내보내기는 최대
BulkImports::Export::MAX_CONCURRENT_RELATION_EXPORTS(5)개입니다. -
모든 관계 내보내기가 완료되면
Import::Offline::Exports::WriteMetadataService가metadata.json.gz를 기록해 업로드하고Import::Offline::Export를 finished로 표시합니다. 그렇지 않으면Import::Offline::ExportWorker가 5초 후에 자신을 다시 큐에 넣습니다.
가져오기#
소스 코드 보기
flowchart TD
accTitle: Import Sidekiq job hierarchy
accDescr: ScheduleImportWorker calls ScheduleImportService, which enqueues BulkImportWorker.
subgraph s1["Import"]
Import::Offline::Imports::ScheduleImportWorker --> Import::Offline::Imports::ScheduleImportService
Import::Offline::Imports::ScheduleImportService --> BulkImportWorker
endImport::Offline::Imports::CreateService는
Import::Offline::Imports::ScheduleImportWorker를 큐에 넣고,
이 워커가 Import::Offline::Imports::ScheduleImportService를 구동합니다:
-
Import::Offline::Imports::MetadataFileReader를 통해 버킷에서metadata.json.gz를 내려받아 읽습니다. -
요청된 모든 엔티티의 소스 전체 경로가 메타데이터의 엔티티 매핑에 존재하는지 검증하고, 실제로 내보내지지 않은 엔티티가 있으면 즉시 실패합니다.
-
요청된 엔티티에 대해
BulkImports::Entity레코드를 생성하고BulkImportWorker를 큐에 넣습니다.
여기서부터 마이그레이션은
직접 전송의 Sidekiq job 실행 계층 구조에 설명된 공통 흐름(BulkImportWorker,
BulkImports::ProcessService, BulkImports::EntityWorker, BulkImports::PipelineWorker)에 다시 합류합니다. 다만 파이프라인이
HTTP가 아니라 오브젝트 스토리지에서 관계 파일을 내려받고(Fog 어댑터와 오브젝트 스토리지 참조),
직접 전송과 다른 점에 설명된 오프라인 전용 스테이지 목록을 사용한다는 점이 다릅니다.
엔드포인트#
오프라인 전송에는 GraphQL API가 없으며 소스 인스턴스의 API를 전혀 호출하지 않습니다. 오프라인 전송은 전적으로
API::OfflineTransfers를 통해 구동되며, 이는
생성된 REST API 레퍼런스에 문서화되어 있습니다:
| 엔드포인트 | 용도 |
|---|---|
| POST /offline_exports | 내보내기를 시작합니다. 오브젝트 스토리지 구성(provider, bucket, credentials)과 내보낼 엔티티 목록을 받습니다. offline_transfer_exports 기능 플래그로 게이트되며 속도 제한이 적용됩니다. Import::Offline::Exports::CreateService에 위임합니다. |
| GET /offline_exports and GET /offline_exports/:id | Import::Offline::ExportsFinder를 통해 사용자의 내보내기 목록을 조회하거나 상세를 표시합니다. |
| POST /offline_imports | 이미 오브젝트 스토리지에 있는 내보내기로부터 가져오기를 시작합니다. 오브젝트 스토리지 구성, 내보내기의 export_prefix, 그리고 각각 대상 네임스페이스를 지정한 가져올 엔티티 목록을 받습니다. offline_transfer_imports 기능 플래그로 게이트되며 속도 제한이 적용됩니다. Import::Offline::Imports::CreateService에 위임합니다. |
Fog 어댑터와 오브젝트 스토리지#
직접 전송은 소스 인스턴스에서 HTTP로 관계 파일을 내려받고, CarrierWave ExportUpload 레코드를 통해 내보내기를 업로드합니다.
반면 오프라인 전송은 이 기능을 위해 만들어진 작은 Fog 래퍼를 통해 사용자가 구성한 버킷을 직접 읽고 씁니다:
Import::Clients::ObjectStorage는 프로바이더에 구애받지 않는 파사드입니다.Import::Offline::Configuration#provider에 따라 프로바이더별 어댑터를 고릅니다.aws또는s3_compatible은Adapters::Aws를,gcs또는gcs_application_default는Adapters::Gcs를,gcs_hmac은Adapters::GcsHmac을 사용합니다. 세 어댑터 모두 평범한Fog::Storage.new(provider: ..., **credentials)클라이언트를 감쌉니다.
s3_compatible(예: MinIO)은 allow_s3_compatible_storage_for_offline_transfer
애플리케이션 설정이 활성화된 경우에만 제공됩니다.
-
gcs_application_default(GCS Application Default Credentials)는 GitLab을 실행하는 인스턴스의 서비스 계정으로 해석되므로 GitLab.com에서는 절대 제공되지 않고, Self-Managed에서는 관리자가allow_application_default_credentials_for_offline_transfer애플리케이션 설정을 활성화한 경우에만 제공됩니다. -
업로드는
directory.files.create를 호출하며, 100MB 임계값을 넘으면 멀티파트 업로드를 사용합니다. 다운로드는directory.files.get을 통해 파일을 청크 단위로 스트리밍합니다. -
Import::Offline::ObjectKeyBuilder는 버킷에서 파일이 어디에 위치하는지에 대한 단일 진실 공급원(single source of truth)입니다:
<export_prefix>/<entity_prefix>/<relation>.<extension> # unbatched
<export_prefix>/<entity_prefix>/<relation>/batch_<n>.<extension> # batched
<export_prefix>/metadata.json.gz # metadata
예: 2026-04-16_19-39-00_export_dJtnb3CV/project_1/repository.tar.gz. 내보내기 시 entity_prefix는
내보내지는 portable에서 직접 파생됩니다(project_1, group_5). 가져오기 시에는
Import::Offline::Configuration#entity_prefix_mapping에서 조회하며, 이 값은 내보내기 중
metadata.json.gz에 기록된 엔티티 매핑으로부터 채워집니다.
- 업로드와 다운로드는 각각 전용 전략 클래스를 가지며, 이는 직접 전송이 HTTP에 사용하는 클래스들과 대응됩니다:
Import::Offline::ExportUploadable은
이를 필요로 하는 내보내기 서비스(예: FileExportService, UploadsExportService)에 믹스인됩니다.
offline_export_id가 있으면 CarrierWave ExportUpload 생성을 건너뛰고 파일을 오브젝트 스토리지로
곧바로 업로드합니다.
BulkImports::FileDownloadService.for_context는 가져오기 쪽의 유일한 분기점입니다. 오프라인 파이프라인 컨텍스트에서는Import::Offline::Imports::ObjectStorageFileDownloadStrategy를 만들고, 그렇지 않으면 직접 전송의 HTTP 전략인Import::BulkImports::HttpFileDownloadStrategy를 만듭니다.ObjectStorageFileDownloadStrategy는 오브젝트 스토리지에서 파일을 스트리밍하고, Gzip 헤더를 검증하며, HTTP 전략과 동일한 방식으로bulk_import_max_download_file_size애플리케이션 설정을 적용합니다.