Oans:为btrfs和XFS打造的快速文件系统去重工具

发布时间:2026/8/29 17:25:08
Oans:为btrfs和XFS打造的快速文件系统去重工具 磁盘空间不够的时候很多人的第一反应是加硬盘。但如果你仔细检查一遍现有数据会发现真正被用上的空间远没有想象中那么多备份目录里躺着同一份文件的好几个副本CI 构建目录堆积了大量内容几乎一样的产物虚拟机镜像目录更是重复数据重灾区。文件系统去重deduplication做的事情就是把内容相同的块合并成同一份物理块让多个文件通过引用计数共享存储从而在不加硬件的情况下回收大量磁盘空间。最近在 Hacker News 上看到的 Oans 项目正是瞄准这个场景。它的定位很直接为 btrfs 和 XFS 提供快速的去重能力。这里真正值得关注的不是“又一个去重工具”而是两个细节第一它强调 fast第二它同时覆盖 btrfs 和 XFS。前者说明它可能在扫描、哈希、调度上做了针对性优化后者意味着它补上了 XFS 生态中长期缺少好用去重工具的一块空白。这篇文章会讲清楚三件事文件系统级去重背后的原理是什么btrfs 与 XFS 在去重能力上的真实差异Oans 这类工具应该怎么部署、使用和验证以及实际运维中容易踩哪些坑。如果你管理存储服务器、备份系统、虚拟化宿主机或者经常被磁盘空间告警打断工作这篇文章值得读完。1. 为什么磁盘去重又值得关注1.1 数据增长快但真正有效的数据有限随着项目规模变大数据增长几乎是无差别的日志要留备份要存虚拟机镜像要放容器镜像要缓存开发环境还要复制好几份数据用来联调。问题是这些数据的“有效信息量”并没有随着占用的磁盘空间一起增长。大量文件只是在不同目录里被复制了多次物理磁盘上保存的是同一份内容的好几份拷贝。从存储成本看这种浪费非常直接。一块企业级 SSD 的价格并不便宜如果其中 30% 到 50% 是重复数据意味着很大一部分硬件投入被白白消耗。传统做法是定期人工清理或者靠“磁盘不够就扩容”的思路硬扛。但人工清理维护成本高扩容又带来预算压力。文件系统级去重提供了一个更优雅的方案不改变目录结构和文件可见性只是在底层把重复的物理块合并掉。1.2 文件级去重与块级去重去重可以发生在不同粒度。最简单的是文件级去重找到两个完全相同的文件保留一份另一个做成指向它的硬链接或 reflink。这种做法实现简单但粒度太粗——两个文件中只有一小段内容相同就无法用文件级去重处理。块级去重则把文件切成固定大小的块常见 4K、64K、128K对每个块计算指纹然后合并内容相同的块。它能发现更细粒度的重复节省更多空间代价是需要扫描更多的数据、计算更多的哈希。btrfs 和 XFS 的去重工具基本都是走块级或扩展区extent级去重。Oans 面向的正是这种更细粒度的去重场景这也是它能在备份目录、虚拟机镜像这类重复率极高的数据上产生明显收益的原因。1.3 适用场景什么数据适合去重去重不是所有数据都适合。最适合的是“读多写少、重复高、变化低”的数据备份目录每天全量备份会产生大量内容相同的文件。虚拟机镜像同一模板创建的多台虚拟机系统部分高度相似。容器镜像仓库不同镜像的公共层可以共享存储。开发与测试副本测试环境复制过来的数据往往只有少量差异。归档数据历史数据很少改动去重完成后能长期保持收益。不适合的是数据库数据文件、活跃的日志文件、频繁随机写入的卷。这类数据写入量大去重后可能很快又产生新块还会因为写时复制带来额外的碎片和性能开销。这是评估 Oans 能否落地时首先要判断的问题。2. 去重的核心原理reflink 与写时复制2.1 reflink 是什么reflinkreference link是 Linux 文件系统中的一个关键机制。它允许文件系统创建多个文件路径这些路径共享同一个物理数据块。共享双方并没有复制真实数据只是各自持有一个“引用”指向同一段磁盘存储。普通复制一个 10GB 文件需要找到 10GB 的空白空间然后逐块写入。而 reflink 复制一个 10GB 文件只需要创建对应的元数据记录几乎瞬间完成并且不占用额外物理空间。只有其中某个副本被修改时文件系统才会为被修改的块分配新空间这就是写时复制Copy-on-WriteCoW的含义。btrfs 和 XFS 都支持 reflink。在命令行中可以通过 cp 命令的--reflink参数直观感受# 创建 reflink 副本 cp --reflinkalways /data/original.img /data/copy.img # 查看两个文件是否共享物理块命令执行后两个文件在逻辑上完全独立但物理层共享数据块。修改其中一个文件不会影响另一个。这正是去重工具能够“安全合并重复数据”的基础只要文件系统支持 reflink去重就不是“删除数据”而是把多个物理副本替换为同一条物理记录。2.2 去重为什么需要哈希去重工具不可能逐字节比较所有文件那样 IO 开销太大。行业标准做法是计算哈希把文件切块后对每个块计算一个短指纹比如 xxHash、Blake2、Blake3 或 SHA-256。指纹相同就认为两个块内容相同然后再做一次被选中的块的实际数据校验避免哈希碰撞带来的误判。哈希算法直接决定扫描速度。SHA-256 安全性高但速度偏慢xxHash 和 Blake3 属于现代高速哈希SSE/AVX2 优化后可以达到每秒数 GB 的吞吐。Oans 强调 fast很大概率是在这个环节做了优化。如果使用者在超大目录上运行哈希性能会直接影响整体去重耗时。这里有个工程细节值得注意块太大重复粒度粗节省空间有限块太小哈希数量多索引开销大。实际工具一般会支持用户调整块大小或自动选择。Oans 这类工具最终会暴露相关参数使用时需要根据数据特征做取舍。2.3 一次去重的完整流程无论工具名字是什么去重的底层流程基本一致扫描目录枚举所有文件和它们的扩展区信息。对文件分块并计算哈希指纹。构建哈希索引找出指纹相同的块。对这些块向文件系统发出 reflink 合并请求。文件系统更新引用计数释放重复的物理块。一个工具做得好的地方往往在第一步和第三步如何减少无意义的 IO、如何控制内存中哈希索引的规模、如何用并行调度把多核 CPU 用满。Oans 的“fast”判断也应当放到这个流程中去理解。3. btrfs 与 XFS两个文件系统的去重能力对比3.1 btrfs天然面向 CoW 与快照btrfs 从设计之初就把 CoW、快照、子卷作为核心特性。它原生支持 reflink也提供了去重相关的 ioctl 接口。很长一段时间里Linux 社区在 btrfs 上做去重主要依赖两个工具duperemove 和 bee。duperemove 是老牌 btrfs 去重工具支持在线去重计算哈希后通过 FIDEDUPERANGE ioctl 合并重复块。bees 则更适合作为后台守护进程持续运行它针对 btrfs 的 CoW 特性做了不少优化尤其适合 Btrfs RAID 场景。两个工具的问题在于它们主要面向 btrfs对 XFS 的支持要么没有要么不够稳定。3.2 XFS老牌文件系统的现代补课XFS 是一个非常成熟的高性能文件系统RHEL/CentOS 系列长期把它作为默认选择。过去很长一段时间XFS 不支持 reflink因此也无法做文件系统级去重。从内核 4.9 开始XFS 引入了 reflink 支持配合快照xfs_fsr 等工具一起完善了存储管理能力。但 XFS 上的去重工具一直没有形成生态。原因不难理解XFS 的主要应用场景是高性能计算和大型服务器这些场景更多关注扩展性和 IOPS对在线去重的需求不如备份系统强烈加上 btrfs 社区已经有了成熟工具愿意为 XFS 开发去重工具的人更少。Oans 把 XFS 作为一等公民支持正好踩中了这个空白。3.3 两个文件系统的差异对比维度btrfsXFS内核支持 reflink原生支持内核 4.9建议新版本CoW 特性核心设计支持 reflink 后才具备快照子卷快照成熟支持但工具链相对简单已有去重工具duperemove、bees 等选择很少默认场景个人 NAS、备份服务器企业级存储、高性能服务器去重风险CoW 可能碎片化老内核上 reflink 支持不完整从表格可以看出去重工具在两者之间的定位差异btrfs 上工具选择多Oans 是“又多了个更好的选择”XFS 上工具选择少Oans 的加入是“补空白”。这两个语境下用户对它的期待完全不同。4. Oans 的设计定位与实践场景4.1 Oans 到底做了什么差异化从项目标题看Oans 的关键词是“fast deduplication for btrfs and XFS”。在 btrfs 上已经有 duperemove 和 beesOans 想要立足就必然要在速度、易用性、稳定性上有更优解。在 XFS 上它几乎是少数能用的选择所以对 XFS 用户来说Oans 的出现本身就很有意义。最合理的差异化路径是扫描阶段更快通过并行 IO、批量预读、线程池调度减少等待。内存控制更好重复数据多的时候哈希索引很容易吃光内存Oans 需要在内存与磁盘之间做平衡。命令更现代输出结构化结果JSON支持增量扫描方便接入监控和自动化。跨文件系统统一同一套命令在 btrfs 和 XFS 上都能工作降低学习成本。当然这些只是基于项目定位的合理推断。具体实现以仓库源码为准。4.2 典型部署场景从实际运维角度看Oans 最值得尝试的场景有三类第一类是备份服务器。备份目录里天然存在大量未变化的文件副本每周全备可能就是大把重复块。每周备份后执行一次离线去重能够显著压缩备份占用空间。第二类是虚拟化宿主机。虚拟机镜像基于同一模板复制时镜像内部大量数据完全相同。对镜像文件做完整去重可以在不加硬盘的情况下多放几台虚拟机。第三类是测试环境。联调时需要把一套生产数据复制到多个测试目录这些副本之间的重复率接近 100%。用去重代替副本拷贝能让磁盘瞬间“省出”很多空间。4.3 不适合的场景去重不是银弹。如果遇到以下情况应该谨慎使用数据库数据文件。数据库的随机写入和并发控制与 CoW 机制叠加可能导致性能下降和大量碎片。频繁修改的大文件。去重后每次修改都会触发新的物理块分配可能反而增加空间占用。不分青红皂白地全盘去重。系统目录、日志目录、临时目录根本没有多少重复数据扫描纯属浪费 CPU。更稳妥的判断是先对静态数据目录做去重跑一两周观察稳定性和收益再决定是否扩大范围。5. 环境准备与安全前置5.1 内核与文件系统要求Oans 依赖 reflink 能力因此内核和文件系统必须满足前提Linux 内核XFS 需要内核 4.9 以上实际建议 5.x 或更高btrfs 对内核版本要求相对宽松。文件系统需要确认目录所在分区确实启用了 reflink 支持。挂载选项XFS 在较新版本中默认开启 reflink老版本需要在创建文件系统时指定-m reflink1。查看文件系统类型和挂载参数df -T /data findmnt -o TARGET,FSTYPE,OPTIONS /data如果输出显示xfs还要进一步确认是否启用了 reflink 相关能力。在没有把握的情况下先在测试环境用一块新盘做验证不要直接在生产分区上跑。5.2 创建测试环境建议在独立的测试分区上进行首次实验。下面以 btrfs 和 XFS 为例创建两个测试挂载点# 假设存在空闲设备 /dev/sdb 和 /dev/sdc # 创建 btrfs 测试分区 mkfs.btrfs -f /dev/sdb mkdir -p /mnt/test-btrfs mount /dev/sdb /mnt/test-btrfs # 创建 XFS 测试分区支持 reflink mkfs.xfs -m reflink1 -f /dev/sdc mkdir -p /mnt/test-xfs mount /dev/sdc /mnt/test-xfsXFS 的-m reflink1参数在老版本中必须显式指定新版本默认开启。如果mkfs.xfs不支持该参数说明工具链太旧需要先升级。做实验之前请确认/dev/sdb和/dev/sdc确实是你打算格式化的设备不要拿现有数据盘操作。5.3 安全前置备份与最小权限去重工具一旦开始执行合并会修改文件系统元数据。虽然 reflink 合并本身不会破坏可见内容但任何操作文件系统的程序都有潜在风险。生产环境中的安全原则是先在测试分区完整跑通一次再考虑生产目录。运行去重前对关键数据做备份或快照。使用独立低权限账号执行扫描尽量不给 root。去重过程中不要并发写入被处理的目录。6. 安装与基础使用6.1 获取 Oans从项目标题的 Show HN 风格来看Oans 很可能是以源码形式发布。安装方式通常是从 GitHub 或类似平台克隆源码然后通过构建工具编译。不同语言工具链不同具体命令以仓库 README 为准。通用流程如下git clone oans-project-repo-url cd oans # 如果项目使用 Rust 工具链 cargo build --release # 构建产物通常在 target/release/oans ./target/release/oans --help如果项目使用 Go则可能是go build如果提供预编译二进制也可以直接下载使用。这里的关键不是具体命令而是任何去重工具都应该先看--help输出确认子命令、参数和输出格式。6.2 扫描scan和 duperemove 类似Oans 大概率会采用“扫描和去重分离”的两阶段设计先扫描构建去重清单再执行去重。这种设计的好处是用户可以先看扫描结果确认重复数据规模和位置再决定是否真正合并。典型用法可能是# 扫描指定目录输出去重清单 ./target/release/oans scan --dir /mnt/test-btrfs --hash xxh3 --output oans-scan.json # 查看扫描结果摘要 cat oans-scan.json扫描命令跑完后重点观察几个数字扫描文件数、重复块数、可节省空间大小。如果可节省空间很小说明这个目录不适合去重如果重复率很高再继续下一步。6.3 执行去重dedupe拿到扫描清单后执行去重# 按扫描结果执行去重 ./target/release/oans dedupe --input oans-scan.json --apply有些工具会带--dry-run参数可以先模拟执行不实际修改文件系统。这个参数非常有用建议任何生产操作前先跑一遍。需要注意的是具体参数名和 JSON 结构可能因项目实现而异。这里提供的是一次通用工作流的示例更准确的信息以 Oans 官方 README 为准。7. 完整示例构造测试数据并验证去重效果7.1 构造高重复率数据为了验证 Oans 的效果我们可以在测试分区里构造一批重复数据。下面的指令在/mnt/test-btrfs下生成一个 64MB 的原始文件然后复制出多个副本cd /mnt/test-btrfs # 生成一个 64MB 的随机文件作为数据源 dd if/dev/urandom oforiginal.bin bs1M count64 # 创建多个目录复制完全相同的文件 mkdir -p docs backups snapshots cp original.bin docs/copy1.bin cp original.bin docs/copy2.bin cp original.bin backups/backup1.bin cp original.bin backups/backup2.bin cp original.bin snapshots/snap1.bin cp original.bin snapshots/snap2.bin这里故意不使用cp --reflink而是普通复制。模拟的是真实场景中“多份完整物理副本”的状态。此时磁盘上应该有 8 份 64MB 的物理数据总共占用约 512MB。7.2 查看去重前的空间占用在 btrfs 上普通du和df观察的是逻辑空间不容易看出物理块占用。可以用btrfs filesystem du查看每个文件的独占空间btrfs filesystem du /mnt/test-btrfs对于 XFS可以用xfs_bmap查看文件的物理块映射xfs_bmap -v /mnt/test-xfs/original.bin如果能确认每个文件都有自己独立的物理块就可以继续去重实验了。7.3 运行 Oans 扫描与去重先扫描./target/release/oans scan --dir /mnt/test-btrfs --output scan.json然后查看scan.json确认检测到 8 个内容完全相同的文件预估可释放空间接近 448MB7 个重复副本的物理空间。确认无误后执行去重./target/release/oans dedupe --input scan.json --apply执行后8 个文件会共享同一组物理块逻辑上仍然是 8 个独立文件但物理占用只有原来的八分之一。7.4 验证文件完整性与空间回收去重完成后第一件事是验证文件内容没有损坏# 校验所有复本的哈希是否一致 sha256sum original.bin docs/*.bin backups/*.bin snapshots/*.bin如果所有哈希都一致说明去重没有破坏文件内容。然后再次查看物理空间占用btrfs filesystem du /mnt/test-btrfs df -h /mnt/test-btrfs如果去重成功btrfs filesystem du会显示每个文件的独占空间大幅下降物理分区可用空间明显增加。注意第一次去重后df显示的已用空间可能变化不明显因为文件系统可能还没有立即回收所有无用块必要时可以先执行sync btrfs filesystem sync /mnt/test-btrfs再重新查看空间。7.5 与 duperemove 做一个速度对比在 btrfs 上可以用 duperemove 做一次对照实验帮助理解 Oans 的“fast”到底体现在哪个环节。先还原子测试数据为物理副本再执行 duperemove# 创建真实副本 cp --reflinknever original.bin docs/copy1.bin # 使用 duperemove 扫描 duperemove -r /mnt/test-btrfs --lookup-extentsyes然后记录 Oans 扫描耗时的对应数据。在相同数据规模下如果 Oans 明显更快说明它在哈希或调度上确实做了优化。如果差距不大也不要意外——小规模数据很难体现性能差异真正能拉开速度差距的是 TB 级目录。8. 常见问题与排查思路问题现象可能原因排查方式解决方案执行时提示文件系统不支持 reflink文件系统太老或未开启 reflink查看/proc/mounts、内核版本升级内核重新挂载或重建文件系统时开启 reflink扫描速度很慢目录文件过多、哈希算法慢观察 CPU 和磁盘 IO 状态减少并发 IO、换用更快的哈希算法、限制扫描目录去重后空间没有明显减少数据本身重复率低或已去重过查看扫描报告中的重复块数量确认目标数据是否合适调整块大小后重新扫描去重过程中系统内存不足哈希索引过大查看工具是否支持内存上限参数开启增量扫描、增大交换空间、减少单次扫描范围文件内容被修改后空间占用爆炸CoW 特性触发新块分配查看文件碎片率和占用变化对频繁修改的数据不要开启持续去重运行时报权限错误普通用户无法操作目标文件检查目录属主和权限使用有权限的最小账号运行不要随意给 root9. 最佳实践与工程建议9.1 生产环境先跑 dry-run无论使用 Oans 还是其他去重工具生产环境的第一步永远是 dry-run。先通过扫描生成清单人工确认清单里的文件目录符合预期再执行真正的去重。不要直接在数据库目录或系统目录上盲目跑工具。9.2 将去重纳入定时任务对于备份目录这类重复率持续增长的数据去重应该是一个周期性任务。例如每周日凌晨执行一次扫描加去重。定时任务需要加锁避免上一轮还没结束下一轮又启动。脚本里最好加上日志输出和错误告警#!/bin/bash set -euo pipefail LOCK_FILE/var/lock/oans-dedupe.lock exec 9$LOCK_FILE if ! flock -n 9; then echo another dedupe task is running, exit exit 1 fi ./target/release/oans scan --dir /data/backup --output /var/lib/oans/scan.json ./target/release/oans dedupe --input /var/lib/oans/scan.json --apply9.3 关注快照与去重的交互btrfs 的快照和去重会互相影响。如果目录上有多个快照去重工具需要处理快照之间的共享块关系。在快照活跃的卷上执行去重时要先确认工具是否能正确识别共享块否则可能做无用功甚至破坏快照的空间优化效果。9.4 预留回滚方案去重操作虽然不会删除文件内容但会改变物理块布局。一旦发现去重后出现异常比如某文件被修改后性能急剧下降最直接的办法是回到去重前的快照。因此建议在去重前对目标子卷创建快照btrfs subvolume snapshot /data/backup /data/backup-pre-dedupe这样可随时回滚。9.5 关注版本与内核兼容性去重工具依赖文件系统的 FIDEDUPERANGE ioctl内核和文件系统版本不一致时行为可能有差异。在 XFS 上使用尤其要注意内核版本。生产环境升级内核之前先在测试环境中重新跑一遍去重流程验证兼容性。10. 总结与后续学习方向Oans 这类工具的价值不只是“又多了一个去重工具”。它在 btrfs 上带来的是速度上的新选择在 XFS 上是生态位上的补全。理解它需要先理解 reflink、哈希指纹、写时复制这些底层机制。实际操作时最关键的三步是先扫描、再 dry-run、最后执行去重每一步都要验证输出。如果你正在管理备份服务器、虚拟化宿主机或者测试环境建议从 btrfs 测试分区开始构造一批重复文件跑通全流程观察空间回收情况和文件完整性。然后再考虑是否引入到生产环境。这个验证过程不复杂但能让你对文件系统级去重有非常直观的体感。后续如果想把这条线继续深入可以研究几个方向去重工具如何做增量扫描、如何控制内存索引规模、FIDEDUPERANGE ioctl 在 btrfs 和 XFS 上的实现差异以及如何在大量小文件场景下优化扫描效率。这些问题每一个都值得单独写一篇实验笔记。