InfoGrab DocsInfoGrab Docs

Gitaly 개발 가이드라인

요약

Gitaly는 GitLab Rails, Workhorse, GitLab Shell에서 사용하는 고수준 Git RPC 서비스입니다. 2019년 5월, Bob Van Landuyt가 Gitaly 프로젝트에 대한 Deep Dive(GitLab 팀 구성원 전용: https://gitlab.com/gitlab-org/create-stage/-/issues/1)를 진행했습니다.

Gitaly는 GitLab Rails, Workhorse, GitLab Shell에서 사용하는 고수준 Git RPC 서비스입니다.

Deep Dive#

2019년 5월, Bob Van Landuyt가 Gitaly 프로젝트에 대한 Deep Dive(GitLab 팀 구성원 전용: https://gitlab.com/gitlab-org/create-stage/-/issues/1)를 진행했습니다. 여기에는 Ruby 개발자로서 기여하는 방법과 앞으로 이 코드베이스에서 작업하게 될 사람들을 위한 도메인별 지식이 포함되었습니다.

YouTube에서 녹화본을 확인할 수 있으며, 슬라이드는 Google SlidesPDF에서도 확인할 수 있습니다.

이 Deep Dive에서 다룬 모든 내용은 GitLab 11.11 기준으로 정확했으며, 세부 사항이 변경되었을 수 있지만 여전히 좋은 입문 자료로 활용할 수 있습니다.

입문자 가이드#

먼저 Gitaly 리포지터리의 Gitaly 기여를 위한 입문자 가이드를 읽으세요. 이 가이드에는 Gitaly 설정 방법, Gitaly의 다양한 구성 요소와 그 역할, 테스트 스위트 실행 방법이 설명되어 있습니다.

새로운 Git 기능 개발#

Git 데이터를 읽거나 쓰려면 Gitaly에 요청을 보내야 합니다. 즉, lib/gitlab/git에 아직 없는 데이터가 필요한 새로운 기능을 개발하는 경우 Gitaly도 수정해야 합니다.

gitlab 리포지터리 어디에도 디스크 접근을 통해 Git 리포지터리에 직접 접촉하는 새 코드가 있어서는 안 됩니다. Git 리포지터리에 직접 접근이 필요한 모든 것은 Gitaly에서 구현하고 RPC를 통해 노출해야 합니다.

Gitaly에서 새로운 기능을 개발하려면:

  • Gitaly 머지 리퀘스트를 올려 필요한 변경 사항을 반영합니다.

  • Gitaly 변경 사항을 활용하는 GitLab 머지 리퀘스트도 함께 올립니다.

  • 두 프로젝트의 변경 사항이 병합되기 전에, 변경 사항이 포함된 로컬 버전의 Gitaly와 수정된 버전의 GitLab을 함께 실행하여 두 프로젝트의 변경 사항을 로컬에서 테스트합니다.

수정된 버전의 Gitaly로 GitLab 테스트를 실행하는 방법은 아래를 참조하세요.

GDK에서는 gdk install을 실행하고 gdk restart로 GDK를 재시작하면 개발용으로 로컬에서 수정한 Gitaly 버전을 사용할 수 있습니다.

Gitaly 버전 호환성 요구사항#

업그레이드 중에는 새로운 Gitaly 클라이언트 버전과 이전 Gitaly 버전 간의 호환성을 유지해야 합니다. 업그레이드 중에 Gitaly 클라이언트(예: Rails)가 Gitaly보다 먼저 새 버전으로 업그레이드될 수 있습니다. 새로운 Gitaly 클라이언트는 아직 업그레이드되지 않은 이전 Gitaly에서 사용할 수 없는 기능에 의존해서는 안 됩니다.

이 정책을 적용하기 위해, Gitaly 클라이언트 개발자는 이미 출시된 버전의 Gitaly에서 사용 가능한 기능만 사용해야 합니다. Gitaly 클라이언트 개발자는 다음에 해당하는 Gitaly 기능을 대상으로 개발해서는 절대 안 됩니다:

  • 아직 개발 중인 기능.

  • Gitaly 클라이언트 업데이트와 동일한 마일스톤에 출시 예정인 기능.

예를 들어:

  • 현재 GitLab 마일스톤이 18.1이고 출시 예정 버전이 18.1이라면, 해당 마일스톤에서 개발된 모든 GitLab 기능은 Gitaly 버전 18.0 이전에서 사용 가능한 기능을 사용해야 합니다.

  • GitLab 18.2가 출시될 때, 해당 릴리즈의 Gitaly 클라이언트는 Gitaly 18.1 이전에서 사용 가능한 기능만 사용해야 합니다.

  • Gitaly 18.2가 출시되면, Gitaly 클라이언트 개발자는 GitLab 18.3 릴리즈에 Gitaly 18.2에서 도입된 Gitaly 기능을 사용할 수 있습니다.

sequenceDiagram participant gl1 as GitLab 18.1.0 participant gl2 as GitLab 18.2.0 participant gl3 as GitLab 18.3.0 gl2 -->> gl1: "Clients uses Gitaly 18.1.0" gl3 -->> gl2: "Clients uses Gitaly 18.2.0"

Gitaly 관련 테스트 실패#

테스트 스위트가 Gitaly 문제로 인해 실패한다면, 첫 번째 단계로 다음 명령을 실행해 보세요:

rm -rf tmp/tests/gitaly

RSpec 테스트 중에 Gitaly 인스턴스는 gitlab/log/gitaly-test.log에 로그를 기록합니다.

TooManyInvocationsError 오류#

개발 및 테스트 중에 Gitlab::GitalyClient::TooManyInvocationsError 실패를 경험할 수 있습니다. GitalyClient는 단일 Rails 요청 또는 Sidekiq 실행에서 Gitaly가 30번 이상 호출될 때 이 오류를 발생시켜 잠재적인 n+1 문제를 차단하려고 시도합니다.

임시 조치로 GITALY_DISABLE_REQUEST_LIMITS=1을 내보내어 오류를 억제할 수 있습니다. 이렇게 하면 개발 환경에서 n+1 감지가 비활성화됩니다.

GitLab CE 또는 EE 리포지터리에 이슈를 올려 문제를 보고하세요. ~Gitaly ~performance ~"technical debt" 라벨을 포함하고, 이슈에 TooManyInvocationsError의 전체 스택 트레이스와 오류 메시지를 포함해야 합니다. 가능하다면 알려진 실패 테스트도 포함하세요.

n+1 문제의 원인을 격리해야 합니다. 일반적으로 배열의 각 요소에 대해 Gitaly가 호출되는 루프가 원인입니다. 문제를 격리할 수 없는 경우 Gitaly Team 구성원에게 도움을 요청하세요.

원인을 찾은 후, 다음과 같이 allow_n_plus_1_calls 블록으로 감싸세요:

# n+1: link to n+1 issue
Gitlab::GitalyClient.allow_n_plus_1_calls do
  # original code
  commits.each { |commit| ... }
end

이 블록으로 코드를 감싸면, 해당 코드 경로는 n+1 감지에서 제외됩니다.

요청 횟수#

커밋 및 기타 Git 데이터는 이제 Gitaly를 통해 가져옵니다. 이러한 가져오기는 데이터베이스와 마찬가지로 배치 처리할 수 있습니다. 이를 통해 클라이언트와 Gitaly 자체 성능이 향상되고, 결과적으로 사용자 경험도 개선됩니다. 성능을 안정적으로 유지하고 성능 저하를 방지하기 위해 Gitaly 호출 횟수를 셀 수 있으며, 호출 횟수를 테스트에서 검증할 수 있습니다. 이를 위해 :request_store 플래그가 설정되어야 합니다.

describe 'Gitaly Request count tests' do
  context 'when the request store is activated', :request_store do
    it 'correctly counts the gitaly requests made' do
      expect { subject }.to change { Gitlab::GitalyClient.get_request_count }.by(10)
    end
  end
end

로컬에서 수정한 Gitaly 버전으로 테스트 실행#

일반적으로 GitLab CE/EE 테스트는 GITALY_SERVER_VERSION에 지정된 버전으로 고정된 tmp/tests/gitaly의 로컬 Gitaly 복제본을 사용합니다. GITALY_SERVER_VERSION 파일은 리포지터리의 사용자 지정 커밋을 사용하기 위한 브랜치와 SHA도 지원합니다.

Gitaly의 자동 배포 도입으로 인해 GITALY_SERVER_VERSION의 형식이 Omnibus 구문에 맞게 정렬되었습니다. 더 이상 =revision을 지원하지 않으며, 파일 내용을 Git 참조(브랜치 또는 SHA)로 평가합니다. 시맨틱 버전과 일치하는 경우에만 v를 앞에 붙입니다.

수정된 버전의 Gitaly에 대해 로컬에서 테스트를 실행하려면 tmp/tests/gitaly를 심볼릭 링크로 교체할 수 있습니다. 이렇게 하면 rspec을 실행할 때마다 Gitaly를 재설치할 필요가 없어 훨씬 빠릅니다.

이 디렉터리에 config.tomlpraefect.config.toml 파일이 있어야 합니다. config.tomlconfig.toml.example에서, praefect.config.tomlconfig.praefect.toml.example에서 복사할 수 있습니다. 복사 후에는 모든 경로가 올바르게 지정되도록 파일을 편집하세요.

rm -rf tmp/tests/gitaly
ln -s /path/to/gitaly tmp/tests/gitaly

테스트를 실행하기 전에 로컬 Gitaly 디렉터리에서 make를 실행해야 합니다. 그렇지 않으면 Gitaly가 부팅에 실패합니다.

테스트 실행 사이에 로컬 Gitaly를 변경하면 수동으로 make를 다시 실행해야 합니다.

CI 테스트는 로컬에서 수정한 Gitaly 버전을 사용하지 않습니다. CI에서 사용자 지정 Gitaly 버전을 사용하려면, 이 섹션 처음에 설명된 대로 GITALY_SERVER_VERSION을 업데이트해야 합니다.

변경 사항이 포크에 있는 경우와 같이 다른 Gitaly 리포지터리를 사용하려면, 테스트 실행 시 GITALY_REPO_URL 환경 변수를 지정할 수 있습니다:

GITALY_REPO_URL=https://gitlab.com/nick.thomas/gitaly bundle exec rspec spec/lib/gitlab/git/repository_spec.rb

Gitaly 포크가 비공개인 경우 Deploy Token을 생성하고 URL에 지정할 수 있습니다:

GITALY_REPO_URL=https://gitlab+deploy-token-1000:token-here@gitlab.com/nick.thomas/gitaly bundle exec rspec spec/lib/gitlab/git/repository_spec.rb

CI/CD에서 사용자 지정 Gitaly 리포지터리를 사용하려면, 예를 들어 GitLab 포크가 항상 Gitaly 포크를 사용하도록 하려면 GITALY_REPO_URLCI/CD 변수로 설정하세요.

로컬에서 수정한 Gitaly RPC 클라이언트 사용#

새 엔드포인트를 추가하거나 기존 엔드포인트에 새 매개변수를 추가하는 등 RPC 클라이언트를 변경하는 경우, Gitaly protobuf 사양 가이드를 따르세요. 그런 다음:

  • Gitaly의 tools/protogem 디렉터리에서 bundle install을 실행합니다.

  • Gitaly의 루트 디렉터리에서 RPC 클라이언트 gem을 빌드합니다:

BUILD_GEM_OPTIONS=--skip-verify-tag make build-proto-gem
  • Gitaly의 _build 디렉터리에서 새로 생성된 .gem 파일의 압축을 풀고 gemspec을 생성합니다:
gem unpack gitaly.gem &&
gem spec gitaly.gem > gitaly/gitaly.gemspec
  • Rails의 Gemfile에서 gitaly 행을 다음으로 변경합니다:
gem 'gitaly', path: '../gitaly/_build'
  • bundle install을 실행하여 수정된 RPC 클라이언트를 사용합니다.

새 변경 사항을 시도할 때마다 2-5단계를 다시 실행하세요.


개발 문서로 돌아가기

피처 플래그로 RPC 감싸기#

Gitaly에서 새로운 기능을 피처 플래그 뒤에 두는 단계입니다.

Gitaly#

  • 패키지 범위의 플래그 이름을 생성합니다:
var findAllTagsFeatureFlag = "go-find-all-tags"
  • featureflag 패키지를 사용하여 코드에 스위치를 생성합니다:
if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) {
  // go implementation
} else {
  // ruby implementation
}
  • Prometheus 메트릭을 생성합니다:
var findAllTagsRequests = prometheus.NewCounterVec(
  prometheus.CounterOpts{
    Name: "gitaly_find_all_tags_requests_total",
    Help: "Counter of go vs ruby implementation of FindAllTags",
  },
  []string{"implementation"},
)

func init() {
  prometheus.Register(findAllTagsRequests)
}

if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) {
  findAllTagsRequests.WithLabelValues("go").Inc()
  // go implementation
} else {
  findAllTagsRequests.WithLabelValues("ruby").Inc()
  // ruby implementation
}
  • 테스트에서 헤더를 설정합니다:
