[英]Git checkout in post-receive hook: “Not a git repository '.'”
[英]Huge Git repository checkout at post-receive hook is extremely slow
我们正在为我们的项目使用 Git。 存储库相当大( .git
文件夹大约 8Gb)。
我们在接收后挂钩中使用git checkout -f
来更新工作树。
问题在于,即使是几个稍有改动的文件也需要很长时间才能签出,大约需要 20 秒。 我不知道为什么这么长。
可能是存储库大小的问题?
我应该尝试使用哪些步骤或工具来进一步定位和调查问题?
感谢您的任何帮助。
问候, 亚历克斯
原始答案(2012 年 11 月)
我确认如果你保持一个那么大的 git 目录 ( .git
),git 会显着变慢。
你可以在这个线程中看到一个插图(不是因为大文件,而是因为大量的文件和提交历史):
测试 repo 有 400 万次提交、线性历史记录和大约 130 万个文件。
.git
目录的大小约为 15GB,并已重新打包为 '
git repack -a -d -f --max-pack-size=10g --depth=100 --window=250
在一台功能强大的机器上(即大量 ram 和闪存),这个重新包装花费了大约 2 天的时间。
索引文件的大小为 191 MB。
至少,您可以考虑拆分存储库,将二进制文件隔离在它们自己的 git 存储库中,并使用子模块来跟踪源代码存储库和二进制存储库。
最好将大型二进制文件(尤其是生成它们时)存储在源引用之外。
建议使用“工件”存储库,例如Nexus 。
显示保留这些二进制文件的全 git 解决方案是 git-annex 或 git-media,如“ 如何处理大型 git 存储库? ”中所述。
2016 年 2 月更新:git 2.8(2016 年 3 月)应该会显着提高git checkout
性能。
请参阅David Turner ( dturner-tw
)的提交a672095 (2016 年 1 月 22 日)和提交 d9c2bd5 (2015 年 12 月 21 日)。
(由Junio C Hamano 合并gitster
提交 201155c ,2016 年 2 月 3 日)
unpack-trees
:修复意外的二次行为
在解包树时(例如,在
git checkout
期间),当我们命中一个超出我们路径的缓存条目时,我们会中断迭代。这为 Twitter 的 monorepo 上 master 和
master^20000
之间的 git checkout 提供了大约 45% 的加速。
一般来说,加速将取决于 repostitory 结构、更改数量和 packfile 打包决策。
do_compare_entry
: 使用已经计算好的路径在 traverse_trees 中,我们为
traverse_info
生成完整的遍历路径。
后来,在 do_compare_entry 中,我们过去常做大量工作来比较traverse_info
和 cache_entry 的名称,而不计算该路径。
但是因为我们已经有了那条路,所以我们不需要做所有的工作。
相反,我们可以将生成的路径放入traverse_info
中,并更直接地进行比较。这使得
git checkout
更快——在 Twitter 的 monorepo 上快了大约 25%。
较深的目录树可能比较浅的目录树受益更多。
使用sparse-checkout ,可以大大加快大型存储库的检出速度。
在 Git 2.33(2021 年第 3 季度)中,这一点得到了进一步改善,其中“ git checkout
” ( man )和git commit
( man )学会了在没有不必要地扩展稀疏索引的情况下工作。
请参阅 Derrick Stolee (derrickstolee) 的提交 e05cdb1 、 提交 70569fa (2021 年 7 月 20 日)和提交 1ba5f45 、 提交 f934f1b 、 提交 daa1ace 、 提交 11042ab 、 提交 0d53d19 (2021 年 6 月 29 日derrickstolee
。
(由Junio C Hamano 合并gitster
提交 506d2a3,2021年 8 月 4 日)
checkout
:停止扩展稀疏索引签字人:Derrick Stolee
之前的更改对
unpack-trees.c
和diff-lib.c
进行了必要的改进,以便根据与树的比较修改稀疏索引。
唯一剩下的工作是删除一些ensure_full_index()
调用并添加测试以验证索引在我们感兴趣的案例中没有扩展。
在这些测试中包括“switch”和“restore”,因为它们与“checkout”共享一个基础实现。以下是 p2000-sparse-operations.sh 的相关性能结果:
Test HEAD~1 HEAD -------------------------------------------------------------------------------- 2000.18: git checkout -f - (full-v3) 0.49(0.43+0.03) 0.47(0.39+0.05) -4.1% 2000.19: git checkout -f - (full-v4) 0.45(0.37+0.06) 0.42(0.37+0.05) -6.7% 2000.20: git checkout -f - (sparse-v3) 0.76(0.71+0.07) 0.04(0.03+0.04) -94.7% 2000.21: git checkout -f - (sparse-v4) 0.75(0.72+0.04) 0.05(0.06+0.04) -93.3%
将完整索引情况与稀疏索引情况进行比较很重要,因为稀疏索引的先前结果因索引扩展而膨胀。
对于索引 v4,这是 88% 的改进。在 HEAD 有超过 200 万条路径的内部存储库和包含约 60,000 条这些路径的稀疏签出定义中,“
git checkout
” ( man )通过此更改从 3.5 秒减少到 297 毫秒。
仅存在约 60,000 条路径的理论最优值为 275 毫秒,因此额外的稀疏目录条目会产生 22 毫秒的开销。
解决此问题的另一种方法是并行化结帐(从 Git 2.32 开始,2021 年第二季度)。
如本补丁中所述(仍在进行中) :
该系列向结帐机器添加了并行工作人员。
缓存条目分布在负责读取、过滤和将 blob 写入工作树的辅助进程中。
这应该有利于调用unpack_trees()
或check_updates()
的所有命令,例如:checkout、clone、sparse-checkout、checkout-index
等。Local: Clone Checkout I Checkout II Sequential 8.180 s ± 0.021 s 6.936 s ± 0.030 s 2.585 s ± 0.005 s 10 workers 3.406 s ± 0.187 s 2.164 s ± 0.033 s 1.050 s ± 0.021 s Speedup 2.40 ± 0.13 3.21 ± 0.05 2.46 ± 0.05
例如,对于 Git 2.32(2021 年第 2 季度),有针对并行结帐的准备性 API 更改。
请参阅Matheus Tavares ( matheustavares
)的提交 ae22751 、 提交 30419e7 、 提交 584a0d1 、 提交 49cfd90 、 提交 d052cc0 (2021 年 3 月 23 日)。
请参阅Jeff Hostetler ( Jeff-Hostetler
)的提交 f59d15b 、 提交 3e9e82c 、 提交 55b4ad0 、 提交 38e9584 (2020 年 12 月 16 日)。
(由Junio C Hamano 合并gitster
提交 c47679d ,2021 年 4 月 2 日)
convert
:添加[async_]convert_to_working_tree_ca()
变体签字人:Jeff Hostetler
签字人:Matheus Tavares
通过添加转换函数的
_ca()
变体,将属性收集与实际转换分开。
这些变体接收预先计算的“structconv_attrs
”,因此不依赖于索引状态。
它们将用于添加并行检查支持的未来补丁中,原因有二:
- 在转换之前,我们已经在
checkout_entry()
中加载了转换属性,以确定路径是否符合并行检查的条件。
因此,对于实际的转换,稍后再次加载它们会很浪费。- 并行工作者将负责读取、转换 blob 并将其写入工作树。
他们无法访问主进程的索引状态,因此无法加载属性。
相反,他们将接收预加载的并调用转换函数的_ca()
变体。
此外,属性机制经过优化以按顺序处理路径,因此无论如何最好将其留给主进程。
和:
在 Git 2.32(2021 年第 2 季度)中,结账机制已学会在可能的情况下并行执行文件的实际写出。
请参阅Matheus Tavares ( matheustavares
)的提交68e66f2 (2021 年 4 月 19 日)和提交 1c4d6f4 、 提交 7531e4b 、 提交 e9e8adf 、 提交 04155bd (2021 年 4 月 18 日)。
(由Junio C Hamano 合并gitster
提交 a1cac26,2021年 4 月 30 日)
parallel-checkout
: 添加配置选项合著者:Jeff Hostetler
签字人:Matheus Tavares
通过引入两个新设置使并行结帐可配置:>-
checkout.workers
和
checkout.thresholdForParallelism
。
第一个定义工作人员的数量(其中一个表示顺序结帐),第二个定义尝试并行结帐的最小条目数。为了确定 checkout.workers 的默认值,并行版本在 linux 存储库中的三个操作期间进行了基准测试,使用冷缓存:克隆 v5.8,从 v2.6.15 检出 v5.8(检出 I)并检出 v5。 8 来自 v5.7(结帐 II)。
下面的四个表显示了 5 次运行的平均运行时间和标准差:SSD 上的本地文件系统、HDD 上的本地文件系统、Linux NFS 服务器和 Amazon EFS(均在 Linux 上)。
每个并行结帐测试都是使用在该环境中带来最佳整体结果的工作人员数量执行的。本地固态硬盘:
Sequential 10 workers Speedup Clone 8.805 s ± 0.043 s 3.564 s ± 0.041 s 2.47 ± 0.03 Checkout I 9.678 s ± 0.057 s 4.486 s ± 0.050 s 2.16 ± 0.03 Checkout II 5.034 s ± 0.072 s 3.021 s ± 0.038 s 1.67 ± 0.03
本地硬盘:
Sequential 10 workers Speedup Clone 32.288 s ± 0.580 s 30.724 s ± 0.522 s 1.05 ± 0.03 Checkout I 54.172 s ± 7.119 s 54.429 s ± 6.738 s 1.00 ± 0.18 Checkout II 40.465 s ± 2.402 s 38.682 s ± 1.365 s 1.05 ± 0.07
Linux NFS 服务器(v4.1,在 EBS 上,单一可用区):
Sequential 32 workers Speedup Clone 240.368 s ± 6.347 s 57.349 s ± 0.870 s 4.19 ± 0.13 Checkout I 242.862 s ± 2.215 s 58.700 s ± 0.904 s 4.14 ± 0.07 Checkout II 65.751 s ± 1.577 s 23.820 s ± 0.407 s 2.76 ± 0.08
EFS(v4.1,在多个可用性区域上复制):
Sequential 32 workers Speedup Clone 922.321 s ± 2.274 s 210.453 s ± 3.412 s 4.38 ± 0.07 Checkout I 1011.300 s ± 7.346 s 297.828 s ± 0.964 s 3.40 ± 0.03 Checkout II 294.104 s ± 1.836 s 126.017 s ± 1.190 s 2.33 ± 0.03
上述基准测试表明,并行校验对位于 SSD 或分布式文件系统上的存储库最有效。
对于旋转磁盘和/或旧机器上的本地文件系统,并行性并不总能带来良好的性能。
出于这个原因,checkout.workers 的默认值为 1,又名
顺序结帐。为了确定
checkout.thresholdForParallelism
的默认值,在“本地 SSD”设置中执行了另一个基准测试,其中并行检查显示是有益的。
这一次,我们比较了git checkout -f
( man )的运行时间,在有和没有并行的情况下,在从 Linux 工作树中随机删除越来越多的文件之后。
下面的“顺序回退”列对应于 checkout.workers 为 10 但checkout.thresholdForParallelism
等于待更新文件数加一的执行(因此我们最终按顺序写入)。
每个测试用例被抽样 15 次,每个样本随机删除一组不同的文件。
以下是结果:sequential fallback 10 workers speedup 10 files 772.3 ms ± 12.6 ms 769.0 ms ± 13.6 ms 1.00 ± 0.02 20 files 780.5 ms ± 15.8 ms 775.2 ms ± 9.2 ms 1.01 ± 0.02 50 files 806.2 ms ± 13.8 ms 767.4 ms ± 8.5 ms 1.05 ± 0.02 100 files 833.7 ms ± 21.4 ms 750.5 ms ± 16.8 ms 1.11 ± 0.04 200 files 897.6 ms ± 30.9 ms 730.5 ms ± 14.7 ms 1.23 ± 0.05 500 files 1035.4 ms ± 48.0 ms 677.1 ms ± 22.3 ms 1.53 ± 0.09 1000 files 1244.6 ms ± 35.6 ms 654.0 ms ± 38.3 ms 1.90 ± 0.12 2000 files 1488.8 ms ± 53.4 ms 658.8 ms ± 23.8 ms 2.26 ± 0.12
从以上数字来看,100 个文件似乎是阈值设置的合理默认值。
注意:最多 1000 个文件,我们观察到并行代码的执行时间随着文件数量的增加而下降。
这是一个相当奇怪的行为,但在多次重复中观察到。
超过 1000 个文件时,执行时间会随着文件数量的增加而增加,正如人们所预料的那样。关于测试环境:本地 SSD 测试是在运行 Manjaro Linux 的 i7-7700HQ(4 核超线程)上执行的。
本地 HDD 测试在运行 Debian 的 Intel(R) Xeon(R) E3-1230(也是 4 核超线程)、HDD Seagate Barracuda 7200.14 SATA 3.1 上执行。
NFS 和 EFS 测试在具有 4 个 vCPU 的 Amazon EC2 c5n.xlarge 实例上执行。
Linux NFS 服务器在具有 2 个 vCPUS 和 1 TB EBS GP2 卷的 m6g.large 实例上运行。
在每次计时之前,linux 存储库被删除(或检出回到之前的状态),并执行sync && sysctl vm.drop_caches=3
。
git config
现在包含在其手册页中:
checkout.workers
更新工作树时要使用的并行工作者的数量。 默认为一个,即顺序执行。 如果设置为小于 1 的值,Git 将使用与可用逻辑核心数一样多的工作线程。 此设置和
checkout.thresholdForParallelism
会影响所有执行结帐的命令。 例如结帐、克隆、重置、稀疏结帐等。注意:并行检查通常为位于 SSD 或 NFS 上的存储库提供更好的性能。 对于旋转磁盘和/或具有少量内核的机器上的存储库,默认的顺序检出通常表现更好。 存储库的大小和压缩级别也可能影响并行版本的性能。
checkout.thresholdForParallelism
当使用少量文件运行并行检查时,子进程生成和进程间通信的成本可能超过并行化收益。
此设置允许定义应尝试并行检出的最小文件数。
默认值为 100。
并且,仍然是 Git 2.32(2021 年第二季度),“并行结帐”的最后部分:
请参阅提交 87094fc 、 提交 d590422 、 提交 2fa3cba 、 提交 6a7bc9d 、 提交 d0e5d35 、 提交 70b052b 、 提交 6053950 、 提交 9616882 (2021 年 5 月 4 日),作者Matheus Tavares ( matheustavares
) 。
(由Junio C Hamano 合并gitster
提交 a737e1f ,2021 年 5 月 16 日)
checkout-index
:添加并行结帐支持签字人:Matheus Tavares
允许 checkout-index 使用并行结帐框架,遵循 checkout.workers 配置。
checkout-index 中有两个代码路径调用
checkout_entry()
,因此可以使用并行结帐:
checkout_file()
,用于写入命令行明确给出的路径; 和checkout_all()
,用于在给出--all
选项时写入索引中的所有路径。在这两种操作模式下,checkout-index 不会在
checkout_entry()
失败时立即中止。
相反,它会在以非零退出代码退出之前尝试检查所有剩余路径。
为了在使用并行检查时保持这种行为,我们必须允许run_parallel_checkout()
在我们退出之前尝试写入排队的条目,即使我们已经从之前的checkout_entry()
调用中得到错误代码。然而,
checkout_all()
不会返回错误,它调用exit()
代码 128。我们可以让它在退出前调用run_parallel_checkout()
,但如果我们统一两个结帐的退出路径,代码将更容易理解-cmd_checkout_index()
中的索引模式,并让此函数负责与并行结帐 API 的交互。
所以让我们这样做吧。有了这个变化,我们还必须考虑是否要继续使用 128 作为
git checkout-index --all
( man )的错误代码,而我们使用 1 作为git checkout-index
( man )<path>
(即使当实际错误是一样的)。
由于只为--all
使用代码 128 没有太大价值,并且文档中没有提及它(因此更改它不太可能破坏任何现有脚本),让我们在checkout_entry()
上使用代码 1 退出两种模式checkout_entry()
错误。
在 Git 2.33(2021 年第 3 季度)之前,并行检查代码路径没有初始化用于以面向未来的方式与工作进程通信的对象 ID 字段。
请参阅Matheus Tavares ( matheustavares
)的提交 3d20ed2 (2021 年 5 月 17 日)。
(由Junio C Hamano 合并gitster
提交 bb6a63a ,2021 年 6 月 10 日)
parallel-checkout
:将新的object_id
algo 字段发送给工作人员签字人:Matheus Tavares
存储 SHA-1 名称的
object_id
在哈希数组的末尾有一些未使用的字节。
由于不使用这些字节,因此通常也不会将它们初始化为任何值。
但是,在parallel_checkout.c:send_one_item()
中,缓存条目的object_id
被复制到一个缓冲区中,该缓冲区稍后通过管道write()
发送给结帐工作人员。
这使得 Valgrind 抱怨将未初始化的字节传递给系统调用。但是,自从cf09832 (
hash
:将算法成员添加到 struct object_id,2021 年 4 月 26 日,Git v2.32.0-rc0—— 合并列在批次 #15中)(“散列:将算法成员添加到 structobject_id",
2021 -04-26),这里使用hashcpy()
不再足够,因为它不会从object_id
复制新的算法字段。
让我们添加并使用一个新函数,它通过在目标object_id
中填充哈希数组的末尾来满足我们复制所有重要object_id
数据同时仍然避免未初始化字节的要求。
通过此更改,我们也不再需要将send_one_item()
的目标缓冲区初始化为零,因此让我们从xcalloc()
切换到xmalloc()
以明确这一点。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.