GitHub Importer 개발자 문서

GitHub Importer에는 두 가지 유형의 importers가 있습니다.

  • 순차적으로 가져오는 중입니다. import:github Rake 작업에서 사용됩니다.
  • 병렬로 가져오는 중입니다. 다른 모든 것에서 사용됩니다.

이 두 importer의 차이점은:

  • 순차적으로 가져오는 importer는 모든 작업을 단일 스레드에서 처리하므로 디버깅 목적이나 Rake 작업에 더 적합합니다.
  • 병렬 importer는 Sidekiq을 사용합니다.

Prerequisites

  • 기본적으로 활성화된 github_importergithub_importer_advance_stage 큐를 처리하는 Sidekiq workers가 필요합니다.
  • GitHub API와 상호 작용하기 위해 Octokit을 사용합니다.

Code structure

이 importer의 코드 기반은 다음 디렉토리로 분할됩니다.

  • lib/gitlab/github_import: 리소스를 가져오는 데 사용되는 클래스와 같은 대부분의 코드가 들어있는 디렉토리입니다.
  • app/workers/gitlab/github_import: Sidekiq workers가 들어있는 디렉토리입니다.
  • app/workers/concerns/gitlab/github_import: 여러 Sidekiq workers에서 재사용되는 몇 가지 모듈이 들어있는 디렉토리입니다.

Architecture overview

GitHub 프로젝트를 가져올 때 작업이 별도의 단계로 나눠지고, 각 단계는 실행되는 일련의 Sidekiq 작업으로 구성됩니다. 각 단계 사이에 현재 단계의 모든 작업이 완료되었는지 주기적으로 확인하는 작업이 예약되며, 이러한 경우 가져오기 프로세스가 다음 단계로 진행됩니다. 이 작업을 처리하는 worker는 Gitlab::GithubImport::AdvanceStageWorker라고 불립니다.

단계

1. RepositoryImportWorker

이 worker는 Projects::ImportService.new.execute를 호출하고, 이는 importer.execute를 호출합니다.

이 상황에서 importerGitlab::ImportSources.importer(project.import_type)의 인스턴스로, github 가져오기 유형에 대해 ParallelImporter에 매핑됩니다.

ParallelImporter는 다음 worker를 예약합니다.

2. Stage::ImportRepositoryWorker

이 worker는 저장소와 위키를 가져오고, 완료되면 다음 단계를 예약합니다.

3. 단계::ImportBaseDataWorker

이 worker는 라벨, 마일스톤 및 릴리스와 같은 기본 데이터를 가져옵니다. 이 작업은 단일 스레드에서 수행되며 충분히 빠르기 때문에 병렬로 수행할 필요가 없습니다.

4. 단계::ImportPullRequestsWorker

이 worker는 모든 풀 리퀘스트를 가져옵니다. 각각의 풀 리퀘스트마다 Gitlab::GithubImport::ImportPullRequestWorker worker 작업이 예약됩니다.

5. 단계::ImportCollaboratorsWorker

이 worker는 외부 협력자가 아닌 직접적인 리포지토리 협력자만을 가져옵니다. 각 협력자마다 Gitlab::GithubImport::ImportCollaboratorWorker worker 작업이 예약됩니다.

참고: 이 단계는 선택 사항입니다(Gitlab::GithubImport::Settings로 제어됨) 및 기본적으로 선택됩니다.

6. 단계::ImportPullRequestsMergedByWorker (deprecated)

이 worker는 풀 리퀘스트의 merged-by 사용자 정보를 가져옵니다. List pull requests API는이 정보를 제공하지 않습니다. 따라서이 데이터를 가져 오려면이 단계에서 각 병합 된 풀 요청을 개별적으로 검색해야합니다. 가져온 각 풀 요청에 대해 Gitlab::GithubImport::PullRequests::ImportMergedByWorker 작업이 예약됩니다.

