- 전제 조건
- 코드 구조
- 아키텍처 개요
-
단계
- 1. RepositoryImportWorker
- 2. Stage::ImportRepositoryWorker
- 3. Stage::ImportBaseDataWorker
- 4. Stage::ImportPullRequestsWorker
- 5. Stage::ImportCollaboratorsWorker
- 6. Stage::ImportPullRequestsMergedByWorker (deprecated)
- 7. Stage::ImportPullRequestsReviewRequestsWorker (deprecated)
- 8. Stage::ImportPullRequestsReviewsWorker (deprecated)
- 9. Stage::ImportIssuesAndDiffNotesWorker
- 10. Stage::ImportIssueEventsWorker
- 11. Stage::ImportNotesWorker (deprecated)
- 12. Stage::ImportAttachmentsWorker
- 13. Stage::ImportProtectedBranchesWorker
- 14. Stage::FinishImportWorker
- Stage 진행
- Import 작업 ID 새로고침
- GitHub 속도 제한
- 사용자 조회 캐싱
- Sidekiq 중단 시간 증가
sidekiq_options dead: false
- 레이블 및 마일스톤 매핑
- 로그
- 메트릭 대시보드
GitHub 가져오기 개발자 문서
GitHub 가져오기는 두 가지 다른 유형의 가져오기기를 제공합니다.
- 순차적 가져오기기(import:github Rake 작업에서 사용)
- 병렬 가져오기기(나머지에서 사용)
이 두 가져오기기의 차이점은 다음과 같습니다:
- 순차적 가져오기기는 모든 작업을 단일 스레드에서 수행하므로 디버깅 목적이나 Rake 작업에 더 적합합니다.
- 병렬 가져오기기는 Sidekiq을 사용합니다.
전제 조건
- 기본적으로 활성화된
github_importer
및github_importer_advance_stage
큐를 처리하는 Sidekiq 워커. - GitHub API와 상호 작용하는 데 사용되는 Octokit.
코드 구조
가져오기기의 코드베이스는 다음 디렉터리로 분할됩니다:
-
lib/gitlab/github_import
: 이 디렉터리에는 리소스를 가져오는 데 사용되는 클래스와 같은 대부분의 코드가 포함되어 있습니다. -
app/workers/gitlab/github_import
: 이 디렉터리에는 Sidekiq 워커가 포함되어 있습니다. -
app/workers/concerns/gitlab/github_import
: 이 디렉터리에는 여러 Sidekiq 워커에서 재사용되는 몇 가지 모듈이 포함되어 있습니다.
아키텍처 개요
GitHub 프로젝트를 가져올 때 작업은 별도의 단계로 나뉘어 각 단계는 실행되는 일련의 Sidekiq 작업으로 구성되며, 현재 단계의 모든 작업이 완료되었는지 주기적으로 확인하고, 이 경우 가져오기 프로세스를 다음 단계로 진행하는 작업이 예약됩니다. 이를 처리하는 워커는 Gitlab::GithubImport::AdvanceStageWorker
라고 합니다.
- 가져오기는 API 요청을 통해 시작됩니다.
POST /import/github
- API 엔드포인트가
Import::GitHubService
를 호출합니다. - 이는
Gitlab::LegacyGithubImport::ProjectCreator
를 호출합니다. - 해당 서비스는
Projects::CreateService
를 호출합니다. - 해당 서비스는
@project.import_state.schedule
를 호출합니다. - 해당 서비스는
project.add_import_job
를 호출합니다. - 해당 서비스는
RepositoryImportWorker
를 호출합니다.
단계
1. RepositoryImportWorker
이 워커는 Projects::ImportService.new.execute
를 호출하여,
importer.execute
를 호출합니다.
이 문맥에서 importer
는 Gitlab::ImportSources.importer(project.import_type)
의 인스턴스로, github
가져오기 유형에 대해 ParallelImporter
로 매핑됩니다.
ParallelImporter
는 다음 워커를 예약합니다.
2. Stage::ImportRepositoryWorker
이 워커는 리포지터리와 위키를 가져오고, 작업이 완료되면 다음 단계를 예약합니다.
3. Stage::ImportBaseDataWorker
이 워커는 라벨, 마일스톤 및 릴리스와 같은 기본 데이터를 가져옵니다. 이 작업은 충분히 빠르게 수행될 수 있기 때문에 단일 스레드에서 수행됩니다.
4. Stage::ImportPullRequestsWorker
이 워커는 모든 풀 리퀘스트를 가져옵니다. 각 풀 리퀘스트마다 Gitlab::GithubImport::ImportPullRequestWorker
워커의 작업이 예약됩니다.
5. Stage::ImportCollaboratorsWorker
이 워커는 외부 협력자가 아닌 직접적인 리포지터리 공동 작업자만 가져옵니다.
각 공동 작업자에 대해 Gitlab::GithubImport::ImportCollaboratorWorker
워커의 작업이 예약됩니다.
Gitlab::GithubImport::Settings
로 제어)하며 기본적으로 선택됩니다.6. Stage::ImportPullRequestsMergedByWorker (deprecated)
이 워커는 풀 리퀘스트의 merged-by 사용자 정보를 가져옵니다. 리스트 풀 리퀘스트 API는 이 정보를 제공하지 않습니다. 따라서 이 단계는 이 정보를 가져오기 위해 각 Merge된 풀 리퀘스트를 개별적으로 가져와야 합니다. 가져온 각 풀 리퀘스트에 대해 Gitlab::GithubImport::PullRequests::ImportMergedByWorker
작업이 예약됩니다.
github_import_extended_events
피처 플래그가 활성화되어 있을 때 건너뛰어지며, 풀 리퀘스트의 Merge된 정보는 10. Stage::ImportIssueEventsWorker
단계에서 가져오기 때문에 이 단계는 해당 피처 플래그와 함께 제거될 예정입니다.7. Stage::ImportPullRequestsReviewRequestsWorker (deprecated)
이 Worker는 pull request의 지정된 리뷰어를 가져옵니다. 각 pull request에 대해, 이 Worker는 다음을 수행합니다:
- 지정된 모든 리뷰 요청을 가져옵니다.
- 가져온 각 리뷰 요청에 대해
Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker
작업을 예약합니다.
github_import_extended_events
피처 플래그가 활성화되면 건너뜁니다. 왜냐하면 pull request 리뷰 요청 정보가
10. Stage::ImportIssueEventsWorker
stage에서 가져오기 때문입니다. 이 stage는 플래그와 함께 제거될 것입니다.8. Stage::ImportPullRequestsReviewsWorker (deprecated)
이 Worker는 pull request의 리뷰를 가져옵니다. 각 pull request에 대해, 이 Worker는 다음을 수행합니다:
- 모든 리뷰 페이지를 가져옵니다.
- 가져온 각 리뷰에 대해
Gitlab::GithubImport::PullRequests::ImportReviewWorker
작업을 예약합니다.
github_import_extended_events
피처 플래그가 활성화되면 건너뜁니다. 왜냐하면 pull request 리뷰 정보가
10. Stage::ImportIssueEventsWorker
stage에서 가져오기 때문입니다. 이 stage는 플래그와 함께 제거될 것입니다.9. Stage::ImportIssuesAndDiffNotesWorker
이 Worker는 모든 이슈와 pull request 코멘트를 가져옵니다. 각 이슈에 대해, Gitlab::GithubImport::ImportIssueWorker
Worker를 위한 작업을 예약합니다. pull request 코멘트의 경우, 대신
Gitlab::GithubImport::DiffNoteImporter
Worker를 위한 작업을 예약합니다.
이 Worker는 이슈와 diff note를 병렬로 처리하므로 이전 작업이 완료될 때까지 별도의 stage를 예약하고 대기할 필요가 없습니다.
이슈는 pull request를 가져오는 것과 별도로 가져오기 때문에 “이슈” API에만 이슈 및 pull request의 라벨이 포함되어 있습니다. 이슈를 가져오고 라벨 링크를 설정하는 것은 별도의 API 데이터를 크롤링할 필요성을 제거하므로 프로젝트를 가져오는 데 필요한 API 호출 수를 줄일 수 있습니다.
10. Stage::ImportIssueEventsWorker
이 Worker는 모든 이슈와 pull request 이벤트를 가져옵니다. 각 이벤트에 대해, Gitlab::GithubImport::ImportIssueEventWorker
Worker를 위한 작업을 예약합니다.
특정 측면으로 인해 GitHub API의 특성으로 인해 하나의 stage로 이슈 및 pull request 이벤트를 가져올 수 있습니다. 이것은 사실상 근본적으로 이슈 및 pull request가 하나의 테이블에 저장되기 때문입니다. 따라서 전역적으로 고유한 ID를 가지고 있고:
- 모든 pull request는 이슈입니다.
- 이슈는 pull request가 아닙니다.
따라서 이슈 및 pull request에는 대부분의 관련된 것에 대한 공통 API가 있습니다.
github_import_extended_events
플래그가 활성화된 경우, 이 stage는 pull request merged by
, pull request reviews
, pull request review requests
및 notes
를 가져오기 위한 역할을 합니다. 이는
timeline events endpoint
에서도 해당 정보가 포함되어 있기 때문입니다.
timeline events
endpoint를 사용하여 pull request review requests
를 가져오기 위해 이벤트를 순차적으로 처리해야 합니다. 가져오기 Worker는 보장된 순서로 실행되지 않기 때문에 pull request review requests
이벤트는 처음에 Redis 정렬 디렉터리에 배치됩니다. 그 후 Gitlab::GithubImport::ReplayEventsWorker
에 의해 순차적으로 사용되게 됩니다.
github_import_extended_events
플래그가 활성화된 경우 필수적입니다. 그렇지 않으면 선택적인 stage이며 import options를 사용하여 실행할 수 있습니다.11. Stage::ImportNotesWorker (deprecated)
이 Worker는 이슈 및 pull request의 일반 코멘트를 가져옵니다. 각 코멘트에 대해, Gitlab::GithubImport::ImportNoteWorker
Worker를 위한 작업을 예약합니다.
일반 코멘트는 GitHub API에서 반환되는 것으로 이슈 및 pull request에 대한 코멘트를 기다리기 위해 모든 이슈와 pull request가 가져오기 완료될 때까지 기다려야 합니다.
github_import_extended_events
플래그가 활성화된 경우 코멘트가 10. Stage::ImportIssueEventsWorker
stage에서 가져오기 때문에 건너뜁니다. 이 stage는 플래그와 함께 제거될 것입니다.12. Stage::ImportAttachmentsWorker
이 Worker는 Markdown 내부에 연결된 코멘트 첨부 파일을 가져옵니다. 프로젝트의 Markdown 텍스트에서 각 entity에 대해 작업을 예약합니다:
- 모든 릴리스에 대해
Gitlab::GithubImport::Importer::Attachments::ReleasesImporter
작업을 예약합니다. - 각 코멘트에 대해
Gitlab::GithubImport::Importer::Attachments::NotesImporter
작업을 예약합니다. - 각 이슈에 대해
Gitlab::GithubImport::Importer::Attachments::IssuesImporter
작업을 예약합니다. - 각 merge request에 대해
Gitlab::GithubImport::Importer::Attachments::MergeRequestsImporter
작업을 예약합니다.
각 작업:
- 특정 레코드 내의 모든 첨부 파일 링크를 반복합니다.
- 첨부 파일을 다운로드합니다.
- 이전 링크를 GitLab에 새로 생성된 링크로 교체합니다.
Gitlab::GithubImport::Settings
에 의해 제어되는 추가적인 가져오기 시간을 소비할 수 있는 선택적인 stage입니다.13. Stage::ImportProtectedBranchesWorker
이 Worker는 보호된 브랜치 규칙을 가져옵니다.
GitHub에 존재하는 각 규칙에 대해, Gitlab::GithubImport::ImportProtectedBranchWorker
에 대한 작업을 예약합니다.
각 작업은 GitHub와 GitLab의 브랜치 보호 규칙을 비교하고 엄격한 규칙을 GitLab의 브랜치에 적용합니다.
14. Stage::FinishImportWorker
이 Worker는 일부 하우스키퍼를 수행하고 가져오기를 완료로 표시함으로써 가져오기 프로세스를 완료합니다.
Stage 진행
Stage 진행은 다음 두 가지 방법 중 하나로 수행됩니다:
- 다음 stage를 직접적으로 Worker에 예약합니다.
- 모든 현재 stage의 작업이 완료되었을 때
Gitlab::GithubImport::AdvanceStageWorker
에 대한 작업을 예약합니다.
첫 번째 접근 방법은 작업을 단일 스레드에서 수행하는 Worker에만 사용해야 합니다. 그 외의 경우에는 AdvanceStageWorker
를 사용해야 합니다.
첫 번째 접근 방법의 예는 ImportBaseDataWorker
가 PullRequestWorker
를 직접적으로 부르는 방식입니다.
두 번째 접근 방법의 예는 PullRequestsWorker
가 자신의 작업이 완료됐을 때 AdvanceStageWorker
를 예약하는 방식입니다.
작업을 예약할 때 AdvanceStageWorker
에는 프로젝트 ID, Redis 키 디렉터리 및 다음 stage의 이름이 주어집니다. Redis 키는 실행 중인 stage가 완료되었는지 여부를 확인하는 데 사용됩니다 (Gitlab::JobWaiter
에 의해 생성됨). stage가 아직 완료되지 않았을 경우 AdvanceStageWorker
는 자신을 다시 예약합니다. stage가 완료된 후나 마지막 호출 이후에 더 많은 작업이 완료되면
AdvanceStageworker
는 가져오기 JID를 새로 고치고
다음 stage의 Worker를 예약합니다.
AdvanceStageWorker
작업을 예약하는 수를 줄이기 위해 이 Worker는 일시적으로 작업이 완료될 때까지 대기합니다. 작은 프로젝트의 경우, 이렇게 하면 가져오기 프로세스가 약간 느려질 수 있지만,
시스템에 가해지는 압력을 줄일 수 있습니다.
Import 작업 ID 새로고침
GitLab에는 주기적으로 실행되며 프로젝트 가져오기 작업을 24시간 이상 새로 고침하지 않은 경우 실패로 표시하는 Gitlab::Import::StuckProjectImportJobsWorker
라는 워커가 포함되어 있습니다. GitHub 프로젝트의 경우 큰 프로젝트를 가져오는 데는 GitHub 속도 제한에 따라 몇 일이 소요될 수 있습니다(아래 자세한 내용 참조), 하지만 이로 인해 Gitlab::Import::StuckProjectImportJobsWorker
가 우리의 가져오기를 실패로 표시하지 않길 원합니다.
이를 방지하기 위해 가져오기의 만료 시간을 주기적으로 새로고침합니다. 이는 가져오기 작업의 다양한 단계에서 가져오기 작업의 JID를 데이터베이스에 저장한 다음 이 JID TTL을 새로 고침합니다. 이는 ProjectImportState#refresh_jid_expiration
을 호출하거나 현재 워커의 jid를 전달하여 RefreshImportJidWorker를 사용하여 수행됩니다. 이 TTL을 새로 고침함으로써 작업을 수행하는 한 가져오기가 실패로 표시되지 않도록 할 수 있습니다.
GitHub 속도 제한
GitHub는 시간당 5000개의 API 호출 속도 제한이 있습니다. 프로젝트를 가져오는 데 필요한 요청 수는 주로 프로젝트에 참여하는 고유한 사용자 수(예: 이슈 작성자)에 의해 지배됩니다. 왜냐하면 이들을 GitLab 사용자에 매핑하기 위해 사용자의 이메일 주소가 필요하기 때문입니다. 이외에도 이슈 페이지 및 코멘트와 같은 다른 데이터는 일반적으로 가져오기에 몇십 개의 요청만 필요합니다.
우리는 속도 제한을 다음과 같은 방법으로 처리합니다:
- 속도 제한에 걸리면 작업을 자동으로 재예약하여 속도 제한이 재설정될 때까지 실행되지 않도록 합니다.
- GitHub 사용자를 GitLab 사용자에 매핑한 내용을 Redis에 캐시합니다.
사용자 캐싱에 대한 자세한 내용은 아래에서 찾을 수 있습니다.
사용자 조회 캐싱
GitHub 사용자를 GitLab 사용자에 매핑할 때 우리는 (최악의 경우) 다음을 수행해야 합니다:
- 사용자의 이메일 주소를 가져오는 API 호출 1회.
- 해당하는 GitLab 사용자가 있는지 확인하기 위한 데이터베이스 쿼리 2회. 하나는 GitHub 사용자 ID를 기반으로 사용자를 찾으려고 하고, 두 번째 쿼리는 GitHub 이메일 주소를 사용하여 사용자를 찾으려고 합니다.
GitHub Enterprise에서 가져올 때는 GitHub 사용자 ID로 검색을 수행하지 않습니다.
이 프로세스가 상당히 비용이 많이 들기 때문에 이러한 조회 결과를 Redis에 캐시합니다. 조회된 사용자마다 다섯 가지 키를 저장합니다:
- GitHub 사용자 이름을 이메일 주소로 매핑하는 Redis 키.
- GitHub 이메일 주소를 GitLab 사용자 ID로 매핑하는 Redis 키.
- GitHub 사용자 ID를 GitLab 사용자 ID로 매핑하는 Redis 키.
- GitHub 사용자 이름을 ETAG 헤더로 매핑하는 Redis 키.
- 프로젝트에 대한 이메일 조회가 수행되었는지를 나타내는 Redis 키.
우리는 두 가지 유형의 조회를 캐시합니다:
- 긍정적인 조회(존재하는 GitLab 사용자 ID)를 의미합니다.
- 부정적인 조회(존재하지 않는 GitLab 사용자 ID)를 의미합니다. 이를 캐시함으로써 우리는 GitLab 데이터베이스에 존재하지 않는 사용자에 대해 동일한 작업을 수행하지 않도록 합니다.
이러한 키의 만료 시간은 24시간입니다. 긍정적인 조회 캐시를 검색할 때 TTL을 자동으로 새로 고침합니다. 부정적인 조회의 TTL은 절대로 새로 고침되지 않습니다.
이메일 조회가 비어 있거나 부정적인 조회인 경우, 각 프로젝트에 대해 헤더에 캐시된 ETAG와 함께 조건부 요청이 수행됩니다. 조건부 요청은 GitHub API 속도 제한에 포함되지 않습니다.
이 캐싱 레이어 덕분에 새로 등록된 GitLab 계정이 해당하는 GitHub 계정에 연결되지 않을 수 있습니다. 그러나 이는 캐시 키가 만료되거나 새 프로젝트가 가져올 때 해결됩니다.
사용자 캐시 조회는 프로젝트 전반에 걸쳐 공유됩니다. 이는 가져오기된 프로젝트의 수가 많을수록 필요한 GitHub API 호출이 적어진다는 것을 의미합니다.
이에 대한 코드는 다음 위치에 있습니다:
lib/gitlab/github_import/user_finder.rb
lib/gitlab/github_import/caching.rb
Sidekiq 중단 시간 증가
Sidekiq 프로세스가 종료될 때 실행 중인 작업이 끝나기를 기다린 다음 그 작업을 중단합니다. 중단은 작업을 종료하고 다시 대기열에 넣습니다. 저희의 vendor sidekiq-reliable-fetcher
gem은 작업이 영구적으로 종료되기 전에 최대 3
회의 중단만 허용합니다. 중단된 작업은 Kibana에 json.interrupted_count
를 기록합니다.
이 한도는 Sidekiq 재시작 시간 사이에 작업을 완료할 수 없는 작업으로부터 보호합니다.
큰 가져오기의 경우, 우리의 GitHub stage 워커들(Stage::
로 네임스페이스 지정됨)은 완료하는 데 많은 시간이 소요됩니다. 기본적으로 가져오기는 sidekiq-reliable-fetcher
가 그들을 완료하기 전에 영구적으로 중지시킬 수 있기 때문에 실패의 위험에 처합니다.
재시작 시 이전 상태에서 작업을 계속하는 Stage 워커는 .resumes_work_when_interrupted!
를 호출하여 sidekiq-reliable-fetcher
의 중단 한도를 20
로 늘릴 수 있습니다.
module Gitlab
module GithubImport
module Stage
class MyWorker
resumes_work_when_interrupted!
# ...
end
end
end
end
재시작 시 전체 작업을 계속하지 않는 Stage 워커는 이 메서드를 호출해서는 안 됩니다. 예를 들어 이미 가져온 객체를 건너뛰지만 매번 처음부터 루프를 시작하는 워커입니다.
전체 작업을 완전히 계속하는 Stage 워커의 예는 다음을 실행하는 서비스입니다.
sidekiq_options dead: false
일반적으로 워커의 재시도가 소진되면 Sidekiq dead 세트로 이동하여 인스턴스 관리자가 다시 시도할 수 있습니다.
GithubImport::Queue
는 GitHub 가져오기 워커에서 이러한 상황을 방지하기 위해 Sidekiq 워커 옵션 dead: false
를 설정합니다.
이유는 다음과 같습니다:
- dead 세트에는 최대 제한이 있으며, 객체 가져오기 워커(즉,
ObjectImporter
를 포함하는 워커)가 대규모로 실패하면 dead 세트로 스팸 메시지를 보내 다른 워커를 밀어낼 수 있습니다. - stage 워커(즉,
StageMethods
를 포함하는 워커)는 재시도가 소진되면 가져오기를 실패하므로 재시도는 아무런 작업도 수행하지 않습니다.
레이블 및 마일스톤 매핑
데이터베이스에 압력을 줄이기 위해 문제 및 Merge Request에 레이블 및 마일스톤을 설정할 때 데이터베이스를 쿼리하지 않습니다. 대신, 레이블 및 마일스톤을 가져오고 캐시한 다음 문제/Merge Request에 할당할 때 이 캐시를 재사용합니다. 사용자 조회와 마찬가지로 이러한 캐시 키는 사용되지 않은 상태로 24시간이 경과하면 자동으로 만료됩니다.
사용자 조회 캐시와 달리 레이블 및 마일스톤 캐시는 가져오고 있는 프로젝트로 범위가 지정됩니다.
해당 코드는 다음 위치에 있습니다:
lib/gitlab/github_import/label_finder.rb
lib/gitlab/github_import/milestone_finder.rb
lib/gitlab/cache/import/caching.rb
로그
가져오기 진행 상황은 logs/importer.log
파일에서 확인할 수 있습니다. 각 관련 가져오기는 "import_type": "github"
및 "project_id"
로 로그가 기록됩니다.
마지막 로그 항목에는 가져와서 가져온 객체 수가 보고됩니다:
{
"message": "GitHub 프로젝트 가져오기 완료",
"duration_s": 347.25,
"objects_imported": {
"fetched": {
"diff_note": 93,
"issue": 321,
"note": 794,
"pull_request": 108,
"pull_request_merged_by": 92,
"pull_request_review": 81
},
"imported": {
"diff_note": 93,
"issue": 321,
"note": 794,
"pull_request": 108,
"pull_request_merged_by": 92,
"pull_request_review": 81
}
},
"import_source": "github",
"project_id": 47,
"import_stage": "Gitlab::GithubImport::Stage::FinishImportWorker"
}
메트릭 대시보드
GitHub 가져오기의 건강 상태를 평가하기 위해 GitHub 가져오기 대시보드에서 시간에 따른 총 가져온 객체 vs. 가져온 객체 수에 대한 정보를 제공합니다.