import (
  "google.golang.org/grpc/metadata"

  "gitlab.com/gitlab-org/gitaly/internal/featureflag"
)

//...

md := metadata.New(map[string]string{featureflag.HeaderKey(findAllTagsFeatureFlag): "true"})
ctx = metadata.NewOutgoingContext(context.Background(), md)

c, err = client.FindAllTags(ctx, rpcRequest)
require.NoError(t, err)

GitLab Rails#

피처 플래그를 설정하여 Rails 콘솔에서 테스트합니다:

Feature.enable('gitaly_go_find_all_tags')

플래그 이름과 Rails 콘솔에서 사용하는 이름의 차이에 주의하세요. 두 이름 사이에는 차이가 있습니다(대시가 밑줄로 교체되고 이름 접두사가 변경됨). 모든 플래그에 반드시 gitaly_ 접두사를 붙이세요.

GitLab에서 설정하지 않으면, 피처 플래그는 콘솔에서 false로 읽히고 Gitaly는 기본값을 사용합니다. 기본값은 GitLab 버전에 따라 다릅니다.

GDK로 테스트#

플래그가 올바르게 설정되고 Gitaly에 전달되는지 확인하려면, GDK를 사용하여 통합을 확인할 수 있습니다:

  • 플래그의 상태를 관찰할 수 있어야 합니다. 이를 확인하려면 Prometheus 메트릭을 가져와서 활성화해야 합니다:

