# Gitaly developers guide

> 原文：[https://docs.gitlab.com/ee/development/gitaly.html](https://docs.gitlab.com/ee/development/gitaly.html)

*   [Deep Dive](#deep-dive)
*   [Beginner’s guide](#beginners-guide)
*   [Developing new Git features](#developing-new-git-features)
    *   [`gitaly-ruby`](#gitaly-ruby)
*   [Gitaly-Related Test Failures](#gitaly-related-test-failures)
*   [Legacy Rugged code](#legacy-rugged-code)
*   [`TooManyInvocationsError` errors](#toomanyinvocationserror-errors)
*   [Request counts](#request-counts)
*   [Running tests with a locally modified version of Gitaly](#running-tests-with-a-locally-modified-version-of-gitaly)
    *   [Use a locally modified version of Gitaly RPC client](#use-a-locally-modified-version-of-gitaly-rpc-client)
*   [Wrapping RPCs in Feature Flags](#wrapping-rpcs-in-feature-flags)
    *   [Gitaly](#gitaly)
    *   [GitLab Rails](#gitlab-rails)
    *   [Testing with GDK](#testing-with-gdk)

# Gitaly developers guide[](#gitaly-developers-guide "Permalink")

[Gitaly](https://gitlab.com/gitlab-org/gitaly)是 GitLab Rails，Workhorse 和 GitLab Shell 使用的高级 Git RPC 服务.

## Deep Dive[](#deep-dive "Permalink")

在 2019 年 5 月，Bob Van Landuyt 主持了有关 GitLab 的[Gitaly 项目](https://gitlab.com/gitlab-org/gitaly)以及如何以 Ruby 的形式进行的 Deep Dive（仅限 GitLab 团队成员： `https://gitlab.com/gitlab-org/create-stage/issues/1` : [//gitlab.com/gitlab-org/create-stage/issues/1](https://gitlab.com/gitlab-org/gitaly) ）开发人员，以便与将来可能在代码库这一部分工作的任何人共享他的特定领域知识.

您可以[在 YouTube 上](https://www.youtube.com/watch?v=BmlEWFS8ORo)找到[录音](https://www.youtube.com/watch?v=BmlEWFS8ORo) ，在[Google 幻灯片](https://docs.google.com/presentation/d/1VgRbiYih9ODhcPnL8dS0W98EwFYpJ7GXMPpX-1TM6YE/edit)和[PDF 中](https://gitlab.com/gitlab-org/create-stage/uploads/a4fdb1026278bda5c1c5bb574379cf80/Create_Deep_Dive__Gitaly_for_Create_Ruby_Devs.pdf)找到[幻灯片](https://docs.google.com/presentation/d/1VgRbiYih9ODhcPnL8dS0W98EwFYpJ7GXMPpX-1TM6YE/edit) .

截至 GitLab 11.11 为止，本次深入介绍中涉及的所有内容都是准确的，尽管自那时以来特定细节可能有所更改，但它仍应作为一个很好的介绍.

## Beginner’s guide[](#beginners-guide "Permalink")

首先阅读 Gitaly 存储库的[Gitaly 贡献初学者指南](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/beginners_guide.md) . 它描述了如何设置 Gitaly，Gitaly 的各个组件以及它们的作用以及如何运行其测试套件.

## Developing new Git features[](#developing-new-git-features "Permalink")

要读取或写入 Git 数据，必须向 Gitaly 发出请求. 这意味着如果您要开发一项新功能，则需要`lib/gitlab/git`尚不可用的数据，则必须对 Gitaly 进行更改.

> 这是一个尚未明确定义的新过程. 如果您想提供 Git 功能，但遇到困难，请与 Gitaly 团队或`@jacobvosmaer-gitlab` .

"新功能"是指`lib/gitlab/git`中从`lib/gitlab/git`外部调用的任何方法或类. 有关从`lib/gitlab/git`内部调用的新方法，请参见下面的"修改现有 Git 功能".

在`lib/gitlab/git`之外的任何地方，都不应有任何通过磁盘访问（例如 Rugged， `git` ， `rm -rf` ）接触 Git 存储库的新代码.

添加新的 Gitaly 功能的过程是：

*   探索/原型制作
*   在[`gitaly-proto`](https://gitlab.com/gitlab-org/gitaly-proto)设计和创建新的 Gitaly RPC
*   发行新版本的`gitaly-proto`
*   用 Go 或 Ruby [在 Gitaly 中](https://gitlab.com/gitlab-org/gitaly)编写 RPC 的实现和测试
*   release a new version of Gitaly
*   在调用新 Gitaly RPC 的 GitLab CE / EE，GitLab Workhorse 或 GitLab Shell 中编写客户端代码

这些步骤经常重叠. 在测试和开发过程中，可以使用未发行的 Gitaly 和`gitaly-proto`版本.

*   有关使用未发布的协议编写服务器端代码的说明，请参见[Gitaly 存储库](https://gitlab.com/gitlab-org/gitaly/blob/master/CONTRIBUTING.md#development-and-testing-with-a-custom-gitaly-proto) .
*   有关使用修改后的 Gitaly 版本运行 GitLab CE 测试的说明，请参见[下文](#running-tests-with-a-locally-modified-version-of-gitaly) .
*   在 GDK 中运行`gdk install`并重新启动`gdk run` （或`gdk run app` ）以使用本地修改的 Gitaly 版本进行开发

### `gitaly-ruby`[](#gitaly-ruby "Permalink")

可以在[`gitaly-ruby`](https://gitlab.com/gitlab-org/gitaly/tree/master/ruby)使用 Ruby 代码在[`gitaly-ruby`](https://gitlab.com/gitlab-org/gitaly/tree/master/ruby)实现和测试 RPC. 这应该使对不喜欢编写 Go 代码的开发人员的贡献更加容易.

[Gitaly 存储库](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/ruby_endpoint.md)中提供[了](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/ruby_endpoint.md)有关此方法[的文档](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/ruby_endpoint.md) .

## Gitaly-Related Test Failures[](#gitaly-related-test-failures "Permalink")

如果您的测试套件因 Gitaly 问题而失败，请首先尝试运行：

```
rm -rf tmp/tests/gitaly 
```

在 RSpec 测试期间，Gitaly 实例会将日志写入`gitlab/log/gitaly-test.log` .

## Legacy Rugged code[](#legacy-rugged-code "Permalink")

尽管 Gitaly 可以处理所有 Git 访问，但许多 GitLab 客户仍在 NFS 上运行 Gitaly. 由于 N + 1 个 Gitaly 调用和其他原因，用于 Git 调用的传统 Rugged 实现可能比 Gitaly RPC 更快. 有关更多详细信息，请参见[问题](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/57317) .

在 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`目录中.

**注意：**除非与[Gitaly Team](https://gitlab.com/groups/gl-gitaly/group_members)明确讨论，否则您无需添加或修改与 Rugged 相关的代码. 此代码不适用于 GitLab.com 或其他不使用 NFS 的 GitLab 实例.

## `TooManyInvocationsError` errors[](#toomanyinvocationserror-errors "Permalink")

在开发和测试过程中，您可能会遇到`Gitlab::GitalyClient::TooManyInvocationsError`故障. 该`GitalyClient`将试图通过时 Gitaly 被称为 30 倍以上在单个的 Rails 请求或 Sidekiq 执行提高此错误来阻止对潜在的 n + 1 点的问题.

作为临时措施，导出`GITALY_DISABLE_REQUEST_LIMITS=1`以消除该错误. 这将在开发环境中禁用 n + 1 检测.

请在 GitLab CE 或 EE 存储库中提出问题，以报告该问题. 包括标签〜Gitaly〜性能〜"技术债务". 请确保问题包含完整的堆栈跟踪和`TooManyInvocationsError`错误消息. 如果可能，还包括任何已知的失败测试.

找出 n + 1 问题的根源. 通常，这将是一个循环，导致对数组中的每个元素调用 Gitaly. 如果您无法找出问题所在，请与[Gitaly 小组](https://gitlab.com/groups/gl-gitaly/group_members)成员联系以寻求帮助.

找到源之后，将其包装在`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 检测中排除此代码路径.

## Request counts[](#request-counts "Permalink")

提交和其他 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 
```

## Running tests with a locally modified version of Gitaly[](#running-tests-with-a-locally-modified-version-of-gitaly "Permalink")

通常，GitLab CE / EE 测试在`tmp/tests/gitaly`使用 Gitaly 的本地克隆，固定在`GITALY_SERVER_VERSION`指定的版本上. `GITALY_SERVER_VERSION`文件还支持分支和 SHA，以在[https://gitlab.com/gitlab-org/gitaly 中](https://gitlab.com/gitlab-org/gitaly)使用自定义提交.

**注：**通过引入自动部署 Gitaly 的，格式`GITALY_SERVER_VERSION`用总括的语法一致. 它不再支持`=revision` ，它将评估文件内容作为 Git 引用（分支或 SHA），仅当它与 semver 匹配时才在`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 存储库（例如，如果您的更改出现在 fork 上），则可以在运行测试时指定`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](../user/project/deploy_tokens/index.html)并在 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 中使用自定义的 Gitaly 存储库，例如，如果您希望 GitLab 分支始终使用自己的 Gitaly 分支，请将`GITALY_REPO_URL`设置为[CI 环境变量](../ci/variables/README.html#gitlab-cicd-environment-variables) .

### Use a locally modified version of Gitaly RPC client[](#use-a-locally-modified-version-of-gitaly-rpc-client "Permalink")

如果要对 RPC 客户端进行更改，例如添加新端点或向现有端点添加新参数，请遵循[Gitaly proto](https://gitlab.com/gitlab-org/gitaly/blob/master/proto/README.md)指南. 在推送具有更改的分支（例如`new-feature-branch` ）之后：

1.  将 Rails 的`Gemfile`的`gitaly`行更改为：

    ```
    gem 'gitaly', git: 'https://gitlab.com/gitlab-org/gitaly.git', branch: 'new-feature-branch' 
    ```

2.  运行`bundle install`以使用修改后的 RPC 客户端.

* * *

[Return to Development documentation](README.html)

## Wrapping RPCs in Feature Flags[](#wrapping-rpcs-in-feature-flags "Permalink")

以下是在功能标记后在 Gitaly 中选通新功能的步骤.

### Gitaly[](#gitaly "Permalink")

1.  创建一个包范围的标志名：

    ```
    var findAllTagsFeatureFlag = "go-find-all-tags" 
    ```

2.  使用`featureflag`包在代码中创建一个开关：

    ```
    if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) {
      // go implementation
    } else {
      // ruby implementation
    } 
    ```

3.  创建 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
    } 
    ```

4.  Set headers in tests:

    ```
    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[](#gitlab-rails "Permalink")

1.  通过设置功能标记在 Rails 控制台中进行测试：

    **注意：请**注意标志的名称以及在 Rails 控制台中使用的标志的名称. 它们之间是有区别的（用下划线代替短划线，并且更改了名称前缀）. 确保在所有标志`gitaly_`加上`gitaly_` .

    ```
    Feature.enable('gitaly_go_find_all_tags') 
    ```

### Testing with GDK[](#testing-with-gdk "Permalink")

为确保正确设置该标志并将其放入 Gitaly 中，可以使用 GDK 检查集成：

1.  标志的状态必须是可观察的. 要检查它，您需要通过获取 Prometheus 指标来启用它：
    1.  导航到 GDK 的根目录.
    2.  确保您已经为 Gitaly 签出了正确的分支.
    3.  使用`make gitaly-setup`重新编译它，并使用`gdk restart gitaly`重新启动服务.
    4.  确保您的设置正在运行： `gdk status | grep praefect` `gdk status | grep praefect` .
    5.  检查使用了什么配置文件： `cat ./services/praefect/run | grep praefect` `-config`标志的`cat ./services/praefect/run | grep praefect`值
    6.  在配置文件中取消注释`prometheus_listen_addr`并运行`gdk restart gitaly` .
2.  Make sure that the flag is not enabled yet:
    1.  执行触发更改所需的任何操作（项目创建，提交提交，观察历史记录等）.
    2.  检查当前指标列表是否具有新的功能标记计数器：

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

3.  一旦观察到新功能标志的度量标准并递增，就可以启用新功能：
    1.  导航到 GDK 的根目录.
    2.  启动一个 Rails 控制台：

        ```
         bundle install && bundle exec rails console 
        ```

    3.  检查功能标志列表：

        ```
         Feature :: Gitaly . server_feature_flags 
        ```

        应该禁用`"gitaly-feature-go-find-all-tags"=>"false"` .

    4.  启用它：

        ```
         Feature . enable ( 'gitaly_go_find_all_tags' ) 
        ```

    5.  退出 Rails 控制台并执行触发更改所需的任何操作（项目创建，提交提交，观察历史记录等）.
    6.  通过观察该功能的度量来确认该功能已启用：

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