InfoGrab DocsInfoGrab Docs

GitHub 임포터 개발자 문서

요약

GitHub 임포터는 Sidekiq을 사용하는 병렬 임포터입니다. github_importer 및 github_importer_advance_stage 큐를 처리하는 Sidekiq 워커(기본적으로 활성화됨). Octokit(GitHub API와 상호작용하는 데 사용됨).

GitHub 임포터는 Sidekiq을 사용하는 병렬 임포터입니다.

전제 조건#

  • github_importergithub_importer_advance_stage 큐를 처리하는 Sidekiq 워커(기본적으로 활성화됨).

  • Octokit(GitHub API와 상호작용하는 데 사용됨).

코드 구조#

임포터 코드베이스는 다음 디렉터리로 나뉩니다:

  • lib/gitlab/github_import: 리소스 임포트에 사용되는 클래스 등 대부분의 코드가 포함된 디렉터리.

  • app/workers/gitlab/github_import: Sidekiq 워커가 포함된 디렉터리.

  • app/workers/concerns/gitlab/github_import: 다양한 Sidekiq 워커에서 재사용되는 모듈 몇 가지가 포함된 디렉터리.

아키텍처 개요#

GitHub 프로젝트를 임포트할 때 작업은 별도의 단계(Stage)로 나뉩니다. 각 단계는 실행되는 Sidekiq job 집합으로 구성됩니다. 모든 단계 사이에는 현재 단계의 모든 작업이 완료되었는지 주기적으로 확인하는 job이 예약되며, 완료된 경우 다음 단계로 임포트 프로세스를 진행합니다. 이를 처리하는 워커는 Gitlab::GithubImport::AdvanceStageWorker입니다.

단계(Stages)#

1. RepositoryImportWorker#

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

이 맥락에서 importerGitlab::ImportSources.importer(project.import_type)의 인스턴스이며, github 임포트 타입의 경우 ParallelImporter에 매핑됩니다.

ParallelImporter는 다음 워커의 job을 예약합니다.

2. Stage::ImportRepositoryWorker#

이 워커는 리포지터리와 위키를 임포트하고, 완료되면 다음 단계를 예약합니다.

3. Stage::ImportBaseDataWorker#

이 워커는 라벨, 마일스톤, 릴리스 등의 기본 데이터를 임포트합니다. 이 작업은 충분히 빠르게 수행될 수 있으므로 병렬로 처리할 필요 없이 단일 스레드에서 수행됩니다.

4. Stage::ImportPullRequestsWorker#

이 워커는 모든 Pull Request를 임포트합니다. 각 Pull Request에 대해 Gitlab::GithubImport::ImportPullRequestWorker 워커의 job이 예약됩니다.

5. Stage::ImportCollaboratorsWorker#

이 워커는 외부 협력자가 아닌 직접 리포지터리 협력자만 임포트합니다. 각 협력자에 대해 Gitlab::GithubImport::ImportCollaboratorWorker 워커의 job을 예약합니다.

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

6. Stage::ImportIssuesAndDiffNotesWorker#

이 워커는 모든 이슈와 Pull Request 댓글을 임포트합니다. 각 이슈에 대해 Gitlab::GithubImport::ImportIssueWorker 워커의 job을 예약합니다. Pull Request 댓글의 경우 Gitlab::GithubImport::DiffNoteImporter 워커의 job을 예약합니다.

이 워커는 이슈와 diff 노트를 병렬로 처리하므로 별도의 단계를 예약하고 이전 단계가 완료될 때까지 기다릴 필요가 없습니다.

이슈는 Pull Request와 별도로 임포트됩니다. 이슈와 Pull Request 모두에 대한 라벨을 포함하는 API는 "이슈" API뿐이기 때문입니다. 동일한 워커에서 이슈를 임포트하고 라벨 링크를 설정하면 API 데이터를 별도로 크롤링할 필요가 없어져 프로젝트 임포트에 필요한 API 호출 횟수가 줄어듭니다.

7. Stage::ImportIssueEventsWorker#

