单仓在 GB 级很好用,到 TB 级、百万文件、每小时上千次提交之后,瓶颈就从磁盘转到了协议和索引。完整克隆要拉全量历史,检出要写满工作区,按历史遍历的操作随提交量变慢。服务端实现通常把内容存储、变更组织和派生数据拆成独立层次,再按仓库分片。本文面向正在评估单仓规模上限的平台工程师。
单仓从 GB 级涨到 TB 级,最先撞上哪堵墙
克隆是头一处瓶颈。客户端默认要拿全量对象和全量历史。仓库越大,首次拉取越慢。检出受文件系统元数据操作牵制,几十万个文件写进工作区,机器再多核也快不起来。
按历史遍历的操作更麻烦。git log、git blame、代码搜索要沿提交链回溯,代价随提交数增长,与当前工作区大小无关。历史一长,这些命令就做不到交互级响应。
目录级权限为什么在单仓里难做
权限按目录切,判断依据却分散在历史里。文件改过名、移动过目录,路径和权限的对应关系就不再是一条静态规则。服务端如果在读路径上逐条判断,每次取文件都要查一遍,规则一多延迟就上来。
可行的做法是把判断放在服务端入口,按目录前缀预先算出可见集合,再对取对象、列目录、拉历史这些请求统一过滤。代价是要维护一份权限索引,权限一变更就得让它跟上历史。
派生数据放在服务端算,省下的是什么
代码索引、构建缓存、代码搜索结果都能从提交内容重新算出来,属于派生数据。让每个客户端各自现算,重复的是同一份计算。服务端算一次,所有客户端共享,省下的是重复的机器和时间。
约束是派生数据必须和提交对应。提交进来之后排队生成,客户端读到的是某个提交对应的版本。它丢了重算即可,所以能和版本库本身的数据分开存、分开扩。
服务端分层解决了什么
内容存储层按内容寻址保存文件内容,上层不关心底层是本地文件还是对象存储。变更组织层把提交和文件树组织成可增量读取的结构。派生数据层独立于前两层,单独扩容和重建。三层里只有变更组织必须强一致,另外两层都能靠重建收敛。
分片之后新增哪些代价
按仓库分片能绕开单实例的容量上限,代价落在几处。跨分片的引用和事务要协调,原来一个事务里能做完的事现在拆成多步,中间任何一步失败都得有人收拾。引用一致性也要在分片之间维持。客户端看到的分支状态不能前后矛盾,这一条在写入频繁时最难保证。
客户端兼容层同样要处理。客户端不知道仓库被分成了几片,服务端要么把分片细节藏起来,要么按路径把请求路由过去。迁移期间新旧仓库并存,双写、校验、切换都要留回退路径。
客户端这一侧能缓解什么
- 浅克隆只取最近若干次提交,克隆时间和磁盘占用一起下降,代价是历史查询受限。
- 稀疏检出只把需要的目录写进工作区,检出时间和本地磁盘占用随之下降。
- 按需取内容让客户端先拿目录树,打开文件时才下载正文。
- 只取指定路径的历史,避开全仓库的提交链遍历。
这些手段能把日常操作拉回可用范围,换来的复杂度要由工具链承担。构建系统、IDE 和 CI 都得适应不完整的工作区,缓存和增量构建的假设也要跟着改。
常见问题
单仓到什么规模就该考虑分片? 看克隆时间和历史操作的响应,别只看体积。日常操作还能接受就不必分。
浅克隆能当长期方案吗? 日常开发可以。需要完整历史做 blame 或二分排查时会受限,要留一条取全量的路径。
派生数据和版本库能放同一套存储吗? 可以,但两者生命周期不同,分开存更容易各自扩容和重建。