참고: github_import_extended_events 기능 플래그가 활성화될 때 이 단계는 건너 뜁니다. 풀 리퀘스트가 정보를 병합한 10. 단계::ImportIssueEventsWorker 단계에서 가져 오기 때문입니다. 이 단계는 기능 플래그와 함께 제거될 것입니다.

7. 단계::ImportPullRequestsReviewRequestsWorker (deprecated)

이 worker는 풀 리퀘스트에 할당된 리뷰어를 가져옵니다. 각 풀 리퀘스트에 대해이 worker는 다음을 수행합니다.

  • 모든 할당된 리뷰 요청을 가져옵니다.
  • 가져온 각 리뷰 요청에 대해 Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker 작업을 예약합니다.

참고: github_import_extended_events 기능 플래그가 활성화될 때 이 단계는 건너 뜁니다. 풀 요청의 리뷰 요청 정보는 10. 단계::ImportIssueEventsWorker 단계에서 가져 오기 때문입니다. 이 단계는 기능 플래그와 함께 제거될 것입니다.

8. 단계::ImportPullRequestsReviewsWorker (deprecated)

이 worker는 풀 리퀘스트의 리뷰를 가져옵니다. 각 풀 리퀘스트에 대해이 worker는 다음을 수행합니다.

  • 모든 리뷰 페이지를 가져옵니다.
  • 각 가져온 리뷰에 대해 Gitlab::GithubImport::PullRequests::ImportReviewWorker 작업을 예약합니다.

참고: github_import_extended_events 기능 플래그가 활성화될 때 이 단계는 건너 뜁니다. 풀 리퀘스트의 리뷰 정보는 10. 단계::ImportIssueEventsWorker 단계에서 가져 오기 때문입니다. 이 단계는 기능 플래그와 함께 제거될 것입니다.

9. 단계::ImportIssuesAndDiffNotesWorker

이 worker는 모든 이슈 및 풀 리퀘스트 코멘트를 가져옵니다. 각 이슈에 대해 Gitlab::GithubImport::ImportIssueWorker worker 작업을 예약하고, 풀 리퀘스트 코멘트의 경우 대신 Gitlab::GithubImport::DiffNoteImporter worker 작업을 예약합니다.

이 worker는 이슈 및 diff notes를 병렬로 처리하기 때문에 별도의 스테이지를 예약하고 이전 작업이 완료될 때까지 기다릴 필요가 없습니다.

이슈는 풀 리퀘스트와 별도로 가져 오기 때문에 “이슈” API에만 이슈 및 풀 리퀘스트의 라벨이 포함되어 있습니다. 이슈를 가져 오고 라벨 링크를 동일한 worker에서 설정하면 API 데이터를 별도로 크롤링할 필요가 없으므로 프로젝트를 가져 오는 데 필요한 API 호출 수를 줄일 수 있습니다.

10. 단계::ImportIssueEventsWorker

이 worker는 모든 이슈 및 풀 리퀘스트 이벤트를 가져옵니다. 각 이벤트에 대해 Gitlab::GithubImport::ImportIssueEventWorker worker 작업을 예약합니다.

GitHub API의 특정 측면으로 인해 이슈 및 풀 리퀘스트 이벤트를 단일 단계로 가져올 수 있습니다. 내부적으로 보면 GitHub의 이슈 및 풀 리퀘스트가 동일한 테이블에 저장되어 있습니다. 따라서 전역적으로 고유한 ID가 있으므로:

  • 모든 풀 리퀘스트는 이슈입니다.
  • 이슈는 풀 리퀘스트가 아닙니다.

그러므로 이슈 및 풀 리퀘스트는 대부분의 관련된 것에 대해 공통 API를 가지고 있습니다.

github_import_extended_events 기능 플래그가 활성화된 경우이 단계는 pull request merged by, pull request reviews, pull request review requestsnotes를 가져 올 때 사용됩니다. 이는 타임라인 이벤트 엔드포인트가 그러한 정보를 포함하고 있기 때문에 가능합니다.

