GitHub 가져오기 개발자 문서

GitHub 가져오기는 두 가지 다른 유형의 가져오기기를 제공합니다.

  • 순차적 가져오기기(import:github Rake 작업에서 사용)
  • 병렬 가져오기기(나머지에서 사용)

이 두 가져오기기의 차이점은 다음과 같습니다:

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

전제 조건

  • 기본적으로 활성화된 github_importergithub_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라고 합니다.

단계

1. RepositoryImportWorker

이 워커는 Projects::ImportService.new.execute를 호출하여, importer.execute를 호출합니다.

이 문맥에서 importerGitlab::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 워커의 작업이 예약됩니다.

note
이 단계는 선택 사항입니다(Gitlab::GithubImport::Settings로 제어)하며 기본적으로 선택됩니다.

6. Stage::ImportPullRequestsMergedByWorker (deprecated)

이 워커는 풀 리퀘스트의 merged-by 사용자 정보를 가져옵니다. 리스트 풀 리퀘스트 API는 이 정보를 제공하지 않습니다. 따라서 이 단계는 이 정보를 가져오기 위해 각 Merge된 풀 리퀘스트를 개별적으로 가져와야 합니다. 가져온 각 풀 리퀘스트에 대해 Gitlab::GithubImport::PullRequests::ImportMergedByWorker 작업이 예약됩니다.

note
이 단계는 github_import_extended_events 피처 플래그가 활성화되어 있을 때 건너뛰어지며, 풀 리퀘스트의 Merge된 정보는 10. Stage::ImportIssueEventsWorker 단계에서 가져오기 때문에 이 단계는 해당 피처 플래그와 함께 제거될 예정입니다.

7. Stage::ImportPullRequestsReviewRequestsWorker (deprecated)

이 Worker는 pull request의 지정된 리뷰어를 가져옵니다. 각 pull request에 대해, 이 Worker는 다음을 수행합니다:

  • 지정된 모든 리뷰 요청을 가져옵니다.
  • 가져온 각 리뷰 요청에 대해 Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker 작업을 예약합니다.
note
이 stage는 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 작업을 예약합니다.
note
이 stage는 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 requestsnotes를 가져오기 위한 역할을 합니다. 이는 timeline events endpoint 에서도 해당 정보가 포함되어 있기 때문입니다.

timeline events endpoint를 사용하여 pull request review requests를 가져오기 위해 이벤트를 순차적으로 처리해야 합니다. 가져오기 Worker는 보장된 순서로 실행되지 않기 때문에 pull request review requests 이벤트는 처음에 Redis 정렬 디렉터리에 배치됩니다. 그 후 Gitlab::GithubImport::ReplayEventsWorker에 의해 순차적으로 사용되게 됩니다.

note
이 stage는 github_import_extended_events 플래그가 활성화된 경우 필수적입니다. 그렇지 않으면 선택적인 stage이며 import options를 사용하여 실행할 수 있습니다.

11. Stage::ImportNotesWorker (deprecated)

이 Worker는 이슈 및 pull request의 일반 코멘트를 가져옵니다. 각 코멘트에 대해, Gitlab::GithubImport::ImportNoteWorker Worker를 위한 작업을 예약합니다.

일반 코멘트는 GitHub API에서 반환되는 것으로 이슈 및 pull request에 대한 코멘트를 기다리기 위해 모든 이슈와 pull request가 가져오기 완료될 때까지 기다려야 합니다.

note
이 stage는 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 작업을 예약합니다.

각 작업:

  1. 특정 레코드 내의 모든 첨부 파일 링크를 반복합니다.
  2. 첨부 파일을 다운로드합니다.
  3. 이전 링크를 GitLab에 새로 생성된 링크로 교체합니다.
note
이는 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를 사용해야 합니다.

첫 번째 접근 방법의 예는 ImportBaseDataWorkerPullRequestWorker직접적으로 부르는 방식입니다.

두 번째 접근 방법의 예는 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 사용자에 매핑하기 위해 사용자의 이메일 주소가 필요하기 때문입니다. 이외에도 이슈 페이지 및 코멘트와 같은 다른 데이터는 일반적으로 가져오기에 몇십 개의 요청만 필요합니다.

우리는 속도 제한을 다음과 같은 방법으로 처리합니다:

  1. 속도 제한에 걸리면 작업을 자동으로 재예약하여 속도 제한이 재설정될 때까지 실행되지 않도록 합니다.
  2. GitHub 사용자를 GitLab 사용자에 매핑한 내용을 Redis에 캐시합니다.

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

사용자 조회 캐싱

GitHub 사용자를 GitLab 사용자에 매핑할 때 우리는 (최악의 경우) 다음을 수행해야 합니다:

  1. 사용자의 이메일 주소를 가져오는 API 호출 1회.
  2. 해당하는 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

로그

  • GitLab 13.7에서 도입.
  • GitLab 14.1에서 도입된 가져온 객체 수 도입.
  • Gitlab::GithubImport::Logger가 GitLab 14.2에서 도입.
  • import_source가 GitLab 14.2에서 import_type으로 이름이 변경되었습니다.

가져오기 진행 상황은 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. 가져온 객체 수에 대한 정보를 제공합니다.