考古问题竟然被我刷到,我司的Git-LFS server就是我写的。
Git-LFS主要解决的问题是Repo包含太多(out-dated)的大文件会导致clone/push的速度很慢,也很影响Git Server的性能,例如Machine Learning的动辄几个GB的model,Game Development的素材。
所以通过Git-LFS把这些大的binary或者asset文件放在其他地方例如 AWS S3,便可以减轻Git Server的负担,加快clone/push的速度(虽然据我所知Git-LFS client的上传下载依然是单线程的),但假设你的机器和S3在同一个Data Center,那么即使单线程,上传下载也能达到 1Gbps 左右。
首先,LFS 不是 GitHub 发明的。即使回到 GitHub 发布这条新闻的 2015 年,我还是会说「GitHub 终于支持 LFS 了。是什么导致 LFS 支持这么晚才发布?」
LFS 解决的问题是大文件在 git 的存储,尤其是那些不能 diff 也不需要 diff 的二进制大文件。git 的数据结构设计为保留历史上出现过的每一个文件的每一个版本,这对于代码等小文本文件来说不是难题,而且文件拆分得越细,就有越多万年不改动的文件,在 git 的数据结构中这个文件的这个版本只出现一次。在这个文件不发生变化时,每一个 commit 里的这个文件都只是一个指针,指向同一个实体文件。
大文件如果偶尔改东一下的话,这种存储方式的性能就很糟糕,因为每次改动都要储存一个这个文件的新版本,而且是完整地存储。LFS 能够解决这个问题,解决的办法是把大文件放在 git 核心数据结构之外,单独进行 diff 和传输。LFS 的本质是指针文件,当一个大文件被添加到 LFS 之后,它在 git 上就变成了一个指向 LFS 的 hash,而不再是文件本身。
如果 git 本身和 LFS 都是用指针,为什么 LFS 更适合用来存储二进制大文件?因为它们同步时 diff 的方式不一样。git 本身是用 rsync 同步的,rsync 非常擅长处理文件大体不变但中间这里加一点东西、那里减一点东西的变化,它只需要同步变化那一点的数据。想象我和你各自拥有一个文件略为不同的两个版本,rsync 可以在不需要把一份文件完全传输到另一端的前提下,找出这两个版本哪里有差异,然后之传输差异的部分。想要深入理解 rsync 如何做到这一点的,可以去看 rsync 的算法。
rsync 的这种特性在处理变化毫无规律的二进制文件时一点优势都没有,甚至还有劣势。这时候还不如给每个文件算一个 sha256 的 hash,只要 hash 不一样就传输整个文件。因此在 git 之上有了 LFS 这个扩展,它只看指针的 hash 是否相同。