타임라인 이벤트 엔드포인트를 사용하여 pull request review requests를 가져 오기 위해 이벤트를 순차적으로 처리해야 합니다. 가져오기 worker는 보장된 순서로 실행되지 않기 때문에 pull request review requests 이벤트는 초기에 Redis 정렬 목록에 배치됩니다. 이후에 Gitlab::GithubImport::ReplayEventsWorker에 의해 순차적으로 사용됩니다.

참고: github_import_extended_events 기능 플래그가 활성화되어있는 경우이 단계는 필수적입니다. 그렇지 않으면이 단계는 선택 사항이며 import options을 사용하여 실행할 수 있습니다.

11. 단계::ImportNotesWorker (deprecated)

이 worker는 이슈 및 풀 리퀘스트에 대한 일반적인 코멘트를 가져옵니다. 각 코멘트마다 Gitlab::GithubImport::ImportNoteWorker worker를 스케줄합니다.

일반 코멘트는 GitHub API가 이슈 및 풀 리퀘스트의 코멘트를 반환하기 때문에 별도로 가져와야 합니다. 이는 일반 코멘트를 가져오기 전에 모든 이슈 및 풀 리퀘스트가 가져와질 때까지 기다려야 한다는 것을 의미합니다.

참고: 이 단계는 github_import_extended_events 기능 플래그가 활성화되면 노트가 10. Stage::ImportIssueEventsWorker 단계에서 가져와지기 때문에 건너뜁니다. 이 단계는 기능 플래그와 함께 제거될 것입니다.

12. 단계::ImportAttachmentsWorker

이 worker는 마크다운 내부에 연결된 노트 첨부 파일을 가져옵니다. 프로젝트 내의 각 엔티티에 대해 다음의 작업을 스케줄합니다:

  • 모든 릴리즈에 대해 Gitlab::GithubImport::Importer::Attachments::ReleasesImporter
  • 모든 노트에 대해 Gitlab::GithubImport::Importer::Attachments::NotesImporter
  • 모든 이슈에 대해 Gitlab::GithubImport::Importer::Attachments::IssuesImporter
  • 모든 병합 요청에 대해 Gitlab::GithubImport::Importer::Attachments::MergeRequestsImporter

각 작업:

  1. 특정 레코드 내의 모든 첨부 파일 링크를 반복합니다.
  2. 첨부 파일을 다운로드합니다.
  3. 이전 링크를 GitLab의 새로 생성된 링크로 대체합니다.

참고: 이는 Gitlab::GithubImport::Settings에 의해 제어되는 선택적인 단계로 상당한 추가 가져오기 시간을 소비할 수 있습니다.

13. 단계::ImportProtectedBranchesWorker

이 worker는 보호된 브랜치 규칙을 가져옵니다. GitHub에 존재하는 모든 규칙에 대해 Gitlab::GithubImport::ImportProtectedBranchWorker 작업을 스케줄합니다.

각 작업은 GitHub와 GitLab의 브랜치 보호 규칙을 비교하고 가장 엄격한 규칙을 GitLab의 브랜치에 적용합니다.

14. 단계::FinishImportWorker

이 worker는 일부 하우스키퍼(캐시 삭제 등)를 수행하고 가져오기를 완료로 표시하여 가져오기 프로세스를 완료합니다.

단계 진행

단계를 진행하는 방법은 다음 중 하나로 수행됩니다:

  • 다음 단계용 worker를 직접 스케줄합니다.
  • 모든 현재 단계의 작업이 완료됐을 때 Gitlab::GithubImport::AdvanceStageWorker 작업을 스케줄하는 방법입니다.

첫 번째 접근 방식은 모든 작업을 단일 스레드에서 수행하는 worker에만 사용되어야 하며, 나머지에는 AdvanceStageWorker를 사용해야 합니다.

