Help us learn about your current experience with the documentation. Take the survey.

提升单体仓库性能

单体仓库是包含子项目的仓库。单个应用程序通常包含相互依赖的项目。例如,后端、Web 前端、iOS 应用和 Android 应用。单体仓库很常见,但它们可能带来性能风险。一些常见问题:

  • 大型二进制文件。
  • 许多具有长历史的文件。
  • 大量同时进行的克隆和推送操作。
  • 垂直扩展限制。
  • 网络带宽限制。
  • 磁盘带宽限制。

GitLab 本身基于 Git 构建。它的 Git 存储服务 Gitaly 会遇到与单体仓库相关的性能限制。我们学到的东西可以帮助您更好地管理自己的单体仓库。

  • 哪些仓库特征会影响性能。
  • 一些优化单体仓库的工具和步骤。

为单体仓库优化 Gitaly

Git 将对象压缩成 packfiles 以节省空间。当您克隆、获取或推送时,Git 会使用 packfiles。它们减少了磁盘空间和网络带宽,但创建 packfile 需要大量 CPU 和内存。

大型单体仓库比小型仓库有更多的提交、文件、分支和标签。当对象变大,传输时间变长时,packfile 创建会变得更昂贵和更慢。在 Git 中,git-pack-objects 过程是最消耗资源的操作,因为它:

  1. 分析提交历史和文件。
  2. 确定需要发送回客户端的文件。
  3. 创建 packfile。

来自 git clonegit fetch 的流量会在服务器上启动 git-pack-objects 进程。像 GitLab CI/CD 这样的自动化持续集成系统会导致大量此类流量。大量的自动化 CI/CD 流量会发送许多克隆和获取请求,可能会给您的 Gitaly 服务器带来压力。

使用这些策略来减少您 Gitaly 服务器的负载。

启用 Gitaly pack-objects 缓存

启用 Gitaly pack-objects 缓存,它可以减少克隆和获取的服务器负载。

当 Git 客户端发送克隆或获取请求时,git-pack-objects 生成的数据可以被缓存以供重用。如果您的单体仓库被频繁克隆,启用 Gitaly pack-objects 缓存 可以减少服务器负载。启用后,Gitaly 会维护一个内存缓存,而不是为每个克隆或获取调用重新生成响应数据。

有关更多信息,请参阅 Pack-objects cache

配置 Git bundle URIs

在低延迟的第三方存储(如 CDN)上创建和存储 Git bundles。Git 首先从您的 bundle 下载包,然后从您的 Git 远程获取任何剩余的对象和引用。这种方法可以更快地引导您的对象数据库,并减少 Gitaly 的负载。

  • 它加快了网络连接到 GitLab 服务器较差的用户克隆和获取的速度。
  • 通过预加载 bundle,减少了运行 CI/CD 作业的服务器的负载。

要了解更多信息,请参阅 Bundle URIs

配置 Gitaly 协商超时

当尝试获取或归档仓库时,如果您遇到以下情况,可能会发生 fatal: the remote end hung up unexpectedly 错误:

  • 大型仓库。
  • 并行处理多个仓库。
  • 并行处理同一个大型仓库。

要缓解此问题,请增加 默认协商超时值

正确配置硬件大小

单体仓库通常用于拥有许多用户的大型组织。为了支持您的单体仓库,您的 GitLab 环境应匹配 GitLab 测试平台和支持团队提供的 参考架构 之一。这些架构是在保持性能的同时大规模部署 GitLab 的推荐方式。

减少 Git 引用数量

在 Git 中,references 是指向特定提交的分支和标签名称。Git 将引用作为松散文件存储在您仓库的 .git/refs 文件夹中。要查看仓库中的所有引用,请运行 git for-each-ref

当仓库中的引用数量增加时,查找特定引用所需的寻道时间也会增加。每次 Git 解析引用时,增加的寻道时间会导致延迟增加。

为了解决这个问题,Git 使用 pack-refs 创建一个包含该仓库所有引用的单一 .git/packed-refs 文件。这种方法减少了引用所需的存储空间。它还减少了寻道时间,因为在单个文件中查找比在整个目录中查找所有文件更快。

Git 使用松散文件处理新创建或更新的引用。在您运行 git pack-refs 之前,它们不会被清理并添加到 .git/packed-refs 文件中。Gitaly 在 housekeeping 期间运行 git pack-refs。虽然这有助于许多仓库,但写入密集型仓库仍然存在这些性能问题:

  • 创建或更新引用会创建新的松散文件。
  • 删除引用需要编辑现有的 packed-refs 文件以移除现有引用。