GDK 루트 디렉터리로 이동합니다.

  • Gitaly의 올바른 브랜치가 체크아웃되어 있는지 확인합니다.

  • make gitaly-setup으로 재컴파일하고 gdk restart gitaly로 서비스를 재시작합니다.

  • 설정이 실행 중인지 확인합니다: gdk status | grep praefect.

  • 어떤 설정 파일이 사용되는지 확인합니다: cat ./services/praefect/run | grep praefect-config 플래그의 값을 확인합니다.

  • 설정 파일에서 prometheus_listen_addr의 주석을 해제하고 gdk restart gitaly를 실행합니다.

  • 플래그가 아직 활성화되지 않았는지 확인합니다:

프로젝트 생성, 커밋 제출, 히스토리 확인 등 변경 사항을 트리거하는 데 필요한 작업을 수행합니다.

  • 현재 메트릭 목록에 피처 플래그에 대한 새 카운터가 있는지 확인합니다:
curl --silent "http://localhost:9236/metrics" | grep go_find_all_tags
  • 새 피처 플래그의 메트릭을 확인하고 증가하는 것을 관찰한 후, 새 기능을 활성화할 수 있습니다:

GDK 루트 디렉터리로 이동합니다.

  • Rails 콘솔을 시작합니다:
bundle install && bundle exec rails console
  • 피처 플래그 목록을 확인합니다:
Feature::Gitaly.server_feature_flags

비활성화되어 있어야 합니다: "gitaly-feature-go-find-all-tags"=>"false".

  • 활성화합니다:
