关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

2026 Gitea 自建仓库服务器租用选型手册:裸仓库目录与备份盘的容量避坑全攻略

发布时间:2026-09-28

2026 Gitea 自建仓库服务器租用选型手册:裸仓库目录与备份盘的容量避坑全攻略

自建代码仓库这件事,最容易被低估的不是机器贵不贵,而是它的负载形状跟别的业务压根不是一回事。我见过太多团队按"跑个内部系统,四核八G肯定够"的思路去配 Gitea,结果第一次全量推送卡了二十分钟,第一次自动 gc 直接把内存吃穿、进程被系统杀掉,仓库从此进入"越用越慢、越慢越不敢 gc"的死循环。等他们意识到要加内存,仓库已经膨胀到 gc 一次要跑几个小时的地步。

问题出在参照系上。Web 服务和数据库的负载相对平滑,你拿平均 CPU 使用率做容量规划问题不大;Git 服务不是这样。它平时几乎不占资源——几十个人一天推几十次,CPU 曲线平得像一条直线——可一旦有人推了个大包、有人拉了个全量克隆、或者后台 gc 碰到触发阈值,资源需求会在几秒钟内冲到平时的几倍甚至十几倍。按稳态配机器的人,最后都栽在这几个尖峰上,而不是栽在平均值上。

更要命的是,Git 操作的峰值不像 HTTP 请求那样可以排队等着。请求排队顶多是慢,用户刷新一下就过去了;git 进程内存不够是直接失败,返回给开发者的就是一行报错,而且失败之后仓库状态往往比之前更糟——松散对象没被打包、pack 数量继续涨、下次 gc 的门槛更高。这个正反馈才是真正让人头疼的地方。

先把本篇的几个关键判断摆出来,后面再一条条拆:① Git 服务是"海量小文件加突发 CPU 与内存"的负载,容量规划必须按峰值做,按平均值做一定翻车;② Gitea 的仓库在磁盘上是裸仓库目录,里面是 objects、pack、refs 这些结构,一个中等项目就是几万到几十万个对象文件,inode 和随机读会比容量更早见底,看盘要看 df -i 而不是只盯 df -h;③ git gc 与 repack 会 fork 子进程、吃满单核、占用大量内存,内存不足是失败不是变慢;④ LFS 把大文件变成平铺的对象目录,容量增长快、inode 消耗大,开了就得单独分区;⑤ 首次克隆是"带宽加单核打包"的双重瓶颈,多核救不了;⑥ 备份盘必须独立于数据盘,容量按"仓库总量乘以保留份数再乘 1.5"估算,没做过恢复演练的备份不算备份。

自建 Git 服务的负载曲线:九成时间闲着,一成时间要命

要理解为什么 Git 服务难配,先看它的资源曲线长什么样。一个三十人团队、两百个仓库的 Gitea 实例,日常状态下 CPU 可能长期在百分之五以下,内存占用也很稳定,看上去给它两台核的机器都算浪费。可这个"稳定"是有欺骗性的——它只统计了"没人做大动作"的那部分时间。

真正吃资源的是三类事件。第一类是大型推送,有人把几百 MB 甚至几个 GB 的 pack 推上来,服务端要解包、校验、写松散对象,然后很可能顺带触发一次 gc。第二类是全量克隆,服务端要为这次请求现场生成一个 pack,这是典型的 CPU 密集活儿。第三类是后台维护任务,无论是 Gitea 自己的定时任务还是 git maintenance,一旦跑起来就是持续的 CPU 与内存占用。

这三类事件有个共同特点:需求是突发的、量级是几倍到十几倍跳变的、而且往往扎堆出现。早上九点半开工,二十个人同时拉取;下午 CI 流水线集中触发,十几个任务同时克隆同一个大仓库;晚上例行维护任务启动,几十个仓库排队 gc。这三个时刻的机器,和平时的机器,几乎不像是同一台。

所以选型的第一条原则就得跟着改:内存按峰值配,磁盘按 inode 和随机读 IOPS 配,CPU 看单核性能而不是只看核数。这三句话看着简单,实际采购时大部分人会本能地按"核数多内存大硬盘大"去挑,然后在最关键的单项上省钱——比如用机械盘当数据盘,或者把内存卡在刚好够稳态的档位。

裸仓库目录里到底躺的是什么:objects、pack、refs 三件套

很多人以为 Gitea 的仓库目录里放的是源码,其实不是。你在 repositories 目录下看到的是一堆以 .git 结尾的裸仓库目录,里面没有 checkout 出来的工作区,只有 Git 自己的对象数据库。这个区别很关键——它决定了这块盘的读写特征,也决定了容量估算方式。

拆开一个裸仓库目录,主要就这么几样。objects 目录是主体,里面既有按哈希前两位分成的二百五十六个子目录(存放松散对象,一个对象一个文件),也有 objects/pack 下的 .pack、.idx 和可能的 .bitmap 文件。refs 目录和 packed-refs 文件存引用,config 存仓库配置,HEAD 指向默认分支,info 和 hooks 各有用途,logs 记引用的变更历史。

