- 심층 탐구
- 초보자 가이드
- 새로운 Git 기능 개발
- Gitaly 관련 테스트 실패
TooManyInvocationsError
오류- 요청 횟수
- 로컬로 수정된 Gitaly 버전으로 테스트 실행
- 기능 플래그로 RPC 래핑
- Praefect 테스트에서 사용
- Gitaly에서 사용하는 Git 참조
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 개발자로 소속된 사람들에게 기여하는 방법과 이 코드베이스의 특정 부분에서 미래에 작업할 수 있는 사람들과 도메인 특화 지식을 공유했습니다.
당사 Deep Dive에서 다루는 모든 내용은 GitLab 11.11을 기준으로 정확했으며, 특정 세부 사항은 변경되었을 수 있지만 여전히 좋은 소개 자료로 사용할 수 있어야 합니다.
초보자 가이드
먼저 Gitaly 저장소의 Gitaly 기여 초보자 가이드를 읽어보세요. Gitaly 설정 방법, Gitaly의 다양한 구성 요소 및 역할, 그리고 테스트 스위트를 실행하는 방법에 대해 설명합니다.
새로운 Git 기능 개발
Git 데이터를 읽거나 쓰려면 Gital로 요청해야 합니다. 따라서 새로운 기능을 개발할 때 lib/gitlab/git
에서 아직 이용할 수 없는 데이터가 필요한 경우 Gitaly에 변경을 가해야 합니다.
gitlab
저장소의 어느 곳에서도 디스크 액세스를 통해 Git 저장소에 직접 접근하는 새 코드가 있어서는 안 됩니다. Git 저장소에 직접적으로 액세스해야 하는 모든 것은 반드시 Gitaly에서 구현되어야 하며 RPC를 통해 노출되어야 합니다.
새로운 기능을 Gitaly에서 개발하는 것이 종종 더 쉽습니다. 이 경우 새로운 기능을 사용하도록 할 GitLab에 대한 변경 사항을 별도의 병합 요청으로 만들어 즉시 Gitaly 병합 후에 병합합니다. 이렇게 하면 변경 사항을 병합하기 전에 변경 사항을 테스트할 수 있습니다.
- 수정된 Gitaly 버전으로 GitLab 테스트를 실행하는 방법은 아래 설명된 부분을 참조하세요.
- GDK에서
gdk install
을 실행하고gdk restart
를 사용하여 개발용으로 로컬로 수정된 Gitaly 버전을 사용하세요.
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,~성능 및 ~기술 부채를 포함해야 합니다. 이 문제에 대한 전체 스택 트레이스와 오류 메시지도 포함시키세요. 가능하다면 알려진 실패하는 테스트를 포함시키세요.
n+1 문제의 원인을 분리하세요. 이는 일반적으로 Gitaly가 배열의 각 요소에 대해 호출되는 반복문입니다. 문제를 분리할 수 없는 경우 Gitaly 팀의 구성원에게 도움을 요청하세요.
원인을 찾으면 다음과 같이 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
파일은 저장소 내 더 이상 =revision
을 지원하지 않고 까다로운 구문에 대응합니다. 의미 있는 버전을 만나면 v
를 앞에 붙입니다.
로컬로 수정된 Gitaly 버전에서 테스트를 실행하려면 tmp/tests/gitaly
을 심볼릭 링크로 바꿀 수 있습니다. 이렇게 함으로써 rspec
을 실행할 때마다 Gitaly를 다시 설치하는 것을 피할 수 있어 성능이 향상됩니다.
이 디렉토리에는 config.toml
및 praefect.config.toml
파일이 포함되어 있어야 합니다. config.toml
은 config.toml.example
을 복사하고, praefect.config.toml
은 config.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
를 업데이트해야 합니다.
변경 내용이 있는 경우, 특히 변경 사항이 fork된 상태인 경우 GITALY_REPO_URL
환경 변수를 실행시킬 때 지정할 수 있습니다.
GITALY_REPO_URL=https://gitlab.com/nick.thomas/gitaly bundle exec rspec spec/lib/gitlab/git/repository_spec.rb
Gitaly의 fork가 비공개이면 배포 토큰을 생성하고 이를 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의 fork가 항상 개인 Gitaly fork를 사용하도록 하려면 CI/CD 변수로 GITALY_REPO_URL
을 설정하세요.
로컬로 수정된 Gitaly RPC 클라이언트 사용
RPC 클라이언트를 변경하려면 새 엔드포인트를 추가하거나 기존 엔드포인트에 새 매개변수를 추가하는 등의 수정을 가할 때 Gitaly protobuf 명세에 따라 가이드를 따르세요. 그런 다음 다음을 수행하세요.
- Gitaly의
tools/protogem
디렉토리에서bundle install
을 실행하세요. -
Gitaly의 루트 디렉토리에서 RPC 클라이언트 젬을 빌드하세요:
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 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에 설정되지 않으면 기능 플래그는 콘솔로부터 거짓으로 읽히며 Gitaly는 기본값을 사용합니다. 기본값은 GitLab 버전에 따라 다릅니다.
GDK로 테스트하기
플래그가 올바르게 설정되고 Gitaly에 적용되는지 확실하려면 GDK를 사용하여 통합을 확인할 수 있습니다:
- 플래그 상태를 관찰할 수 있어야 합니다. 확인하려면 플래그를 활성화해야 합니다.
- 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>/
. Merges가 두 개의 히스토리를 함께 병합합니다. 이 참조 네임스페이스는 다음의 참조들을 사용하여 병합에 관한 정보를 추적합니다:-
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를 참조합니다.