이 워커는 모든 이슈와 Pull Request 이벤트를 임포트합니다. 각 이벤트에 대해 Gitlab::GithubImport::ImportIssueEventWorker 워커의 job을 예약합니다.

GitHub API의 특정 측면 때문에 이슈와 Pull Request 이벤트를 단일 단계에서 임포트할 수 있습니다. 내부적으로 GitHub에서 이슈와 Pull Request는 단일 테이블에 저장되는 것으로 보입니다. 따라서 전역적으로 고유한 ID를 가지며 다음이 적용됩니다:

  • 모든 Pull Request는 이슈입니다.

  • 이슈는 Pull Request가 아닙니다.

따라서 이슈와 Pull Request 모두 대부분의 관련 사항에 대한 공통 API를 가집니다.

타임라인 이벤트 엔드포인트를 사용한 pull request review requests 임포트를 지원하기 위해 이벤트는 순차적으로 처리되어야 합니다. 임포트 워커는 보장된 순서로 실행되지 않으므로 pull request review requests 이벤트는 처음에 Redis 정렬 목록에 배치됩니다. 이후 Gitlab::GithubImport::ReplayEventsWorker에 의해 순서대로 소비됩니다.

8. Stage::ImportAttachmentsWorker#

이 워커는 Markdown 내부에 링크된 노트 첨부 파일을 임포트합니다. 프로젝트에서 Markdown 텍스트가 있는 각 엔티티에 대해 다음 job을 예약합니다:

  • 각 릴리스에 대해 Gitlab::GithubImport::Importer::Attachments::ReleasesImporter.

  • 각 노트에 대해 Gitlab::GithubImport::Importer::Attachments::NotesImporter.

  • 각 이슈에 대해 Gitlab::GithubImport::Importer::Attachments::IssuesImporter.

  • 각 머지 리퀘스트에 대해 Gitlab::GithubImport::Importer::Attachments::MergeRequestsImporter.

각 job은 다음을 수행합니다:

  • 특정 레코드 내부의 모든 첨부 파일 링크를 순회합니다.

  • 첨부 파일을 다운로드합니다.

  • 이전 링크를 GitLab에 새로 생성된 링크로 교체합니다.

이 단계는 선택 사항으로 상당한 추가 임포트 시간을 소비할 수 있습니다(Gitlab::GithubImport::Settings로 제어).

9. Stage::ImportProtectedBranchesWorker#

이 워커는 보호된 브랜치 규칙을 임포트합니다. GitHub에 존재하는 각 규칙에 대해 Gitlab::GithubImport::ImportProtectedBranchWorker의 job을 예약합니다.

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

10. Stage::FinishImportWorker#

이 워커는 일부 하우스키핑(예: 캐시 플러시)을 수행하고 임포트를 완료로 표시하여 임포트 프로세스를 마무리합니다.

단계 진행#

단계 진행은 두 가지 방법 중 하나로 이루어집니다:

  • 다음 단계의 워커를 직접 예약합니다.

  • 현재 단계의 모든 작업이 완료되었을 때 단계를 진행하는 Gitlab::GithubImport::AdvanceStageWorker의 job을 예약합니다.

첫 번째 방법은 단일 스레드에서 모든 작업을 수행하는 워커에서만 사용해야 하며, AdvanceStageWorker는 그 외 모든 경우에 사용해야 합니다.

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

두 번째 방법의 예시는 PullRequestsWorker가 자체 작업이 완료되었을 때 AdvanceStageWorker를 호출하는 방식입니다.

job을 예약할 때 AdvanceStageWorker에는 프로젝트 ID, Redis 키 목록, 다음 단계 이름이 제공됩니다. Redis 키(Gitlab::JobWaiter가 생성)는 실행 중인 단계가 완료되었는지 여부를 확인하는 데 사용됩니다. 단계가 아직 완료되지 않은 경우 AdvanceStageWorker는 자신을 재예약합니다. 단계가 완료되거나 마지막 호출 이후 더 많은 job이 완료된 경우 AdvanceStageWorker는 임포트 JID를 갱신하고(아래에서 자세히 설명) 다음 단계의 워커를 예약합니다.

