Gitaly 개발 가이드라인
Gitaly는 GitLab Rails, Workhorse 및 GitLab Shell에서 사용되는 고급 Git RPC 서비스입니다.
심층 분석
2019년 5월, Bob Van Landuyt는 Gitaly 프로젝트에 대해 심층 분석(오직 GitLab 팀원만: https://gitlab.com/gitlab-org/create-stage/-/issues/1
)을 주최했습니다.
Ruby 개발자로 기여하는 방법과, 향후 이 코드베이스의 일부에서 작업할 수 있는 모든 사람과 도메인별 지식을 공유하는 내용을 포함했습니다.
You can find the YouTube에서 녹화된 콘텐츠와 Google Slides 및 PDF에서 슬라이드를 확인하실 수 있습니다.
이 심층 분석에서 다룬 모든 내용은 GitLab 11.11 기준으로 정확했으며, 특정 세부 사항은 변경되었을 수 있지만 여전히 좋은 소개 자료로 사용될 수 있습니다.
초급자 가이드
Gitaly 리포지토리의 Gitaly 기여를 위한 초급자 가이드를 먼저 읽어보세요.
이 가이드는 Gitaly를 설정하는 방법, Gitaly의 다양한 구성 요소와 그 기능, 테스트 스위트를 실행하는 방법을 설명합니다.
새로운 Git 기능 개발
Git 데이터를 읽거나 쓰려면 Gitaly에 요청해야 합니다.
이는 사용자가 사용할 수 없는 데이터를 필요로 하는 새로운 기능을 개발하는 경우, Gitaly에 변경이 있어야 함을 의미합니다.
디스크 접근을 통해 Git 저장소를 건드리는 새로운 코드는 gitlab
리포지토리 어디에도 없어야 합니다.
Git 저장소에 직접 접근이 필요한 모든 사항은 Gitaly에서 구현되어야 하며, RPC를 통해 공개되어야 합니다.
새로운 기능을 개발하기 위해 Gitaly에 변경을 가할 때, 즉시 Gitaly가 병합된 후에 새로운 기능을 사용하려는 GitLab에 대한 변경 사항을 별도의 병합 요청으로 만드는 것이 더 쉬운 경우가 많습니다.
이렇게 하면 변경 사항을 병합하기 전에 테스트할 수 있습니다.
- 아래에서 수정된 버전의 Gitaly로 GitLab 테스트를 실행하기 위한 지침을 확인하세요.
- GDK에서
gdk install
을 실행하고 로컬 수정 Gitaly 버전을 사용하기 위해gdk restart
로 GDK를 다시 시작하세요.
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 팀 구성원에게 문의하세요.
소스가 발견된 후, 아래와 같이 allow_n_plus_1_calls
블록으로 래핑합니다:
# n+1: n+1 이슈 링크
Gitlab::GitalyClient.allow_n_plus_1_calls do
# 원래 코드
commits.each { |commit| ... }
end
코드가 이 블록으로 래핑된 후, 이 코드 경로는 n+1 감지에서 제외됩니다.
요청 수
커밋 및 기타 Git 데이터는 이제 Gitaly를 통해 가져옵니다. 이러한 가져오기는 데이터베이스처럼 배치될 수 있습니다. 이는 클라이언트와 Gitaly 자체, 따라서 사용자에게도 성능을 향상시킵니다. 성능을 안정적으로 유지하고 성능 감소를 방지하기 위해 Gitaly 호출 수를 계산하고 호출 수를 테스트할 수 있습니다. 이는 :request_store
플래그가 설정되어야 합니다.
describe 'Gitaly 요청 수 테스트' do
context '요청 저장소가 활성화된 경우', :request_store do
it '정확하게 Gitaly 요청 수를 계산한다' do
expect { subject }.to change { Gitlab::GitalyClient.get_request_count }.by(10)
end
end
end
로컬에서 수정된 Gitaly 버전으로 테스트 실행
일반적으로 GitLab CE/EE 테스트는 tmp/tests/gitaly
에 로컬 복제본을 사용하며, 이는 GITALY_SERVER_VERSION
에 지정된 버전에 고정됩니다. GITALY_SERVER_VERSION
파일은 리포지토리에서 사용자 지정 커밋을 사용하기 위해 브랜치 및 SHA도 지원합니다.
참고:
Gitaly의 자동 배포가 도입되면서 GITALY_SERVER_VERSION
의 형식이 Omnibus 구문에 맞춰 조정되었습니다.
이제 =revision
을 지원하지 않으며, 파일 내용을 Git 참조(브랜치 또는 SHA)로 평가합니다. 의미 버전과 일치하는 경우에만 v
를 앞에 붙입니다.
수정된 Gitaly 버전으로 로컬에서 테스트를 실행하고자 한다면 tmp/tests/gitaly
를 심볼릭 링크로 교체할 수 있습니다. 이는 매번 rspec
을 실행할 때 Gitaly 재설치를 피할 수 있어 훨씬 빠릅니다.
이 디렉토리에 config.toml
및 praefect.config.toml
파일이 포함되어 있는지 확인하세요. config.toml.example
에서 config.toml
을 복사하고, config.praefect.toml.example
에서 praefect.config.toml
을 복사할 수 있습니다.
복사한 후 모든 경로가 올바르게 가리키도록 수정하는 것을 잊지 마세요.
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 포크가 비공식적인 경우 배포 토큰을 생성하고 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_URL
을 CI/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'
-
수정된 RPC 클라이언트를 사용하기 위해
bundle install
을 실행합니다.
새로운 변경사항을 시도할 때마다 2-5단계를 다시 실행합니다.
기능 플래그로 RPC 래핑하기
다음은 Gitaly에서 기능 플래그 뒤에 새로운 기능을 설정하기 위한 단계입니다.
Gitaly
-
패키지 범위의 플래그 이름을 생성합니다:
var findAllTagsFeatureFlag = "go-find-all-tags"
-
featureflag
패키지를 사용하여 코드 내에서 스위치를 생성합니다:if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) { // go 구현 } else { // ruby 구현 }
-
Prometheus 메트릭을 생성합니다:
var findAllTagsRequests = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "gitaly_find_all_tags_requests_total", Help: "Go vs Ruby 구현의 FindAllTags 카운터", }, []string{"implementation"}, ) func init() { prometheus.Register(findAllTagsRequests) } if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) { findAllTagsRequests.WithLabelValues("go").Inc() // go 구현 } else { findAllTagsRequests.WithLabelValues("ruby").Inc() // ruby 구현 }
-
테스트에서 헤더를 설정합니다:
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 전용 참조
이 GitLab 전용 참조는 GitLab에서만 사용됩니다(지속적으로 Gitaly를 통해 사용됨):
-
refs/keep-around/<object-id>
. 파이프라인 작업이나 병합 요청이 있는 커밋에 대한 참조입니다.object-id
는 파이프라인이 실행된 커밋을 가리킵니다. -
refs/merge-requests/<merge-request-iid>/
. 병합은 두 가지 이력을 결합합니다. 이 참조 네임스페이스는 다음 참조를 사용하여 병합에 대한 정보를 추적합니다:-
head
. 현재 병합 요청의HEAD
. -
merge
. 병합 요청을 위한 커밋입니다. 모든 병합 요청은refs/keep-around
아래에 커밋 객체를 생성합니다. -
병합 기차가 활성화된 경우:
train
. 병합 기차를 위한 커밋입니다.
-
-
refs/pipelines/<pipeline-iid>
. 파이프라인에 대한 참조입니다. 파이프라인 커밋 객체 ID를 임시로 저장하는 데 사용됩니다. -
refs/environments/<environment-slug>
. 환경에 배포가 수행된 커밋에 대한 참조입니다. -
refs/heads/revert-<source-commit-short-object-id>
. 변경 사항 되돌리기 시 생성된 커밋의 객체 ID에 대한 참조입니다.