- ディープ・ダイブ
- 初心者ガイド
- 新しいGit機能の開発者
- Gitaly関連テストの失敗
- レガシーRuggedコード
TooManyInvocationsError
エラー- リクエスト数
- Gitalyのローカルで変更されたバージョンでのテストの実行
- 機能フラグによるRPCのラッピング
GitLab Developers Guide to Working with Gitaly.
GitalyはGitLab CE/EE、Workhorse、GitLab-Shellで使用される高レベルのGit RPCサービスです。
ディープ・ダイブ
2019年5月、Bob Van Landuytは、GitLabのGitalyプロジェクトと、Ruby開発者としてそれに貢献する方法についてのDeep Dive (GitLabチームメンバー限定:https://gitlab.com/gitlab-org/create-stage/issues/1
)を開催し、将来コードベースのこの部分で働く可能性のある人に彼のドメイン固有の知識を共有しました。
録画はYouTubeで、スライドはGoogleスライドとPDFでご覧いただけます。
このディープダイブで取り上げた内容はすべて GitLab 11.11 時点でのものであり、それ以降に具体的な内容が変更されている可能性はありますが、それでも入門書としては十分に役立つはずです。
初心者ガイド
まずはGitalyリポジトリのBeginner’s guide to Gitalycontributionを読んでみてください。 Gitalyのセットアップ方法、Gitalyのさまざまなコンポーネントとその機能、テストスイートの実行方法などが説明されています。
新しいGit機能の開発者
Gitのデータを読み書きするには、Gitalyにリクエストする必要があります。つまり、新しい機能を開発していて、lib/gitlab/git
でまだ利用できないデータが必要な場合は、Gitalyに変更を加える必要があります。
これはまだ明確に定義されていない新しいプロセスです。 もしGitの機能に貢献したくて行き詰まっているのであれば、Gitalyチームか
@jacobvosmaer-gitlab
に連絡してください。
新しい機能」とは、外部から呼び出されるメソッドやクラスを意味しますlib/gitlab/git
。 lib/gitlab/git
lib/gitlab/git
の内部から呼び出される新しいメソッドについては、以下の「既存の Git 機能の変更」を参照ください。
ディスクアクセス(Rugged,git
,rm -rf
など)を介してGitリポジトリに触れる新しいコードは、lib/gitlab/git
以外のどこにもあってはなりません。
Gitalyの新機能を追加する手順は以下の通りです:
- 探索/プロトタイピング
- で新しいGitaly RPCをデザインして作成します。
gitaly-proto
- の新バージョンをリリースしました。
gitaly-proto
- GoまたはRubyで、GitalyのRPCの実装とテストを書いてください。
- Gitalyの新バージョンをリリース
- 新しいGitaly RPCを呼び出すGitLab CE/EE, GitLab Workhorse, GitLab Shellのクライアントコードを記述します。
これらのステップはしばしば重複します。 テストや開発中に、Gitalyとgitaly-proto
の未発表バージョンを使用することは可能です。
- 未発表のプロトコルでサーバーサイドのコードを書く手順については、Gitalyリポジトリを参照してください。
- 変更したバージョンのGitalyでGitLab CEテストを実行する手順は以下をご覧ください。
- GDK で
gdk install
を実行し、gdk run
(またはgdk run app
) を再起動すると、ローカルで変更された Gitaly バージョンを開発者に使用させることができます。
gitaly-ruby
Rubyのコードを使ってGitalyのRPCを実装し、テストすることが可能です。gitaly-ruby
。これにより、Goのコードを書くのが苦手な開発者でも貢献しやすくなるはずです。
Gitalyリポジトリにこのアプローチのドキュメントがあります。
Gitaly関連テストの失敗
イシューの問題でテスト・スイートが失敗する場合、最初のステップとして、テスト・スイートを実行してみてください:
rm -rf tmp/tests/gitaly
RSpec のテスト中、Gitaly インスタンスはgitlab/log/gitaly-test.log
にログを書き込みます。
レガシーRuggedコード
GitalyはすべてのGitアクセスを処理できますが、GitLabの顧客の多くはまだNFSの上でGitalyを実行しています。 Git呼び出しのためのレガシーなRugged実装は、N+1 Gitaly呼び出しやその他の理由により、Gitaly RPCよりも高速である可能性があります。 詳細についてはイシューを参照してください。
GitLabがこれらの非効率性のほとんどを取り除くか、GitデータのためにNFSの使用を中止するまで、最もよく使われるRPCのいくつかのRugged実装は機能フラグによって有効にすることができます:
rugged_find_commit
rugged_get_tree_entries
rugged_tree_entry
rugged_commit_is_ancestor
rugged_commit_tree_entry
rugged_list_commits_by_oid
便利なRakeタスクを使って、これらのフラグを一括して有効または無効にすることができます。 有効にするには
bundle exec rake gitlab:features:enable_rugged
無効にするには
bundle exec rake gitlab:features:disable_rugged
このコードのほとんどは、lib/gitlab/git/rugged_impl
ディレクトリにあります。
TooManyInvocationsError
エラー
開発中やテスト中に、Gitlab::GitalyClient::TooManyInvocationsError
失敗が発生する可能性があります。GitalyClient
は、1回の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: 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 テストではtmp/tests/gitaly
で指定したバージョンに固定された Gitaly のローカルクローンを使いますGITALY_SERVER_VERSION
。 この GITALY_SERVER_VERSION
ファイルはブランチや SHA もサポートしており、https://gitlab.com/gitlab-org/gitalyのカスタムコミットを使うことができます。
GITALY_SERVER_VERSION
のフォーマットが Omnibus の構文に合わせられました。=revision
はサポートされなくなり、ファイルの内容は Git の参照 (ブランチまたは SHA) として評価され、センバーにマッチした場合のみv
が付加されます。変更されたGitalyのバージョンに対してローカルでテストを実行したい場合、tmp/tests/gitaly
をシンボリックリンクで置き換えることができます。rspec
を実行するたびにGitalyを再インストールする必要がなくなるため、この方がはるかに高速です。
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
GitalyのカスタムリポジトリをCIで使うには、例えばGitLabのフォークに常に自分のGitalyフォークを使わせたい場合、GITALY_REPO_URL
をCI環境変数として設定します。
Gitaly RPCクライアントをローカルで修正したバージョンを使用します。
新しいエンドポイントを追加したり、既存のエンドポイントに新しいパラメータを追加するなど、RPCクライアントに変更を加える場合は、Gitalyprotoのガイドに従ってください。 変更を加えたブランチをプッシュした後 (new-feature-branch
など):
-
Rails’
Gemfile
のgitaly
行を次のように変更します:gem 'gitaly', git: 'https://gitlab.com/gitlab-org/gitaly.git', branch: 'new-feature-branch'
-
bundle install
を実行して、変更した RPC クライアントを使用します。
機能フラグによる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コンソールでテストします:
注意:フラグの名前とRailsコンソールで使用されるフラグの名前に注意してください。 両者には違いがあります (ダッシュがアンダースコアに置き換えられ、名前のプレフィックスが変更されています)。 すべてのフラグのプレフィックスは必ずgitaly_
にしてください。Feature.enable('gitaly_go_find_all_tags')
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