첫 번째 접근의 예는 ImportBaseDataWorkerPullRequestWorker직접 호출하는 방식입니다.

두 번째 접근의 예는 PullRequestsWorker완료되었을 때 AdvanceStageWorker를 호출하는 방식입니다.

작업을 스케줄하면 AdvanceStageWorker에 프로젝트 ID, Redis 키 목록 및 다음 단계의 이름이 제공됩니다. (Gitlab::JobWaiter에 의해 생성된 것들) 이들 Redis 키는 실행 중인 단계가 완료됐는지 여부를 확인하는 데 사용됩니다. 단계가 아직 완료되지 않았으면 AdvanceStageWorker는 자신을 재스케줄합니다. 단계가 완료되면 또는 마지막으로 호출된 이후 더 많은 작업이 완료되면 AdvanceStageworker는 가져오기 JID를 새로 고치고 다음 단계의 worker를 스케줄합니다.

AdvanceStageWorker 작업을 스케줄하는 수를 줄이기 위해 이 worker는 다음 행동을 결정하기 전에 잠시 작업이 완료될 때까지 기다립니다. 작은 프로젝트의 경우, 이는 가져오기 프로세스를 약간 늦출 수 있지만, 전체 시스템에 가해지는 압력을 줄일 수 있습니다.

가져오기 작업 ID 새로 고치기

GitLab에는 주기적으로 실행되어 프로젝트 가져오기가 24시간 이상 새로 고치지 않은 경우에 실패로 표시하는 Gitlab::Import::StuckProjectImportJobsWorker라는 worker가 포함되어 있습니다. GitHub 프로젝트의 경우, 별도의 문제가 발생할 수 있습니다. 큰 프로젝트를 가져오는 경우 GitHub 요율 제한에 얼마나 자주 우리가 부딪히는지에 따라 가져오기에 몇 일이 걸릴 수 있지만, 우리는 이러한 이유로 가져오기가 실패로 표시되지 않았으면 합니다.

이를 방지하기 위해 가져오기의 만료 시간을 주기적으로 새로 고칩니다. 이를 위해 가져오기 작업의 JID를 데이터베이스에 저장한 다음, 가져오기 프로세스 동안 여러 단계에서 이 JID TTL을 새로 고칩니다. 이는 ProjectImportState#refresh_jid_expiration를 호출하거나 현재 worker의 JID를 사용하여 RefreshImportJidWorker를 사용하는 방식으로 수행됩니다. 이 TTL을 새로 고침으로써 우리는 여전히 작업을 수행하는 한 가져오기가 실패로 표시되지 않도록 할 수 있습니다.

GitHub 요윤 (Rate Limit)

GitHub의 API 호출 한도는 1시간에 5,000회입니다. 프로젝트를 가져오기 위해 필요한 요청 수는 대부분 프로젝트에 참여한 고유 사용자 수(예: 이슈 작성자)에 의해 크게 좌우됩니다. 왜냐하면 우리는 사용자의 이메일 주소가 필요하여 해당 이메일을 GitLab 사용자에 매핑해야하기 때문입니다. 이슈 페이지 및 댓글과 같은 다른 데이터는 일반적으로 가져오기 위해 수십 개의 요청만 필요합니다.

우리는 다음과 같은 방법으로 요율 한도를 처리합니다.

  1. 요율 한도에 도달하면 자동으로 작업을 재스케줄하여 요율이 재설정될 때까지 실행되지 않도록합니다.
  2. GitHub 사용자를 GitLab 사용자로 매핑하는 캐싱을 Redis에 저장합니다.

사용자 캐싱에 대한 자세한 정보는 아래에서 찾을 수 있습니다.

사용자 조회 캐싱 (Caching user lookups)

