5.2 Compaction、放大效应与 LSM 运维
新的有序文件不断堆进仓库,读取开始跨越越来越多层;后台整理若失控,又会抢走前台写入的带宽。
Compaction 不是“空闲时顺手整理”。它是 LSM 的持续核心工作:选择 overlapping sorted runs,merge 出新 files,丢弃已安全过期的 versions/tombstones,再原子安装新 version。它决定 steady-state write capacity、read latency 与 space usage。
Compaction 为什么必需
如果只 flush 不 compact:
- files/runs 持续增加;
- point lookup 与 range merge 检查更多来源;
- tombstones 和 overwritten values 占用空间;
- open files、metadata 与 cache 压力上升;
- restart/recovery 与 backup enumeration 变重。
compaction 将“每次更新原地维护”的成本变成 background merge,但总成本仍要由设备吞吐和 CPU 承担。
Leveled compaction
典型 leveled layout:
L0: overlapping flush files
L1: sorted files, key ranges non-overlapping within level
L2: larger total target, non-overlapping within level
...当某 level 超过 size/file target,picker 选择 input range,并把与下一层 overlap 的 files 一起 merge。优点通常是 lower-level point lookup 候选少、space amplification 可控;代价是 data 可能在 levels 间多次重写。
level size multiplier、file size、L0 triggers 都是配置策略,不是跨版本固定的“默认 10 倍、64 MB”。以当前 engine options 和 live metadata 为准。
Tiered / size-tiered / universal
这类策略允许多个 sorted runs 在同一 key space 共存,累计到 size/age/number 条件后合并。它通常减少重复搬运,适合偏写 workload,但读取要跨更多 runs,旧 versions 和 tombstones 也可能保留更久。
“tiered”不是所有产品共享的一套精确算法。Cassandra SizeTieredCompactionStrategy、RocksDB Universal Compaction 和其他 engines 的 selection/space rules 不同,不能把一个产品阈值套到另一个。
FIFO 与 time-window
TTL/time-series workload 可用 FIFO 或 time-window strategy,让整批过期 files 被删除,避免把必将过期的旧数据反复 merge。但只有当 query/time ordering、TTL 和 out-of-order writes 满足前提时才适用。
晚到数据、跨窗口 range query、更新旧 key 和 legal retention 都会改变设计。
三类 amplification
Write amplification
physical bytes written by storage stack / logical user bytes written分子边界必须明确:只算 SST compaction、包括 WAL、包括 filesystem/device write amplification,数值会不同。compression 也可能让 physical bytes 小于 logical payload,却不代表没有 compaction CPU/reads。
Read amplification
可按 files/blocks/probes/bytes/IO operations 定义。point negative lookup、point positive lookup 与 range scan 的放大不同,不能用一个“层级数”覆盖所有请求。
Space amplification
physical live storage / logical latest live data还要说明是否含 WAL、obsolete-but-not-deleted files、snapshots、tombstones、replicas 与 backups。只报 ratio 而不报 measurement scope 没有可比性。
这些目标互相制约,但不是固定三角公式。workload、compression、key distribution、retention 与 storage device 会改变 frontier。
Compaction debt 与 steady state
设 logical ingest 为 W bytes/s,average compaction write amplification 为 A,仅 compaction 写带宽需求数量级就是:
W × A还未计 WAL、flush、reads 和 filesystem overhead。若设备可供 background 的持续吞吐低于需求,pending compaction bytes 会增长,最终进入 slowdown/stall。
burst 可由 memtable/L0 暂存,steady state 不能靠无限 backlog 维持。容量规划要看数小时/数天稳定窗口,不是只跑一分钟峰值 benchmark。
Write stall 是保护机制
常见触发:
- immutable memtables 太多;
- L0 file count/bytes 太高;
- pending compaction bytes 超阈值;
- WAL/space limit;
- background threads/IO 跟不上。
slowdown/stall 保护 memory 与 read amplification,不只是“LSM 缺陷开关”。真正问题是为何 compaction capacity 低于 ingest:设备饱和、CPU compression、bad compaction config、oversized values、snapshot retention 或共享资源争用。
应用必须有 backpressure 与 deadline。无限 retry 会加重 stall。
Tombstone 与 snapshot retention
compaction 只有在能证明更旧 value 对所有相关 readers 不再可见时才可丢弃 tombstone。长期 snapshot、transaction、iterator、replication lag 或 backup pin 会延长保留。
症状:
- logical delete 后 space 不降;
- compaction read/write bytes 高;
- point/range read 扫描大量旧 versions;
- oldest snapshot age 持续上升。
删除大量 data 后立刻手动 full compaction 可能造成 I/O storm。先确认 retention blocker 和业务窗口,再选择 range/manual compaction。
Bloom 与 cache 不能替代 compaction
Bloom filter 能避免部分 negative point reads,却不能:
- 移除 overwritten values;
- 回收 tombstones;
- 降低 range merge 的所有成本;
- 减少 obsolete-file space;
- 修复 compaction debt。
block cache 能隐藏部分 I/O,但 files/runs 过多仍消耗 metadata、CPU、iterators 和 cache space。
B+ tree 与 LSM 不是按“读多/写多”二选一
选型至少比较:
| 维度 | 需要问的问题 |
|---|---|
| write | sustained/burst rate、update distribution、durability、value size |
| point read | positive/negative ratio、tail latency、cache budget |
| range read | range width、sort order、snapshot、merge sources |
| space | compression、TTL、tombstone、temporary compaction headroom |
| consistency | transaction model、secondary indexes、constraints |
| operations | backup、repair、upgrade、observability、stall behavior |
| hardware | local SSD、network block/object store、IOPS/bandwidth/endurance |
B+ tree engine 也通过 WAL、buffering、group commit 和 sequential techniques 获得高 write throughput;LSM 也可以通过 filter/cache/compaction 获得好 read performance。最终必须用 representative workload 验证。
Operational metrics
通用指标:
logical write/read rate and latency percentiles
WAL bytes and sync latency
mutable/immutable memtable bytes/count
flush throughput and duration
files/bytes per level or run
pending compaction bytes/debt
compaction read/write bytes and CPU
write slowdown/stall duration
block cache hit by block type
Bloom useful/positive/false-positive counters
tombstone/obsolete versions and oldest snapshot age
disk free headroom and temporary compaction spacemetric names随 engine/version 改变,要以当前官方 property/statistics 定义为准。单独看 estimate-pending-compaction-bytes 不等于 write amplification。
Benchmark 设计
- 数据量超过 memory cache,或明确报告 cache-resident test;
- 预填数据并等待/记录 compaction state;
- 使用真实 key/value size、update/insert/delete ratio;
- 同时运行 point/range reads 与 writes;
- 持续到 compaction steady state;
- 报告 p50/p95/p99、stall、physical bytes 与 space;
- 记录 engine/version/options/hardware;
- crash/reopen 后验证 durability 与 recovery time。
只比较“空库连续写一分钟”主要测 memtable/WAL burst,不足以比较存储结构长期能力。
安全变更顺序
- 先确认 bottleneck 是 compaction,不是 application/OS/device 其他 workload;
- 修改一个主要 option;
- 保留足够 free space 完成最坏 compaction;
- 限制 manual compaction 的 range/并发;
- 观察 read tail latency 与 write stall guardrails;
- 灰度到少量 shards/instances;
- 保留 rollback,但注意某些 on-disk format option 不能无损回退。
本章小结
LSM 的优势来自 batching、immutable sorted runs 和后台 merge,代价则集中在 compaction debt、amplification 与 version retention。选型和调参不能只看“顺序写更快”,而要证明 steady-state ingest、read tail、space 和 recovery 同时满足目标。