松散对象和 pack 的关系是这样的:每次推送新提交,Git 先把新对象以松散形式写进 objects,一个 blob 一个文件;松散对象攒到一定数量(默认大约是六千七百个),或者 pack 文件攒到一定个数,就会触发一次 gc,把这些零散文件打包成一个大的 pack 文件,并在包内做 delta 压缩。所以一个"正常"的仓库磁盘上既有少量大文件,也可能在高峰期堆积大量小文件。

一个中等规模的项目是什么量级?三年历史、几万次提交、几百 MB 的 pack 体积,这样的仓库在企业里非常常见,它对应的对象数是几万到几十万个。如果团队提交频繁且 gc 不及时,高峰期一天新增几千个松散对象文件也不稀奇。把两百个这样的仓库放在一块盘上,你面对的就是几百万个文件的文件系统——这时候容量早就不是主要矛盾了。

还有个容易忽略的点:pack 文件虽然是"大文件",但它不是顺序读那么理想。pack 内的对象是用 delta 链存的,读取某个版本的文件往往要沿着链回溯几个基础对象,等于一次逻辑读对应多次物理随机读。pack 体积越大、历史越长,这个回溯的代价越高。这就是为什么"仓库变大之后什么都变慢",而不仅仅是 clone 变慢。

为什么磁盘还剩一半却慢得要命:inode 比容量更早见底

有个经典故障现场值得一提:运维看 df -h 显示数据盘还剩百分之四十空间,监控系统一切正常,可开发者推送就是失败,日志里赫然写着磁盘空间不足。查了半天才发现 inode 用光了。这种事在 Git 服务器上发生的概率,比在普通 Web 服务器上高得多。

inode 是什么?简单说就是文件系统里"文件"这个概念的身份证,每个文件、每个目录都要占一个。它在格式化的时候就按一个固定比例(比如每 16KB 分配一个)一次性划好了,用完就是用完,哪怕磁盘还剩一半空间,也一个文件都建不出来。df -h 看的是字节,df -i 看的是 inode,两者可以完全不同步。

Git 仓库吃 inode 特别凶,原因前面其实已经埋下了:松散对象一对象一文件,LFS 对象也是一对象一文件,refs、logs 还各有自己的文件。小仓库多的时候尤其明显——两百个仓库,每个仓库哪怕只有一千个松散对象,也是二十万个 inode 就这么没了。而且这个消耗是持续累积的,不会因为某次 gc 打包就回落太多。

所以正确做法是把 inode 使用率当成一个独立监控项,告警阈值设在百分之八十左右,而不是只看磁盘水位。日常巡检的时候 df -i 和 df -h 一起敲,这是个成本为零但能救命的习惯。真到 inode 见底再处理,你面对的是一个几乎无法在线修复的局面。

根上的解决办法要在上架时就定:要么选 XFS(它的 inode 是动态分配的,不会出现"空间有余而 inode 耗尽"),要么在格式化 ext4 时显式调小每个 inode 对应的字节数,给 inode 多预留一些。这两件事都必须趁盘是空的时候做,等数据写满了再想改,就得停机、备份、重做文件系统、再恢复,代价高得离谱。

inode 之外还有另一半问题:随机读。机械盘一次寻道是毫秒级,几万次随机读就是几十秒到几分钟;NVMe 固态盘的随机读 IOPS 是机械盘的成百上千倍。Gitea 这种"海量小文件加随机读"的负载,放在机械盘上,体感会从"有点慢"直接跳到"没法用"。这条不是容量问题,加多大的机械盘都解决不了。

git gc 与 repack 是突发峰值:内存不够是直接失败不是变慢

gc 到底在干什么?把松散对象收进 pack、在包内做 delta 压缩、清理不可达对象、更新 commit-graph 和 bitmap 这类加速结构。听起来像是"整理房间",实际上这是一次算力与内存的集中消耗,而且它会在你最不希望的时刻发生。

资源特征上有三个要点。一是它会 fork 出子进程干活,不是在主进程里慢慢跑;二是 delta 压缩是 CPU 密集且吃内存的操作,Git 会在内存中维护一个滑动窗口去找相似对象,窗口越大压缩率越高、内存消耗也越大;三是打包线程数默认跟 CPU 核数挂钩,多线程意味着同时存在多份窗口内存。核数越多的机器,gc 的峰值内存反而可能更高——这点反直觉,但确实是这样。

内存不足的后果要特别说清楚:它不是"慢一点",是 OOM 干掉进程或者 Git 自己报错退出。而 gc 失败的后果是松散对象继续堆积、pack 数量继续增加、下一次 gc 的门槛更高、内存需求更大,于是下一次更容易失败。这个恶性循环我见过不止一次,最后都是靠临时加内存、手工分批处理才爬出来。所以内存要按峰值预留,不能按稳态,这句话在 Git 服务上不是修辞。