AdvanceStageWorker job 수를 줄이기 위해 이 워커는 다음 액션을 결정하기 전에 job이 완료될 때까지 잠시 기다립니다. 소규모 프로젝트의 경우 임포트 프로세스가 약간 느려질 수 있지만, 전체 시스템에 대한 부하도 줄어듭니다.

사용자 기여 매핑#

GitHub 임포터는 사용자 기여 매핑을 지원합니다. 이를 통해 임포트된 레코드를 플레이스홀더 사용자에게 귀속시키고, 임포트 완료 후 실제 사용자를 할당할 수 있습니다.

핵심 클래스#

클래스 목적
GithubImport::UserFinder GitHub API 응답에서 사용자 데이터를 GitLab User 레코드에 매핑하기 위해 Import::SourceUserMapper를 호출하는 클래스
GithubImport::Importer::CollaboratorImporter 소스 사용자에 대한 플레이스홀더 멤버십을 생성하기 위해 PlaceholderMemberships::CreateService를 호출

삭제된 사용자 및 고스트 사용자 처리#

GitHub에는 삭제된 사용자를 나타내는 특별한 "고스트(ghost)" 사용자(사용자 이름: ghost)가 있습니다. 임포터가 이 사용자를 만나면 Import::SourceUser 또는 플레이스홀더 사용자를 생성하지 않고 직접 GitLab 고스트 사용자에게 매핑합니다.

임포트 job ID 갱신#

GitLab에는 Gitlab::Import::StuckProjectImportJobsWorker라는 워커가 있어 주기적으로 실행되며, 24시간 이상 갱신되지 않은 프로젝트 임포트를 실패로 표시합니다. GitHub 프로젝트의 경우 이것이 약간의 문제가 됩니다. 대규모 프로젝트 임포트는 GitHub 속도 제한에 얼마나 자주 도달하느냐에 따라 며칠이 걸릴 수 있지만(아래에서 자세히 설명), Gitlab::Import::StuckProjectImportJobsWorker가 임포트를 실패로 표시하는 것을 원하지 않습니다.

이를 방지하기 위해 임포트의 만료 시간을 주기적으로 갱신합니다. 이는 임포트 job의 JID를 데이터베이스에 저장한 다음, 임포트 프로세스의 다양한 단계에서 이 JID TTL을 갱신하는 방식으로 작동합니다. 이는 ProjectImportState#refresh_jid_expiration을 호출하거나 RefreshImportJidWorker를 사용하여 현재 워커의 jid를 전달하는 방식으로 수행됩니다. 이 TTL을 갱신함으로써 작업을 수행하는 동안 임포트가 실패로 표시되지 않도록 할 수 있습니다.

GitHub 속도 제한#

GitHub에는 시간당 5,000건의 API 호출 속도 제한이 있습니다. 프로젝트 임포트에 필요한 요청 수는 주로 프로젝트에 관여한 고유 사용자 수(예: 이슈 작성자)에 의해 결정됩니다. GitLab 사용자에게 매핑하기 위해 사용자의 이메일 주소가 필요하기 때문입니다. 이슈 페이지 및 댓글 등 기타 데이터는 임포트에 일반적으로 수십 건의 요청만 필요합니다.

속도 제한은 다음과 같이 처리합니다:

  • 속도 제한에 도달하면 속도 제한이 재설정될 때까지 실행되지 않도록 job을 자동으로 재예약합니다.

  • 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와 함께 조건부 요청(Conditional Request)이 수행됩니다. 조건부 요청은 GitHub API 속도 제한에 포함되지 않습니다.

이 캐싱 레이어로 인해 새로 등록된 GitLab 계정이 해당 GitHub 계정과 연결되지 않을 수 있습니다. 그러나 이는 캐시된 키가 만료되거나 새 프로젝트를 임포트하면 해결됩니다.

사용자 캐시 조회는 프로젝트 간에 공유됩니다. 즉, 임포트되는 프로젝트 수가 많을수록 필요한 GitHub API 호출이 줄어듭니다.

이 코드는 다음에 있습니다:

  • lib/gitlab/github_import/user_finder.rb

  • lib/gitlab/github_import/caching.rb

