在 TiDB/TiKV 的大规模生产实践中,为了应对大 Value 带来的写放大问题,我们通常会开启 Titan 存储引擎。Titan 通过 KV 分离(Key-Value Separation)将大 Value 从 LSM-tree 的 SST 文件中剥离,写入独立的 Blob 文件中。
然而,这种设计在解决 LSM-tree 写放大的同时,也引入了新的挑战:随着数据的频繁更新与删除,Blob 文件内部会产生大量的逻辑空洞(Discardable Data)。当 Titan 的垃圾回收(GC)机制、文件系统的空间分配器(Block Allocator)以及 SSD 的闪存转换层(FTL)三者行为失调时,就会引发严重的物理硬件空洞与文件系统碎片化。
这直接表现为吞吐量骤降、P99 延迟抖动(Write Stall),甚至在磁盘空间充足的情况下频繁触发 I/O 阻塞。本文将从底层技术原理出发,提供一套系统化的深度优化指南。
现象背后的技术本质:为什么会产生空洞与碎片?
要解决问题,必须先理清 Titan、文件系统和 SSD 三者之间的交互链条。
+-------------------------------------------------------+
| Titan 存储引擎 (KV分离) |
| - 频繁的 Blob 文件 GC 与重写 |
+-------------------------------------------------------+
│
▼ (产生大量稀疏写入与文件截断)
+-------------------------------------------------------+
| 文件系统层 (XFS / ext4) |
| - Inode Extent 树极度碎片化 (逻辑碎片) |
+-------------------------------------------------------+
│
▼ (频繁触发 discard/fstrim)
+-------------------------------------------------------+
| SSD 硬件 FTL (闪存转换层) |
| - 物理块空洞化、FTL 映射表频繁抖动、触发强 GC |
+-------------------------------------------------------+
- Titan 的逻辑空洞:当用户更新或删除大 Value 时,Titan 不会立即修改物理 Blob 文件,而是直接写入新值。旧值所在的 Blob 文件区域便成了“逻辑空洞”。
- 文件系统的逻辑碎片:Titan 依赖 GC 线程异步读取、重写这些 Blob 文件。频繁的创建、追加、重写和删除操作,会导致文件系统(如 XFS)的物理块分配器无法连续分配空间。这就产生了大量的 Extent 碎片(区段碎片)。一个 Blob 文件可能被拆成数万个物理不连续的 Extent,导致读取时产生严重的随机 I/O。
- SSD 的物理空洞(FTL 抖动):即使文件系统通过
fallocate或TRIM释放了空间,SSD 的物理 NAND 闪存并不能直接覆盖写入。频繁的空间释放与不连续写入,导致 FTL(Flash Translation Layer)的物理映射表极度稀疏,触发 SSD 内部极其激进的垃圾回收(Garbage Collection),引发写阻滞(Write Stall)。
维度一:Titan 引擎层参数调优(源头治理)
优化第一步是调整 Titan 自身的配置,控制 Blob 文件的生命周期,降低对底层文件系统和硬件的冲击。
1. 提高大 Value 判定阈值,防止小 Blob 泛滥
如果 min-blob-size 设置过小(例如几 KB),会导致生成海量的小 Blob 文件。这些小文件不仅让 LSM-tree 失去了管理优势,还极易引发文件系统 Inode 碎片化。
- 调优建议:生产环境建议将
min-blob-size至少设为 32KB 或 64KB。
[rocksdb.defaultcf.titan]
min-blob-size = "32KB" # 仅对大于该值的数据进行 KV 分离
2. 平滑控制 Titan GC 频率与水线
GC 过于激进会导致严重的写放大,并频繁触发 SSD 物理写入;GC 过于滞后则会导致磁盘空间被空洞占满。我们需要在两者之间寻找平衡。
discardable-ratio(触发 GC 的废弃数据比例阈值):默认值通常是 0.5。如果遭遇严重的 SSD 性能抖动,可以适当调高该值(如 0.6),允许保留更多的空洞以换取更低的 GC 频率;如果磁盘空间吃紧,则降低该值(如 0.4)。max-background-gc:限制并发 GC 线程数,防止其抢占正常读写的 I/O 带宽。
[rocksdb.defaultcf.titan]
discardable-ratio = 0.5 # 触发 Blob 文件重写的水线
max-background-gc = 4 # 限制 GC 线程数,避免 I/O 竞争
merge-small-file-threshold = "8MB" # 将分散的小 Blob 合并,减少碎片
维度二:文件系统(XFS/ext4)与内核优化(承上启下)
文件系统是衔接引擎与硬件的桥梁。对于高吞吐的数据库节点,文件系统的挂载参数与空间分配策略至关重要。
1. 禁用在线 discard,改为周期性 fstrim
如果在挂载文件系统时使用了 discard 参数,每当 Titan GC 删除 Blob 文件时,文件系统都会实时向 SSD 发送 TRIM 指令。这会导致 FTL 频繁阻塞以进行地址映射更新。
- 优化方案:修改
/etc/fstab,去掉discard挂载选项。 - 正确做法:使用
systemctl启用fstrim.timer,或者在业务低峰期通过 Crontab 定时执行fstrim -a。
# 检查挂载参数,确保没有 discard
mount | grep -E "ext4|xfs"
# 启用系统自带的每周定时 TRIM
systemctl enable --now fstrim.timer
2. 优化 XFS 分配行为(针对 XFS 用户)
TiDB 官方推荐使用 XFS 文件系统。针对 XFS,可以通过调整挂载参数来减少碎片的产生:
allocsize:控制文件预分配大小。增加预分配大小(如 64M)可以使文件系统在分配物理块时尽可能连续,从而显著减少 Extent 碎片的数量。
# 示例挂载参数
UUID=xxxx /data xfs defaults,noatime,nodiratime,allocsize=64M,nobarrier 0 0
(注意:在较新版本的 Linux 内核中,nobarrier 参数可能已被废弃或默认启用安全写入,请根据实际内核版本调整)
3. 定期监控与整理文件系统碎片
在不停机的情况下,可以使用文件系统自带工具对 Titan 的数据目录进行碎片整理。
- 碎片率检查(以 XFS 为例):
# 查看指定 Blob 文件的碎片情况
filefrag -v /data/tikv/db/titandb/*.blob | head -n 20
# 检查 XFS 文件系统的整体碎片率
xfs_db -c frag /dev/sdb1
- 在线碎片整理:
如果发现某些大 Blob 文件的 Extent 数量达到了数千甚至上万,建议在低峰期运行xfs_fsr进行在线碎片整理:
# 针对 XFS 文件系统执行碎片整理
xfs_fsr /dev/sdb1
维度三:SSD 硬件与 FTL 层的针对性优化(物理托底)
当 Titan 频繁地在文件系统上写写删删时,SSD 硬件层面的表现决定了性能的底线。
1. 预留足够的 Over-Provisioning (OP) 空间
这是应对 SSD 硬件空洞和写入抖动最有效的方法之一。OP(预留空间)是 SSD 内部不暴露给操作系统的隐藏区域,FTL 专门用它来进行垃圾回收和坏块管理。
- 优化操作:不要把整块 SSD 划分满。在重新初始化设备时,保留 10% - 20% 的未分配空间。例如,一块 1.92TB 的 NVMe SSD,只对其划分 1.6TB 的分区。
- 这能让 FTL 在面对 Titan 高强度的物理重写时,有充足的物理页面进行合并与搬移,显著降低 FTL 强行 GC 带来的延迟尖峰。
2. 锁定 I/O 调度器
对于现代高速 NVMe SSD,内核默认的 I/O 调度器应该设置为 none 或 kyber,避免像 bfq 这样复杂的调度器增加 CPU 开销并限制并发队列深度。
# 查看当前 I/O 调度器
cat /sys/block/nvme0n1/queue/scheduler
# 将调度器临时设为 none(对 NVMe 推荐)
echo none > /sys/block/nvme0n1/queue/scheduler
要永久生效,请通过 udev 规则进行配置。
3. 监控 FTL 的磨损与写放大系数 (WA)
定期通过 smartctl 检查 SSD 的健康状态,重点关注 Percentage Used(磨损度)和底层闪存的实际写入量(通过厂商特定的 SMART ID 查询),评估物理写放大。如果发现物理写放大远远大于 Titan 引擎层的写放大,说明 SSD 内部正在经历严重的 FTL 空洞和无效搬移,应立即增大 OP 空间并平滑 Titan 的 GC 参数。
总结:生产环境落地优化路线图
当遭遇因 Titan 导致的物理空洞和碎片性能瓶颈时,建议按照以下步骤实施优化:
| 实施阶段 | 核心动作 | 预期效果 |
|---|---|---|
| 第一步:基础检查 | 检查并修改 /etc/fstab,禁用 discard,配置定时 fstrim。 |
消除因实时 TRIM 导致的系统级写 Stall。 |
| 第二步:引擎调优 | 提高 min-blob-size 至 32KB+,微调 discardable-ratio(建议 0.45-0.55),限制 max-background-gc 线程。 |
从源头上减少小文件和频繁的 GC 物理重写。 |
| 第三步:文件系统防碎 | 重新挂载增加 allocsize=64M,低峰期尝试执行 xfs_fsr。 |
提升文件分配连续性,降低 CPU 在解析 Extent 树时的开销。 |
| 第四步:物理托底 | 如果更换或重新格式化磁盘,务必留出 15% 空间做 OP(预留空间)。 | 为 SSD 硬件 FTL 提供足够的腾挪空间,彻底平抑 P99 抖动。 |
通过以上三维一体的配置,可以有效缓解 Titan 在大 Value 场景下的底层资源对抗,保证 TiKV 集群在高负载下的高吞吐与低延迟。