触发时机也有讲究。推送之后 Gitea 可能按松散对象数或 pack 数触发自动 gc,也可以交给定期维护任务处理。无论哪种,都要避开业务高峰——放在上午十点跑 gc,等于在开发者最需要推代码的时候跟他们抢资源。把维护窗口挪到凌晨,是成本最低的优化之一。

削峰的手段其实很成熟,只是很多人不知道:在仓库或全局配置里显式限制打包窗口内存、delta 缓存大小和线程数,把峰值压到一个确定的范围内。代价是压缩率略降、耗时变长,换来的却是"一定不会失败"。对一台要稳定跑几年的代码服务器来说,这个交换非常划算——慢几分钟没人会抱怨,失败一次全组人都得停下等你。

还有一个削峰办法是控制并发。几十个仓库同时 gc,内存需求是叠加的。改成串行或者限制同时进行的任务数,内存峰值立刻就下来了。Gitea 的后台任务队列本身就是有并发控制的,别把它调得太大,这条参数上的"性能优化"经常是事故来源。

repack 的内存怎么估:一个能直接代入的口径

先说清楚:没有放之四海皆准的精确数字,pack 内容、delta 链长度、Git 版本都会影响结果。但你可以用一个能直接代入的框架算出八九不离十的量级,再用一次真实压测把它校准。这比拍脑袋定内存靠谱得多。

框架是这样的:峰值内存约等于常驻内存加上并发任务数乘以单任务开销。常驻内存包括 Gitea 自身进程、数据库、系统页缓存的预留;单任务开销约等于打包窗口内存加上 delta 缓存再加上一点基础开销。写成式子就是:峰值内存 ≈ 常驻内存 + 并发任务数 ×(窗口内存 + delta 缓存 + 基础开销)。这几个变量都能从你的配置里直接读出来。

代入一个例子。假设你把窗口内存限制在 512MB、delta 缓存 256MB、打包线程设成 2,单个 repack 任务的量级大约在一 GB 上下;允许两个任务并发就是两 GB;再加上 Gitea 常驻一两 GB、数据库和系统预留一两 GB,那么八 GB 内存的机器是可以跑得住的,十六 GB 会从容很多。这个数字属于预估价格对应的配置档位口径,实际还要看你数据库和 CI 是不是也在这台机器上。

如果你完全不限制窗口内存,那就得按仓库体积粗估。一个量级参考:仓库 pack 体积在一 GB 量级时,单线程 repack 的峰值内存大致在一 GB 上下;pack 体积到十 GB 量级时,峰值可能到四到八 GB 量级。注意这是粗估,不是承诺,实际以压测为准——差别主要来自 delta 链的深度和对象相似度,二进制多的仓库往往比纯代码仓库更吃内存。

想拿到可信数字,办法只有一个:挑你最大的那个仓库,在与生产同配置的机器上跑一次完整的 repack,用带峰值内存统计的命令去测,看最大常驻内存是多少。这个数字就是你要的内存底线,再乘上你打算允许的并发任务数,加上常驻部分,就是采购时该报的内存容量。别嫌麻烦,这一步省下来,后面要用几倍的时间还。

内存之外别忘了磁盘。repack 的过程是先写一份新 pack,成功后再替换旧的,也就是说峰值时刻磁盘上会同时存在两份 pack。所以数据盘要留出仓库总体积一点二倍以上的临时空间,否则 repack 跑到一半盘满,失败事小,留下半截状态事大。这条和前面的 inode 一起看,你会发现 Git 服务的盘从来不是"容量够就行"那么简单。

LFS 该不该开:开了之后盘要怎么分

LFS 解决的是 Git 的老毛病:它天生不擅长存二进制。一个一百 MB 的设计稿,每次改动都会在对象库里留一份完整副本,改十次就是一个 GB,而且这些历史副本会永久跟着仓库走,克隆的人每次都要为它付带宽和时间成本。LFS 的做法是把大内容挪出对象库,只在 Git 里留一个几百字节的指针文件。

但挪出去不等于消失,它换了个地方、换了个形态。Gitea 的 LFS 对象目录是按对象 ID 前几位分层的平铺目录,一个文件对应磁盘上的一个真实文件,不压缩、不做 delta 去重(同内容同 ID 才会复用)。这个形态带来三个直接后果:容量增长快、inode 消耗大、而且对象一旦堆积就很难清理。

难清理这一点值得多说两句。普通 Git 对象你可以靠 gc 把不可达的收走,LFS 对象是否还被引用要反查仓库历史里的指针文件才能判断,清理成本高、风险也高,误删就是不可逆的数据丢失。所以 LFS 目录的增长几乎是单向的——开了 LFS 又不加管控,你会发现盘涨得比代码快得多,而且想瘦身的时候无从下手。

那到底开不开?我的判断很直接:团队确实有二进制资产要版本管理(设计稿、模型权重、安装包、数据集样本、编译产物归档)就开,纯代码仓库开 LFS 纯属给自己找麻烦。折中一点的话,单文件超过几 MB 且会被反复修改的内容才值得进 LFS;偶尔放一个不常变的大文件,直接放仓库里问题也不大。