Sidekiq 인터럽트 증가#

Sidekiq 프로세스가 종료될 때 실행 중인 job이 완료될 때까지 일정 시간 기다린 후 인터럽트합니다. 인터럽트는 job을 종료하고 다시 큐에 넣습니다. 벤더 포함 sidekiq-reliable-fetcher gem은 job이 더 이상 큐에 들어가지 않고 영구적으로 종료되기 전까지 3회의 인터럽트 제한을 설정합니다. 인터럽트된 job은 Kibana에서 json.interrupted_count를 로깅합니다.

이 제한은 Sidekiq 재시작 사이의 시간 안에 완료할 수 없는 job으로부터 보호합니다.

대규모 임포트의 경우 GitHub 단계 워커(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 set)으로 이동하며 인스턴스 관리자가 재시도할 수 있습니다.

GithubImport::Queue는 GitHub 임포터 워커에 이런 일이 발생하지 않도록 Sidekiq 워커 옵션 dead: false를 설정합니다.

그 이유는 다음과 같습니다:

  • 데드 셋에는 최대 제한이 있으며, 객체 임포터 워커(ObjectImporter를 포함하는 워커)가 대량으로 실패하면 데드 셋을 가득 채워 다른 워커를 밀어낼 수 있습니다.

  • Stage 워커(StageMethods를 포함하는 워커)는 재시도가 소진되면 임포트를 실패시키므로, 재시도는 항상 no-op이 됩니다.

라벨 및 마일스톤 매핑#

데이터베이스 부하를 줄이기 위해 이슈와 머지 리퀘스트에 라벨과 마일스톤을 설정할 때 데이터베이스를 쿼리하지 않습니다. 대신 라벨과 마일스톤을 임포트할 때 이 데이터를 캐시하고, 이슈/머지 리퀘스트에 할당할 때 이 캐시를 재사용합니다. 사용자 조회와 마찬가지로 이 캐시 키는 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 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 임포터 대시보드에서 시간에 따라 가져온 객체 수 대비 임포트된 객체 수에 대한 정보를 제공합니다.

GitHub 임포터 개발자 문서

GitLab v19.1
원문 보기
요약

GitHub 임포터는 Sidekiq을 사용하는 병렬 임포터입니다. github_importer 및 github_importer_advance_stage 큐를 처리하는 Sidekiq 워커(기본적으로 활성화됨). Octokit(GitHub API와 상호작용하는 데 사용됨).

GitHub 임포터는 Sidekiq을 사용하는 병렬 임포터입니다.

전제 조건#

  • github_importergithub_importer_advance_stage 큐를 처리하는 Sidekiq 워커(기본적으로 활성화됨).

  • Octokit(GitHub API와 상호작용하는 데 사용됨).

코드 구조#

임포터 코드베이스는 다음 디렉터리로 나뉩니다:

  • lib/gitlab/github_import: 리소스 임포트에 사용되는 클래스 등 대부분의 코드가 포함된 디렉터리.

  • app/workers/gitlab/github_import: Sidekiq 워커가 포함된 디렉터리.

  • app/workers/concerns/gitlab/github_import: 다양한 Sidekiq 워커에서 재사용되는 모듈 몇 가지가 포함된 디렉터리.

아키텍처 개요#

GitHub 프로젝트를 임포트할 때 작업은 별도의 단계(Stage)로 나뉩니다. 각 단계는 실행되는 Sidekiq job 집합으로 구성됩니다. 모든 단계 사이에는 현재 단계의 모든 작업이 완료되었는지 주기적으로 확인하는 job이 예약되며, 완료된 경우 다음 단계로 임포트 프로세스를 진행합니다. 이를 처리하는 워커는 Gitlab::GithubImport::AdvanceStageWorker입니다.

단계(Stages)#

1. RepositoryImportWorker#

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

이 맥락에서 importerGitlab::ImportSources.importer(project.import_type)의 인스턴스이며, github 임포트 타입의 경우 ParallelImporter에 매핑됩니다.

ParallelImporter는 다음 워커의 job을 예약합니다.

2. Stage::ImportRepositoryWorker#

