Kafka日志清理与Compaction#
一句话答案#
Kafka 有两种清理策略:
delete按时间/大小删除整段旧日志(默认,适合普通消息流);compact按 key 只保留每个 key 的最新值(适合”快照”语义,如__consumer_offsets、KTable 状态)。由log.cleanup.policy控制,可叠加。
核心要点
两种清理策略#
| 策略 | 行为 | 删什么 | 典型场景 |
|---|---|---|---|
| delete(默认) | 删除过期/超量的整个 segment | 整段消息 | 普通消息流、日志、事件 |
| compact | 每个 key 只保留最新一条 value | 同 key 的旧值 | 维护”最新状态”快照 |
delete,compact | 两者叠加 | 先压缩再按时间删 | 既要状态又要兜底过期 |
log.cleanup.policy=delete # 或 compact / "delete,compact"propertiesdelete 策略:按什么删#
# 按时间:消息保留多久(三选一,优先级 ms > minutes > hours)
log.retention.hours=168 # 默认 7 天
# 按大小:单个分区日志超过此值就删旧段(-1 表示不限)
log.retention.bytes=-1
# 检查周期
log.retention.check.interval.ms=300000 # 默认 5 分钟扫一次properties要点:
- 以 segment 为最小删除单位——即使段内只有一条没过期,整段也不会被删;所以”保留 7 天”实际可能多留一个段的量。
- active segment(正在写的段)永不被删。
- 时间判断依据段内最大时间戳,而非创建时间。
compact 策略:Log Compaction 原理#
压缩前(同一 key 的多次更新都在):
offset: 0 1 2 3 4 5
key: K1 K2 K1 K1 K3 K2
value: v1 v2 v3 v4 v5 v6
压缩后(每个 key 只留最后一次):
offset: 3 4 5
key: K1 K3 K2
value: v4 v5 v6 ← K1 只剩 offset=3 的 v4,旧的 v1/v3 被清掉plaintext- 目标:日志体积只与 key 的数量有关,而非更新次数 → 适合存”最新状态”。
- offset 不连续:旧 offset 被清掉后会出现空洞,消费端不能假设 offset 连续。
- 墓碑消息(tombstone):value=null 表示删除该 key,保留
delete.retention.ms(默认 24h)后彻底清除,让下游消费者有时间感知删除。 - 由后台 Log Cleaner 线程异步执行,只压缩 inactive segment。
经典应用:__consumer_offsets#
- Kafka 把消费位移存在内部 topic
__consumer_offsets,采用 compact 策略:每个<group, topic, partition>只需保留最新提交的 offset,历史提交无意义 → 天然适合 compaction。 - KTable / Kafka Streams 的状态存储、CDC 的”最新行快照”也都依赖 Log Compaction。
和存储结构的关系#
无论哪种策略都以 segment 为操作单位(delete 删整段、compact 重写段),所以理解清理必须先理解 Kafka存储与日志结构。
面试回答(2分钟版)
Kafka 有两种日志清理策略,由
log.cleanup.policy控制。默认是 delete,按时间或大小删除旧数据:时间上由log.retention.hours默认 7 天控制,大小上由log.retention.bytes控制,后台每隔几分钟扫描一次。关键点是删除以 segment 段为最小单位,整段消息都过期才会删,而且正在写的 active segment 永远不删,所以实际保留量会比配置略多一点。第二种是 compact 日志压缩,它不按时间删,而是对相同 key 只保留最新的那条 value,旧值被清理掉,这样日志大小就只和 key 的数量有关而不是更新次数,特别适合维护”最新状态”的快照语义。压缩后 offset 会变得不连续出现空洞,消费端不能假设 offset 连续;如果要删除某个 key,就发一条 value 为 null 的墓碑消息,保留一段时间让下游感知后再彻底清除。最典型的应用就是 Kafka 存储消费位移的内部 topic__consumer_offsets,每个消费组对每个分区只需要最新提交的 offset,所以用 compact 再合适不过,Kafka Streams 的状态存储也是同理。两种策略还能叠加成delete,compact,先压缩再按时间兜底过期。
追问与易错
追问方向:
- “compact 之后 offset 还连续吗?”→ 不连续,旧值被清理后留下空洞,消费端必须能容忍 offset 跳跃,不能用”下一条 offset = 当前+1”的假设
- “怎么用 compact 删除一个 key?”→ 发送 value 为 null 的墓碑消息,Log Cleaner 会清掉该 key 的所有旧值,墓碑本身保留
delete.retention.ms默认 24 小时后删除 - “
__consumer_offsets为什么用 compact 不用 delete?”→ 位移只关心每个消费组每个分区的最新值,历史提交无意义;若用 delete 按时间删可能把还需要的最新位移删掉导致消费组丢失进度 - “保留 7 天是不是精确 7 天后就删?”→ 不是,删除以段为单位且只删整段过期的,加上检查间隔,实际会略晚于 7 天
易错点:
- ❌ “retention 到期立刻按条删除”——按 segment 整段删,且 active 段不删
- ❌ “compact 会保证 offset 连续”——压缩后 offset 有空洞
- ❌ “compact 和 delete 只能二选一”——可配置
delete,compact同时生效 - ❌ “Log Compaction 是压缩算法(如 gzip)“——是按 key 去重保留最新值,和 Snappy/LZ4 那种数据压缩完全是两回事