다이렉트 트랜스퍼 임포터에 새 관계 추가하기
GitLab v19.4요약
개략적으로 보면, 직접 전송 임포터에 새 관계를 추가하려면 다음 작업이 필요합니다. 버그와 성능 문제가 유입될 위험을 줄이려면 새로 추가한 관계를 기능 플래그 뒤에 두어야 합니다. 내보내는 관계에는 몇 가지 유형이 있습니다.
개략적으로 보면, 직접 전송 임포터에 새 관계를 추가하려면 다음 작업이 필요합니다.
- 내보내기 데이터 목록에 새 관계를 추가합니다.
- 데이터 처리 지침이 담긴 새 ETL(Extract/Transform/Load) 파이프라인을 가져오기 측에 추가합니다.
- 새로 생성한 파이프라인을 가져오기 Stage 목록에 추가합니다.
- 새로 생성한 관계를 UI에 표시할 레이블을 추가합니다.
- 충분한 테스트 커버리지를 확보합니다.
버그와 성능 문제가 유입될 위험을 줄이려면 새로 추가한 관계를 기능 플래그 뒤에 두어야 합니다.
소스에서 내보내기#
내보내는 관계에는 몇 가지 유형이 있습니다.
- ActiveRecord 연관관계.
import_export.yml파일에서 읽어 JSON으로 직렬화한 뒤 NDJSON 파일에 기록합니다. 각 관계는.gz파일로, 컬렉션이면.tar.gz파일로 내보내 업로드하고, 대상 GitLab 인스턴스의 REST API로 제공해 다운로드하고 가져옵니다. - 바이너리 파일. 예를 들어 업로드 파일이나 LFS 오브젝트입니다.
- 내보내지 않고 가져오기 중에 GraphQL API에서 직접 읽는 소수의 관계.
ActiveRecord 연관관계에는 성능을 고려해 GraphQL API 대신 NDJSON을 사용해야 합니다. 중첩이 깊은 연관관계는 네트워크 요청을 많이 만들어 내 전체 마이그레이션 속도를 떨어뜨릴 수 있습니다.
ActiveRecord 관계 내보내기#
직접 전송 임포터의 기본 동작은 파일 기반 임포터에 크게 기반하며,
파일 기반 임포터는 import_export.yml 파일을 사용합니다. 이 파일은
내보내기에 포함할 Project 연관관계 목록을 기술합니다.
Group에는 비슷한 import_export.yml 이 있습니다.
예를 들어 documents라는 새 Project 연관관계에 가져오기 지원을 추가하려면 다음 작업이 필요합니다.
import_export.yml파일에 추가합니다.- 새 관계에 대한 테스트 커버리지를 추가합니다.
- 추가한 관계가 예상대로 내보내지는지 확인합니다.
import_export.yml 파일에 추가#
이 파일에 나열된 연관관계는 위에서 아래 순서로 가져옵니다. 순서에 의존하는 연관관계가 있으면, 그 연관관계를 필요로 하는 연관관계보다 의존성을 먼저 배치합니다. 예를 들어 documents는 머지 리퀘스트보다 먼저 가져와야 하며, 그러지 않으면 유효하지 않습니다.
-
import_export.yml의tree.project에 연관관계를 추가합니다.diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml index 43d66e0e67b7..0880a27dfce2 100644 --- a/lib/gitlab/import_export/project/import_export.yml +++ b/lib/gitlab/import_export/project/import_export.yml @@ -122,6 +122,7 @@ tree: - label: - :priorities - :service_desk_setting + - :documents group_members: - :user[!note] 연관관계가 Enterprise Edition 전용 기능과 관련되면 파일 끝의
ee.tree.project트리에 추가해, Enterprise Edition GitLab 인스턴스에서만 내보내고 가져오도록 합니다.연관관계에 하위 관계를 포함할 필요가 없으면 여기까지로 충분합니다. 다만 하위 관계를 더 포함해야 하면(예: notes) 그 하위 관계를 나열해야 합니다. 예를 들어 documents에는 notes(notes의 award emoji 포함)와 award emoji(documents의)가 있고 이를 마이그레이션하려고 합니다. 이 경우 관계는 다음과 같이 바뀝니다.
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml index 43d66e0e67b7..0880a27dfce2 100644 --- a/lib/gitlab/import_export/project/import_export.yml +++ b/lib/gitlab/import_export/project/import_export.yml @@ -122,6 +122,7 @@ tree: - label: - :priorities - :service_desk_setting + - documents: - :award_emoji - notes: - :award_emoji group_members: - :user -
관계의
included_attributes를 추가합니다. 기본적으로 YAML 파일의included_attributes에 나열되지 않은 관계 속성은 내보내기와 가져오기 양쪽에서 필터링됩니다. 필요한 속성을 포함하려면 다음과 같이included_attributes목록에 추가해야 합니다.diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml index 43d66e0e67b7..dbf0e1275ecf 100644 --- a/lib/gitlab/import_export/project/import_export.yml +++ b/lib/gitlab/import_export/project/import_export.yml @@ -142,6 +142,9 @@ import_only_tree: # Only include the following attributes for the models specified. included_attributes: + documents: + - :title + - :description user: - :id - :public_email -
관계의
excluded_attributes를 추가합니다. 파일에는excluded_attributes목록도 있습니다.Project에는 제외 속성을 추가할 필요가 없지만Group에는 여전히 추가해야 합니다. 이 목록은 내보내기에 포함하지 않고 가져올 때 무시해야 하는 속성을 나타냅니다. 보통 다음과 같은 속성입니다._id또는_ids로 끝나는 모든 것attributes를 포함하는 모든 것(custom_attributes제외)_html로 끝나는 모든 것- 민감한 모든 것(예: 토큰, 암호화된 데이터)
금지된 참조 전체 목록을 참고합니다.
-
관계의
methods를 추가합니다. 관계에 함께 내보내야 하는 메서드(예:document.signature)가 있으면methods섹션에 추가할 수 있습니다. 내보낸 값은 내보내기 결과에 포함되며, 가져올 때 그 값으로 원하는 작업을 할 수 있습니다. 예를 들어 필드에 할당할 수 있습니다.
예를 들어 note_diff_file.diff_export 메서드의 반환값을 내보내고, 가져올 때
이 메서드의 내보낸 값으로 note_diff_file.diff를 설정합니다.
새 관계에 대한 테스트 커버리지 추가#
직접 전송은 내부적으로 파일 기반 임포터를 사용하므로, 파일 기반 임포터 범위에서 새 관계에 대한 테스트 커버리지를 추가해야 합니다. 이 테스트는 직접 전송 임포터의 내보내기 측도 함께 다룹니다. 다음에 테스트를 추가합니다.
spec/lib/gitlab/import_export/project/tree_saver_spec.rb.Group에도 비슷한 파일이 있습니다.- EE 전용 관계는
ee/spec/lib/ee/gitlab/import_export/project/tree_saver_spec.rb입니다.
다른 관계의 예시를 따라 새 테스트를 추가합니다.
추가한 관계의 내보내기 동작 확인#
import_export.yml에 지정한 새 관계는 디스크에 기록되는 내보내기 파일에 자동으로 추가되므로 별도의 작업이 필요하지 않습니다.
관계와 테스트를 추가한 뒤에는 해당 관계가 내보내지는지 수동으로 확인할 수 있습니다. 다음 두 가지에 모두 자동으로 포함되어야 합니다.
- 파일 기반 가져오기와 내보내기. 프로젝트 내보내기 기능으로 내보내고 다운로드해 내보낸 데이터를 확인합니다.
- 직접 전송 내보내기.
export_relationsAPI로 내보내고 다운로드해 내보낸 관계를 확인합니다 (배치 단위로 내보낼 수 있습니다).
바이너리 관계 내보내기#
바이너리 관계 지원을 추가하려면 다음 작업이 필요합니다.
- 디스크에서 내보내기를 수행하는 새 내보내기 서비스를 생성합니다. 예시로
BulkImports::LfsObjectsExportService를 참고합니다. - 관계를
file_relations목록에 추가합니다. BulkImports::FileExportService에 관계를 추가합니다.
대상에서 가져오기#
앞에서 언급했듯이 직접 전송 가져오기에는 세 가지 관계 유형이 있습니다.
export_relationsAPI에서 다운로드하는 NDJSON 내보내기 관계. 예를 들어documents.ndjson.gz입니다.- GraphQL API 관계. 예를 들어 그룹과 프로젝트의 사용자 멤버십을 가져올 때
members정보를 GraphQL로 조회합니다. export_relationsAPI에서 다운로드하는 바이너리 관계. 예를 들어lfs_objects.tar.gz입니다.
직접 전송 임포터는 Extract/Transform/Load 데이터 처리 기법을 기반으로 하므로, 관계 가져오기를 시작하려면 다음을 정의해야 합니다.
- 새 관계 가져오기 파이프라인. 예를 들어
DocumentsPipeline입니다. - 파이프라인이 데이터를 어디서 어떻게 추출할지 알 수 있게 하는 데이터 추출기. 예를 들어
NdjsonPipeline입니다. - 데이터를 필요한 형식으로 변환하는 클래스 집합인 변환기 목록.
- 데이터를 어딘가에 저장하는 로더. 예를 들어 데이터베이스에 행을 저장하거나 새 LFS 오브젝트를 생성합니다.
어떤 유형의 관계를 가져오더라도 Pipeline 클래스 구조는 동일합니다.
module BulkImports
module Common
module Pipelines
class DocumentsPipeline
include Pipeline
def extract(context)
BulkImports::Pipeline::ExtractedData.new(data: file_paths)
end
def transform(context, object)
...
end
def load(context, object)
document.save!
end
end
end
end
end
NDJSON에서 관계 가져오기#
파이프라인 정의#
앞의 예시에서 documents 관계는 NDJSON 파일로 내보내지며, 이 경우 다음 두 가지를 모두 사용할 수 있습니다.
NdjsonPipeline. JSON을 ActiveRecord 오브젝트로 자동 변환하는 기능이 포함되어 있습니다(내부적으로 파일 기반 임포터를 사용합니다).NdjsonExtractor./export_relations/downloadREST API 엔드포인트로 소스 인스턴스에서.ndjson.gz파일을 다운로드합니다.
ETL 파이프라인의 각 단계는 메서드 또는 클래스로 정의할 수 있습니다.
class DocumentsPipeline
include NdjsonPipeline
relation_name 'documents'
extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
end
이 새 파이프라인은 이제 다음을 수행합니다.
- 소스 인스턴스에서
documents.ndjson.gz파일을 다운로드합니다. - NDJSON 파일의 내용을 읽고 JSON을 역직렬화해 ActiveRecord 오브젝트로 변환합니다.
- 프로젝트 범위에서 데이터베이스에 저장합니다.
파이프라인은 다음 중 한 곳에 둘 수 있습니다.
- 그룹과 프로젝트 마이그레이션 양쪽에서 공유해 사용하면
BulkImports::Common::Pipelines네임스페이스. 예를 들어LabelsPipeline은 공통 파이프라인이며 그룹과 프로젝트 Stage 목록 양쪽에서 참조됩니다. - 파이프라인이 프로젝트 마이그레이션에 속하면
BulkImports::Projects::Pipelines네임스페이스. - 파이프라인이 그룹 마이그레이션에 속하면
BulkImports::Groups::Pipelines네임스페이스.
Stage에 새 파이프라인 추가#
직접 전송 임포터는 그룹과 프로젝트 마이그레이션을 Stage 단위로 수행합니다. Stage 목록은 다음에 정의되어 있습니다.
Project의 경우:lib/bulk_imports/projects/stage.rb.Group의 경우:lib/bulk_imports/groups/stage.rb.
각 Stage의 특성은 다음과 같습니다.
- 병렬로 실행되는 파이프라인을 여러 개 가질 수 있습니다.
- 다음 Stage로 넘어가기 전에 완전히 완료되어야 합니다.
다음은 Project Stage에 파이프라인을 추가하는 예시입니다.
module BulkImports
module Projects
class Stage < ::BulkImports::Stage
private
def config
{
project: {
pipeline: BulkImports::Projects::Pipelines::ProjectPipeline,
stage: 0
},
repository: {
pipeline: BulkImports::Projects::Pipelines::RepositoryPipeline,
maximum_source_version: '15.0.0',
stage: 1
},
documents: {
pipeline: BulkImports::Projects::Pipelines::DocumentsPipeline,
minimum_source_version: '19.3.0',
stage: 2
}
end
end
end
end
지정한 내용은 다음과 같습니다.
stage: 2. 따라서 project와 repository Stage가 먼저 완료되어야 이 파이프라인이 Stage 2에서 실행됩니다.minimum_source_version: '19.3.0'. 이 마일스톤에서 내보내기용documents관계를 도입했으므로 이전 GitLab 버전에서는 사용할 수 없습니다. 따라서 이 파이프라인은 소스 버전이 19.3 이상일 때만 실행됩니다.
관계가 더 이상 사용되지 않아 특정 버전까지만 파이프라인을 실행해야 하면 maximum_source_version 속성을 지정할 수 있습니다.
파이프라인 테스트 커버리지 확보#
내보내기 측은 이미 테스트로 다루었으므로 가져오기 측도 동일하게 다루어야 합니다. 직접 전송 임포터에서는 각 파이프라인이 이 예시와 비슷한 별도의 spec 파일을 가집니다.
사용자 지정 연관관계 이름을 가진 관계 가져오기#
ActiveRecord 클래스 이름과 일치하지 않는 연관관계도 있습니다. 예를 들면 다음과 같습니다.
class Release
has_many :links, class_name: 'Releases::Link'
end
이런 연관관계는 releases.ndjson에서 links로 내보내집니다. 그러나 가져올 때 관계 클래스를 상수화하는 시점에는 해당 클래스가 존재하지 않으므로
links를 상수화할 수 없습니다. 클래스는 Releases::Link 여야 합니다.
이 경우 임포터가 올바르게 상수화하는 방법을 알 수 있도록, 연관관계와 그에 대응하는 ActiveRecord 클래스의 맵을 나타내는
OVERRIDES 해시에 이
연관관계 이름을 추가해야 합니다.
module Gitlab
module ImportExport
module Project
class RelationFactory < Base::RelationFactory
OVERRIDES = {
links: 'Releases::Link'
}
end
end
end
end
이렇게 하면 임포터가 내보낸 각 link를 그에 대응하는 Releases::Link 클래스에 매핑합니다.
여러 관계가 참조하는 기존 오브젝트 가져오기#
관계가 여러 연관관계에 걸쳐(또는 하나의 연관관계 안에서 여러 레코드에 걸쳐) 참조되면 중복을 가져오지 않아야 합니다.
예를 들어 여러 이슈와 머지 리퀘스트에 적용된 레이블이 있다고 가정합니다. 이슈와 머지 리퀘스트를 내보내면 내보낸 레이블이 각 레코드의 하위 관계로 포함됩니다. 내보낸 이슈와 머지 리퀘스트를 가져올 때는 레이블을 한 번만 가져와 모든 레코드에서 재사용해야 합니다. 그러지 않으면 중복, 즉 이름이 같은 레이블이 여러 개 생깁니다.
이런 오브젝트를 한 번만 가져와 여러 곳에서 재사용하려면 해당 오브젝트를 기존 오브젝트 관계로 정의해야 합니다.
먼저 레이블 연관관계를
EXISTING_OBJECT_RELATIONS에 추가해야 합니다. 관계가 기존
오브젝트 관계 목록에 추가되면 임포터는 그 관계를 다른 관계와 다르게 처리해야 한다고 판단하고 다른 가져오기 흐름을
거칩니다. 이런 관계는 일반 경로로 가져오는 대신
ObjectBuilder를 사용합니다.
ObjectBuilder는 다음 중 하나를 시도합니다.
- 정의한 파라미터를 기준으로 데이터베이스에서 기존 오브젝트를 찾아 반환합니다.
- 오브젝트가 없으면 새로 생성합니다.
ObjectBuilder에 새 관계를 추가하려면 다음 작업이 필요합니다.
- 앞에서 언급한 대로
EXISTING_OBJECT_RELATIONS에 관계를 추가합니다. - 프로젝트 연관관계인지 그룹 연관관계인지에 따라 Group 또는 Project
ObjectBuilder를 업데이트합니다. - 기존 오브젝트 조회에 사용할 속성을 정의합니다. 예를 들어 레이블은
title,description,created_at으로 검색합니다. 정의한 파라미터를 가진 레이블이 프로젝트에 있으면 새로 생성하지 않고 재사용합니다.
GraphQL API에서 관계 가져오기#
관계를 GraphQL API로 사용할 수 있으면 GraphQlExtractor를 사용하고 파이프라인 클래스에서 변환과 로딩을 수행할 수 있습니다.
MembersPipeline 예시입니다.
module BulkImports
module Common
module Pipelines
class MembersPipeline
include Pipeline
transformer Common::Transformers::ProhibitedAttributesTransformer
transformer Common::Transformers::MemberAttributesTransformer
def extract(context)
graphql_extractor.extract(context)
end
def load(_context, data)
...
member.save!
end
private
def graphql_extractor
@graphql_extractor ||= BulkImports::Common::Extractors::GraphqlExtractor
.new(query: BulkImports::Common::Graphql::GetMembersQuery)
end
end
end
end
end
나머지 단계는 위와 동일합니다.
바이너리 관계 가져오기#
바이너리 관계 파이프라인은 다른 파이프라인과 구조가 같으며, extract/transform/load 단계에서 무엇을 할지만 정의하면 됩니다.
LfsObjectsPipeline 예시입니다.
module BulkImports
module Common
module Pipelines
class LfsObjectsPipeline
include Pipeline
file_extraction_pipeline!
def extract(_context)
download_service.execute
decompression_service.execute
extraction_service.execute
...
end
def load(_context, file_path)
...
lfs_object.save!
end
end
end
end
end
데이터 다운로드를 돕는 헬퍼 서비스 클래스가 여러 개 있습니다.
BulkImports::FileDownloadService: 지정한 위치에서 파일을 다운로드합니다.BulkImports::FileDecompressionService: 필수 검증을 포함한 Gzip 압축 해제 서비스입니다.BulkImports::ArchiveExtractionService: Tar 추출 서비스입니다.
UI 적용#
새 관계의 레이블 추가#
직접 전송에 새 관계를 추가한 뒤에는 그 관계가 UI에서 사람이 읽을 수 있는 형태로 표시되는지 확인해야 합니다.
BULK_IMPORT_STATIC_ITEMS에 새 키-값 쌍을 추가합니다.
diff --git a/app/assets/javascripts/import/constants.js b/app/assets/javascripts/import/constants.js
index 439f453cd9d3..d6b4119a0af9 100644
--- a/app/assets/javascripts/import/constants.js
+++ b/app/assets/javascripts/import/constants.js
@@ -31,6 +31,7 @@ export const BULK_IMPORT_STATIC_ITEMS = {
service_desk_setting: __('Service Desk'),
vulnerabilities: __('Vulnerabilities'),
commit_notes: __('Commit notes'),
+ documents: __('Documents')
};
const STATISTIC_ITEMS = {