当您获取或克隆仓库时,Git 会遍历所有引用。服务器会检查(“遍历”)每个引用的内部图结构,找到任何缺失的对象,并将它们发送给客户端。遍历和检查过程是 CPU 密集型的,会增加延迟。这种延迟在活动频繁的仓库中可能导致连锁反应。每个操作都变慢,每个操作都会延迟后续操作。

要缓解单体仓库中大量引用的影响:

  • 创建自动流程来清理旧分支。

  • 如果某些引用不需要对客户端可见,请使用 transfer.hideRefs 配置设置来隐藏它们。Gitaly 会忽略任何服务器上的 Git 配置,因此您必须在 /etc/gitlab/gitlab.rb 中更改 Gitaly 配置本身:

    gitaly['configuration'] = {
      # ...
      git: {
        # ...
        config: [
          # ...
          { key: "transfer.hideRefs", value: "refs/namespace_to_hide" },
        ],
      },
    }

在 Git 2.42.0 及更高版本中,不同的 Git 操作在进行对象图遍历时可以跳过隐藏的引用。

为单体仓库优化 CI/CD

为了使 GitLab 能够随着您的单体仓库扩展,请优化 CI/CD 作业与仓库的交互方式。

减少 CI/CD 中的并发克隆

通过 错开计划管道的运行时间 来减少 CI/CD 管道并发性,使它们在不同的时间运行。即使相隔几分钟也会有帮助。

CI/CD 负载通常是并发的,因为管道 在特定时间安排。在这些时间段内,对您仓库的 Git 请求可能会激增,影响 CI/CD 进程和用户的性能。

在 CI/CD 过程中使用浅克隆

对于 CI/CD 系统中的 git clonegit fetch 调用,请设置一个较小的 --depth 选项值,例如 10。深度为 10 指示 Git 只请求给定分支的最后 10 次更改。如果您的仓库有大量积压或许多大文件,此更改可以使 Git 获取快得多。它减少了传输的数据量。

GitLab 和 GitLab Runner 默认执行 浅克隆

这个 GitLab CI/CD 管道配置示例设置了 GIT_DEPTH

variables:
  GIT_DEPTH: 10

test:
  script:
    - ls -al

在 CI/CD 操作中使用 git fetch

如果可能,请在 CI/CD 系统上使用 git fetch 而不是 git clone 来保持仓库的工作副本可用。git fetch 需要服务器做的工作较少:

  • git clone 从头开始请求整个仓库。git-pack-objects 必须处理和发送所有分支和标签。
  • git fetch 只请求仓库中缺失的 Git 引用。git-pack-objects 只处理部分 Git 引用。此策略还减少了传输的总数据量。

默认情况下,GitLab 使用 推荐用于大型仓库的 fetch Git 策略

设置 git clone 路径

如果您的单体仓库与基于 fork 的工作流一起使用,请考虑设置 GIT_CLONE_PATH 来控制您克隆仓库的位置。

Git 将 fork 存储为具有独立工作树的独立仓库。GitLab Runner 无法优化工作树的使用。只为给定项目配置和使用 GitLab Runner 执行器。为了使过程更高效,不要在不同项目之间共享它。

GIT_CLONE_PATH 必须在 $CI_BUILDS_DIR 中设置的目录中。您不能从磁盘中选择任何路径。

在 CI/CD 作业中禁用 git clean

git clean 命令会从工作树中删除未跟踪的文件。在大型仓库中,它使用大量磁盘 I/O。如果您重用现有机器,并且可以重用现有工作树,请考虑在 CI/CD 作业中禁用它。例如,GIT_CLEAN_FLAGS: -ffdx -e .build/ 可以避免在运行之间删除工作树中的目录。这可以加快增量构建的速度。

要在 CI/CD 作业中禁用 git clean,为它们设置 GIT_CLEAN_FLAGSnone

默认情况下,GitLab 确保:

  • 您在给定的 SHA 上拥有工作树。
  • 您的仓库是干净的。

有关 GIT_CLEAN_FLAGS 接受的确切参数,请参阅 Git 文档 git clean。可用参数取决于您的 Git 版本。

使用标志更改 git fetch 行为

更改 git fetch 的行为以排除您的 CI/CD 作业不需要的任何数据。如果您的项目包含许多标签,而您的 CI/CD 作业不需要它们,请使用 GIT_FETCH_EXTRA_FLAGS 设置 --no-tags。此设置可以使您的获取更快更紧凑。

即使您的仓库不包含许多标签,--no-tags 在某些情况下也可以提高性能。有关更多信息,请参阅 issue 746GIT_FETCH_EXTRA_FLAGS Git 文档