이 워커는 리포지터리와 위키를 임포트하고, 완료되면 다음 단계를 예약합니다.

3. Stage::ImportBaseDataWorker#

이 워커는 라벨, 마일스톤, 릴리스 등의 기본 데이터를 임포트합니다. 이 작업은 충분히 빠르게 수행될 수 있으므로 병렬로 처리할 필요 없이 단일 스레드에서 수행됩니다.

4. Stage::ImportPullRequestsWorker#

이 워커는 모든 Pull Request를 임포트합니다. 각 Pull Request에 대해 Gitlab::GithubImport::ImportPullRequestWorker 워커의 job이 예약됩니다.

5. Stage::ImportCollaboratorsWorker#

이 워커는 외부 협력자가 아닌 직접 리포지터리 협력자만 임포트합니다. 각 협력자에 대해 Gitlab::GithubImport::ImportCollaboratorWorker 워커의 job을 예약합니다.

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

6. Stage::ImportIssuesAndDiffNotesWorker#

이 워커는 모든 이슈와 Pull Request 댓글을 임포트합니다. 각 이슈에 대해 Gitlab::GithubImport::ImportIssueWorker 워커의 job을 예약합니다. Pull Request 댓글의 경우 Gitlab::GithubImport::DiffNoteImporter 워커의 job을 예약합니다.

이 워커는 이슈와 diff 노트를 병렬로 처리하므로 별도의 단계를 예약하고 이전 단계가 완료될 때까지 기다릴 필요가 없습니다.

이슈는 Pull Request와 별도로 임포트됩니다. 이슈와 Pull Request 모두에 대한 라벨을 포함하는 API는 "이슈" API뿐이기 때문입니다. 동일한 워커에서 이슈를 임포트하고 라벨 링크를 설정하면 API 데이터를 별도로 크롤링할 필요가 없어져 프로젝트 임포트에 필요한 API 호출 횟수가 줄어듭니다.

7. Stage::ImportIssueEventsWorker#

이 워커는 모든 이슈와 Pull Request 이벤트를 임포트합니다. 각 이벤트에 대해 Gitlab::GithubImport::ImportIssueEventWorker 워커의 job을 예약합니다.

GitHub API의 특정 측면 때문에 이슈와 Pull Request 이벤트를 단일 단계에서 임포트할 수 있습니다. 내부적으로 GitHub에서 이슈와 Pull Request는 단일 테이블에 저장되는 것으로 보입니다. 따라서 전역적으로 고유한 ID를 가지며 다음이 적용됩니다:

  • 모든 Pull Request는 이슈입니다.

  • 이슈는 Pull Request가 아닙니다.

따라서 이슈와 Pull Request 모두 대부분의 관련 사항에 대한 공통 API를 가집니다.

타임라인 이벤트 엔드포인트를 사용한 pull request review requests 임포트를 지원하기 위해 이벤트는 순차적으로 처리되어야 합니다. 임포트 워커는 보장된 순서로 실행되지 않으므로 pull request review requests 이벤트는 처음에 Redis 정렬 목록에 배치됩니다. 이후 Gitlab::GithubImport::ReplayEventsWorker에 의해 순서대로 소비됩니다.

8. Stage::ImportAttachmentsWorker#

이 워커는 Markdown 내부에 링크된 노트 첨부 파일을 임포트합니다. 프로젝트에서 Markdown 텍스트가 있는 각 엔티티에 대해 다음 job을 예약합니다:

  • 각 릴리스에 대해 Gitlab::GithubImport::Importer::Attachments::ReleasesImporter.

  • 각 노트에 대해 Gitlab::GithubImport::Importer::Attachments::NotesImporter.

  • 각 이슈에 대해 Gitlab::GithubImport::Importer::Attachments::IssuesImporter.

  • 각 머지 리퀘스트에 대해 Gitlab::GithubImport::Importer::Attachments::MergeRequestsImporter.

각 job은 다음을 수행합니다:

  • 특정 레코드 내부의 모든 첨부 파일 링크를 순회합니다.

  • 첨부 파일을 다운로드합니다.

  • 이전 링크를 GitLab에 새로 생성된 링크로 교체합니다.

