KV存储笔记:Compaction 策略与写放大
发布于:
前言
Compaction 是 LSM-Tree 存储引擎的核心操作,负责合并多个 SSTable 文件,减少文件数量并回收空间。本文从 KV 存储系统的实际实现角度,介绍 Compaction 在 KV 存储中的策略选择、写放大控制、性能优化以及实际应用案例。
注:本文聚焦于 KV 存储系统中 Compaction 的具体实现。关于 LSM-Tree 和 Compaction 的通用原理,请参考「存储基础系列」的相关文章。
1. Compaction 的目的
Compaction 的主要目的:
- 减少文件数量:合并多个小文件为一个大文件
- 回收空间:删除已覆盖或已删除的数据
- 优化读取:减少查询时需要访问的文件数量
- 控制层级:维护 LSM-Tree 的层级结构
2. Compaction 策略
2.1 Leveled Compaction
将数据组织成多个层级,每个层级有固定的大小限制:
- L0:MemTable 刷盘后的文件,可能有重叠
- L1-Ln:每层文件大小递增,且同一层内文件不重叠
- Compaction:将上层文件合并到下层
优点:
- 读取性能稳定
- 空间放大可控
缺点:
- 写放大较大
- Compaction 开销高
2.2 Size-Tiered Compaction
将大小相近的文件合并:
- 当同层文件数量达到阈值时触发合并
- 合并后的文件大小翻倍
- 继续合并直到达到最大文件大小
优点:
- 写放大相对较小
- 实现简单
缺点:
- 读取可能需要访问多个文件
- 空间放大较大
2.3 Tiered + Leveled 混合策略
结合两种策略的优点:
- 上层使用 Size-Tiered
- 下层使用 Leveled
- 平衡写放大和读性能
3. 写放大问题
3.1 写放大的来源
写放大(Write Amplification)是指实际写入磁盘的数据量大于用户写入的数据量:
- 重复写入:数据在 Compaction 过程中被多次写入
- 索引更新:需要更新索引结构
- 元数据写入:需要写入文件元数据
3.2 写放大的计算
写放大 = 实际写入磁盘的数据量 / 用户写入的数据量
3.3 如何减少写放大
- 调整 Compaction 策略:选择写放大较小的策略
- 增加层级大小:减少 Compaction 频率
- 批量 Compaction:合并多个小 Compaction 为一个大 Compaction
- 延迟 Compaction:在低峰期执行 Compaction
4. Compaction 的触发条件
4.1 基于文件数量
- 当某一层的文件数量达到阈值时触发
- 简单直接,易于实现
4.2 基于文件大小
- 当文件总大小达到阈值时触发
- 可以更好地控制空间使用
4.3 基于时间
- 定期执行 Compaction
- 保证数据及时整理
4.4 基于读取放大
- 监控读取需要访问的文件数量
- 当读取放大过大时触发 Compaction
5. Compaction 的性能优化
5.1 并行 Compaction
- 多个 Compaction 任务并行执行
- 充分利用多核 CPU 和磁盘 IO
5.2 增量 Compaction
- 只合并变化的部分
- 减少 Compaction 的数据量
5.3 优先级调度
- 优先 Compaction 热点数据
- 延迟 Compaction 冷数据
5.4 IO 限流
- 控制 Compaction 的 IO 速率
- 避免影响正常读写操作
6. Compaction 与查询性能
6.1 文件数量对查询的影响
- 文件数量越多,查询需要访问的文件越多
- Compaction 可以减少文件数量,提高查询性能
6.2 Bloom Filter 的作用
- 每个 SSTable 可以配备 Bloom Filter
- 快速判断键是否存在于文件中
- 减少不必要的文件访问
7. KV 存储系统中的 Compaction 实现案例
7.1 RocksDB 的 Compaction 策略
RocksDB 支持多种 Compaction 策略:
- Leveled Compaction:默认策略,适合大多数场景
- Universal Compaction:适合写多读少的场景
- FIFO Compaction:适合时序数据,自动删除最旧的数据
- Tiered Compaction:适合写放大敏感的场景
7.2 LevelDB 的 Compaction 策略
LevelDB 使用 Leveled Compaction:
- 固定层级大小:每层大小固定,便于控制
- 自动触发:当文件数量或大小达到阈值时自动触发
- 优先级调度:优先 Compaction 上层文件
7.3 BadgerDB 的 Compaction 策略
BadgerDB 使用特殊的 Compaction 策略:
- Value Log Compaction:专门针对大 Value 的 Compaction
- LSM Compaction:针对 Key 的 Compaction
- 分离设计:Key 和 Value 分别 Compaction
8. KV 存储系统中的特殊优化
8.1 TTL 数据的 Compaction
KV 存储系统通常支持 TTL(Time To Live),Compaction 需要:
- 过期检测:在 Compaction 时检测并删除过期数据
- 批量删除:批量删除过期数据,提高效率
- 时间窗口:根据时间窗口组织数据,便于批量删除
8.2 大 Value 的 Compaction
KV 存储系统的 Value 可能很大,Compaction 需要:
- Value 分离:大 Value 存储在单独的文件中
- 增量 Compaction:只 Compaction 变化的部分
- 流式处理:支持流式处理大 Value,避免内存占用过大
8.3 多版本数据的 Compaction
KV 存储系统可能需要多版本,Compaction 需要:
- 版本合并:合并多个版本,只保留最新版本
- 版本清理:清理旧版本,回收空间
- 快照支持:支持快照,避免清理正在使用的版本
9. 性能调优实践
9.1 Compaction 参数调优
// KV 存储系统的 Compaction 参数配置示例
struct CompactionOptions {
// Leveled Compaction 参数
int max_levels = 7; // 最大层级数
size_t level0_file_num = 4; // L0 文件数量阈值
size_t level0_size = 256 * MB; // L0 大小阈值
// 写放大控制
double max_write_amplification = 10.0; // 最大写放大
int compaction_priority = 1; // Compaction 优先级
// 性能优化
int max_compaction_threads = 4; // 最大 Compaction 线程数
size_t compaction_buffer_size = 64 * MB; // Compaction 缓冲区大小
bool enable_parallel_compaction = true; // 启用并行 Compaction
};
9.2 动态调整策略
// KV 存储系统的动态 Compaction 调整
class AdaptiveCompaction {
void adjust_compaction_strategy() {
// 根据系统负载动态调整
if (write_amplification > threshold_) {
// 写放大过大,减少 Compaction 频率
increase_compaction_interval();
}
if (read_amplification > threshold_) {
// 读放大过大,增加 Compaction 频率
decrease_compaction_interval();
}
// 根据 IO 负载调整
if (io_utilization > threshold_) {
// IO 负载高,降低 Compaction 优先级
reduce_compaction_priority();
}
}
};
10. 监控与诊断
10.1 关键指标
KV 存储系统需要监控的 Compaction 指标:
- 写放大:实际写入量 / 用户写入量
- 读放大:查询需要访问的文件数量
- 空间放大:实际占用空间 / 逻辑数据大小
- Compaction 延迟:Compaction 操作的耗时
- Compaction 吞吐:Compaction 的数据量
10.2 问题诊断
常见问题及诊断方法:
- 写放大过大:检查 Compaction 策略,考虑切换到 Tiered
- 读放大过大:检查文件数量,增加 Compaction 频率
- Compaction 延迟高:检查 IO 负载,调整 Compaction 优先级
- 空间放大过大:检查删除操作,确保 Compaction 及时清理
11. 小结
Compaction 是 LSM-Tree 存储引擎的关键操作,需要在写放大、读性能、空间效率之间找到平衡。
核心要点:
- Compaction 的目的:减少文件数量、回收空间、优化读取、控制层级
- 策略选择:Leveled、Tiered、混合策略各有优劣
- 写放大控制:通过调整策略、增加层级大小、批量 Compaction 等方式减少写放大
- 性能优化:并行 Compaction、增量 Compaction、优先级调度、IO 限流
在 KV 存储系统中的特殊考虑:
- TTL 支持:需要在 Compaction 时处理过期数据
- 大 Value 处理:需要特殊设计避免 Compaction 开销过大
- 多版本支持:需要合并和清理多个版本
选择合适的 Compaction 策略和优化方案,可以显著提升 KV 存储系统的整体性能。不同的 KV 存储系统根据其应用场景,会采用不同的 Compaction 实现策略。