盘怎么分是重点。LFS 目录应该单独放一块盘,或者至少独立成一个分区并配上配额。理由是它的增长曲线和仓库盘完全不同:仓库盘增长慢、随机读密集、inode 敏感;LFS 盘增长快、以顺序读写为主、容量敏感。分开之后,LFS 把盘撑爆也不会连累仓库变成只读,故障域被切开了,这一点在半夜处理故障时价值极高。

清理策略要提前定,别指望事后补救。可行的做法是给 LFS 目录设容量配额和告警,给团队定清楚哪些类型允许进 LFS、保留多久;真需要清理时,先完整备份、再在测试实例上验证清理命令的效果,确认无误才上生产。Gitea 自带的检查与修复类命令能用,但跑之前请务必确认备份是可恢复的。

SSH 与 HTTP(S) 的并发模型差异:核数和内存决定并发上限

两种协议在服务端的行为差别很大。SSH 方式下,每次推送或拉取都会在服务端拉起进程处理,连接断开才回收;进程内的开销跟仓库大小正相关,大仓库尤其明显。所以 SSH 的并发上限本质上是由内存和核数共同决定的,跟带宽关系反而不大。

单个连接要多少内存?小仓库可能只要几十 MB,大仓库几百 MB 到一 GB 以上都有可能,取决于 pack 体积和 delta 搜索的窗口大小。这意味着"能同时服务多少人"不能拍脑袋——一个装了几个 GB 大仓库的服务器,二十个并发克隆就可能把内存吃干,而同样的并发在全是小仓库的机器上毫无压力。

HTTP(S) 方式走的是 Web 服务,连接可复用,接入层可以做缓存、限流、审计,也更容易穿透企业网络(四百四十三端口基本不会被防火墙拦)。但要注意,真正的大操作最终还是会落到 Git 子进程上,所以 HTTP 并不是"省资源"的银弹,它省的是连接管理的开销,打包和压缩那份开销一分不少。

实践上的分配我一般这样建议:日常开发推拉走 SSH,认证简单、开发者习惯、密钥管理成熟;CI 与镜像同步走 HTTP(S),方便限流、便于审计、也更容易接企业的统一认证。如果团队要求统一认证和完整审计日志,那就全部走 HTTPS,代价是服务端要配好反向代理和超时时间——大仓库克隆耗时长,代理层默认超时经常把它掐断。

并发上限怎么测?写个脚本并发克隆或拉取最重的那个仓库,逐步加大并发,盯内存和负载,找到开始报错或明显超时的那个点,然后把这个数除以二作为生产环境的并发上限。别用理论值——理论值忽略了你的仓库实际长什么样。

核数的正确用法也在这里说清:多核主要用来并行处理多个连接,不是加速单次操作。所以核数按并发量配,单核性能按 gc 和打包速度配,两者都要看。一台三十二核但单核羸弱的老机器,跑 Gitea 的体感可能还不如一台八核高频的新机器,原因就在这儿。

首次克隆为什么救不了:带宽加单核打包的双重瓶颈

有个场景值得单独讲:一个两 GB 的仓库,端口带宽一百兆,理论上传完不过十几分钟,可实际克隆花了四十分钟。登上服务器一看,一个 CPU 核心跑满百分百,其余三十一个核心在睡觉。这个画面非常典型,也很容易被误读成"带宽不够"。

真实原因是打包环节。服务端要为这次克隆生成一个 pack,delta 压缩的主体环节是单线程执行的,多核帮不上忙;即便开启了 bitmap 能减少对象遍历,最终生成传输包的开销也还在,而且热路径上仍然是单核在扛。这就是为什么很多人加了带宽、加了核数,克隆时间纹丝不动。

结论要敢于说死:多核救不了首次克隆慢。这条我反复跟客户讲,讲完还是有人不信,非要先加一轮配置再回头找我。要改善首次克隆,得从别的方向下手。

客户端侧最好用的是浅克隆,只取最近若干次提交,CI 场景尤其合适——流水线要的是能编译,不是完整历史。再进一步是按需克隆,先只拉元数据、用到哪个文件再去取。两种方式都能把首次拉取的量级砍掉一个数量级,代价是本地仓库历史不完整,需要的话可以后续再补。

服务端侧的缓解手段包括:开启 bitmap 与 commit-graph 这类加速结构、用定期维护任务预先把仓库整理到位、给常用分支预生成可用包。但最有效的其实是本地镜像或代理缓存——让 CI 的高频拉取走局域网,第二次以后的拉取根本不碰公网和主库。这一招对 CI 密集团队的效果,比升级任何硬件都明显。

带宽账也要单独算。首次克隆和 CI 全量拉取是出网的绝对大头,估算时应该按"每天几次全量拉取乘以仓库大小"来算,而不是按"几十个人日常推拉"来算。后者一天可能不到几个 GB,前者一天能轻松上百 GB。很多团队带宽买小了,都是栽在这个估算口径上。

