ScyllaDB TWCS 与 TTL 使用指南:单一 TTL 值、SSTable 过期清理与写放大避坑
【免费下载链接】scylladbNoSQL data store using the Seastar framework, compatible with Apache Cassandra and Amazon DynamoDB项目地址: https://gitcode.com/GitHub_Trending/sc/scylladb
导读
本文基于 ScyllaDB 官方文档中面向 Time-Window Compaction Strategy(TWCS)用户的 TTL 警告(docs/rst_include/warning-ttl-twcs.rst)展开,它被同时嵌入 CQL Compaction 参考、Compaction Strategies 选型指南 与 Compaction 知识库文章 的 TWCS 章节。该警告虽然短小,却浓缩了 TWCS 场景下最容易踩坑的三个关键点:单一 TTL 值、tombstone compaction 的写放大代价、以及覆盖写/显式删除对过期 SSTable 清理的阻碍。读完本文,你将理解 TWCS 如何"整表过期"地清理数据、为什么混合 TTL 会拖慢清理、以及如何规避写放大与数据复活风险。
一、为什么 TWCS 用户需要特别关注 TTL
Time-Window Compaction Strategy(TWCS)专为时间序列数据设计,其核心思想是:SSTable 按时间窗口(time window)分组,窗口内的 SSTable 用 STCS 合并,不同窗口的 SSTable 永不互相合并。这一设计最大的红利是——当某个时间窗口内的全部数据都过期后,整个 SSTable 可以被整体丢弃(drop),无需逐行扫描清理。
TWCS 的完整工作流程如下(详见 docs/kb/compaction.rst 的 "Time-window Compaction Strategy" 一节):
- 配置时间窗口,窗口由
compaction_window_size(数量)与compaction_window_unit(单位:MINUTES/HOURS/DAYS)共同决定; - 在窗口内创建的 SSTable 使用 Size-tiered Compaction Strategy(STCS)进行合并;
- 一个窗口结束后,该窗口内累积的所有 SSTable 被压缩合并为单个 SSTable;
- 最终产生的这个 SSTable 永远不会与其他时间窗口的 SSTable 合并。
这种"窗口隔离 + 窗口内合并"的布局,使得整窗口数据可以在 TTL 到期后一次性从磁盘上移除——这正是 TWCS 高效处理时序数据的关键。但该红利有一个严格前提:窗口内的数据必须拥有一致的存活期。官方警告的第一条正是针对这一点。
出处:warning-ttl-twcs.rst 第一条 caution 明确写道:"We strongly recommend using a single TTL value for any given table."
二、警告一:坚持单一 TTL 值,避免混合 TTL
2.1 官方建议原文
warning-ttl-twcs.rst 的第一条 caution 提出三点:
- 强烈建议任何给定表只使用一个 TTL 值;
- 这意味着坚持使用表 schema 中指定的默认 TTL(
default_time_to_live); - 对同一张表使用多个 TTL 值可能导致清理过期数据时效率低下,因为SSTable 会一直保留到其全部数据都过期("an SSTable will remain untilallof its data is expired")。
2.2 为什么"全部数据过期"是低效的
在 LSM 架构下,SSTable 是数据持久化的最小单位之一。ScyllaDB 判断某个 SSTable 能否直接丢弃的依据是该 SSTable 中是否所有数据都已过期。当表中存在多种 TTL 时,同一个 SSTable 内会混杂不同存活期的数据行:
- 短 TTL 的行早已过期,但长 TTL 的行仍然存活;
- 于是整个 SSTable 无法被标记为"完全过期"(fully expired),也就不能整文件丢弃;
- 它必须继续留在磁盘上等待长 TTL 的数据也到期,在此期间持续占用存储空间,并在读取路径上被反复访问。
从源码可以印证"完全过期"判定确实是 TWCS 清理路径的核心机制。在 compaction/compaction_group_view.hh 中,compaction group 暴露了fully_expired_sstables(sstables, compaction_time)虚接口;compaction/compaction.cc 通过_table_s.fully_expired_sstables(_sstables, gc_clock::now())获取完全过期的 SSTable 集合;而在 TWCS 的压实调度中(compaction/time_window_compaction_strategy.cc),会先调用table_s.fully_expired_sstables(candidates, compaction_time)找出完全过期集合,若全部候选都过期则直接返回has_only_fully_expired::yes的描述符,由 compaction/compaction_manager.cc 走"仅删除过期文件、不执行数据合并"的快速路径。也就是说:只有"完全过期"的 SSTable 才能享受免合并直接删除的红利,任何一行存活数据都会破坏这一条件。
2.3 如何在 schema 中设定单一默认 TTL
正确做法是在建表时通过default_time_to_live指定全表统一的存活期,例如:
CREATE TABLE metrics_by_day ( day timestamp, sensor text, value double, PRIMARY KEY (day, sensor) ) WITH CLUSTERING ORDER BY (sensor ASC) AND default_time_to_live = 86400 -- 全表统一 24 小时 TTL AND compaction = { 'class' : 'TimeWindowCompactionStrategy', 'compaction_window_unit' : 'DAYS', 'compaction_window_size' : 1 };这样所有写入的数据都拥有相同的存活期,配合 1 天一个窗口的 TWCS,每天窗口内的数据会在同一天集体过期,SSTable 可被整文件高效回收。
2.4 必须避免的混合 TTL 场景
- 对同一张表的部分行写入时显式指定
USING TTL,而其他行依赖默认 TTL——两者存活期不一致,过期时间参差; - 应用中"同一逻辑表"被多个不同 TTL 的业务复用(例如日志表既存 1 小时临时数据又存 30 天审计数据);
- 时序数据乱序写入(例如为旧数据显式设置更早的时间戳,或读修复将旧数据拉回当前 MemTable),使新旧数据混杂进同一个 SSTable,详见 docs/kb/compaction.rst 的 "When time-series data gets out of order" 一节。
若确实需要多种存活期,更稳妥的做法是按 TTL 拆分物理表(例如 hot 表 TTL=1 天、cold 表 TTL=30 天),让每张表内部保持单一 TTL。
三、警告二:tombstone compaction 可以救急,但要付出写放大代价
3.1 当混合 TTL 已无法避免时
如果表内已经存在部分过期的 SSTable(例如历史遗留的混合 TTL 表),ScyllaDB 提供基于 tombstone 的压实(tombstone compaction)来提前回收其中的过期数据,避免它们长期占盘。官方警告承认这条路可行,但同时明确指出:"Tombstone compaction can be enabled to remove data from partially expired SSTables, but this creates additional WA (write amplification)."
3.2 控制 tombstone compaction 的 CQL 参数
tombstone 相关参数是所有压缩策略共有的(参见 docs/cql/compaction.rst 的 "Common options" 一节):
compaction = { 'class' : 'compaction_strategy_name', 'enabled' : true, 'tombstone_threshold' : 0.2, 'tombstone_compaction_interval' : 86400, 'unchecked_tombstone_compaction' : false }| 参数 | 默认值 | 含义 |
|---|---|---|
tombstone_threshold | 0.2 | 可回收 tombstone 占数据的比例阈值(0–1 的小数)。某张表超过该阈值时,对该 SSTable 启动一次单独压实 |
tombstone_compaction_interval | 86400 秒(1 天) | 两次 tombstone 压实之间的下界间隔:SSTable 在时间 X 被压实后,最早要到 X + interval 才会再次被考虑,但不保证到期后立即被压实 |
unchecked_tombstone_compaction | false | 若设为 true,则忽略tombstone_threshold,仅按tombstone_compaction_interval决定是否对 SSTable 压实 |
3.3 为什么会产生写放大
tombstone compaction 的本质是重写部分过期(partially expired)的 SSTable:读取其中仍存活的数据、剥离过期数据与 tombstone,写出新的 SSTable 再删除旧文件。这一过程:
- 把"本来可以整文件丢弃"的操作,退化成"必须读出-过滤-重写"的完整压实流程;
- 重写的数据量就是额外的写放大(WA),占用磁盘 IO 与临时空间;
- 因此它只是缓解混合 TTL 之痛的手段,而非治本之策——治本仍是 2.3 节的单一默认 TTL。
从源码看,expired_sstable_check_frequency_seconds(默认 600 秒,定义于 compaction/time_window_compaction_strategy.hh)控制 TWCS 多久检查一次可整体丢弃的完全过期 SSTable(校验逻辑见 compaction/time_window_compaction_strategy.cc)。该频率只影响"整文件丢弃"的及时性,并不影响tombstone 压实本身的开销。
四、警告三:尽量避免覆盖写与显式删除
warning-ttl-twcs.rst 的第二条 caution 内容如下:
"Avoid overwriting data and deleting data explicitly at all costs, as this can potentially block an expired SSTable from being purged, due to the checks that are performed to avoid data resurrection."
4.1 覆盖写与删除为什么"阻塞"过期 SSTable
SSTable 的整文件丢弃要求"所有数据均已过期"。而覆盖写与显式删除会引入比过期更复杂的状态:
- 覆盖写:新值写入后,旧值所在 SSTable 中的数据成为"被覆盖的过期版本"(shadowed data)。从 tombstone/版本语义看,只要存在可能被新值遮蔽的旧数据,清理逻辑就必须谨慎;
- 显式删除:删除操作本身会生成tombstone,tombstone 的存活期与普通数据行不同,且必须在所有可能被它遮蔽的数据被清理后才能清除(防止删除记录消失后旧数据"复活"——即数据复活,data resurrection)。
ScyllaDB 为此执行严格的"防复活"检查:一个 SSTable 能否被清除,不仅看 TTL 是否到期,还要确认不存在可能被其遮蔽、却仍存活的 tombstone 或覆盖版本。这条保护逻辑是造成"过期 SSTable 无法被及时 purge"的直接原因。在 docs/kb/compaction.rst 的增量压实说明中也有同类机制的描述:为防压实中途崩溃导致数据复活,ICS 会额外写出包含可清理 tombstone 的辅助 run,且这些 tombstone 会一直保留到所有被遮蔽数据都被删除。
4.2 覆盖写场景下的额外空间与放大代价
在 docs/architecture/compaction/compaction-strategies.rst 的策略选型矩阵中,对 Overwrite(同一数据单元被反复覆盖)负载的注释指出:STCS/ICS 会产生显著的空间放大(SA),LCS 则表现为写放大(WA)——这些放大的根源同样是"旧版本数据长期滞留于 SSTable"。对 TWCS 用户而言,覆盖写越频繁,窗口内 SSTable 中"被覆盖的旧版本 + 存活的新版本"就越混杂,整文件过期的条件越难达成。
4.3 时序场景的实践建议
对时序负载(TWCS 的主战场),官方文档给出如下操作纪律:
- 避免显式设置时间戳的查询写入,防止乱序数据混入当前窗口(乱序会直接破坏窗口纯净性,详见 docs/kb/compaction.rst);
- 定期执行修复(repair),使数据以不混杂的方式流式传输,维持窗口内数据的时间一致性;
- 用TTL 过期代替显式删除:需要淘汰旧数据时,让数据自然到期,而不是发 DELETE;需要覆盖时,尽量让新数据写入新窗口而非原地覆盖旧窗口;
- 若确实存在乱序/覆盖需求,评估 STCS 或 ICS 是否比 TWCS 更契合(策略选型矩阵参见 docs/architecture/compaction/compaction-strategies.rst 的 "Which strategy is best" 一节)。
五、TWCS + TTL 最佳实践清单
综合 warning-ttl-twcs.rst 的两条 caution 与仓库文档,给出可直接落地的检查清单:
- 建表时设置全表默认 TTL(
default_time_to_live),并让所有写入依赖该默认值; - 不为同一张表混用多个
USING TTL值;确有多种存活期需求时拆分为多张表; - 保持时序数据有序写入,避免显式旧时间戳写入与读修复引入的乱序数据;
- 用 TTL 自然过期代替 DELETE、用追加新窗口代替原地覆盖,为整文件丢弃创造条件;
- 只有对已存在的部分过期 SSTable 才考虑启用 tombstone compaction(
tombstone_threshold/tombstone_compaction_interval),并清醒地接受其写放大成本; - 监控
expired_sstable_check_frequency_seconds(默认 600 秒)的检查节奏,必要时调整其值以加快完全过期 SSTable 的回收。
六、延伸阅读
- 警告原文:docs/rst_include/warning-ttl-twcs.rst
- TWCS 参数完整参考(
compaction_window_unit、compaction_window_size、expired_sstable_check_frequency_seconds、min_threshold、max_threshold):docs/cql/compaction.rst - 四种压缩策略选型与放大权衡:docs/architecture/compaction/compaction-strategies.rst
- 压缩原理与 TWCS 乱序数据专题:docs/kb/compaction.rst
- 实现层:TWCS 选项校验与完全过期检测 compaction/time_window_compaction_strategy.cc、完全过期 SSTable 判定 compaction/compaction.cc、
has_only_fully_expired描述符 compaction/compaction_descriptor.hh
一句话总结:TWCS 的高效过期清理依赖"窗口内数据同生共死"——单一默认 TTL 让整窗口同步到期、整文件直接丢弃;混合 TTL 迫使系统退回到 tombstone compaction 的重写路径(付出写放大);而覆盖写与显式删除则会触发防复活检查,进一步阻塞过期 SSTable 的清除。把握这三条,才能让 TWCS 真正发挥时序数据压舱石的作用。
【免费下载链接】scylladbNoSQL data store using the Seastar framework, compatible with Apache Cassandra and Amazon DynamoDB项目地址: https://gitcode.com/GitHub_Trending/sc/scylladb
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考