为单体仓库优化 Git

为了使 GitLab 能够随着您的单体仓库扩展,请优化仓库本身。

避免在开发中使用浅克隆

避免在开发中使用浅克隆。浅克隆大大增加了推送更改所需的时间。浅克隆与 CI/CD 作业配合得很好,因为检出后仓库内容不会改变。

对于本地开发,请改用 部分克隆,以:

  • 使用 git clone --filter=blob:none 过滤掉 blob
  • 使用 git clone --filter=tree:0 过滤掉树

有关更多信息,请参阅 减少克隆大小

分析您的仓库以发现问题

大型仓库通常在 Git 中会遇到性能问题。git-sizer 项目可以分析您的仓库,并帮助您了解潜在问题。它可以帮助您制定缓解策略来防止性能问题。分析您的仓库需要一个完整的 Git 镜像或裸克隆,以确保所有 Git 引用都存在。

使用 git-sizer 分析您的仓库:

  1. 安装 git-sizer

  2. 运行以下命令以克隆与 git-sizer 兼容的裸 Git 格式的仓库:

    git clone --mirror <git_repo_url>
  3. 在您的 Git 仓库目录中,运行带有所有统计信息的 git-sizer

    git-sizer -v

处理后,git-sizer 的输出应如下例所示。每一行都包含该仓库方面的 关注级别。更高的关注级别用更多的星号表示。具有极高关注级别的项目用感叹号表示。在此示例中,几个项目具有较高的关注级别:

Processing blobs: 1652370
Processing trees: 3396199
Processing commits: 722647
Matching commits to trees: 722647
Processing annotated tags: 534
Processing references: 539
| Name                         | Value     | Level of concern               |
| ---------------------------- | --------- | ------------------------------ |
| Overall repository size      |           |                                |
| * Commits                    |           |                                |
|   * Count                    |   723 k   | *                              |
|   * Total size               |   525 MiB | **                             |
| * Trees                      |           |                                |
|   * Count                    |  3.40 M   | **                             |
|   * Total size               |  9.00 GiB | ****                           |
|   * Total tree entries       |   264 M   | *****                          |
| * Blobs                      |           |                                |
|   * Count                    |  1.65 M   | *                              |
|   * Total size               |  55.8 GiB | *****                          |
| * Annotated tags             |           |                                |
|   * Count                    |   534     |                                |
| * References                 |           |                                |
|   * Count                    |   539     |                                |
|                              |           |                                |
| Biggest objects              |           |                                |
| * Commits                    |           |                                |
|   * Maximum size         [1] |  72.7 KiB | *                              |
|   * Maximum parents      [2] |    66     | ******                         |
| * Trees                      |           |                                |
|   * Maximum entries      [3] |  1.68 k   | *                              |
| * Blobs                      |           |                                |
|   * Maximum size         [4] |  13.5 MiB | *                              |
|                              |           |                                |
| History structure            |           |                                |
| * Maximum history depth      |   136 k   |                                |
| * Maximum tag depth      [5] |     1     |                                |
|                              |           |                                |
| Biggest checkouts            |           |                                |
| * Number of directories  [6] |  4.38 k   | **                             |
| * Maximum path depth     [7] |    13     | *                              |
| * Maximum path length    [8] |   134 B   | *                              |
| * Number of files        [9] |  62.3 k   | *                              |
| * Total size of files    [9] |   747 MiB |                                |
| * Number of symlinks    [10] |    40     |                                |
| * Number of submodules       |     0     |                                |

对大型二进制文件使用 Git LFS

将二进制文件(如包、音频、视频或图形)存储为 Git Large File Storage (Git LFS) 对象。

当用户将文件提交到 Git 时,Git 使用 blob 对象类型 来存储和管理其内容。Git 不能高效处理大型二进制数据,因此大型 blob 对 Git 来说是问题。如果 git-sizer 报告超过 10 MB 的 blob,您通常在仓库中有大型二进制文件。大型二进制文件对服务器和客户端都会造成问题:

  • 对于服务器:与基于文本的源代码不同,二进制数据通常已经压缩。Git 无法进一步压缩二进制数据,这会导致大型 packfile。大型 packfile 需要更多的 CPU、内存和带宽来创建和发送。
  • 对于客户端:Git 将 blob 内容存储在 packfile 中(通常在 .git/objects/pack/)和常规文件中(在 worktrees 中),二进制文件比基于文本的源代码需要多得多的空间。

Git LFS 将对象存储在外部,例如对象存储中。您的 Git 包含指向对象位置的指针,而不是二进制文件本身。这可以提高仓库性能。有关更多信息,请参阅 Git LFS 文档