备份的三种做法:全量拷贝、git bundle、镜像仓库怎么取舍

先明确要备份什么,这是最容易漏的一步。完整的可恢复状态至少包含五样:裸仓库目录、LFS 对象目录、数据库(议题、合并请求、用户、权限都在这里)、配置文件与密钥(尤其是签名密钥,丢了会话全掉)、附件与头像等用户上传内容。只备份仓库目录是最常见的疏漏,等真要恢复时才发现议题和权限全没了。

第一种是全量目录拷贝,或者直接用 Gitea 自带的导出命令打包。优点是完整、恢复路径短、出问题时不用来回拼;缺点是体积大、每次耗时随仓库总量线性增长、而且需要一致性窗口——最好先停止写入再拷贝,否则可能拿到一个数据库与仓库文件不同步的快照。适合做低频的兜底全量。

第二种是 git bundle,把单个仓库的全部引用打包成一个文件。它的优点非常实在:单文件、好搬运、好校验,恢复时直接当远程仓库克隆回来就能验证内容是好的。缺点同样明显:每个仓库要单独执行一遍,仓库多了要自己写循环脚本;而且它不包含数据库、不包含 LFS,只能作为仓库层的备份手段。

第三种是镜像仓库,用裸镜像方式克隆到备份位置,之后靠定期更新做增量。优点是可以增量同步、可以用完整性检查命令校验对象、恢复时直接就能当仓库用;缺点是不含数据库和 LFS,而且镜像保留全部历史,体积会随原仓库一起增长。它是三种方式里"日常性价比"最高的一个。

我自己的取舍是这样的:仓库用镜像做日常增量(快、可校验、占用可控),数据库单独定时导出,LFS 目录用同步工具做增量,配置与密钥单独归档保存;在此之上再加一层每周一次的全量导出作为兜底。这样日常备份轻,兜底备份全,两层互为保险,任何单层失效都不至于全盘皆输。

校验环节不能省。备份完要能做到三件事:能列出来、能过完整性检查、能真的克隆出来。建议写个定时任务,每月随机挑几个仓库恢复到临时目录验证一次,把结果记到日志里。这个动作花不了多少资源,但它决定了你在真正的故障面前是"有备份"还是"以为有备份"。

备份盘必须独立:容量按仓库总量乘以保留份数再乘 1.5 来算

先说红线:备份盘必须独立于数据盘。同一块物理盘上的两个目录,盘坏了两个一起完蛋,这不叫备份,这叫自我安慰。同理,磁盘阵列也不是备份——它防的是单盘硬件故障,防不了误删、防不了逻辑损坏、也防不了整机故障。

再进一步,理想情况下备份最好也不在同一台机器上。至少要做到数据盘与备份盘物理分离,条件允许再加一份异地副本。我知道很多小团队预算有限,那就退而求其次:本机第二块物理盘加一份定期打包传到对象存储,成本不高但能扛住"整机挂掉"这种最坏情况。

容量估算的口径是这样的:备份盘容量约等于仓库总量乘以保留份数再乘一点五。那个一点五不是拍脑袋加的,它涵盖了三件事——增量过程中需要的新旧共存空间、重写包时的临时翻倍开销、以及做恢复演练时需要的腾挪余地。省掉这个系数,你迟早会在某次备份时撞上"空间不足"。

代入一个例子。仓库总量两百 GB(含 LFS),打算保留三份(日、周、月),那就是两百乘以三再乘一点五,约九百 GB,备份盘按一 TB 起配比较稳。如果这台机器上还要跑 repack,临时空间要另外算,不能从这九百 GB 里挤。

别忘了增长。代码仓库的增长通常是缓慢但持续的,一旦开了 LFS 就会变得陡峭。备份盘容量要按"一年后的仓库总量"来算,而不是按今天的总量。我一般会让客户把过去半年的增长曲线拉出来,外推一年,再乘上那个一点五,得出的数字才敢下单。

最后是恢复演练,这句话值得单独成段:没验证过的备份不算备份。至少每季度完整演练一次——拿备份在一台临时机器上起一个 Gitea,克隆几个仓库、检查议题是否完整、试着下载几个 LFS 文件、确认权限和用户在。演练耗时也要记下来,真出事的时候,这个耗时就是你的实际停机时长,你得事先知道它是四十分钟还是四小时。

一张表看清四个目录该怎么摆盘

把上面讲的这些落成一张摆盘表,采购前照着核对一遍,能挡掉大部分低级错误。表里"最常见的错误摆法"那一列,基本都是我在真实故障现场见过的,不是凭空编的。