이 단계는 선택 사항으로 상당한 추가 임포트 시간을 소비할 수 있습니다(Gitlab::GithubImport::Settings로 제어).

9. Stage::ImportProtectedBranchesWorker#

이 워커는 보호된 브랜치 규칙을 임포트합니다. GitHub에 존재하는 각 규칙에 대해 Gitlab::GithubImport::ImportProtectedBranchWorker의 job을 예약합니다.

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

10. Stage::FinishImportWorker#

이 워커는 일부 하우스키핑(예: 캐시 플러시)을 수행하고 임포트를 완료로 표시하여 임포트 프로세스를 마무리합니다.

단계 진행#

단계 진행은 두 가지 방법 중 하나로 이루어집니다:

  • 다음 단계의 워커를 직접 예약합니다.

  • 현재 단계의 모든 작업이 완료되었을 때 단계를 진행하는 Gitlab::GithubImport::AdvanceStageWorker의 job을 예약합니다.

첫 번째 방법은 단일 스레드에서 모든 작업을 수행하는 워커에서만 사용해야 하며, AdvanceStageWorker는 그 외 모든 경우에 사용해야 합니다.

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

두 번째 방법의 예시는 PullRequestsWorker가 자체 작업이 완료되었을 때 AdvanceStageWorker를 호출하는 방식입니다.

job을 예약할 때 AdvanceStageWorker에는 프로젝트 ID, Redis 키 목록, 다음 단계 이름이 제공됩니다. Redis 키(Gitlab::JobWaiter가 생성)는 실행 중인 단계가 완료되었는지 여부를 확인하는 데 사용됩니다. 단계가 아직 완료되지 않은 경우 AdvanceStageWorker는 자신을 재예약합니다. 단계가 완료되거나 마지막 호출 이후 더 많은 job이 완료된 경우 AdvanceStageWorker는 임포트 JID를 갱신하고(아래에서 자세히 설명) 다음 단계의 워커를 예약합니다.

AdvanceStageWorker job 수를 줄이기 위해 이 워커는 다음 액션을 결정하기 전에 job이 완료될 때까지 잠시 기다립니다. 소규모 프로젝트의 경우 임포트 프로세스가 약간 느려질 수 있지만, 전체 시스템에 대한 부하도 줄어듭니다.

사용자 기여 매핑#

GitHub 임포터는 사용자 기여 매핑을 지원합니다. 이를 통해 임포트된 레코드를 플레이스홀더 사용자에게 귀속시키고, 임포트 완료 후 실제 사용자를 할당할 수 있습니다.

핵심 클래스#

클래스 목적
GithubImport::UserFinder GitHub API 응답에서 사용자 데이터를 GitLab User 레코드에 매핑하기 위해 Import::SourceUserMapper를 호출하는 클래스
GithubImport::Importer::CollaboratorImporter 소스 사용자에 대한 플레이스홀더 멤버십을 생성하기 위해 PlaceholderMemberships::CreateService를 호출

삭제된 사용자 및 고스트 사용자 처리#

GitHub에는 삭제된 사용자를 나타내는 특별한 "고스트(ghost)" 사용자(사용자 이름: ghost)가 있습니다. 임포터가 이 사용자를 만나면 Import::SourceUser 또는 플레이스홀더 사용자를 생성하지 않고 직접 GitLab 고스트 사용자에게 매핑합니다.

임포트 job ID 갱신#

GitLab에는 Gitlab::Import::StuckProjectImportJobsWorker라는 워커가 있어 주기적으로 실행되며, 24시간 이상 갱신되지 않은 프로젝트 임포트를 실패로 표시합니다. GitHub 프로젝트의 경우 이것이 약간의 문제가 됩니다. 대규모 프로젝트 임포트는 GitHub 속도 제한에 얼마나 자주 도달하느냐에 따라 며칠이 걸릴 수 있지만(아래에서 자세히 설명), Gitlab::Import::StuckProjectImportJobsWorker가 임포트를 실패로 표시하는 것을 원하지 않습니다.