Feature.enable('gitaly_go_find_all_tags')
  • Rails 콘솔을 종료하고 프로젝트 생성, 커밋 제출, 히스토리 확인 등 변경 사항을 트리거하는 데 필요한 작업을 수행합니다.

  • 메트릭을 통해 기능이 활성화되었는지 확인합니다:

curl --silent "http://localhost:9236/metrics" | grep go_find_all_tags

테스트에서 Praefect 사용#

기본적으로 테스트의 Praefect는 인메모리 선출 전략을 사용합니다. 이 전략은 더 이상 사용되지 않으며 프로덕션에서는 더 이상 사용되지 않습니다. 주로 유닛 테스트 목적으로 유지됩니다.

더 현대적인 선출 전략은 PostgreSQL 데이터베이스와의 연결이 필요합니다. 이 동작은 테스트 실행 시 기본적으로 비활성화되어 있지만, 환경에서 GITALY_PRAEFECT_WITH_DB=1을 설정하여 활성화할 수 있습니다.

이를 위해 PostgreSQL이 실행 중이어야 하고 데이터베이스가 생성되어 있어야 합니다. GDK를 사용하는 경우 다음과 같이 설정할 수 있습니다:

  • 데이터베이스를 시작합니다: gdk start db

  • GDK에서 환경을 로드합니다: eval $(cd ../gitaly && gdk env)

  • 데이터베이스를 생성합니다: createdb --encoding=UTF8 --locale=C --echo praefect_test

Gitaly에서 사용하는 Git 참조#

Gitaly는 GitLab에 Git 서비스를 제공하기 위해 많은 Git 참조(refs)를 사용합니다.

표준 Git 참조#

다음 표준 Git 참조는 GitLab이 (Gitaly를 통해) 모든 Git 리포지터리에서 사용합니다:

  • refs/heads/. 브랜치에 사용됩니다. git branch 문서를 참조하세요.

  • refs/tags/. 태그에 사용됩니다. git tag 문서를 참조하세요.

GitLab 전용 참조#

Git 참조가 가리키지 않는 커밋 체인은 하우스키핑이 실행될 때 제거될 수 있습니다. GitLab 프로세스나 UI에서 계속 접근 가능해야 하는 커밋 체인의 경우, GitLab은 하우스키핑이 이를 제거하지 않도록 이 커밋 체인에 GitLab 전용 참조를 생성합니다.

이러한 커밋 체인은 사용자가 리포지터리에 무엇을 하든 관계없이 유지됩니다. 예를 들어 브랜치를 삭제하거나 강제 푸시하더라도 유지됩니다.

기존 GitLab 전용 참조#

다음 GitLab 전용 참조는 GitLab이 (Gitaly를 통해) 독점적으로 사용합니다:

  • refs/keep-around/<object-id>. 머지 리퀘스트, 파이프라인, 노트의 UI에서 사용되는 커밋을 가리킵니다. keep-around 참조에는 생명주기가 없으므로 새로운 기능에는 사용하지 마세요.

  • refs/merge-requests/<merge-request-iid>/. 병합은 두 히스토리를 합칩니다. 이 ref 네임스페이스는 다음 하위 refs를 사용하여 병합에 대한 정보를 추적합니다:

head. 머지 리퀘스트의 현재 HEAD.

  • merge. 머지 리퀘스트의 커밋. 모든 머지 리퀘스트는 refs/keep-around 아래에 커밋 객체를 생성합니다.

  • 머지 트레인이 활성화된 경우: train. 머지 트레인의 커밋.

  • refs/pipelines/<pipeline-iid>. 파이프라인에 대한 참조. 파이프라인 커밋 객체 ID를 임시로 저장하는 데 사용됩니다.

  • refs/environments/<environment-slug>. 환경에 배포가 수행된 커밋에 대한 참조.

새로운 GitLab 전용 참조 생성#

GitLab 전용 참조는 GitLab UI가 계속 작동하도록 보장하는 데 유용하지만, 생성된 Git 리포지터리의 성능 저하를 일으킬 수 있으므로 신중하게 관리해야 합니다.

새로운 GitLab 전용 참조를 생성할 때:

  • Gitaly가 새 참조를 숨겨진 참조로 간주하는지 확인합니다. 숨겨진 참조는 사용자가 pull 또는 fetch할 때 접근할 수 없습니다. GitLab 전용 참조를 숨겨진 참조로 만들면 최종 사용자의 Git 성능에 영향을 미치지 않도록 방지합니다.

  • 정의된 생명주기가 있는지 확인합니다. PostgreSQL과 마찬가지로 Git 리포지터리는 무한한 양의 데이터를 처리할 수 없습니다. 많은 수의 참조를 추가하면 결국 성능 문제가 발생합니다. 따라서 생성된 GitLab 전용 참조는 가능한 경우 다시 제거되어야 합니다.

  • 참조가 지원하는 기능의 네임스페이스로 지정되는지 확인합니다. 성능 문제를 진단하려면 참조가 GitLab의 특정 기능 또는 모델과 연결되어 있어야 합니다.

GitLab 전용 참조에 대한 변경 사항 테스트#

GitLab 전용 참조가 생성되는 시점을 변경하면, 고아 Git 객체가 제거되기 전에 유예 기간이 있기 때문에 변경 사항이 배포된 한참 후에 GitLab UI 또는 프로세스가 실패할 수 있습니다.

GitLab 전용 참조에 대한 변경 사항을 테스트하려면:

git gc --prune=now