目录与内容 推荐盘位 主要压力类型 容量估算口径 最常见的错误摆法
裸仓库目录(各仓库的 .git)独立 NVMe 数据盘,与系统盘分离海量小文件的随机读、inode 消耗、gc 时的突发写当前总量乘以一年增长系数,再留一点二倍以上的 repack 临时空间直接放在系统盘,仓库涨到系统盘满,整机一起挂
LFS 对象目录独立盘或独立分区,配容量配额容量线性增长快、顺序读写为主、inode 持续消耗按单次资产大小乘以版本迭代次数估算,预留一年增量和仓库目录混放,LFS 撑爆导致仓库不可写
数据库(议题、合并请求、权限)与仓库盘分离,独占一块或同盘独立分区小随机写、连接数、备份导出时的短时读压力按仓库与用户规模估算,量级远小于仓库盘但不可省不单独备份,只备份仓库目录,恢复后权限与议题全丢
备份目录(镜像、导出包、数据库导出)独立于数据盘的物理盘,最好再加一份异地周期性大块顺序写、恢复演练时的集中读仓库总量乘以保留份数再乘一点五备份目录建在数据盘上,同一块盘坏了两份一起没
日志与临时目录系统盘内但必须配轮转与上限持续追加写,无管控时增长无上限按日增量乘以保留天数,配硬性上限不做轮转,几个月后把系统盘吃满

一万网络上的两套搭法:小团队起步档与正经团队档

说了这么多原理,落到采购上其实就是两套搭法。第一套给小团队:十到三十人、几十个仓库、总量几十 GB、不开 LFS 或者只放少量资产。这种规模我建议四到八核、十六到三十二 GB 内存、NVMe 数据盘两百到五百 GB。内存给到十六 GB 起步不是浪费,那多出来的几 GB 就是留给 gc 峰值的保险。

这一档在采购上可以走两条路。如果团队还在验证阶段、仓库量也没定下来,先用弹性云起步更划算——一万云 ¥25 起(以官网实时价为准),先跑起来把流程和备份策略跑顺,等仓库规模和 LFS 量涨上来再迁到独享资源上。别一上来就按三年后的规模下单,那笔钱花得太早。

第二套给正经团队:上百人、几百个仓库、明确要开 LFS、CI 每天几十次拉取。这一档的建议是十六核以上、六十四 GB 以上内存、数据盘 NVMe 一 TB 起,备份盘独立两 TB 起。注意这里内存和备份盘是大头,CPU 反而不是——除非你的并发推拉确实很高。

如果走独享物理机,一万网络的裸金属档位里 E5-2698v4×2 ¥3999 起(以官网实时价为准)是可以对得上这套需求的,多核对并发推送和日常服务有实打实的帮助。但我必须再强调一遍前面讲过的:多核对单次 gc 和首次克隆帮助有限,别指望靠加核解决克隆慢的问题,那笔钱得花在内存、NVMe 和备份盘上。

为什么这类场景我一般首推一万网络?理由很实在。深耕 IDC 19 年(成立于 2007 年),深圳南山总部,自营机柜最快 1 分钟上架,临时加机器不用等;硬件故障 10 分钟自动迁移,7×24 中文工单平均 5 分钟响应,半夜出问题找得到人说话;免费的系统盘每日 3 份快照、30 秒回滚,系统盘层面算是上了保险。不过要提醒一句:这个快照保护的是系统盘,仓库数据盘的备份得你自己按前面那套镜像加数据库导出的方式做,别把快照当成数据备份。

网络这块也顺带说一句。BGP 多线加 CN2 GIA 回国低延迟,对有异地办公室或者经常出差的团队,克隆体验的差别是能直接感受到的——同一个仓库,办公室里二十秒,出差在外三分钟,这种落差很影响心情。至于数据盘、备份盘的增配费用,属于预估价格,以咨询为准,签约前一定把盘位数量和是否独立物理盘写进配置单,口头承诺不算数。

七个真实踩过的坑:为什么坑、怎么避

坑一:把仓库目录放在系统盘

为什么坑:系统盘一般只有几十到几百 GB,还要装系统、放日志、留快照空间。仓库目录一旦放上去,增长就是不可逆的,等系统盘满了,整机都会出问题,而且这时候连迁移都很困难——系统盘满了,很多运维命令都跑不动。怎么避:上架第一天就把仓库目录挂到独立数据盘上,用独立挂载点,并把 Gitea 配置里的相关路径改过去。这个改动必须在初始化之前做,事后迁移虽然可行但风险高。

坑二:备份盘和数据盘是同一块物理盘

为什么坑:物理盘故障是两个目录一起丢,备份形同虚设;更隐蔽的问题是,盘满的时候备份和数据会互相挤,最后两边都写不进去。怎么避:备份必须是独立的物理盘,配置单上写清楚"第二块盘"而不是"第二个目录"。预算实在紧张,至少加一份定期传走的异地副本,别让两份数据睡在同一块盘上。

坑三:没预留 gc 的内存峰值

为什么坑:gc 失败不是变慢而是中断,失败后松散对象继续堆积,形成越失败越难成功的正反馈。等你发现的时候,仓库已经膨胀到需要停机几个小时才能整理完。怎么避:按前面给的口径估算峰值并留出余量,同时显式限制打包窗口内存、缓存和线程数,把峰值压到确定范围内;维护任务排在业务低峰,并发任务数不要调高。

