因为 LevelDB 的增删改都是通过追加写来实现的,所以需要通过后台线程的 compaction 来:
除了从外部调用 CompactRange,LevelDB 有几种情况会自动触发 compaction:
Minor Compaction 比较简单,基本代码路径是:DBImpl::CompactMemTable => DBImpl::WriteLevel0Table => BuildTable。
Compaction 会对 LevelDB 的性能和稳定性带来一定影响:
常见的做法是,控制 compaction 的速度(比如 RocksDB 的 Rate Limiter),让 compaction 的过程尽可能平缓,不要引起 CPU、I/O、缓存失效的毛刺。 这种做法带来一个问题:compaction 的速度应该控制在多少?Compaction 的速度如果太快,会影响系统性能;Compaction 的速度如果太慢,会阻塞写请求。 这个速度和具体的硬件能力、工作负载高度相关,往往只能设置一个“经验值”,比较难通用。同时这种做法只能在一定程度上减少系统毛刺、抖动,Compaction 带来的写放大依然是那么大。
所以,总的写放大是 4 + 11(n-1) = 11n - 7 倍。
假设有 5 个 level,写放大最大是 48 倍——也就是说,外部写入 1GB 的数据,内部观察到的 I/O 写流量会有 48GB。
关于 LSM-Tree 的写放大有不少论文进行了详细的介绍、讨论和提出优化方案,比如: