一、XFS 设计哲学
1.1 起源:SGI IRIX 与大规模存储
XFS 诞生于 1993 年的硅谷图形公司(Silicon Graphics, Inc.),最初运行在 IRIX 操作系统上。 SGI 的核心业务是高性能计算和影视后期制作,客户需要处理的文件动辄几十 GB 甚至数 TB。 当时主流的 EFS(Extent File System)在面对这类工作负载时已经力不从心:元数据操作串行、单线程分配、文件大小受限。 XFS 就是为了解决这些问题而设计的——从第一天起,它的目标就是「大文件、高并发、可扩展」。
1994 年,XFS 在 IRIX 5.3 上正式发布。2001 年,SGI 将 XFS 移植到 Linux 内核(2.4 系列),并以 GPL 协议开源。 此后 XFS 在 Linux 社区持续演进,逐步成为企业级工作负载的首选文件系统之一。 2014 年起,Red Hat Enterprise Linux 7 将 XFS 设为默认文件系统(Default File System),这一决定延续至今。
1.2 核心设计原则
XFS 的设计围绕四个核心原则展开:
可扩展性(Scalability)。文件系统的元数据结构必须能随磁盘容量线性扩展,不能在容量翻倍时出现性能悬崖。 XFS 通过分配组(Allocation Group)实现元数据的水平分区,允许多个 CPU 同时操作不同区域的元数据。
大文件友好。XFS 从设计之初就使用 64 位地址空间,理论上支持的最大文件大小为 8 EiB,最大卷大小同样为 8 EiB。 作为对比,ext4 的最大卷大小为 1 EiB,最大文件大小为 16 TiB。
高效的元数据管理。所有元数据结构——inode 索引、空闲空间、区段映射——都使用 B+树(B+Tree)组织。 B+树 在有序查找、范围扫描和插入删除方面都有 O(log n) 的时间复杂度,适合管理数百万乃至数十亿条记录。
日志保护。XFS 使用元数据日志(Metadata Journal)保证崩溃一致性。 只有元数据写入日志,数据本身不写日志,这在保障一致性的同时避免了日志带来的写放大。
1.3 与 ext4 的定位差异
ext4 是一个通用文件系统,擅长处理大量小文件的日常工作负载,启动速度快,工具链成熟。 XFS 的设计侧重点不同:它在大文件顺序 I/O、高并发元数据操作、大容量卷管理方面有结构性优势。
下面这张表总结了两者的关键差异:
| 维度 | ext4 | XFS |
|---|---|---|
| 最大卷大小 | 1 EiB | 8 EiB |
| 最大文件大小 | 16 TiB | 8 EiB |
| 元数据结构 | H-Tree(目录)+ 区段树 | 全面 B+树 |
| 并行分配 | 单一块组锁 | 分配组级别并行 |
| 在线缩容 | 支持 | 不支持 |
| 默认日志模式 | ordered | ordered |
| reflink 支持 | 否 | 是(内核 4.9 起) |
XFS 不支持在线缩容(Online Shrink),这是它的一个已知限制。 如果预计卷大小会频繁变化,需要在部署前考虑这一点。
1.4 XFS 在 Linux 内核中的位置
XFS 的内核代码位于 fs/xfs/ 目录下,代码量约
15 万行(Linux 6.5)。 它通过 VFS(Virtual File
System)层向上提供标准的 POSIX
文件操作接口,向下通过块设备层(Block
Layer)与存储设备交互。 XFS 在 VFS
和块设备层之间实现了自己的一套完整元数据管理和空间分配逻辑。
用户态应用程序
│
▼
┌──────────────────────┐
│ VFS 层 │
│ open/read/write/... │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ XFS 文件系统层 │
│ ├─ 分配组管理 │
│ ├─ B+树元数据 │
│ ├─ 日志子系统 │
│ └─ 延迟分配引擎 │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ 块设备层 │
│ bio → request │
└──────────┬───────────┘
│
▼
存储设备
二、Allocation Groups 并行设计
2.1 分配组的基本概念
分配组(Allocation Group,AG)是 XFS
实现并行化的核心机制。 mkfs.xfs
在格式化时将整个卷(Volume)划分为若干等大的 AG,每个 AG
独立管理自己的 inode、空闲空间和区段映射。 默认情况下,AG
的大小由 mkfs.xfs 根据卷大小自动计算,通常在
256 MiB 到 1 TiB 之间。 AG 的数量通常在 4 到
数千个之间,取决于卷的总容量。
# 查看已有 XFS 文件系统的 AG 数量和大小
xfs_info /dev/sda1典型输出如下:
meta-data=/dev/sda1 isize=512 agcount=4, agsize=65536 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=1, sparse=1, rmapbt=0
= reflink=1 bigtime=1 inobtcount=1 nrext64=0
data = bsize=4096 blocks=262144, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0, ftype=1
log =internal bsize=4096 blocks=16384, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
输出中 agcount=4 表示有 4
个分配组,agsize=65536 blks 表示每个 AG 包含
65536 个块。
2.2 AG 的内部结构
每个 AG 都有一套完整且独立的元数据结构,可以看作一个「迷你文件系统」。 AG 内部的布局从磁盘偏移 0 开始,依次排列如下:
AG 内部布局:
┌──────────────────────────────────────────────┐
│ 超级块副本(Superblock Copy) │ 扇区 0
├──────────────────────────────────────────────┤
│ AGF(AG Free Space Header) │ 扇区 1
├──────────────────────────────────────────────┤
│ AGI(AG Inode Header) │ 扇区 2
├──────────────────────────────────────────────┤
│ AGFL(AG Free List) │ 扇区 3
├──────────────────────────────────────────────┤
│ B+树根节点区域 │
│ ├─ 按块号排序的空闲空间 B+树(bnobt) │
│ ├─ 按大小排序的空闲空间 B+树(cntbt) │
│ ├─ inode B+树(inobt) │
│ ├─ 空闲 inode B+树(finobt) │
│ └─ 反向映射 B+树(rmapbt,可选) │
├──────────────────────────────────────────────┤
│ 数据区域 │
│ (inode 块、数据块、空闲块混合分布) │
└──────────────────────────────────────────────┘
超级块副本。每个 AG
都保存一份超级块(Superblock)的副本。 如果 AG 0
的主超级块损坏,xfs_repair 可以从其他 AG
的副本中恢复。
AGF(AG Free Space Header)。记录该 AG 的空闲空间统计信息和两棵空闲空间 B+树的根节点位置。
AGI(AG Inode Header)。记录该 AG 的 inode 分配信息和 inode B+树的根节点位置。
AGFL(AG Free List)。保留一小部分预留块,供 B+树 在分裂或合并时使用,避免在元数据操作的关键路径上再去做空间分配。
2.3 并行分配机制
AG 设计的核心价值在于并行性。 每个 AG 有自己独立的锁(Per-AG Lock),当多个线程同时创建文件或分配磁盘空间时,只要它们落在不同的 AG 上,就不会产生锁竞争。
XFS 的 inode 分配器会尽量在目标目录所在的 AG 中分配新
inode,同时也会考虑各 AG 的负载均衡。 具体策略定义在内核源码
fs/xfs/xfs_ialloc.c 中的
xfs_ialloc_ag_select() 函数里。
简化后的选择逻辑如下:
inode 分配的 AG 选择逻辑:
1. 优先选择父目录所在的 AG
2. 如果该 AG 的空闲 inode 不足,则轮转到下一个 AG
3. 每个 AG 维护一个 inode 计数,避免单个 AG 过载
4. 最终选择空闲 inode 最多的 AG
这种设计在高并发场景下效果明显。 例如,一台多核服务器上运行着数十个进程同时写日志文件。 如果使用 ext4,所有进程在分配 inode 时会竞争同一把全局锁。 而在 XFS 上,这些进程的 inode 分配会分散到不同的 AG 上,锁竞争显著降低。
2.4 AG 数量的选择
AG 数量并非越多越好。过多的 AG 会导致:
- 每个 AG 的空间太小,大文件无法在单个 AG 内获得连续区段;
- B+树的数量增加,内存开销上升;
- 超级块副本占用的空间增加。
mkfs.xfs
的默认策略已经经过仔细调优,大多数场景下不需要手动调整。
如果确实需要,可以通过 -d agcount=N 或
-d agsize=N 参数指定:
# 手动指定 AG 大小为 1 TiB
mkfs.xfs -d agsize=1099511627776 /dev/sda1
# 手动指定 AG 数量为 8
mkfs.xfs -d agcount=8 /dev/sda1三、B+Tree 元数据管理
3.1 为什么选择 B+树
文件系统的元数据管理本质上是一个索引问题:给定一个逻辑文件偏移,快速找到对应的物理块号;给定一段空闲空间需求,快速找到满足条件的连续区间。
B+树 是解决这类问题的经典数据结构。它的核心特性如下:
- 所有数据都存储在叶节点,内部节点只存储索引键;
- 叶节点之间通过指针相连,支持高效的范围扫描;
- 树的高度通常只有 2-3 层,即使管理数十亿条记录,查找操作也只需要 2-3 次磁盘 I/O。
XFS 在元数据管理中全面使用 B+树,包括以下几种:
| B+树类型 | 键值 | 用途 |
|---|---|---|
| bnobt | 起始块号 | 按块号排序的空闲空间索引 |
| cntbt | 区段大小 | 按大小排序的空闲空间索引 |
| inobt | inode 号 | 已分配 inode 的索引 |
| finobt | inode 号 | 有空闲槽位的 inode 块索引 |
| bmbt | 文件逻辑偏移 | 文件区段映射 |
| rmapbt | 物理块号 | 反向映射(物理块→owner) |
| refcntbt | 物理块号 | 引用计数(reflink 使用) |
3.2 空闲空间 B+树
每个 AG 维护两棵空闲空间 B+树(Free Space B+Tree),它们索引的是同一份数据,但排序方式不同:
- bnobt(By Block Number B+Tree):按空闲区段的起始块号排序。用于在指定位置附近查找空闲空间,支持局部性分配。
- cntbt(By Count B+Tree):按空闲区段的大小排序。用于快速找到满足特定大小需求的最小可用区段。
这种双 B+树 设计的好处在于:无论是按位置查找还是按大小查找,都能在 O(log n) 时间内完成,不需要遍历整个空闲空间列表。
空闲空间双 B+树 结构示意:
bnobt(按块号排序) cntbt(按大小排序)
┌────────────┐ ┌────────────┐
│ 根节点 │ │ 根节点 │
└─────┬──────┘ └─────┬──────┘
│ │
┌───┴───┐ ┌───┴───┐
▼ ▼ ▼ ▼
┌────┐ ┌────┐ ┌────┐ ┌────┐
│叶1 │→│叶2 │ │叶1 │→│叶2 │
└────┘ └────┘ └────┘ └────┘
叶节点内容: 叶节点内容:
块100, 长度5 长度2, 块500
块200, 长度8 长度5, 块100
块500, 长度2 长度8, 块200
当需要分配一段连续空间时,XFS 的空间分配器(Space Allocator)根据分配策略选择使用哪棵树:
- 精确大小分配:先查 cntbt,找到大小最接近需求的区段;
- 邻近分配:先查 bnobt,在目标位置附近找空闲区段,以提高数据局部性。
3.3 Inode B+树
每个 AG 维护一棵 inode B+树(inobt),记录该 AG 中所有已分配的 inode 块(Inode Chunk)。 XFS 将 inode 按 64 个一组分配,称为一个 inode 块。 inobt 的叶节点记录每个 inode 块的状态:块的起始位置和一个 64 位位图(Bitmap),指示哪些 inode 槽位已分配、哪些空闲。
从内核 3.16 起,XFS 引入了空闲 inode B+树(finobt,Free Inode B+Tree)。 finobt 只索引那些至少有一个空闲槽位的 inode 块。 它的作用是加速 inode 分配:当需要分配新 inode 时,不必遍历整棵 inobt 去寻找有空闲槽位的块,直接查 finobt 即可。
3.4 文件区段 B+树
每个文件的区段映射(Extent Map)存储在 inode 的数据叉(Data Fork)中。 XFS 使用一种分层策略来管理区段映射:
内联格式(Extent List)。当文件的区段数量较少(通常 ≤ 19 个区段,取决于 inode 大小)时,区段记录直接存储在 inode 内部。 每条区段记录(Extent Record)占 128 位,格式如下:
区段记录格式(128 位):
┌──────┬────────────────────┬────────────────────┬──────────────┐
│ flag │ 逻辑偏移(54位) │ 物理块号(52位) │ 块数(21位) │
│ 1位 │ 文件内偏移 │ 磁盘上的起始块 │ 区段长度 │
└──────┴────────────────────┴────────────────────┴──────────────┘
其中 flag 位标识该区段是否为「未写入」状态(Unwritten Extent),用于预分配。
B+树格式(BMap B+Tree)。当文件的区段数量超过 inode 能容纳的上限时,XFS 将区段映射转换为 B+树。 B+树 的根节点存储在 inode 内部,叶节点存储在外部磁盘块上。 每个叶节点包含多条区段记录,以文件逻辑偏移为键排序。
这种设计使得小文件(只有几个区段)不需要额外的磁盘 I/O 来读取区段映射——一切都在 inode 内部完成。 而大文件(可能有数百万个区段)则能通过 B+树 高效索引,避免线性扫描。
3.5 反向映射 B+树
反向映射 B+树(Reverse Mapping B+Tree,rmapbt)是 XFS v5 格式引入的可选特性。 它记录的是物理块到所属者(Owner)的映射:给定一个物理块号,rmapbt 能告诉你这个块属于哪个文件的哪个偏移,或者属于哪个元数据结构。
rmapbt 的主要用途包括:
- 在线一致性检查:
xfs_scrub工具利用 rmapbt 交叉验证正向映射和反向映射是否一致。 - 在线修复:如果正向映射损坏,可以从反向映射中重建。
- reflink 支持:reflink 操作需要知道一个物理块是否有多个引用者,rmapbt 提供了这一信息。
启用 rmapbt 会增加约 1-2% 的空间开销和少量写入开销,但在数据保护方面的价值通常值得这个代价。
# 格式化时启用反向映射 B+树
mkfs.xfs -m rmapbt=1 /dev/sda13.6 引用计数 B+树
引用计数 B+树(Reference Count B+Tree,refcntbt)用于支持 reflink 特性。 当多个文件共享同一物理区段时,refcntbt 记录每个共享区段的引用计数。 只有当引用计数降为 0 时,物理空间才会被真正释放。
refcntbt 的叶节点格式如下:
refcntbt 叶节点记录:
┌────────────────┬──────────────┬──────────────┐
│ 起始物理块号 │ 块数 │ 引用计数 │
└────────────────┴──────────────┴──────────────┘
四、日志设计
4.1 元数据日志的基本原理
XFS 使用预写日志(Write-Ahead Log,WAL)来保证崩溃一致性(Crash Consistency)。 核心规则很简单:任何元数据修改,必须先写入日志,然后才能写入元数据的最终位置。 如果系统在中途崩溃,重新挂载时内核会重放(Replay)日志中的记录,将元数据恢复到一致状态。
XFS 默认只记录元数据的日志,不记录数据。这是
ordered 模式(也是 ext4 的默认模式)。
在这种模式下,内核保证:在元数据日志提交之前,相关的数据已经写入磁盘。
这样做的好处是避免了数据日志带来的写放大——对于大文件顺序写入场景,这一点尤为重要。
4.2 日志结构
XFS 的日志由连续的日志记录(Log Record)组成,每条日志记录包含一个或多个日志项(Log Item)。 日志记录的头部包含序列号(Log Sequence Number,LSN),用于在恢复时确定记录的先后顺序。
日志是一个环形缓冲区(Circular Buffer)。新的日志记录追加到尾部,已经提交并刷盘的记录可以被覆盖。 日志的头部位置(Head)和尾部位置(Tail)由超级块中的 LSN 字段追踪。
日志环形缓冲区:
tail head
│ │
▼ ▼
┌────────┬───────┬───────┬───────┬────────┐
│已释放 │ rec 5 │ rec 6 │ rec 7 │ 已释放 │
│(可覆盖)│ │ │ │(可覆盖)│
└────────┴───────┴───────┴───────┴────────┘
4.3 日志事务与预留
XFS 的日志系统使用事务(Transaction)模型。 一个事务包含一组逻辑上相关的元数据修改,要么全部提交,要么全部回滚。 在事务开始前,XFS 会在日志中预留(Reserve)足够的空间,确保事务在写入过程中不会因为日志空间不足而失败。
预留机制的一个重要细节是:XFS 使用两阶段提交(Two-Phase Commit)。 第一阶段将日志项写入内存中的日志缓冲区(In-Core Log Buffer,iclog)。 第二阶段将 iclog 刷写到磁盘上的日志设备。
事务提交流程:
1. 事务开始 → 预留日志空间
2. 修改内存中的元数据
3. 将修改记录写入 iclog(内存)
4. 事务提交 → iclog 标记为待刷写
5. iclog 满或显式 fsync → 刷写到磁盘日志
6. 日志落盘后 → 元数据可以写入最终位置
7. 元数据落盘后 → 日志空间可回收
4.4 日志条带单元
在使用 RAID 阵列(RAID Array)时,XFS 允许将日志的写入粒度对齐到 RAID 条带单元大小(Log Stripe Unit)。 这样可以避免日志写入跨越条带边界,减少 RAID 层面的读-改-写(Read-Modify-Write)操作。
# 格式化时指定日志条带单元,单位为字节
mkfs.xfs -l su=65536 /dev/sda1
# 挂载时指定日志写入大小
mount -o logbsize=256k /dev/sda1 /mnt/datalogbsize
挂载选项控制内存中日志缓冲区的大小。 较大的
logbsize
可以减少日志刷写的频率,但会增加崩溃后需要重放的数据量。
对于写入密集的工作负载,将 logbsize 设为 256k
通常是一个合理的起点。
4.5 外部日志设备
XFS 支持将日志放置在独立的块设备上(External Log Device),通常是一块高速 SSD 或 NVMe 设备。 这种配置将日志 I/O 和数据 I/O 分离到不同的设备上,避免两者之间的 I/O 竞争。
# 格式化时指定外部日志设备
mkfs.xfs -l logdev=/dev/nvme0n1 /dev/sda1
# 挂载时指定外部日志设备
mount -o logdev=/dev/nvme0n1 /dev/sda1 /mnt/data外部日志设备在以下场景中特别有价值:
- 数据设备是大容量 HDD 阵列,日志设备是低延迟 NVMe SSD;
- 写入密集的数据库工作负载,日志提交延迟是关键瓶颈;
- 需要将日志写入与数据写入的磁盘寻道完全隔离。
4.6 日志大小规划
日志太小会导致事务频繁等待日志空间回收,影响写入吞吐量。 日志太大则浪费磁盘空间,并且崩溃恢复时间变长。
mkfs.xfs
的默认日志大小已经针对卷大小做了合理计算。
如果需要手动调整,可以参考以下经验值:
| 卷大小 | 推荐日志大小 |
|---|---|
| < 1 TiB | 默认即可(通常 64-128 MiB) |
| 1-10 TiB | 128-512 MiB |
| > 10 TiB | 512 MiB - 2 GiB |
# 手动指定日志大小为 512 MiB
mkfs.xfs -l size=512m /dev/sda1五、延迟分配与预分配
5.1 延迟分配
延迟分配(Delayed Allocation,delalloc)是 XFS
的一个关键性能优化。 当应用程序调用 write()
写入数据时,XFS 并不立即分配物理磁盘块。
相反,它只是在内存中记录「这段数据需要 N
个块」,并将数据暂存在页缓存(Page Cache)中。
真正的物理块分配推迟到数据实际刷写(Writeback)到磁盘时才执行。
这种设计有三个好处:
减少碎片。延迟分配允许 XFS
在真正分配时看到完整的写入模式。
例如,应用程序可能通过多次小的 write()
调用写入一个大文件。 如果每次 write()
都立即分配,分配器只能看到每次请求的几个块,难以做出全局最优的分配决策。
延迟到 writeback
时,分配器能看到积累的全部待分配数据,从而分配更大的连续区段。
减少元数据操作。多次小写入合并为一次大分配,减少了 B+树 的插入操作和日志写入。
支持取消分配。如果数据在刷写前被删除或截断,就不需要分配和释放物理块,减少了不必要的 I/O。
5.2 预分配机制
XFS 的推测性预分配(Speculative Preallocation)在延迟分配的基础上更进一步。 当文件正在增长时,XFS 会预测文件的最终大小,并提前预留比当前需要更多的空间。
预分配的大小策略定义在内核源码
fs/xfs/xfs_iomap.c 中。 简化后的逻辑如下:
预分配大小计算(简化):
如果文件大小 < 64 KiB:
预分配 = 文件当前大小的 2 倍
如果文件大小在 64 KiB 到 1 GiB 之间:
预分配 = 逐步增加,最大到 64 MiB
如果文件大小 > 1 GiB:
预分配 = 64 MiB(默认上限)
预分配的区段在 inode 中标记为「未写入」(Unwritten),这意味着:
- 这些区段已经在磁盘上预留了物理空间;
- 如果读取这些区段,会返回零,不会泄露旧数据;
- 当应用程序真正写入这些区段时,只需要将「未写入」标记清除即可,不需要重新分配。
5.3 allocsize 挂载选项
allocsize 挂载选项允许管理员覆盖 XFS
的默认预分配大小上限:
# 设置预分配大小上限为 1 GiB
mount -o allocsize=1g /dev/sda1 /mnt/data较大的 allocsize 适用于:
- 已知文件最终会很大的场景(如视频录制、数据库表空间);
- 追求最大顺序 I/O 性能的场景。
较大的 allocsize 的代价:
- 如果文件实际没有增长到预分配的大小,预留的空间会在文件关闭后释放,但在文件打开期间这些空间对其他文件不可用;
- 在存储空间紧张的系统上,过大的预分配可能导致空间提前耗尽的假象。
从内核 4.13 起,XFS
的推测性预分配默认行为改为动态模式(Dynamic Speculative
Preallocation),自动根据文件增长模式调整预分配大小,大多数场景下不再需要手动设置
allocsize。
5.4 fallocate 与显式预分配
除了 XFS 的自动预分配之外,应用程序还可以通过
fallocate() 系统调用显式预分配空间:
#include <fcntl.h>
int fd = open("/mnt/data/bigfile", O_WRONLY | O_CREAT, 0644);
// 预分配 10 GiB 空间,不写入数据
fallocate(fd, 0, 0, 10LL * 1024 * 1024 * 1024);fallocate() 在 XFS
上的实现非常高效:它直接操作区段
B+树,在元数据层面标记一段连续空间为「未写入」,不需要实际写入数据。
这在需要一次性预留大量空间的场景中(如数据库预扩展表空间文件)非常有用。
XFS 还支持 fallocate() 的
FALLOC_FL_INSERT_RANGE 和
FALLOC_FL_COLLAPSE_RANGE
标志,允许在文件中间插入或移除区段,无需复制数据。
六、Reflink 与 CoW
6.1 Reflink 的基本原理
引用链接(Reflink)是 XFS 从内核 4.9 开始支持的特性,允许多个文件共享同一物理数据块,而不需要实际复制数据。 这在概念上类似于硬链接(Hard Link),但硬链接共享的是 inode,而 reflink 共享的是数据区段。 两个通过 reflink 创建的文件拥有独立的 inode 和独立的元数据,只是它们的区段记录指向相同的物理块。
reflink 文件共享示意:
文件 A 的 inode 文件 B 的 inode
├─ 区段 [0, 100) → 块 1000 ├─ 区段 [0, 100) → 块 1000 ← 共享
├─ 区段 [100, 200) → 块 2000 ├─ 区段 [100, 200) → 块 2000 ← 共享
└─ 区段 [200, 300) → 块 3000 └─ 区段 [200, 250) → 块 5000 ← 独立
refcntbt 记录:
块 1000, 长度 100, 引用计数 = 2
块 2000, 长度 100, 引用计数 = 2
块 3000, 长度 100, 引用计数 = 1
块 5000, 长度 50, 引用计数 = 1
6.2 写时复制
当任何一方修改共享的数据块时,XFS 触发写时复制(Copy-on-Write,CoW)操作:
- 分配新的物理块;
- 将原始数据复制到新块中;
- 在新块上执行修改;
- 更新修改方的区段映射,指向新块;
- 将原始块的引用计数减 1。
这个过程对应用程序完全透明。CoW 操作只发生在实际写入时,读取共享数据不会触发复制。
6.3 cp –reflink
GNU coreutils 从 8.24 版本起支持 --reflink
选项,可以利用文件系统的 reflink
能力创建文件的即时副本:
# 创建 reflink 副本(接近瞬时完成)
cp --reflink=always source.img dest.img
# 自动检测文件系统是否支持 reflink
cp --reflink=auto source.img dest.imgreflink 复制与普通复制的性能差异极其显著。 对于一个 100
GiB 的文件,普通 cp 需要数分钟完成数据复制。
cp --reflink
只需要复制元数据(区段映射和引用计数),通常在毫秒级别完成。
6.4 使用 duperemove 进行去重
duperemove
是一个用户态工具,能扫描文件系统中的重复数据块,并利用
reflink 将它们去重:
# 安装 duperemove
yum install duperemove # RHEL/CentOS
apt install duperemove # Debian/Ubuntu
# 扫描并去重指定目录
duperemove -dr /mnt/data/vm-images/-d
参数表示执行去重操作(不仅仅是扫描),-r
表示递归处理子目录。
duperemove 的工作原理:
- 将文件分割为固定大小的块(默认 128 KiB);
- 计算每个块的校验和(Hash);
- 找到校验和相同的块对;
- 对校验和相同的块进行逐字节比较确认;
- 使用
FIDEDUPERANGEioctl 调用让文件系统执行 reflink 去重。
去重后,重复数据块只保留一份物理副本,多个文件通过 reflink 共享。 后续写入任何共享块时,CoW 机制自动保证数据独立性。
6.5 Reflink 与快照语义
reflink 可以实现文件级别的快照(Snapshot)语义,无需卷管理器(Volume Manager)的支持。 对于虚拟机镜像管理、容器存储等场景,这种文件级快照非常实用:
# 为虚拟机磁盘创建快照
cp --reflink=always vm-disk.qcow2 vm-disk-snap-$(date +%Y%m%d).qcow2与 LVM 快照(LVM Snapshot)相比,reflink 快照有以下优势:
- 不需要预留快照空间;
- 快照数量不受 LVM 卷组限制;
- 每个快照都是普通文件,可以独立管理;
- 不会因为快照过多导致性能退化(LVM 快照的链式 CoW 会随快照数量增加而变慢)。
6.6 启用 Reflink
reflink 需要在 mkfs.xfs 格式化时启用。在
xfsprogs 4.17 及以后的版本中,reflink
默认启用。
# 显式启用 reflink(通常不需要,默认已启用)
mkfs.xfs -m reflink=1 /dev/sda1
# 检查现有文件系统是否启用了 reflink
xfs_info /dev/sda1 | grep reflink注意:reflink 与 DAX(Direct Access)模式不兼容。 如果需要在持久内存(Persistent Memory)设备上使用 DAX 模式,不能同时启用 reflink。
七、实时子卷(Realtime Subvolume)
7.1 实时子卷的用途
实时子卷(Realtime Subvolume,RT)是 XFS 从 IRIX 时代继承的特性。 它允许将文件系统分为两个部分:常规数据区域和实时数据区域。 实时区域使用独立的存储设备(RT Device),采用固定大小的区段分配策略,为对带宽(Bandwidth)有严格要求的工作负载提供可预测的 I/O 性能。
在 SGI 的原始应用场景中,RT 子卷用于影视后期的视频流采集和回放。 这类工作负载要求在指定时间窗口内完成固定量的 I/O,不允许因元数据操作或空间碎片导致的延迟毛刺。
7.2 RT 子卷的工作方式
RT 子卷的空间管理与常规数据区域不同:
- 常规数据区域使用 B+树 管理可变大小的区段;
- RT 区域使用位图(Bitmap)管理固定大小的区段。
固定大小区段的好处是分配和释放都是 O(1) 操作,不涉及树的分裂或合并,延迟可预测。
# 格式化时指定 RT 设备和 RT 区段大小
mkfs.xfs -r rtdev=/dev/nvme1n1,extsize=1m /dev/sda1
# 挂载时指定 RT 设备
mount -o rtdev=/dev/nvme1n1 /dev/sda1 /mnt/data7.3 将文件分配到 RT 子卷
要将文件分配到 RT 子卷,需要在文件的 inode 标志中设置
XFS_XFLAG_REALTIME。 可以通过
xfs_io 工具完成:
# 创建文件并标记为实时文件
xfs_io -f -c "chattr +rt" /mnt/data/video-stream.raw
# 查看文件的 RT 标志
xfs_io -c "lsattr" /mnt/data/video-stream.raw也可以通过设置目录的继承属性(Inherit Attribute),让该目录下新创建的文件自动分配到 RT 子卷:
# 设置目录的 RT 继承标志
xfs_io -c "chattr +rt" /mnt/data/video/7.4 RT 子卷的限制
RT 子卷在 Linux 上的使用有一些限制需要注意:
- RT 子卷不支持 reflink;
- RT 子卷的空间不会被
df命令的常规输出反映,需要使用xfs_info查看; - RT 子卷上的文件不能跨区域引用常规数据区域的数据;
- 社区对 RT 子卷的测试和维护力度弱于常规数据区域。
在现代 Linux 环境中,RT 子卷的使用场景已经比较小众。 大多数高带宽 I/O 需求可以通过 NVMe SSD、合理的预分配和 I/O 调度器调优来满足,不一定需要 RT 子卷。
八、XFS 诊断工具
8.1 xfs_info:查看文件系统参数
xfs_info 是最常用的 XFS
诊断工具,用于查看已挂载文件系统的格式化参数:
xfs_info /mnt/data输出会显示块大小、AG 数量和大小、inode
大小、日志大小、是否启用了 reflink、rmapbt 等特性。
在排查问题时,xfs_info
通常是第一个要执行的命令。
8.2 xfs_db:低级元数据调试
xfs_db 是 XFS
的低级调试工具,可以直接读取和修改磁盘上的元数据结构。
它只能在未挂载的文件系统上使用(或以只读模式操作已挂载的文件系统)。
常见用法:
# 以只读模式打开文件系统
xfs_db -r /dev/sda1
# 查看超级块信息
xfs_db> sb 0
xfs_db> p
# 查看 AG 0 的空闲空间头
xfs_db> agf 0
xfs_db> p
# 查看指定 inode 的信息
xfs_db> inode 131
xfs_db> p
# 查看 inode 的区段映射
xfs_db> bmapxfs_db
还可以用于统计文件系统的碎片程度:
# 统计空闲空间碎片
xfs_db -r -c "frag -f" /dev/sda18.3 xfs_repair:文件系统修复
xfs_repair 用于检查和修复不一致的 XFS
文件系统。它必须在文件系统未挂载的状态下运行。
# 标准修复
xfs_repair /dev/sda1
# 仅检查,不修复
xfs_repair -n /dev/sda1
# 如果日志损坏,先清零日志再修复
xfs_repair -L /dev/sda1xfs_repair 的工作流程:
- 阶段 1:读取并验证超级块。如果主超级块损坏,尝试从其他 AG 的副本恢复。
- 阶段 2:扫描并验证所有 AG 的元数据结构——空闲空间 B+树、inode B+树等。
- 阶段 3:扫描所有 inode,验证区段映射、目录结构。
- 阶段 4:检查目录树的连通性,修复孤立 inode。
- 阶段 5:重建 AG 级别的空闲空间 B+树。
- 阶段 6:重建 AG 级别的 inode B+树。
- 阶段 7:最终验证和写回。
-L
参数(清零日志)应该作为最后手段使用。它会丢弃日志中未提交的事务,可能导致最近修改的元数据丢失。
8.4 xfs_fsr:在线碎片整理
xfs_fsr(XFS File System Reorganizer)是 XFS
的在线碎片整理(Online Defragmentation)工具。
它通过重新分配文件的物理区段来减少碎片,工作原理是:
- 创建一个临时文件;
- 将原文件的数据以连续方式写入临时文件;
- 交换原文件和临时文件的区段映射;
- 删除临时文件。
# 对整个文件系统执行碎片整理
xfs_fsr /mnt/data
# 只对指定文件执行碎片整理
xfs_fsr /mnt/data/database/data.ibd
# 设置碎片整理的运行时间限制(秒)
xfs_fsr -t 3600 /mnt/data检查文件的碎片程度:
# 使用 filefrag 查看文件的区段分布
filefrag -v /mnt/data/database/data.ibd输出示例:
Filesystem type is: 58465342
File size of /mnt/data/database/data.ibd is 10737418240 (2621440 blocks of 4096 bytes)
ext: logical_offset: physical_offset: length: expected: flags:
0: 0.. 524287: 262144.. 786431: 524288:
1: 524288.. 1048575: 1048576.. 1572863: 524288: 786432:
2: 1048576.. 1572863: 2097152.. 2621439: 524288: 1572864:
3: 1572864.. 2097151: 3145728.. 3670015: 524288: 2621440:
4: 2097152.. 2621439: 4194304.. 4718591: 524288: 3670016: last,eof
/mnt/data/database/data.ibd: 5 extents found
5 个区段对于一个 10 GiB 的文件来说碎片程度极低,通常不需要整理。
8.5 xfs_scrub:在线一致性检查
xfs_scrub 是较新的工具(内核 4.15
起),可以在文件系统挂载状态下执行一致性检查:
# 对已挂载的文件系统执行在线检查
xfs_scrub /mnt/data
# 检查并尝试自动修复
xfs_scrub -y /mnt/dataxfs_scrub 利用反向映射
B+树(rmapbt)交叉验证元数据的一致性。 因此,在启用了 rmapbt
的文件系统上,xfs_scrub 的检查能力更强。
九、XFS 性能调优
9.1 mkfs.xfs 格式化参数
格式化参数在文件系统的整个生命周期中都无法更改,因此需要在格式化时仔细规划。
块大小
XFS 的默认块大小(Block Size)为 4096 字节,与大多数 Linux 发行版的页大小一致。 对于主要存储大文件的场景,可以考虑使用更大的块大小来减少元数据开销:
# 使用 64 KiB 块大小(仅适用于 64 KiB 页大小的内核,如 ARM64 某些配置)
mkfs.xfs -b size=65536 /dev/sda1注意:块大小不能超过系统页大小。在 x86_64 的标准 4 KiB 页大小内核上,块大小最大为 4 KiB。
Inode 大小
XFS 的默认 inode 大小为 512 字节(ext4 为 256 字节)。 更大的 inode 可以存储更多的内联区段记录和扩展属性(Extended Attribute),减少间接块的使用:
# 使用 1024 字节的 inode
mkfs.xfs -i size=1024 /dev/sda1对于扩展属性使用较多的场景(如 SELinux、ACL、Samba),较大的 inode 有助于将这些属性存储在 inode 内部,避免额外的磁盘 I/O。
RAID 对齐
在 RAID 阵列上格式化 XFS 时,正确设置条带单元(Stripe Unit)和条带宽度(Stripe Width)至关重要:
# 假设 RAID 10 阵列:4 块盘,每块盘条带大小 64 KiB
# su = 条带单元大小,sw = 数据盘数量
mkfs.xfs -d su=64k,sw=4 /dev/md0su(Stripe Unit)是单块磁盘上的条带大小。
sw(Stripe
Width)是数据盘的数量(不含校验盘)。
正确设置这两个参数后,XFS
的空间分配器会尽量按条带宽度的倍数分配区段,最大化顺序 I/O
性能。
目录块大小
目录块大小(Directory Block Size)影响目录操作的性能。 默认值等于文件系统块大小。对于包含大量小文件的目录(如邮件服务器的 Maildir),可以增大目录块大小:
# 设置目录块大小为 64 KiB
mkfs.xfs -n size=65536 /dev/sda19.2 挂载选项调优
inode64
inode64 挂载选项允许 XFS 在整个卷的所有 AG
中分配 inode,而不是仅在卷的前 1 TiB 范围内。
在大容量卷上,这可以更好地利用所有 AG 的并行能力:
mount -o inode64 /dev/sda1 /mnt/data在 64 位系统上,inode64 从内核 3.7
起默认启用。 如果需要与某些 32 位 NFS 客户端兼容,可以使用
inode32 回退到旧行为。
noatime 与 relatime
noatime 禁止更新文件的访问时间戳(Access
Time),减少不必要的元数据写入:
mount -o noatime /dev/sda1 /mnt/datarelatime(Relative Access
Time)是一个折中方案:只有当访问时间早于修改时间时才更新。
大多数 Linux 发行版默认使用 relatime。
对于读密集的工作负载(如静态文件服务器),noatime
可以进一步减少 I/O。
logbsize
logbsize
控制内存中日志缓冲区的大小,影响日志写入效率:
# 设置日志缓冲区大小为 256 KiB
mount -o logbsize=256k /dev/sda1 /mnt/data合法值包括 32k、64k、128k、256k。
较大的值可以减少日志刷写次数,适合写入密集的工作负载。
如果文件系统使用了日志条带单元(-l su=),logbsize
必须是条带单元的整数倍。
allocsize
前面在延迟分配章节已经讨论过。在内核 4.13
之前的版本上,对于大文件写入场景,显式设置
allocsize 可能有帮助:
mount -o allocsize=1g /dev/sda1 /mnt/data9.3 数据库工作负载调优
XFS 是许多数据库系统推荐的底层文件系统。以下是针对数据库工作负载的具体调优建议。
MySQL / InnoDB
InnoDB 使用大文件存储表空间数据,XFS 的大文件支持和预分配能力与 InnoDB 配合良好。
推荐配置:
# mkfs 参数
mkfs.xfs -f -d su=64k,sw=4 -l su=64k,size=512m /dev/sda1
# mount 参数
mount -o noatime,logbsize=256k,allocsize=64m /dev/sda1 /mnt/mysqlInnoDB 的 innodb_flush_method 建议设为
O_DIRECT,绕过页缓存,避免双重缓冲。
PostgreSQL
PostgreSQL 使用大量 8 KiB 的页文件,每个表和索引都对应一个或多个文件。
推荐配置:
# mkfs 参数
mkfs.xfs -f -d su=64k,sw=4 /dev/sda1
# mount 参数
mount -o noatime,logbsize=256k /dev/sda1 /mnt/pgdataPostgreSQL 的 full_page_writes 参数与 XFS
日志之间存在交互: XFS 的元数据日志不保护 PostgreSQL
的数据页,因此 full_page_writes 应保持为
on(默认值)。
通用建议
- 将 WAL 日志和数据文件放在不同的 XFS 文件系统上(如果使用不同的磁盘);
- 使用
nobarrier挂载选项时需要确认存储层有电池保护的写缓存(Battery-Backed Write Cache,BBWC),否则会有数据丢失风险; - 避免在数据库文件所在的文件系统上运行碎片整理。
9.4 I/O 调度器配合
XFS 本身不涉及 I/O 调度,但正确选择 I/O 调度器(I/O Scheduler)可以进一步提升性能。
对于 NVMe SSD,推荐使用
none(无调度器),因为 NVMe
设备有自己的硬件队列管理:
echo none > /sys/block/nvme0n1/queue/scheduler对于 HDD 或 SATA SSD,mq-deadline
通常是好的选择:
echo mq-deadline > /sys/block/sda/queue/scheduler9.5 调优参数速查表
| 参数 | 作用 | 推荐值 | 适用场景 |
|---|---|---|---|
mkfs -d su=X,sw=Y |
RAID 条带对齐 | 与硬件一致 | RAID 阵列 |
mkfs -l size=X |
日志大小 | 128m-2g | 写入密集 |
mkfs -i size=X |
inode 大小 | 512(默认) | 通常无需调整 |
mount -o noatime |
禁用访问时间 | 启用 | 读密集 |
mount -o logbsize=X |
日志缓冲区 | 256k | 写入密集 |
mount -o allocsize=X |
预分配上限 | 默认 | 大文件顺序写 |
mount -o inode64 |
64 位 inode | 默认启用 | 大容量卷 |
十、XFS 在生产环境
10.1 RHEL 默认文件系统
从 Red Hat Enterprise Linux 7 开始,XFS 取代 ext4 成为默认文件系统。 Red Hat 做出这一决定的理由在官方文档中有明确说明:
- XFS 在大容量卷和大文件场景下的性能和可扩展性优于 ext4;
- XFS 在多核并发 I/O 场景下的表现更稳定;
- Red Hat 的企业客户(数据库、虚拟化、大数据)更多地需要这些特性。
RHEL 上的 XFS 最大支持卷大小为 500 TiB(受限于 Red Hat 的支持策略,而非文件系统本身的技术限制)。
SUSE Linux Enterprise Server(SLES)默认使用 Btrfs,但也完整支持 XFS。 Debian 和 Ubuntu 默认使用 ext4,但 XFS 在这些发行版上同样是一等公民。
10.2 数据库工作负载
XFS 是 MySQL、PostgreSQL、MongoDB、Cassandra 等主流数据库的推荐文件系统之一。
数据库工作负载对文件系统的核心需求包括:
- 大文件顺序 I/O:数据库的表空间文件通常在 GB 到 TB 级别,需要高效的顺序读写;
fsync()延迟:事务提交的延迟直接取决于日志的fsync()延迟;- 并发写入:多个连接同时写入不同的表和索引;
- 空间预分配:数据库倾向于提前分配表空间,避免运行时扩展文件的开销。
XFS 在这四个维度上都有良好的表现。 特别是延迟分配和预分配机制,使得数据库文件的区段连续性通常优于 ext4。
10.3 虚拟化与容器存储
在虚拟化环境中,XFS 的 reflink 能力为虚拟机镜像管理提供了有效支持。
典型用法:维护一个基础镜像(Golden Image),通过
cp --reflink 为每台虚拟机创建独立的磁盘文件。
这些文件在创建时几乎不占用额外空间,只有当虚拟机修改数据时才通过
CoW 分配新的物理块。
# 基础镜像
/mnt/vm-pool/base-centos8.qcow2 # 10 GiB
# 为 50 台虚拟机创建 reflink 副本
for i in $(seq 1 50); do
cp --reflink=always base-centos8.qcow2 vm-${i}.qcow2
done50 个 reflink 副本的元数据开销通常只有几 MiB,而普通复制需要 500 GiB 的磁盘空间。
在容器环境中,Podman 和 CRI-O 的 overlay 存储驱动(Overlay Storage Driver)在 XFS 上运行时可以利用 reflink 加速镜像层的复制操作。
10.4 大规模部署经验
监控指标
在生产环境中运行 XFS 时,建议监控以下指标:
# AG 级别的空闲空间统计
xfs_spaceman -c "freesp -s" /mnt/data
# 文件系统级别的计数器
cat /sys/fs/xfs/sda1/stats/stats/sys/fs/xfs/<device>/stats/stats
文件提供丰富的运行时统计信息,包括:
| 指标 | 含义 |
|---|---|
| xs_write_calls | write 系统调用次数 |
| xs_read_calls | read 系统调用次数 |
| xs_log_writes | 日志写入次数 |
| xs_log_blocks | 日志写入的块数 |
| xs_iflush_count | inode 刷写次数 |
| xs_abt_lookup | 空闲空间 B+树 查找次数 |
| xs_blk_alloc | 块分配请求次数 |
这些指标可以导入 Prometheus 或 Grafana 进行长期监控。
容量规划
XFS 不支持在线缩容,因此容量规划需要前瞻性思考:
- 预留至少 10-15% 的空闲空间,避免空间不足时的分配效率下降;
- 当使用率超过 85% 时,空闲区段的碎片化会加速,分配大段连续空间变得困难;
- 使用
xfs_growfs可以在线扩容,前提是底层块设备支持扩容。
# 在线扩容(底层设备已经扩容后)
xfs_growfs /mnt/data备份策略
XFS 提供专用的备份和恢复工具:
# 使用 xfsdump 进行完整备份
xfsdump -l 0 -f /backup/full.dump /mnt/data
# 使用 xfsdump 进行增量备份(级别 1)
xfsdump -l 1 -f /backup/incr.dump /mnt/data
# 从备份恢复
xfsrestore -f /backup/full.dump /mnt/restorexfsdump 理解 XFS
的内部结构,能正确处理扩展属性、ACL 和多流数据叉。
相比通用的 tar 或
rsync,xfsdump
在备份和恢复大量文件时效率更高。
10.5 XFS 与其他文件系统的选型
| 场景 | 推荐文件系统 | 理由 |
|---|---|---|
| 通用桌面 | ext4 | 成熟稳定,工具链完善,支持在线缩容 |
| 大文件存储 | XFS | 大文件性能好,区段管理高效 |
| 数据库 | XFS 或 ext4 | 两者都可以,XFS 在大文件和并发方面略优 |
| 需要文件级快照 | XFS(reflink) | 无需卷管理器,操作简单 |
| 需要数据校验和 | Btrfs 或 ZFS | XFS 不提供数据校验和 |
| 需要在线缩容 | ext4 | XFS 不支持在线缩容 |
| NFS 服务器 | XFS | 大容量、高并发场景下表现好 |
10.6 XFS 的演进方向
XFS 社区在持续推进几个重要方向:
在线修复(Online
Repair)。xfs_scrub
已经具备在线检查能力,社区正在开发在线修复功能,目标是在不卸载文件系统的情况下修复检测到的不一致。相关代码的主要维护者是
Darrick J. Wong。
大区段计数(Large Extent
Counters)。传统 XFS inode 中的区段计数字段只有 31
位,限制了单个文件最多约 21 亿个区段。nrext64
特性将区段计数扩展到 64 位,消除了这一限制。该特性在内核
5.19 中合入。
父指针(Parent Pointers)。为每个 inode
记录其父目录的信息,使 xfs_repair
能在不扫描整棵目录树的情况下重建目录结构。该特性在内核 6.7
中合入。
原子文件交换(Atomic File Swap)。支持两个文件的区段映射原子性交换,为应用程序提供安全的文件替换机制,避免数据丢失窗口。
参考文献
规范与设计文档
XFS 官方文档,“XFS Filesystem Disk Structures”,https://xfs.wiki.kernel.org/。XFS 磁盘格式的权威参考,详细描述了超级块、AG 结构、B+树 格式。
Linux 内核文档,“XFS Filesystem”,https://docs.kernel.org/filesystems/xfs.html。包含挂载选项、sysfs 接口和管理操作的说明。
Adam Sweeney 等,“Scalability in the XFS File System”,USENIX Annual Technical Conference,1996。XFS 的原始设计论文,阐述了 AG 并行设计和 B+树 元数据管理的动机与实现。
源码
Linux 内核源码,
fs/xfs/xfs_ialloc.c:inode 分配器实现,包括 AG 选择逻辑。可在 https://elixir.bootlin.com/linux/latest/source/fs/xfs/xfs_ialloc.c 查阅。Linux 内核源码,
fs/xfs/xfs_iomap.c:I/O 映射和预分配逻辑。可在 https://elixir.bootlin.com/linux/latest/source/fs/xfs/xfs_iomap.c 查阅。Linux 内核源码,
fs/xfs/xfs_alloc.c:空间分配器核心实现,包括 bnobt 和 cntbt 操作。可在 https://elixir.bootlin.com/linux/latest/source/fs/xfs/xfs_alloc.c 查阅。Linux 内核源码,
fs/xfs/xfs_log.c:日志子系统核心实现。可在 https://elixir.bootlin.com/linux/latest/source/fs/xfs/xfs_log.c 查阅。Linux 内核源码,
fs/xfs/xfs_reflink.c:reflink 和 CoW 实现。可在 https://elixir.bootlin.com/linux/latest/source/fs/xfs/xfs_reflink.c 查阅。
书籍与演讲
Darrick J. Wong,“XFS: Recent and Future Adventures in Filesystem Evolution”,Linux Plumbers Conference,2019。介绍了 rmapbt、reflink 和在线修复的设计与进展。
Christoph Hellwig,“XFS: The Big Storage File System for Linux”,Linux Foundation Collaboration Summit,2012。概述了 XFS 在 Linux 上的架构和性能特性。
Red Hat,“Managing file systems”,RHEL 9 Documentation,https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/managing_file_systems/。包含 XFS 的管理和调优建议。
工具
xfsprogs 项目,包含
mkfs.xfs、xfs_info、xfs_db、xfs_repair、xfs_fsr等工具。源码仓库:https://git.kernel.org/pub/scm/fs/xfs/xfsprogs-dev.git/duperemove 项目,文件级去重工具,支持 XFS 和 Btrfs 的 reflink 去重。项目地址:https://github.com/markfasheh/duperemove
xfsdump 项目,XFS 专用的备份和恢复工具。源码仓库:https://git.kernel.org/pub/scm/fs/xfs/xfsdump-dev.git/
上一篇: ext4 架构与调优 下一篇: Btrfs:写时复制文件系统
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
存储工程索引
汇总本站存储工程系列文章,覆盖 HDD、SSD、NVMe、持久内存、索引结构、压缩、分布式存储与对象存储。
【存储工程】存储引擎概览:堆文件到 LSM-Tree 的演化路径
数据库系统的架构可以划分为两大层:上层的查询处理层负责解析 SQL、生成执行计划、优化查询;下层的存储引擎(Storage Engine)负责把数据持久化到磁盘,并在需要时高效地把数据取回来。查询处理层决定"做什么",存储引擎决定"怎么存、怎么取"。同一个查询处理层可以对接不同的存储引擎——MySQL 的 InnoDB…
【存储工程】B-Tree 与 B+Tree:页式存储引擎的工程实现
数据库要把数据存到磁盘上,又要以尽可能少的磁盘 I/O 把数据找回来。这个矛盾催生了一系列面向磁盘的索引结构(Disk-oriented Index),其中最成功的就是 B-Tree 家族。从 1970 年 Rudolf Bayer 和 Edward McCreight 在波音科学研究实验室提出 B-Tree 起,这个…
【存储工程】索引结构:从 B+Tree 到倒排索引
数据库里存了一亿行数据,要找出 userid 42 的那一行。没有索引的做法是全表扫描(Full Table Scan)——从第一个数据页读到最后一个数据页,逐行比对。假设每个数据页 16 KB,一亿行占 20 GB,即使顺序读能跑到 500 MB/s,也需要 40 秒。加一个 B+Tree 索引,三次磁盘 I/O 就…