坑四:用机械盘跑大量小仓库

为什么坑:随机读是机械盘的死穴,几毫秒一次寻道,几万次就是几十秒。小仓库多的场景下,页面打开、拉取、推送全会变慢,而且这种慢是渐进的,一开始不明显,等明显了已经迁移困难。怎么避:数据盘一律 NVMe,容量可以小一点但介质不能将就。容量不够可以加盘,介质选错只能整体重来。

坑五:没限制单仓库体积,一个仓库塞几十 GB 二进制

为什么坑:单仓库越大,克隆越慢、gc 越吃内存、repack 时间越长,而且这个恶化是非线性的。一个几十 GB 的仓库能把整台机器的维护窗口拖垮,还会连累同机上的其他仓库。怎么避:给团队定下仓库体积上限与二进制准入规则,大文件走 LFS,超限的仓库拆分或做历史瘦身。规则要在仓库还小的时候立,等它长大了再拆,代价高十倍。

坑六:没做日志轮转

为什么坑:Gitea 自己的日志、反向代理访问日志、系统认证日志都在持续追加,没人管的话几个月就能把系统盘吃满。而且这类故障总是突然发生——前一天还好好的,第二天早上全部服务不可用。怎么避:日志轮转和保留天数在上线前配好,配硬性大小上限,同时把日志水位纳入监控。有条件的把日志单独挂一个分区,把故障域隔开。

坑七:只盯磁盘水位,忽略 inode 告警

为什么坑:inode 耗尽的表现是"磁盘还有空间却写不进去",排查方向很容易跑偏,很多人会以为是权限或者配额问题,白白浪费时间。怎么避:把 inode 使用率作为独立监控项,阈值设在百分之八十;文件系统在初始化时就选好——动态分配 inode 的文件系统,或者格式化时把每个 inode 对应的字节数调小,提前把余量留够。

上线前必须跑完的验收与压测清单

机器到手别急着迁数据,先把这几项跑完。验收的价值不在于核实配置单,而在于留下一份基线——半年后仓库变慢了,你要有东西可以对比。

第一项是磁盘随机读。用测试工具跑小粒度随机读,务必绕过页缓存、跑够时间,看 IOPS 和延迟是否符合 NVMe 该有的水平。这一项直接决定多仓库场景下的体感,也是最容易发现"说好的固态其实是机械盘"的地方。顺手再跑一次顺序写,把数字记下来。

第二项是 inode 总量。装好文件系统后先看一下 inode 总数,按你预期的对象数量估算够不够用。这一步只需要几秒钟,但如果跳过,半年后你得停机、备份、重做文件系统、再恢复,代价不可同日而语。

第三项是 gc 压测,这是最关键的一项。挑最大的那个仓库,跑一次完整 repack,用能统计峰值内存的方式记录最大常驻内存和耗时。跑之前先确认数据盘剩余空间够放两份 pack。如果这次压测就失败了,恭喜你,在上线的第一天而不是半年后发现了问题。

第四项是并发压测。写个脚本并发克隆或拉取最重的仓库,逐步加压,找到开始报错或明显超时的那个点,除以二作为生产并发上限。这个数字要写进运维手册——它是你未来判断"要不要扩容"的依据。

第五项是备份恢复演练。完整跑一遍:镜像同步、数据库导出、配置文件归档,然后在一台临时机器上起一个 Gitea 实例,用备份恢复,克隆几个仓库验证、检查议题完整、试着下载 LFS 文件。把整个过程的耗时记下来,这个耗时就是真实故障时的停机时长。

第六项是把监控项落地。至少要监控:数据盘与备份盘的水位、inode 使用率、内存使用峰值、gc 失败日志、后台任务队列长度、克隆与推送的响应耗时。前两项是容量告警,中间三项是健康告警,最后一项是体感告警,缺哪一类都会在关键时刻两眼一抹黑。

买之前客户问得最多的几件事

十来个人的团队自建 Gitea,最低能用什么配置起步?

我给的建议是四核十六 GB 内存起步,数据盘 NVMe 两百 GB。为什么内存要十六 GB 而不是八 GB?因为 Gitea 本身、数据库、系统加起来就要好几 GB,剩下那点才是留给 gc 峰值的,八 GB 的话一次大仓库 gc 就可能翻车。这属于预估价格对应的配置口径,实际以咨询为准。别在内存上省这一档,事后加内存比一开始配够麻烦得多。

仓库目录能不能就放在系统盘,省一块盘的钱?

不建议,这个钱省不得。系统盘要装系统、放日志、留快照空间,本身就是紧张的;仓库目录放上去之后增长不可逆,等它把系统盘吃满,整台机器都会出问题,而且盘满了连迁移命令都跑不动。正确做法是上架第一天就挂独立数据盘,把仓库目录和 LFS 目录都指过去。一块盘的差价,远低于一次停机事故的成本。

