From 3762cb7fc245f12522fa615bfed08e9a3b1905ed Mon Sep 17 00:00:00 2001 From: Mole Shang <135e2@135e2.dev> Date: Thu, 17 Sep 2026 18:55:11 +0800 Subject: juicefs-gc: new --- content/juicefs-gc/db-delfiles.png | Bin 0 -> 528719 bytes content/juicefs-gc/gc-stats.png | Bin 0 -> 1075359 bytes content/juicefs-gc/index.md | 86 ++++++++++++++++++++++++++++++++++++ content/juicefs-gc/mem-pprof.png | Bin 0 -> 85166 bytes content/juicefs-gc/oom.png | Bin 0 -> 117932 bytes content/juicefs-gc/storage-incr.png | Bin 0 -> 58862 bytes 6 files changed, 86 insertions(+) create mode 100644 content/juicefs-gc/db-delfiles.png create mode 100644 content/juicefs-gc/gc-stats.png create mode 100644 content/juicefs-gc/index.md create mode 100644 content/juicefs-gc/mem-pprof.png create mode 100644 content/juicefs-gc/oom.png create mode 100644 content/juicefs-gc/storage-incr.png diff --git a/content/juicefs-gc/db-delfiles.png b/content/juicefs-gc/db-delfiles.png new file mode 100644 index 0000000..82f3096 Binary files /dev/null and b/content/juicefs-gc/db-delfiles.png differ diff --git a/content/juicefs-gc/gc-stats.png b/content/juicefs-gc/gc-stats.png new file mode 100644 index 0000000..6abb509 Binary files /dev/null and b/content/juicefs-gc/gc-stats.png differ diff --git a/content/juicefs-gc/index.md b/content/juicefs-gc/index.md new file mode 100644 index 0000000..8f18c8f --- /dev/null +++ b/content/juicefs-gc/index.md @@ -0,0 +1,86 @@ ++++ +title = "JuiceFS 与如何倾倒垃圾" +date = 2026-09-18 +authors = ["135e2 (Mole Shang)"] +[extra] +enable_img_caption = true +[taxonomies] +tags = ["技术"] ++++ + +又名:如何优雅地删除 1.3 PB 的垃圾文件。 + + + +## 背景 + +> JuiceFS 是一款面向云原生设计的高性能分布式文件系统,在 Apache 2.0 开源协议下发布。提供完备的 POSIX 兼容性,可将几乎所有对象存储接入本地作为海量本地磁盘使用,亦可同时在跨平台、跨地区的不同主机上挂载读写。 + +[东南大学开源镜像站 (Intranet)](https://mirrors.seu.edu.cn) 于 2025/09 迁移到新的 JuiceFS 存储后端,[整体架构](https://os.nju.edu.cn/media/2026/slides/03-%E9%95%9C%E5%83%8F%E7%AB%99%E4%BA%91%E5%8E%9F%E7%94%9FFS%E4%B8%8E%E5%8F%AF%E8%A7%82%E6%B5%8B%E6%80%A7%E5%AE%9E%E8%B7%B5.pdf)优化后性能符合预期。但经历过一次严重的[生产事故](https://idawnlight.com/archives/juicefs-rescue-from-meta-dump/)后,出于谨慎考虑一直没有升级版本。 + +在 2026/08 鼓起勇气平滑升级至 1.4.1 后,后端服务器出现了稳定的存储增长,经排查后确认为 `juicefs gc` 服务没有正常定期运行。同时,由于镜像站的业务涉及每天千万级的元数据读写,一个月内产生了 >100GiB 的数据库存储占用并持续增加。 + +![](./storage-incr.png) + +因为现在已经没有人收垃圾了,参考 [JuiceFS 内部实现](https://juicefs.com/docs/zh/community/internals/#delfiles)观察各个表结构,可以看到 DB 中已经积累了约4亿个待清理的文件: + +![哈哈,发臭了](./db-delfiles.png) + +当我们一如既往地尝试重新执行 `gc` 以清理过期元数据时,不出意外地出意外了:回收过程吃掉了太多内存,直接导致系统 OOM 重启。 + +## 定位 + +> 因为 juicefs gc 命令会扫描对象存储中的所有对象,所以对于数据量较大的文件系统执行这个命令会有一定开销。另外使用此命令之前请确保您不需要回滚到旧版本元数据,并且建议您备份对象存储数据。 +> +> —— [官方文档提示](https://juicefs.com/docs/zh/community/administration/status_check_and_maintenance#gc) + +再一次地,官方文档对此讳莫如深:主线开发版的确提到了[可能的性能优化](https://github.com/juicedata/juicefs/issues/7420),但在尝试给我们的系统移植所有相关 patch 并启用了 16G swapfile 之后,依然无法解决 gc 吃掉大量内存的问题。 + +![](./oom.png) + +万不得已,还是得读源码 :( + +简单修改一下程序,我们可以很方便地开一个用来监控的 Goroutine,在检测到 OOM 前收集一份程序的堆栈采样。不难发现大部分内存都分给了 `ScanPendingFiles` 下的 `xorm.(*Session).Find`,它的逻辑大概是这样的: + +![](./mem-pprof.png) + +```go +func (m *dbMeta) scanPendingFiles(ctx Context, scan pendingFileScan) error { + // ... + var dfs []delfile + if err := m.simpleTxn(ctx, func(s *xorm.Session) error { + if ok, err := s.IsTableExist(&delfile{}); err != nil { + return err + } else if !ok { + return nil + } + return s.Find(&dfs) + }); err != nil { + return err + } + + for _, ds := range dfs { + if _, err := scan(ds.Inode, ds.Length, ds.Expire); err != nil { + return err + } + } + + return nil +} +``` + +根据 [XORM](https://xorm.io/docs/chapter-05/4.find/) 文档所示,`Find` 等同于直接把整个表里的内容都塞到了这个内存的 `delfile` 数组里。之前执行千万级的加载或许还看不出问题,但考虑到我们上亿的数据量,OOM 也就不足为奇了。 + +做过优化的朋友们显然能一眼看出问题所在:这个查询完全可以是流式的,类似 JS 的 `filter`,事实上 XORM 也支持[流水线遍历](https://xorm.io/docs/chapter-05/6.iterate/);而更诡异的是在 JuiceFS 仓库的同一个文件里相隔不远的地方也正使用了[类似的 `Iterate` 模式](https://github.com/juicedata/juicefs/blob/7ac228ebca24885736521b2688ccf0478b903e9b/pkg/meta/sql.go#L4045)去处理 `chunk` 表的遍历——不禁让人善意揣测是否是故意留下的坑以推销他的企业版支持。 + +## 恢复 + +参考已有的实现进行[修复](https://github.com/seu-mirrors/juicefs/commit/6acb36fe6da09b9e23d82e2399b7ffcea4370672)后,内存占用终于回到了可接受的水平;但由于 `jfs_delfile` 这张表的数据量还是上亿级的,跑了近一周才最终完成 `gc`: + +![当然这里的1.3PB是指过期被回收的文件总大小](./gc-stats.png) + +而这还没完,PostgreSQL 内部的垃圾回收需要执行 `VACUUM FULL`,其要求[数据库本身的独占锁](https://www.postgresql.org/docs/current/sql-vacuum.html),显然对生产业务是不可接受的;还是使用 [`pg_repack`](https://reorg.github.io/pg_repack/) 完成了磁盘回收。 + +--- + +这件事情告诉我们,及时保持环境卫生还是很重要的。 \ No newline at end of file diff --git a/content/juicefs-gc/mem-pprof.png b/content/juicefs-gc/mem-pprof.png new file mode 100644 index 0000000..45c4b38 Binary files /dev/null and b/content/juicefs-gc/mem-pprof.png differ diff --git a/content/juicefs-gc/oom.png b/content/juicefs-gc/oom.png new file mode 100644 index 0000000..7422926 Binary files /dev/null and b/content/juicefs-gc/oom.png differ diff --git a/content/juicefs-gc/storage-incr.png b/content/juicefs-gc/storage-incr.png new file mode 100644 index 0000000..0c88973 Binary files /dev/null and b/content/juicefs-gc/storage-incr.png differ -- cgit v1.2.3