Gitaly 개발 가이드라인

GitLab v19.1
원문 보기
요약

Gitaly는 GitLab Rails, Workhorse, GitLab Shell에서 사용하는 고수준 Git RPC 서비스입니다. 2019년 5월, Bob Van Landuyt가 Gitaly 프로젝트에 대한 Deep Dive(GitLab 팀 구성원 전용: https://gitlab.com/gitlab-org/create-stage/-/issues/1)를 진행했습니다.

Gitaly는 GitLab Rails, Workhorse, GitLab Shell에서 사용하는 고수준 Git RPC 서비스입니다.

Deep Dive#

2019년 5월, Bob Van Landuyt가 Gitaly 프로젝트에 대한 Deep Dive(GitLab 팀 구성원 전용: https://gitlab.com/gitlab-org/create-stage/-/issues/1)를 진행했습니다. 여기에는 Ruby 개발자로서 기여하는 방법과 앞으로 이 코드베이스에서 작업하게 될 사람들을 위한 도메인별 지식이 포함되었습니다.

YouTube에서 녹화본을 확인할 수 있으며, 슬라이드는 Google SlidesPDF에서도 확인할 수 있습니다.

이 Deep Dive에서 다룬 모든 내용은 GitLab 11.11 기준으로 정확했으며, 세부 사항이 변경되었을 수 있지만 여전히 좋은 입문 자료로 활용할 수 있습니다.

입문자 가이드#

먼저 Gitaly 리포지터리의 Gitaly 기여를 위한 입문자 가이드를 읽으세요. 이 가이드에는 Gitaly 설정 방법, Gitaly의 다양한 구성 요소와 그 역할, 테스트 스위트 실행 방법이 설명되어 있습니다.

새로운 Git 기능 개발#

Git 데이터를 읽거나 쓰려면 Gitaly에 요청을 보내야 합니다. 즉, lib/gitlab/git에 아직 없는 데이터가 필요한 새로운 기능을 개발하는 경우 Gitaly도 수정해야 합니다.

gitlab 리포지터리 어디에도 디스크 접근을 통해 Git 리포지터리에 직접 접촉하는 새 코드가 있어서는 안 됩니다. Git 리포지터리에 직접 접근이 필요한 모든 것은 Gitaly에서 구현하고 RPC를 통해 노출해야 합니다.

Gitaly에서 새로운 기능을 개발하려면:

  • Gitaly 머지 리퀘스트를 올려 필요한 변경 사항을 반영합니다.

  • Gitaly 변경 사항을 활용하는 GitLab 머지 리퀘스트도 함께 올립니다.

  • 두 프로젝트의 변경 사항이 병합되기 전에, 변경 사항이 포함된 로컬 버전의 Gitaly와 수정된 버전의 GitLab을 함께 실행하여 두 프로젝트의 변경 사항을 로컬에서 테스트합니다.

수정된 버전의 Gitaly로 GitLab 테스트를 실행하는 방법은 아래를 참조하세요.

GDK에서는 gdk install을 실행하고 gdk restart로 GDK를 재시작하면 개발용으로 로컬에서 수정한 Gitaly 버전을 사용할 수 있습니다.

Gitaly 버전 호환성 요구사항#

업그레이드 중에는 새로운 Gitaly 클라이언트 버전과 이전 Gitaly 버전 간의 호환성을 유지해야 합니다. 업그레이드 중에 Gitaly 클라이언트(예: Rails)가 Gitaly보다 먼저 새 버전으로 업그레이드될 수 있습니다. 새로운 Gitaly 클라이언트는 아직 업그레이드되지 않은 이전 Gitaly에서 사용할 수 없는 기능에 의존해서는 안 됩니다.

이 정책을 적용하기 위해, Gitaly 클라이언트 개발자는 이미 출시된 버전의 Gitaly에서 사용 가능한 기능만 사용해야 합니다. Gitaly 클라이언트 개발자는 다음에 해당하는 Gitaly 기능을 대상으로 개발해서는 절대 안 됩니다:

  • 아직 개발 중인 기능.

  • Gitaly 클라이언트 업데이트와 동일한 마일스톤에 출시 예정인 기능.

예를 들어:

  • 현재 GitLab 마일스톤이 18.1이고 출시 예정 버전이 18.1이라면, 해당 마일스톤에서 개발된 모든 GitLab 기능은 Gitaly 버전 18.0 이전에서 사용 가능한 기능을 사용해야 합니다.

  • GitLab 18.2가 출시될 때, 해당 릴리즈의 Gitaly 클라이언트는 Gitaly 18.1 이전에서 사용 가능한 기능만 사용해야 합니다.

  • Gitaly 18.2가 출시되면, Gitaly 클라이언트 개발자는 GitLab 18.3 릴리즈에 Gitaly 18.2에서 도입된 Gitaly 기능을 사용할 수 있습니다.

sequenceDiagram participant gl1 as GitLab 18.1.0 participant gl2 as GitLab 18.2.0 participant gl3 as GitLab 18.3.0 gl2 -->> gl1: "Clients uses Gitaly 18.1.0" gl3 -->> gl2: "Clients uses Gitaly 18.2.0"

Gitaly 관련 테스트 실패#

테스트 스위트가 Gitaly 문제로 인해 실패한다면, 첫 번째 단계로 다음 명령을 실행해 보세요:

rm -rf tmp/tests/gitaly

RSpec 테스트 중에 Gitaly 인스턴스는 gitlab/log/gitaly-test.log에 로그를 기록합니다.

TooManyInvocationsError 오류#

개발 및 테스트 중에 Gitlab::GitalyClient::TooManyInvocationsError 실패를 경험할 수 있습니다. GitalyClient는 단일 Rails 요청 또는 Sidekiq 실행에서 Gitaly가 30번 이상 호출될 때 이 오류를 발생시켜 잠재적인 n+1 문제를 차단하려고 시도합니다.

임시 조치로 GITALY_DISABLE_REQUEST_LIMITS=1을 내보내어 오류를 억제할 수 있습니다. 이렇게 하면 개발 환경에서 n+1 감지가 비활성화됩니다.

GitLab CE 또는 EE 리포지터리에 이슈를 올려 문제를 보고하세요. ~Gitaly ~performance ~"technical debt" 라벨을 포함하고, 이슈에 TooManyInvocationsError의 전체 스택 트레이스와 오류 메시지를 포함해야 합니다. 가능하다면 알려진 실패 테스트도 포함하세요.

n+1 문제의 원인을 격리해야 합니다. 일반적으로 배열의 각 요소에 대해 Gitaly가 호출되는 루프가 원인입니다. 문제를 격리할 수 없는 경우 Gitaly Team 구성원에게 도움을 요청하세요.

원인을 찾은 후, 다음과 같이 allow_n_plus_1_calls 블록으로 감싸세요:

# n+1: link to n+1 issue
Gitlab::GitalyClient.allow_n_plus_1_calls do
  # original code
  commits.each { |commit| ... }
end

이 블록으로 코드를 감싸면, 해당 코드 경로는 n+1 감지에서 제외됩니다.

요청 횟수#

커밋 및 기타 Git 데이터는 이제 Gitaly를 통해 가져옵니다. 이러한 가져오기는 데이터베이스와 마찬가지로 배치 처리할 수 있습니다. 이를 통해 클라이언트와 Gitaly 자체 성능이 향상되고, 결과적으로 사용자 경험도 개선됩니다. 성능을 안정적으로 유지하고 성능 저하를 방지하기 위해 Gitaly 호출 횟수를 셀 수 있으며, 호출 횟수를 테스트에서 검증할 수 있습니다. 이를 위해 :request_store 플래그가 설정되어야 합니다.

describe 'Gitaly Request count tests' do
  context 'when the request store is activated', :request_store do
    it 'correctly counts the gitaly requests made' do
      expect { subject }.to change { Gitlab::GitalyClient.get_request_count }.by(10)
    end
  end
end

로컬에서 수정한 Gitaly 버전으로 테스트 실행#

일반적으로 GitLab CE/EE 테스트는 GITALY_SERVER_VERSION에 지정된 버전으로 고정된 tmp/tests/gitaly의 로컬 Gitaly 복제본을 사용합니다. GITALY_SERVER_VERSION 파일은 리포지터리의 사용자 지정 커밋을 사용하기 위한 브랜치와 SHA도 지원합니다.

Gitaly의 자동 배포 도입으로 인해 GITALY_SERVER_VERSION의 형식이 Omnibus 구문에 맞게 정렬되었습니다. 더 이상 =revision을 지원하지 않으며, 파일 내용을 Git 참조(브랜치 또는 SHA)로 평가합니다. 시맨틱 버전과 일치하는 경우에만 v를 앞에 붙입니다.

수정된 버전의 Gitaly에 대해 로컬에서 테스트를 실행하려면 tmp/tests/gitaly를 심볼릭 링크로 교체할 수 있습니다. 이렇게 하면 rspec을 실행할 때마다 Gitaly를 재설치할 필요가 없어 훨씬 빠릅니다.

이 디렉터리에 config.tomlpraefect.config.toml 파일이 있어야 합니다. config.tomlconfig.toml.example에서, praefect.config.tomlconfig.praefect.toml.example에서 복사할 수 있습니다. 복사 후에는 모든 경로가 올바르게 지정되도록 파일을 편집하세요.

rm -rf tmp/tests/gitaly
ln -s /path/to/gitaly tmp/tests/gitaly

테스트를 실행하기 전에 로컬 Gitaly 디렉터리에서 make를 실행해야 합니다. 그렇지 않으면 Gitaly가 부팅에 실패합니다.

테스트 실행 사이에 로컬 Gitaly를 변경하면 수동으로 make를 다시 실행해야 합니다.

CI 테스트는 로컬에서 수정한 Gitaly 버전을 사용하지 않습니다. CI에서 사용자 지정 Gitaly 버전을 사용하려면, 이 섹션 처음에 설명된 대로 GITALY_SERVER_VERSION을 업데이트해야 합니다.

변경 사항이 포크에 있는 경우와 같이 다른 Gitaly 리포지터리를 사용하려면, 테스트 실행 시 GITALY_REPO_URL 환경 변수를 지정할 수 있습니다:

GITALY_REPO_URL=https://gitlab.com/nick.thomas/gitaly bundle exec rspec spec/lib/gitlab/git/repository_spec.rb

Gitaly 포크가 비공개인 경우 Deploy Token을 생성하고 URL에 지정할 수 있습니다:

GITALY_REPO_URL=https://gitlab+deploy-token-1000:token-here@gitlab.com/nick.thomas/gitaly bundle exec rspec spec/lib/gitlab/git/repository_spec.rb

CI/CD에서 사용자 지정 Gitaly 리포지터리를 사용하려면, 예를 들어 GitLab 포크가 항상 Gitaly 포크를 사용하도록 하려면 GITALY_REPO_URLCI/CD 변수로 설정하세요.

로컬에서 수정한 Gitaly RPC 클라이언트 사용#

새 엔드포인트를 추가하거나 기존 엔드포인트에 새 매개변수를 추가하는 등 RPC 클라이언트를 변경하는 경우, Gitaly protobuf 사양 가이드를 따르세요. 그런 다음:

  • Gitaly의 tools/protogem 디렉터리에서 bundle install을 실행합니다.

  • Gitaly의 루트 디렉터리에서 RPC 클라이언트 gem을 빌드합니다:

BUILD_GEM_OPTIONS=--skip-verify-tag make build-proto-gem
  • Gitaly의 _build 디렉터리에서 새로 생성된 .gem 파일의 압축을 풀고 gemspec을 생성합니다:
gem unpack gitaly.gem &&
gem spec gitaly.gem > gitaly/gitaly.gemspec
  • Rails의 Gemfile에서 gitaly 행을 다음으로 변경합니다:
gem 'gitaly', path: '../gitaly/_build'
  • bundle install을 실행하여 수정된 RPC 클라이언트를 사용합니다.

새 변경 사항을 시도할 때마다 2-5단계를 다시 실행하세요.


개발 문서로 돌아가기

피처 플래그로 RPC 감싸기#

Gitaly에서 새로운 기능을 피처 플래그 뒤에 두는 단계입니다.

Gitaly#

  • 패키지 범위의 플래그 이름을 생성합니다:
var findAllTagsFeatureFlag = "go-find-all-tags"
  • featureflag 패키지를 사용하여 코드에 스위치를 생성합니다:
if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) {
  // go implementation
} else {
  // ruby implementation
}
  • Prometheus 메트릭을 생성합니다:
var findAllTagsRequests = prometheus.NewCounterVec(
  prometheus.CounterOpts{
    Name: "gitaly_find_all_tags_requests_total",
    Help: "Counter of go vs ruby implementation of FindAllTags",
  },
  []string{"implementation"},
)

func init() {
  prometheus.Register(findAllTagsRequests)
}

if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) {
  findAllTagsRequests.WithLabelValues("go").Inc()
  // go implementation
} else {
  findAllTagsRequests.WithLabelValues("ruby").Inc()
  // ruby implementation
}
  • 테스트에서 헤더를 설정합니다:
import (
  "google.golang.org/grpc/metadata"

  "gitlab.com/gitlab-org/gitaly/internal/featureflag"
)

//...

md := metadata.New(map[string]string{featureflag.HeaderKey(findAllTagsFeatureFlag): "true"})
ctx = metadata.NewOutgoingContext(context.Background(), md)

c, err = client.FindAllTags(ctx, rpcRequest)
require.NoError(t, err)

GitLab Rails#

피처 플래그를 설정하여 Rails 콘솔에서 테스트합니다:

Feature.enable('gitaly_go_find_all_tags')

플래그 이름과 Rails 콘솔에서 사용하는 이름의 차이에 주의하세요. 두 이름 사이에는 차이가 있습니다(대시가 밑줄로 교체되고 이름 접두사가 변경됨). 모든 플래그에 반드시 gitaly_ 접두사를 붙이세요.

GitLab에서 설정하지 않으면, 피처 플래그는 콘솔에서 false로 읽히고 Gitaly는 기본값을 사용합니다. 기본값은 GitLab 버전에 따라 다릅니다.

GDK로 테스트#

플래그가 올바르게 설정되고 Gitaly에 전달되는지 확인하려면, GDK를 사용하여 통합을 확인할 수 있습니다:

  • 플래그의 상태를 관찰할 수 있어야 합니다. 이를 확인하려면 Prometheus 메트릭을 가져와서 활성화해야 합니다:

GDK 루트 디렉터리로 이동합니다.

  • Gitaly의 올바른 브랜치가 체크아웃되어 있는지 확인합니다.

  • make gitaly-setup으로 재컴파일하고 gdk restart gitaly로 서비스를 재시작합니다.

  • 설정이 실행 중인지 확인합니다: gdk status | grep praefect.

  • 어떤 설정 파일이 사용되는지 확인합니다: cat ./services/praefect/run | grep praefect-config 플래그의 값을 확인합니다.

  • 설정 파일에서 prometheus_listen_addr의 주석을 해제하고 gdk restart gitaly를 실행합니다.

  • 플래그가 아직 활성화되지 않았는지 확인합니다:

프로젝트 생성, 커밋 제출, 히스토리 확인 등 변경 사항을 트리거하는 데 필요한 작업을 수행합니다.

  • 현재 메트릭 목록에 피처 플래그에 대한 새 카운터가 있는지 확인합니다:
curl --silent "http://localhost:9236/metrics" | grep go_find_all_tags
  • 새 피처 플래그의 메트릭을 확인하고 증가하는 것을 관찰한 후, 새 기능을 활성화할 수 있습니다:

GDK 루트 디렉터리로 이동합니다.

  • Rails 콘솔을 시작합니다:
bundle install && bundle exec rails console
  • 피처 플래그 목록을 확인합니다:
Feature::Gitaly.server_feature_flags

비활성화되어 있어야 합니다: "gitaly-feature-go-find-all-tags"=>"false".

  • 활성화합니다:
Feature.enable('gitaly_go_find_all_tags')
  • Rails 콘솔을 종료하고 프로젝트 생성, 커밋 제출, 히스토리 확인 등 변경 사항을 트리거하는 데 필요한 작업을 수행합니다.

  • 메트릭을 통해 기능이 활성화되었는지 확인합니다:

curl --silent "http://localhost:9236/metrics" | grep go_find_all_tags

테스트에서 Praefect 사용#

기본적으로 테스트의 Praefect는 인메모리 선출 전략을 사용합니다. 이 전략은 더 이상 사용되지 않으며 프로덕션에서는 더 이상 사용되지 않습니다. 주로 유닛 테스트 목적으로 유지됩니다.

더 현대적인 선출 전략은 PostgreSQL 데이터베이스와의 연결이 필요합니다. 이 동작은 테스트 실행 시 기본적으로 비활성화되어 있지만, 환경에서 GITALY_PRAEFECT_WITH_DB=1을 설정하여 활성화할 수 있습니다.

이를 위해 PostgreSQL이 실행 중이어야 하고 데이터베이스가 생성되어 있어야 합니다. GDK를 사용하는 경우 다음과 같이 설정할 수 있습니다:

  • 데이터베이스를 시작합니다: gdk start db

  • GDK에서 환경을 로드합니다: eval $(cd ../gitaly && gdk env)

  • 데이터베이스를 생성합니다: createdb --encoding=UTF8 --locale=C --echo praefect_test

Gitaly에서 사용하는 Git 참조#

Gitaly는 GitLab에 Git 서비스를 제공하기 위해 많은 Git 참조(refs)를 사용합니다.

표준 Git 참조#

다음 표준 Git 참조는 GitLab이 (Gitaly를 통해) 모든 Git 리포지터리에서 사용합니다:

  • refs/heads/. 브랜치에 사용됩니다. git branch 문서를 참조하세요.

  • refs/tags/. 태그에 사용됩니다. git tag 문서를 참조하세요.

GitLab 전용 참조#

Git 참조가 가리키지 않는 커밋 체인은 하우스키핑이 실행될 때 제거될 수 있습니다. GitLab 프로세스나 UI에서 계속 접근 가능해야 하는 커밋 체인의 경우, GitLab은 하우스키핑이 이를 제거하지 않도록 이 커밋 체인에 GitLab 전용 참조를 생성합니다.

이러한 커밋 체인은 사용자가 리포지터리에 무엇을 하든 관계없이 유지됩니다. 예를 들어 브랜치를 삭제하거나 강제 푸시하더라도 유지됩니다.

기존 GitLab 전용 참조#

다음 GitLab 전용 참조는 GitLab이 (Gitaly를 통해) 독점적으로 사용합니다:

  • refs/keep-around/<object-id>. 머지 리퀘스트, 파이프라인, 노트의 UI에서 사용되는 커밋을 가리킵니다. keep-around 참조에는 생명주기가 없으므로 새로운 기능에는 사용하지 마세요.

  • refs/merge-requests/<merge-request-iid>/. 병합은 두 히스토리를 합칩니다. 이 ref 네임스페이스는 다음 하위 refs를 사용하여 병합에 대한 정보를 추적합니다:

head. 머지 리퀘스트의 현재 HEAD.

  • merge. 머지 리퀘스트의 커밋. 모든 머지 리퀘스트는 refs/keep-around 아래에 커밋 객체를 생성합니다.

  • 머지 트레인이 활성화된 경우: train. 머지 트레인의 커밋.

  • refs/pipelines/<pipeline-iid>. 파이프라인에 대한 참조. 파이프라인 커밋 객체 ID를 임시로 저장하는 데 사용됩니다.

  • refs/environments/<environment-slug>. 환경에 배포가 수행된 커밋에 대한 참조.

새로운 GitLab 전용 참조 생성#

GitLab 전용 참조는 GitLab UI가 계속 작동하도록 보장하는 데 유용하지만, 생성된 Git 리포지터리의 성능 저하를 일으킬 수 있으므로 신중하게 관리해야 합니다.

새로운 GitLab 전용 참조를 생성할 때:

  • Gitaly가 새 참조를 숨겨진 참조로 간주하는지 확인합니다. 숨겨진 참조는 사용자가 pull 또는 fetch할 때 접근할 수 없습니다. GitLab 전용 참조를 숨겨진 참조로 만들면 최종 사용자의 Git 성능에 영향을 미치지 않도록 방지합니다.

  • 정의된 생명주기가 있는지 확인합니다. PostgreSQL과 마찬가지로 Git 리포지터리는 무한한 양의 데이터를 처리할 수 없습니다. 많은 수의 참조를 추가하면 결국 성능 문제가 발생합니다. 따라서 생성된 GitLab 전용 참조는 가능한 경우 다시 제거되어야 합니다.

  • 참조가 지원하는 기능의 네임스페이스로 지정되는지 확인합니다. 성능 문제를 진단하려면 참조가 GitLab의 특정 기능 또는 모델과 연결되어 있어야 합니다.

GitLab 전용 참조에 대한 변경 사항 테스트#

GitLab 전용 참조가 생성되는 시점을 변경하면, 고아 Git 객체가 제거되기 전에 유예 기간이 있기 때문에 변경 사항이 배포된 한참 후에 GitLab UI 또는 프로세스가 실패할 수 있습니다.

GitLab 전용 참조에 대한 변경 사항을 테스트하려면:

git gc --prune=now