4、核心原理:Clone-on-Write (CoW) 与 稀疏文件 (Sparse File) 的关系
聊增量快照,有两个底层机制你绕不开:写时克隆(CoW)和稀疏文件。
很多人觉得这是两个独立的概念。其实不然。我个人的理解是——CoW 决定了快照“怎么变”,稀疏文件决定了快照“怎么存”。两者配合,才让增量快照既快又省空间。
这一章,我们就来拆解这对“黄金搭档”。
4.1 写时克隆:快照的“延迟复制”哲学
先说说 CoW 的核心思想。说白了就一句话:不复制,直到不得不复制。
你创建一个快照时,系统不会真的把数据全拷一遍。它只是把原始数据块的指针“冻结”住。后续的写入,才触发真正的复制动作。
关键点:CoW 让快照创建变成了 O(1) 操作——不管数据多大,创建快照都是一瞬间的事。
具体流程是这样的:
- 创建快照:元数据层记录当前时间点的数据块映射表。物理数据不动。
- 首次写入:应用要修改某个数据块。系统发现这个块属于快照保护范围。
- 复制原块:先把原始数据块复制到快照预留空间(或者新位置)。
- 更新指针:快照指向旧块副本,活跃卷指向新写入位置。
- 写入完成:应用继续,快照数据完好无损。
嗯,这里要注意:CoW 的代价不在创建时,而在写入时。每次修改都会多一次读+写。我在项目中遇到过,如果工作负载是随机小写密集型的,CoW 的写放大效应会很明显。所以生产环境里,快照频率和写入性能之间需要做权衡。
4.2 稀疏文件:快照的“按需分配”存储
稀疏文件是什么?你想想看,一个 1TB 的虚拟磁盘快照,实际只改了 10MB 数据。如果系统老老实实分配 1TB 空间给它,那太浪费了。
稀疏文件解决的就是这个问题。它只记录实际写入数据的块,未写入的部分在磁盘上不占空间。
我的经验:在 Linux 下,你可以用 ls -ls 查看文件的实际占用块数,和 ls -lh 看到的“虚拟大小”对比。差距往往几十倍甚至上百倍。这就是稀疏文件的威力。
稀疏文件的核心机制:
- 空洞(Hole):文件中未写入数据的区域。文件系统不会为空洞分配物理块。
- 按需分配:只有写入数据时,文件系统才分配实际的磁盘块。
- 读取空洞:读取未写入区域时,文件系统返回全零。应用层无感知。
说白了,稀疏文件让快照的存储成本跟“变化量”成正比,而不是跟“原始数据大小”成正比。这正是增量快照能省空间的根本原因。
4.3 两者的协同关系
CoW 和稀疏文件不是各自为战,而是深度配合的。我画了一张图来说明它们的关系:
从图中可以看出:CoW 是逻辑层面的策略,稀疏文件是物理层面的实现。CoW 决定了“什么时候复制”,稀疏文件决定了“复制后的数据怎么存”。
4.4 实际场景中的配合
我拿一个真实场景来说明。假设你有一个 100GB 的数据库卷,创建快照后,应用修改了 2GB 的数据。
| 阶段 | CoW 做了什么 | 稀疏文件做了什么 | 磁盘占用 |
|---|---|---|---|
| 创建快照 | 冻结元数据指针 | 创建稀疏文件,大小显示 100GB,实际占用 0 | 几乎为 0 |
| 修改 2GB 数据 | 复制 2GB 原块到快照区 | 为这 2GB 分配物理块 | 约 2GB |
| 持续修改 | 持续复制被修改的原块 | 只分配有数据的块 | 等于变化量 |
你看,最终快照占用的空间就是 2GB,而不是 100GB。这就是两者配合的效果。
我曾经踩过的坑:有一次,我在一个不支持稀疏文件的老文件系统上部署快照。结果创建快照时,系统直接分配了完整大小的空间,100GB 的卷瞬间占满磁盘。从那以后,我每次选型都会确认底层文件系统是否原生支持稀疏文件。ext4、XFS、Btrfs、ZFS 都支持,但某些网络文件系统(NFS 旧版本)可能不支持。
4.5 性能与空间权衡
CoW + 稀疏文件不是没有代价的。我总结了几点:
- 写放大:每次修改都需要先读原块再写副本。对于顺序写场景影响不大,但随机写场景下,写放大可能达到 2x-3x。
- 碎片化:频繁的 CoW 操作会导致数据块分散。快照链越长,碎片越严重。我建议定期合并快照链。
- 空洞回收:删除快照后,稀疏文件中的空洞不会自动回收。需要手动执行
fallocate或fstrim来释放空间。 - 元数据开销:稀疏文件需要维护大量的 extent 映射。文件系统在管理大量小空洞时,元数据本身也会占用空间。
我的建议:对于写密集型的 OLTP 数据库,快照频率不要太高。我一般建议每小时一次,或者配合日志归档来做。对于备份场景,每天一次快照通常就足够了。
4.6 小结
CoW 和稀疏文件,一个管逻辑,一个管物理。两者结合,让增量快照在创建时几乎零成本,在存储时只占变化量。理解这对组合,你就掌握了增量快照最核心的底层原理。