从接触 SeaweedFS 到现在,我遇到过不少朋友问同一个问题:集群跑着跑着,想看看 volume 到底占了多大磁盘,结果有人贴df -h,有人贴 master 的 volume 列表,还有人直接把数据目录整个du -sh出来——不能说谁错,但这种片段式信息放到排障和容量规划里根本不够用。这篇是 SeaweedFS 系列的第 5 节内容,专门把“查看 volume 磁盘占用”这件事拆开讲清楚,包括不同视角的数据口径、几个常用查询方式、以及占用数字异常时的排查和回收方法。
先说结论:SeaweedFS 里“磁盘占用”不是一个单一数字,而是要分三层看。
1. 明确三层占用口径:逻辑数据、文件空洞、物理块
1.1 为什么df -h和 master 看到的数字经常对不上
很多人第一反应是登录 volume server 敲df -h,看一下挂载点还剩多少。这个操作本身没毛病,但它反映的是整个数据目录甚至整块磁盘的使用率,包含了索引文件、日志、临时文件、快照文件,还有其他业务可能共用的空间。而 master 的/vol/status接口返回的是每个 volume 文件的相关统计,两者口径差得很远。
更本质的原因是,一个 volume 在 volume server 本地并不是一个文件夹,而是以 volume id 命名的一组文件,核心是xxx.dat和xxx.idx。.dat是数据文件,追加写入;.idx是索引文件,记录每个文件片段的偏移量。你通过 Filer 的挂载路径看到的是文件、目录这些逻辑结构,而 volume server 硬盘上躺着的是一堆编号文件,它们之间没有一一对应的关系。所以直接拿df的输出去回答“volume 占了多少”,只能得到一个很粗的磁盘水位,得不到 volume 颗粒度的答案。
想要准确回答这个问题,至少要看三个层面的数据:
| 查看层面 | 数据含义 | 典型来源 |
|---|---|---|
| 逻辑数据量 | 每个 volume 里实际存了多少有效字节 | master 的 volume 状态接口 |
| 文件空洞量 | 已逻辑删除但还在 .dat 文件里占位的字节 | 本地文件ls与du对比 |
| 物理磁盘占用 | 数据文件真正消耗的文件系统块 | du、df等本地命令 |
1.2 先记住 volume 状态接口里的几个关键字段
不同版本 SeaweedFS 对返回结果里字段名的写法略有差异,但出现次数最高的几个字段含义是稳定的。只要你看过一遍/vol/status的输出,后面排查就会熟练很多:
Id:volume 编号,也就是对应本地xxx.dat里的 xxx。Size:volume 数据文件的当前大小,通常可以理解为文件逻辑长度。DataSize:当前仍有效的文件数据字节数,也就是真正“活着”的内容。FileCount:该 volume 里文件片段的数量,注意它不是业务上的用户文件数量,而是 chunk 层面的数量。DeleteCount与DeletedBytes:已标记删除的文件片段数量和字节数。这两个数字是判断 volume“虚胖”的关键指标。
一个 volume 的Size明显大于DataSize,且DeletedBytes不低时,基本可以判定这个 volume 处于“空间膨胀”状态,常见于大量删除但未做 vacuum 回收的场景。
1.3 从顶层理解:master、volume server、本地文件系统的三角关系
在动手敲任何命令之前,把三角关系理清楚能省下很多时间:master 只负责 volume 的元信息和调度决策,它知道每个 volume 分布在哪台 volume server 上、副本数是多少、每个 volume 的统计状态是什么,但它不直接掌握本地文件的物理块分配情况;volume server 负责读写数据和维护本地文件,但它向 master 上报的数据也只是统计值,不是du结果;真正决定磁盘可用空间的是下游的文件系统。
所以,完整排查一条链路是:先通过 master 的接口拿到全局逻辑视图,再到对应的 volume server 上确认物理文件大小,最后对比两者的差值定位异常。下面一个个来。
2. 从 master 拿全局视图:/vol/status接口用法
2.1 请求方式和最小可用命令
master 默认监听在 9333 端口,最简单的一条命令:
curl -s "http://127.0.0.1:9333/vol/status?pretty=y"不加pretty=y也可以,只是输出会被压缩成一大行,肉眼不太好看。如果你只关心机器可读的 JSON,不关心排版,用?format=json或者在请求头带上正常 JSON 解析就行。生产环境我一般写成:
curl -s "http://127.0.0.1:9333/vol/status?format=json"如果集群有多个 master,最好把请求发到当前 leader 上,或者在 master 前面挂了负载均衡就发到负载均衡地址,避免连到非 leader 节点拿不到最新状态。具体怎么找 leader,可以直接看 master 的日志输出,或者通过weed shell进去看集群状态,后面会提到。
2.2 返回结构怎么读
以新一点的版本为例,响应体里通常包含版本号、volume 总数、空闲 volume 列表、最大 volume 数量限制,以及核心的Volumes数组。我调过不少版本,字段大小写在不同版本间有小幅变化,但大体结构是稳定的。一个典型条目类似:
{ "Id": 5, "Collection": "", "Size": 1073741824, "ModifiedAtSecond": 1712839234, "FileCount": 8321, "DeleteCount": 1203, "DeletedBytes": 214748364, "ReadOnly": false, "CompactRevision": 4, "DataSize": 858993459 }这里Size是 1 GiB,DataSize只有约 800 MiB,DeletedBytes约 200 MiB,说明有约 20% 的空间已经被逻辑删除。如果你要快速统计整个集群的逻辑数据总量,把DataSize求和就是;如果想知道磁盘上实际被 volume 文件占掉多少,把Size求和只能算一个近似值,因为文件系统还有稀疏空洞的问题,这部分得看本地du。
2.3 一个能直接用的统计脚本
因为接口返回的是 JSON,拿jq或 Python 处理都很快。我平时用的一个巡检片段是这样:
curl -s "http://master:9333/vol/status?format=json" | jq -r ' .Volumes[] | [.Id, .Collection, .Size, .DataSize, .FileCount, .DeleteCount, .DeletedBytes] | @tsv ' | sort -t $'\t' -k7 -n -r | head -20jq字段名如果和你版本不一致,把Volumes换成实际返回的键名就行。跑出来的结果会按DeletedBytes从大到小排序,排在最前面的基本就是要特别关注的 volume。如果你的环境里没有jq,用 Python 同样可以实现:
curl -s "http://master:9333/vol/status?format=json" | python3 -c ' import sys, json data = json.load(sys.stdin) vols = data.get("Volumes") or data.get("volumes") or [] for v in vols: deleted = v.get("DeletedBytes", 0) if deleted and deleted > 0: print(v.get("Id"), v.get("Size"), v.get("DataSize"), v.get("FileCount"), deleted) '需要提醒一句:当集群里的 volume 数量很多时,这个接口返回的 JSON 会很大,哪怕只是正则过滤也可能吃几十 MB 内存,建议用流式解析或加上筛选条件,不要无脑全量拖到浏览器里看。
2.4 辅助手段:weed shell里的快捷命令
如果你手边有weed shell,进入交互终端后可以敲volume.list查看所有 volume 的基本信息,输入volume.disk能直观看到各 volume server 的磁盘使用情况。相比直接请求 HTTP 接口,weed shell的好处是它会自动处理 master 连接和部分格式转换,适合不想写脚本的场景。当然,内部拿到的数据源仍然是 master 的 volume 状态信息,所以它给出的Size、DataSize这些字段都是同一口径。
2.5 顺手把指标接到 prometheus 的思路
如果你已经在用 Prometheus 监控 SeaweedFS,会发现 volume server 和 master 都开了/metrics端点。拉到指标里可以画出每个 volume server 的磁盘水位趋势曲线。曲线的好处是能发现“逻辑删除但物理没回收”的渐进过程:如果DeletedBytes持续上涨而业务删文件的量并没有那么大,就要检查是不是有应用在批量覆盖写或者临时文件反复创建删除。这种趋势问题只看一次快照很容易漏掉。
3. 到 volume server 本地确认物理真相:du与ls的差值就是空洞
3.1 用两个命令快速判断 volume 文件有没有“虚胖”
如果说 master 接口告诉我们的是“应该有多少”,那本地文件系统告诉我们的是“实际占了多少”。二者一旦对不上,问题就在文件空洞上。
先看单卷文件:
ls -l /data/volumes/25.dat du -h /data/volumes/25.datls -l显示的 Size 是文件的逻辑长度,du -h显示的是文件实际占用的磁盘块大小。对 SeaweedFS 的数据文件来说,如果曾经写入大量数据后又做了很多删除,.dat文件会在中间留下大量空洞,文件系统不会自动回收这些已经被“挖掉”的块,于是ls的结果可能远大于du。反过来,如果一个刚创建不久还没写满的 volume 文件,du和ls的差距也可能存在,因为文件系统按块分配,尾部有未写满的块。但只要差距比例明显异常,就说明空洞率不低。
批量检查整个数据目录更常用:
cd /data/volumes for f in *.dat; do ls_size=$(stat -c '%s' "$f") du_blocks=$(stat -c '%b' "$f") blk_size=$(stat -c '%B' "$f") echo "$f ls=$ls_size du=$((du_blocks * blk_size))" done这个循环会把每个 volume 文件的逻辑大小和物理占用按字节打出来,差值大的文件后面往往跟着高DeletedBytes,基本可以锁定 target。实际生产环境中,我见过一个 2 GiB 的.dat文件du之后只有 800 MiB,空洞率超过 50%,这就是批量删除后一直没做 vacuum 的典型结果。
3.2 分清数据目录里还有哪些东西
找一个 volume server 的数据目录,用ls -lh看一下,通常会有以下几种文件:
total 47G -rw-r--r-- 1 root root 11G 5月 11 23:12 0.dat -rw-r--r-- 1 root root 231M 5月 11 23:12 0.idx -rw-r--r-- 1 root root 11G 5月 12 01:03 1.dat -rw-r--r-- 1 root root 230M 5月 12 01:03 1.idx占磁盘空间的大头永远是*.dat,*.idx只是索引数据,一般占数据量的几个百分点,但如果文件数量非常多,索引的体积也会明显膨胀。个别版本或开启 WAL 后还会出现日志文件,这类日志大小相对可控,不过排查磁盘占用时也不能完全无视。专门的 “查看 volume 磁盘占用” 场景下,先盯住所有.dat文件总和就够了,因为它决定了磁盘使用量的绝大部分。
3.3 空间分布不均时怎么快速定位热点目录
数据目录可能不是一块盘,volume server 支持配置多个目录。配置多目录后,du -sh每个目录对比,能找到哪个盘已经告急、哪个盘还很空。如果你不想一层层du,直接从/vol/status拿到每个 volume 所在的 server 和磁盘位置也会更快,但 master 接口不一定直接给出目录路径,所以最终还是得回到本地确认。
如果数据目录的文件数量非常多,直接du -sh /data/volumes可能等得人着急。这时候我一般会先看df -h确定整体水位,再按du -h --max-depth=1分批看,尽量减小一次扫描的范围。另外记得用ionice或nice降低 du 对线上读写的影响,尤其在高负载集群上,这也是一个容易被忽略的细节。
4. 数据膨胀的典型根因与排查链路
4.1 根源在追加写和删除标记机制
先说一个反直觉的事实:在 SeaweedFS 里删除一批文件,volume 占用的磁盘空间通常不会马上下降。原因是数据写入.dat文件时是追加写,删除动作只是把索引条目标记为 deleted,数据本身还在文件里,相当于在文件里挖了一个或几个“坑”。这样的设计保证了写入性能的高效和简单,但也把空间回收的动作延后到了 compact / vacuum 阶段。
这个机制和很多传统文件系统删除后立即释放空间的经验不一样,所以新手最容易在这里踩坑。你从 Filer 挂载目录里把文件删掉,df上却看不出什么变化,这不是删除失败,而是回收时机还没到。
4.2 一次批量删除后磁盘不降反升的完整排查过程
我之前处理过一个实际案例,场景是:业务侧做了一轮历史数据清理,预计删掉约 700 GB 的数据,清理完成以后却发现集群某台 volume server 的磁盘水位不降,甚至在 vacuum 之前还略有上升。
排查步骤如下:
第一步,先看整体视图。登录 master,拉取/vol/status?format=json,把DataSize和Size各求一个总和,发现Size总量和清理前的数据总量差不多,而DeletedBytes总量突然涨了一截,这说明删除确实执行了,但只是逻辑删除。
第二步,定位具体 volume。用上面那个按DeletedBytes排序的脚本,找到删除字节数最大的几个 volume。同时去对应 volume server 本地执行ls -l和du -h对比,果然那几个 volume 的.dat文件逻辑大小没有缩小,用stat算出的实际块占用也没变。
第三步,和业务侧确认。查了清理任务的日志,发现业务方通过 Filer 的 Delete API 删除了对象,符合预期,问题不在业务代码,而是维护流程里没有把“删除后的 vacuum” 纳入例行操作。
第四步,在低峰期执行 vacuum。等到凌晨访问量很低的时候,对这几个膨胀 volume 所在集群做了一次 vacuum,操作完成后DeletedBytes明显下降,Size总和也随之缩水,磁盘水位真正降下来了。
这个过程里最值得注意的教训是:只看 Filer 的目录结构或业务成功日志,容易产生“数据已经清理完”的假象;只有把 master 的逻辑统计和本地的物理统计对上,才能确认空间是否真正释放。
4.3 其他容易造成误判的几种情况
除了删除不回收,还有几个情况容易让磁盘占用看起来“不合常理”:
- 复制副本数。同一个 volume 会在多台 volume server 上各有一份数据文件,
/vol/status返回的每条记录是按 volume 实例来的,统计时如果忘乘以副本数,会把总占用低估一到两倍,具体取决于副本配置。 - collection 的隔离效果。不同 collection 的数据集中在各自的 volume 编号段里,统计时如果不按 collection 分组,很难看出哪一个业务在快速吃空间。
- 临时 compact 文件。vacuum 或 compact 过程中,volume server 会生成临时文件,如果进程被中断或临时文件没清理干净,磁盘占用会残留一份不大不小的副本。遇到磁盘占用异常时,可以在数据目录里扫一下临时文件开头或结尾命名的文件。
5. 空间回收实操:vacuum 与 volume 均衡
5.1 三种触发 vacuum 的方式
SeaweedFS 的 vacuum 机制,简单说就是 master 找出垃圾比例达到阈值的 volume,通知 volume server 把有效数据重写到一个新文件,完成后再用新文件替换旧文件,这样空洞就被压缩掉了,磁盘空间才能归还给文件系统。这个动作官方文档里叫 compact + vacuum,本质是同一套流程。
常用的触发方式有三种:
第一种,HTTP 接口触发:
curl -X POST "http://master:9333/vol/vacuum?garbageThreshold=0.3"第二种,weed shell会话内执行:
weed shell volume.vacuum -garbageThreshold 0.3第三种,老版本提供独立命令行工具:
weed vacuum -garbageThreshold 0.3新版本我更推荐weed shell的方式,因为它会输出详细日志,还能看到 master 对每个 volume 的筛选结果。garbageThreshold默认是 0.3,意思是垃圾比例超过 30% 才会对这个 volume 做 compact。如果遇到磁盘告急想激进回收,可以临时调低,比如 0.1;但不建议一直设置很低的值,否则每次小规模删除都会触发全量 compact,IO 放大很严重。
5.2 vacuum 过程中发生了什么
执行 vacuum 时,master 会先遍历所有 volume,选出满足垃圾比例的候选,然后并行向这些 volume 所在的 volume server 发送 compact 请求。volume server 收到请求后,会把该 volume 中仍有效的文件片段读出来,写入一个新的临时数据文件,同时重建索引。
这个过程中有几个实际影响:
- 单个 volume 在 vacuum 期间需要额外的临时磁盘空间存放新文件,如果磁盘已经接近写满,执行 vacuum 可能因空间不足而失败。
- compact 期间该 volume 的读写会有短暂锁定,对在线业务一般影响不大,但如果集群容量很小、请求并发很高,还是能感知到延迟波动。
- 操作完成后,旧的
.dat文件被删除,新文件生效,master 上的Size字段会明显下降。我通常会在 vacuum 前后各拉一次状态做对比,确认回收效果。
5.3 执行前后的对比验证
以我常用的流程为例。vacuum 前先抓一下关键值:
curl -s "http://master:9333/vol/status?format=json" | jq '[.Volumes[].Size] | add' curl -s "http://master:9333/vol/status?format=json" | jq '[.Volumes[].DataSize] | add'跑到磁盘水位告急的那台 volume server,再记录:
du -sh /data/volumes执行完 vacuum 后,重复同样的命令。如果一切正常,Size总和会明显下降,DataSize总和基本不变或略有下降,本地du也会释放出一块空间。如果你的DataSize总和也出现明显变化,先看看是不是有业务在删除数据,或者有 compaction 之外的临时文件没清理。
5.4 空间分布不均时用 volume 迁移均衡
有时候磁盘占用大不是因为总量超了,而是集中在少数几台机器上。此时单纯 vacuum 只能解决空洞问题,解决不了分布问题。可以通过weed shell里的volume.balance或手动把某个 volume 迁移到空闲节点。
均衡操作要谨慎评估,第一步还是先看分布:
volume.disk如果某台 server 的已用空间明显高于其他节点,且还有大量空闲 volume 可以写入,可以利用 balance 命令把一部分 volume 迁移过去。迁移是复制数据再切换的过程,会占用一定的网络带宽和临时空间,建议结合业务低峰期进行。有时候手动指定迁移比全量 balance 更可控,特别是你已经定位到某个 collection 的 volume 集中在一台机器上时。
6. 顺着这个需求延伸出的巡检与维护建议
6.1 一条 cron 就能防住大部分“虚胖”问题
做完整套排查之后,我把这台集群的巡检固化成了一个定时任务,频率不高,每 10 分钟跑一次,只拉关键字段并把DeletedBytes高、Size与DataSize比值大的 volume 打印出来。一旦比值连续多次超过阈值,就说明删除后没有及时回收,有必要安排 vacuum 或检查业务删除模式。
一个可以快速上手的 cron 脚本片段:
#!/bin/bash MASTER="http://127.0.0.1:9333" ALERT=60 curl -s "$MASTER/vol/status?format=json" | jq -r ' .Volumes[] | select((.Size > 0) and ((.Size - .DataSize) * 100 / .Size) > 60) | [.Id, .Size, .DataSize, .DeletedBytes] | @tsv ' | while read -r id size datasize deleted; do echo "volume $id 空洞/删除占比过高: size=$size data=$datasize deleted=$deleted" done阈值按你的业务场景调。默认 60% 算比较宽松,如果集群本来就经常删除,可以放宽到 75% 再告警,避免告警风暴。
6.2 vacuum 节奏和触发时机的经验
vacuum 不是越频繁越好。频繁 compact 会带来明显的读写放大,尤其在大容量 HDD 上,一次全量 vacuum 可能要跑几十分钟甚至更久。建议节奏是这样:
- 日常删除量小:每周固定一次低峰 vacuum,
garbageThreshold保持默认。 - 批量清理任务后:次日低峰做一次定向 vacuum,把该 collection 或相关 volume 的垃圾尽快回收。
- 磁盘水位告急:临时调低阈值立即处理,但处理完恢复默认,并检查是否有删除流程异常。
还有一个细节:compact 过程中生成的临时文件在异常中断时可能会残留,如果发现 vacuum 结束后磁盘空间没有完全恢复,建议去数据目录扫一下临时文件并手动清理。这是我在一次虚拟机意外断电后踩过的坑,后来在维护文档里专门加了这条备注。
6.3 最后一个小技巧:别只看峰值,要看垃圾回收趋势
排查磁盘占用如果只看某一天的快照,很可能会忽略“这个 volume 是不是每天都在膨胀”的事实。我建议至少把下面三个指标纳入日常观察曲线:
- 所有 volume 的
DataSize总和:代表集群的真正有效数据。 - 所有 volume 的
Size总和:代表数据文件占用的逻辑大小。 - 各 volume server 数据目录的
du总和:代表实际物理占用。
当三条曲线的间距拉大时,基本就是删除后回收不及时的信号。反之,如果Size和du曲线趋势正常,只是DataSize在涨,说明是正常写入增长,不需要特殊处理。
SeaweedFS 的 volume 磁盘占用这个问题,其实只要把“逻辑数据量、文件空洞、物理块”这三个概念分清楚,再配上 master 接口和本地du两把尺子,日常维护就不会再有什么糊涂账。从我做这套集群的经验来看,大部分“磁盘莫名其妙没了”的疑问,最后的答案都在聚合删除未回收和副本数统计错误这两个坑里。希望这篇能帮你少走几步弯路。