为什么我加了 CPU 核数,克隆速度一点没变?

因为克隆慢的瓶颈不在核数。服务端要为这次克隆现场生成一个 pack,打包与 delta 压缩的主体是单线程执行的,多核帮不上忙;带宽只决定传输那段时间的下限。所以加核数对首次克隆基本无效。真正的缓解手段在别处:客户端用浅克隆或按需拉取,服务端开启加速结构并做定期维护,CI 高频拉取走本地镜像或代理缓存。

git gc 总是失败,是不是内存不够?要加多少?

大概率是内存峰值不够,而且失败会形成恶性循环。加多少要看你的仓库:按常驻内存加并发任务数乘以单任务开销来估,单任务开销又取决于你设的打包窗口内存、缓存和线程数。最靠谱的办法是拿最大的仓库跑一次完整 repack,测出峰值内存,再加上余量。同时把窗口内存和线程数显式限制住,把峰值压到确定范围内,这比单纯加内存更治本。

LFS 到底开不开?开了之后盘要多大?

有二进制资产要版本管理就开,纯代码仓库开了是给自己找麻烦。容量按"单个资产大小乘以迭代次数乘以资产数量"来估,再留一年的增量——LFS 对象不做压缩也不做增量存储,改一次就是一份新的。盘位上强烈建议单独一块盘或独立分区加配额,让它撑爆时不会连累仓库不可写。清理很麻烦,所以配额和准入规则要提前定。

备份盘用同一台机器的第二块盘行不行?

同一台机器的第二块物理盘是可以接受的底线方案,前提是它确实是独立物理盘而不是同一个逻辑卷上的两个目录。它能扛住单盘故障,但扛不住整机故障、机房故障或者误删。所以我一般建议在这个基础上再加一份定期传走的异地副本,成本不高,但覆盖面完全不同。另外记得备份容量按仓库总量乘以保留份数再乘一点五来算。

仓库里已经塞了几个 GB 的历史二进制,还有救吗?

有救,但要趁早,而且要接受这是个小工程。思路是把历史里的大文件迁到 LFS 或者从历史中彻底剔除,然后重写历史、强制推送、让所有人重新克隆。难点在于历史重写会让所有人的本地仓库与远程不一致,需要协调停机窗口。仓库越大,这个过程越慢、风险越高,所以最好的办法永远是别让它长到这一步——体积上限和二进制准入规则要提前立。

能不能先租小的,等仓库涨上来再扩?

可以,而且我推荐这么做。先用弹性配置跑起来,把备份策略、监控项、维护窗口这些流程跑顺,等仓库规模和 LFS 量真的涨上来了再迁到独享资源上,资源利用率会高很多。像一万云 ¥25 起(以官网实时价为准)就能先起步。要提醒的是,迁移时记得把仓库目录、LFS 目录、数据库、配置密钥这四样一起搬,漏掉数据库是最常见的迁移事故。

这些判断是怎么来的

文中关于裸仓库目录结构、gc 与 repack 的资源特征、LFS 存储形态、bundle 与镜像仓库的用法,来自 Git 与 Gitea 官方文档对这些机制的说明,加上实际运维与压测中反复验证过的经验;涉及具体数值的部分都是量级口径,不同仓库的 delta 链长度、二进制占比差异很大,请以你自己的压测结果为准。

价格方面,文中出现的裸金属 E5-2698v4×2 ¥3999 起、一万云 ¥25 起,来自 https://www.idc10000.net/ 官网公开页面,属官网明示价,以官网实时价为准;文中所有涉及数据盘、备份盘增配与定制配置的费用,均为预估价格,以咨询为准,具体以签约时最新报价与合同为准。

把钱花在刀刃上:Gitea 选购的四条顺序

如果只能记住四句话,那就是这四条。第一,内存按峰值配,把 gc 那一下的开销算进去,别按平均值省钱。第二,数据盘认准 NVMe 和 inode 余量,容量反而是最容易后补的一项。第三,备份盘必须是独立物理盘,容量按仓库总量乘以保留份数再乘一点五,并且每季度真恢复一次。第四,LFS 单独分区加配额,仓库体积上限写在团队规范里。

反过来说,最不值得花钱的地方也很清楚:靠堆 CPU 核数解决克隆慢,靠加大机械盘解决小文件慢,靠给系统盘扩容解决仓库增长。这三笔钱花下去,问题一分不会少。

最后一句立场:自建代码仓库不是重负载业务,但它是典型的"配错一次、难受三年"的业务。它的峰值藏得很深,等你看见的时候往往已经付出了代价。宁可一开始把内存和盘位配得宽一点,也别等到 gc 失败的凌晨三点再来算这笔账。


上一篇:2026 多台服务器要共用一个目录:GlusterFS 卷类型和脑裂该怎么防

下一篇:2026 定时任务漏跑和重复跑:分片、幂等和补偿该怎么配服务器