이를 방지하기 위해 임포트의 만료 시간을 주기적으로 갱신합니다. 이는 임포트 job의 JID를 데이터베이스에 저장한 다음, 임포트 프로세스의 다양한 단계에서 이 JID TTL을 갱신하는 방식으로 작동합니다. 이는 ProjectImportState#refresh_jid_expiration을 호출하거나 RefreshImportJidWorker를 사용하여 현재 워커의 jid를 전달하는 방식으로 수행됩니다. 이 TTL을 갱신함으로써 작업을 수행하는 동안 임포트가 실패로 표시되지 않도록 할 수 있습니다.

GitHub 속도 제한#

GitHub에는 시간당 5,000건의 API 호출 속도 제한이 있습니다. 프로젝트 임포트에 필요한 요청 수는 주로 프로젝트에 관여한 고유 사용자 수(예: 이슈 작성자)에 의해 결정됩니다. GitLab 사용자에게 매핑하기 위해 사용자의 이메일 주소가 필요하기 때문입니다. 이슈 페이지 및 댓글 등 기타 데이터는 임포트에 일반적으로 수십 건의 요청만 필요합니다.

속도 제한은 다음과 같이 처리합니다:

  • 속도 제한에 도달하면 속도 제한이 재설정될 때까지 실행되지 않도록 job을 자동으로 재예약합니다.

  • 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와 함께 조건부 요청(Conditional Request)이 수행됩니다. 조건부 요청은 GitHub API 속도 제한에 포함되지 않습니다.

이 캐싱 레이어로 인해 새로 등록된 GitLab 계정이 해당 GitHub 계정과 연결되지 않을 수 있습니다. 그러나 이는 캐시된 키가 만료되거나 새 프로젝트를 임포트하면 해결됩니다.

사용자 캐시 조회는 프로젝트 간에 공유됩니다. 즉, 임포트되는 프로젝트 수가 많을수록 필요한 GitHub API 호출이 줄어듭니다.

이 코드는 다음에 있습니다:

  • lib/gitlab/github_import/user_finder.rb

  • lib/gitlab/github_import/caching.rb

Sidekiq 인터럽트 증가#

Sidekiq 프로세스가 종료될 때 실행 중인 job이 완료될 때까지 일정 시간 기다린 후 인터럽트합니다. 인터럽트는 job을 종료하고 다시 큐에 넣습니다. 벤더 포함 sidekiq-reliable-fetcher gem은 job이 더 이상 큐에 들어가지 않고 영구적으로 종료되기 전까지 3회의 인터럽트 제한을 설정합니다. 인터럽트된 job은 Kibana에서 json.interrupted_count를 로깅합니다.

이 제한은 Sidekiq 재시작 사이의 시간 안에 완료할 수 없는 job으로부터 보호합니다.

대규모 임포트의 경우 GitHub 단계 워커(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 set)으로 이동하며 인스턴스 관리자가 재시도할 수 있습니다.

GithubImport::Queue는 GitHub 임포터 워커에 이런 일이 발생하지 않도록 Sidekiq 워커 옵션 dead: false를 설정합니다.

그 이유는 다음과 같습니다:

  • 데드 셋에는 최대 제한이 있으며, 객체 임포터 워커(ObjectImporter를 포함하는 워커)가 대량으로 실패하면 데드 셋을 가득 채워 다른 워커를 밀어낼 수 있습니다.

  • Stage 워커(StageMethods를 포함하는 워커)는 재시도가 소진되면 임포트를 실패시키므로, 재시도는 항상 no-op이 됩니다.

라벨 및 마일스톤 매핑#

데이터베이스 부하를 줄이기 위해 이슈와 머지 리퀘스트에 라벨과 마일스톤을 설정할 때 데이터베이스를 쿼리하지 않습니다. 대신 라벨과 마일스톤을 임포트할 때 이 데이터를 캐시하고, 이슈/머지 리퀘스트에 할당할 때 이 캐시를 재사용합니다. 사용자 조회와 마찬가지로 이 캐시 키는 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 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 임포터 대시보드에서 시간에 따라 가져온 객체 수 대비 임포트된 객체 수에 대한 정보를 제공합니다.