GitHub 사용자를 GitLab 사용자로 매핑하는 경우 (최악의 경우) 다음을 수행해야합니다.

  1. 사용자의 이메일 주소를 가져 오기 위해 1회의 API 호출을 실행합니다.
  2. GitHub 사용자 ID를 기반으로 사용자를 찾는 데이터베이스 쿼리 2회를 수행합니다. 하나의 쿼리는 GitHub 사용자 ID를 기반으로 사용자를 찾으려고 시도하고, 두 번째 쿼리는 그들의 GitHub 이메일 주소를 사용하여 사용자를 찾는 데 사용됩니다.

사용자 불일치를 피하기 위해 GitHub 사용자 ID에 의한 검색은 GitHub Enterprise에서 가져올 때 수행되지 않습니다.

이 프로세스가 비용이 많이 들기 때문에 이러한 조회 결과를 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 중단 증가 (Increasing Sidekiq interrupts)

Sidekiq 프로세스가 중단될 때 실행 중인 작업이 종료되기를 기다린 후 중단됩니다. 중단은 작업을 종료시키고 다시 대기열에 넣습니다. 우리의 vendored sidekiq-reliable-fetcher gem는 작업이 영구적으로 종료되기전에 3회의 중단으로 제한합니다. 중단된 작업은 Kibana에 json.interrupted_count를 기록합니다.

이 한도는 Sidekiq 재시작 사이에 완료될 수 없는 작업으로부터 보호를 제공합니다.

대규모 가져오기에서 우리의 GitHub stage 작업자들 ( Stage::에 네임스페이스를 사용하는)은 완료되기까지 여러 시간이 소요됩니다. 기본적으로 sidekiq-reliable-fetcher는 이 작업이 완료되기 전에 영구적으로 중단될 우려가 있으므로 가져오기가 실패할 수 있습니다.

재시작 시 작업을 완전히 계속하는 단계 작업자는 .resumes_work_when_interrupted!sidekiq-reliable-fetcher의 중단 한도를 20으로 증가시킬 수 있습니다.

module Gitlab
  module GithubImport
    module Stage
      class MyWorker
        resumes_work_when_interrupted!

        # ...
      end
    end
  end
end

재시작 시 작업을 완전히 계속하지 않는 단계 작업자는 이 메소드를 호출해서는 안됩니다. 예를 들어 이미 가져온 객체를 건너뛰고 각 번으로부터 루프를 시작하는 작업자 등입니다.

작업을 완전히 재개하는 단계 작업자의 예는 다음과 같습니다.

sidekiq_options dead: false

일반적으로 worker의 재시도가 고갈되면 Sidekiq dead set로 이동하여 관리자에 의해 다시 시도될 수 있습니다.

GithubImport::Queue는 GitHub 가져오기 worker에 대한 이러한 작업을 방지하기 위해 Sidekiq worker 옵션 dead: false를 설정합니다.

이유는 다음과 같습니다. - dead set에는 최대 리미트가 있으며, 객체 가져오기 worker(여기에는 ObjectImporter가 포함된 worker)가 대규모로 실패하면 dead set을 스팸처럼 사용하여 다른 worker를 밀어낼 수 있습니다. - Stage worker(여기에는 StageMethods가 포함된 worker)는 재시도가 고갈된 경우 가져오기에 실패하므로, 재시도는 작동하지 않을 것이라는 것이 보장됩니다.

라벨 및 마일스톤 매핑

데이터베이스에 압력을 줄이기 위해 이슈와 병합 요청에 라벨 및 마일스톤을 설정할 때 쿼리하지 않습니다. 대신, 라벨 및 마일스톤을 가져오고 나중에 이것을 재사용하기 위해 데이터를 캐시에 저장합니다. 사용자 조회와 마찬가지로 이러한 캐시 키는 사용되지 않은 경우 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"로 로깅됩니다.

마지막 로그 항목에서 가져온 객체 수가 보고됩니다. json { "message": "GitHub project import finished", "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 가져오기 대시보드를 사용하여 시간 경과에 따른 총 가져오기와 가져온 객체 수에 대한 정보를 제공합니다.