NAS里的Btrfs越用越慢?八成是碎片在作妖,我那台下载做种机治完负载轻了一半

这事说起来有点丢人。我那台N100小主机装飞牛快两年了,盘是ext4和Btrfs各一块的混搭——这个选型我当初还写过一篇怎么选的账。Btrfs那块管照片库和几个容器,去年入冬开始不对劲:凌晨没人用,负载却常年趴在2以上,iostat一看%util八成往上,盘明明没坏SMART全绿。我在日志和内存里绕了小半个月的弯路,还差点按容器起不来那篇的思路把Docker全数重装。后来一条filefrag下去,真凶露馅:一个两百多兆的数据库文件,碎片数两千七百多。
先说清楚:碎片不是Btrfs的Bug,是它的本性
ext4那种文件系统,改文件基本是原地改,数据块前后挨着,读起来磁头顺着扫就行。Btrfs不这么干,它的招牌是写时复制(CoW):你要改一个块,它不碰原块,先找个新地方写个新的,再把指针指过去。快照、校验、压缩这些本事全靠这一手——改之前旧版本天然还在,快照就是这么白捡的。
代价也在这。改一点、拷一块,改得越碎,盘上的数据块被甩得越散。更要命的是写时复制天生不能就地补洞:文件里改了4K,Btrfs可能得把整段逻辑块重新安排一遍。下载做种这种一天到晚小碎笔的活计,等于拿凿子在盘上到处刻字,刻上一年,一个「逻辑上连续」的文件物理上七零八落。机械盘寻道是物理动作,磁头来回跑,延迟就上去了;主控再勤快也架不住这种读法。
所以这事有鲜明的画像:全是照片视频这种一次写完不怎么动的盘,基本无感,你别瞎折腾;又做种又跑数据库容器的盘,碎片是慢性病,早晚要治。我家两块盘两种命,就是这么分的工。
第一步:先看病,别上来就吃药
SSH上飞牛(怎么开那扇门,开SSH那篇有步骤),装没装e2fsprogs都无所谓,filefrag基本是标配。对着可疑文件来一发:
filefrag -v /vol1/@containers/immich/db/immich.db
输出末尾一行的extents数字就是碎片数。给你个我心里的大致标尺:几百兆的文件,extents在两位数,健康;三位数,开始攒了;四位数千往上了,就是它没跑了。我那块盘上Immich的数据库两千七,qBittorrent的resume文件八百多,Jellyfin的缓存库一千二——一个个全是惯犯。
要批量摸底也行,把整个目录扫一遍按碎片数排个序:
find /vol1/@containers -type f -size +10M -exec filefrag {} \; | sort -t: -k2 -rn | head -20
顺带说一句,光看碎片数也别慌过头。Btrfs小文件天生extents偏高,几K的小文件碎不碎对性能毫无影响,别被数字吓着去瞎治。盯大文件、盯被反复写的文件,才是正经看病。
还有一个免费佐证,iostat。没装的话yum install sysstat或者apt一把就有,跑iostat -x 5看五秒刷新:如果%util常年高位、r_await数字也难看,共享文件夹又没多少流量、SMART也没异常,那盘在干无谓的活,十有八九就是磁头被碎片赶着满盘跑。我当时就是凌晨两点盯着这组数字起了疑心——没人用机,盘忙什么忙。
再交个底,这病有个免费的对照组:同一台机器上我那块ext4下载盘,同样做种同样跑容器,%util常年两成上下。两块盘一个忙死一个闲死,活的干得还一样,这对比基本就是自供状。你手里要是也有混搭的盘,先这么互看一眼,比自己猜省事。
第二步:治本的那招,叫chattr +C
病根是写时复制,那治法就是把写时复制对这个目录关掉。Linux上就一条命令的事:
chattr +C /vol1/@containers/immich/db
加C这个属性之后,这个目录里新建的文件不再走写时复制,改数据就地改,碎片从根上断了。但有两个死规矩必须先刻在脑子里。
规矩一:C只管新文件。已经碎掉的旧文件,加一万遍C也不会自己拼回去。所以正确顺序是:先把目录里的数据挪出去(mv到别的盘或者临时目录),chattr +C设好,再把数据挪回来——挪回来的动作等于重写一遍文件,这一遍就是「顺带整理碎片」。我那几个惯犯文件就是这么搬的,两千七百个extents搬完变回两位数。
规矩二:关了CoW,快照校验压缩对这个目录全失效。快照吃的就是写时复制的老本,CoW没了快照对它就是空话。所以别给整块Btrfs盘无脑加C,照片库那种吃快照保命的地方,一个属性都别碰。只给数据库、下载、缓存这种「碎但可再生」的目录关,这是我在选型文坑二里没展开说的半句话,这次补齐。
第三步:容器目录搬家,顺路把账算清
实际操作里最省心的路数是容器数据卷整目录搬。停容器、把数据目录mv出去、chattr +C、mv回来、起容器,五分钟的事。停起容器的讲究在容器升级那篇里写过,数据卷和配置目录别搞混,搬错地方容器起来也是空壳。如果你的容器是Compose管的,一停一起更是一条命令的事,Compose那篇有现成的抄法。
搬完别急着收工,观察一两周再下结论。我的实测结果:凌晨做种时段系统负载从2.3落到1.1左右,盘的%util从八成掉回三成。不敢说人人都这个数——负载这东西和容器数量、内存大小都有关,容器内存那笔账是另一个维度的病,别混着治。但方向是确定的:小碎笔写不动CoW了,盘就不用半夜陪它折返跑。
有两类盘,这个病压根不用治
一类是快照/校验依赖度高的。照片库、全家文档这种不可再生的东西,快照是保命层,我在快照那篇里算过它的账,为了一点碎片把保护撤了,本末倒置。碎片顶多让读慢一点,快丢了才是大事。
另一类是固态盘。固态没有磁头寻道这个物理动作,碎片造成的随机读对它伤害有限。缓存盘、系统盘上的Btrfs,我的建议是别折腾——真要关心固态的命,该看的是写入量和掉速那套,测速那篇和TBW那篇才是对症的体检表。
这病说白了就一句话:机械盘、CoW、高频小碎笔,三样凑齐才会犯。占全了按上面三步走,占不全的别自己吓自己。
防复发:新建目录时就该想好的三件事
治完这次,防下次才是正经。我自己是这么排的:
| 目录用途 | 加不加C | 理由 |
| 数据库容器(Immich/MySQL等) | 加 | 高频小写是碎片主力,数据可再生可重建 |
| 下载/做种目录 | 机械盘加,固态随意 | qB的碎碎念最伤CoW,做种目录快照意义不大 |
| 照片库、家庭文档 | 坚决不加 | 快照校验是保命层,性能让位安全 |
| Jellyfin转码缓存 | 加 | 纯临时文件,写完就扔,CoW一点便宜占不到 |
建容器那天就把数据卷指到加过C的目录上,比事后搬家省事十倍。装Immich那会儿没人告诉我这些,我是在装Immich那篇写完几个月后才吃的这个亏。另外洗盘巡检和碎片治理别搞混:scrub管数据对不对,这篇文章管读得快不快,两层账各管各的,都是Btrfs该做的保养,排班时错开跑就行。
顺序收个尾:先filefrag看数字确认是不是碎片,是的话挑数据库和下载目录chattr +C加搬家,照片库一根手指都别动,固态盘不折腾。两周一复查,负载掉下来就是治对了。要是治完还卡,那就不是碎片的锅,回头按内存、网络那条线再查一遍,别在一棵树上吊死。

发表评论