news 2026/10/16 12:12:03

Cassandra Compaction策略选型:STCS、LCS与TWCS实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cassandra Compaction策略选型:STCS、LCS与TWCS实战解析

打开你的Cassandra集群的nodetool tablestatus,看到某张表SSTable数量冲到几十甚至上百,后台compaction永远跟不上写入,查询慢得像在拖泥巴——这类问题十有八九不是硬件不行,而是当初建表时随手选错了compaction策略。

Cassandra的compaction策略,简单说就是决定SSTable文件“什么时候合并、怎么合并、跟谁合并”的一套后台规则。它直接影响磁盘占用、写入放大、读取效率,甚至决定你在半夜会不会被告警电话吵醒。今天只聊常规生产环境里最常见的三种策略:SizeTieredCompactionStrategy(STCS)、LeveledCompactionStrategy(LCS)和TimeWindowCompactionStrategy(TWCS)。我会把它们的原理、适用场景、具体配置写法,以及我在实际维护中踩过的坑一次性讲清楚。无论你是刚接手Cassandra集群的运维,还是在设计数据模型的后端开发,这篇都适合你翻一翻。

1. 内容整体设计与思路拆解

1.1 为什么compaction策略值得单独抠细节

Cassandra底层用的是LSM-Tree结构,所有写入先到内存中的Memtable,然后按顺序落盘成不可变的SSTable。SSTable一旦生成就不会再改写,所以同一张表的多个副本数据可能散落在不同SSTable里。查询时需要把多个SSTable的数据合并读取,你会发现文件越多,读路径越慢。

Compaction就是后台线程挑出一些SSTable,读出来重新排序、合并,剔除掉过期的数据和墓碑(tombstone),写成一两个新的SSTable。这个过程听起来简单,但不同策略对“挑哪些文件、什么时候挑、合并成多大”有完全不同的决策路径,也就带来了完全不同的IO特征和空间需求。

选了不合适的策略,最常见的现象就是:SSTable越堆越多,磁盘空间被重复数据吃光,读放大高得离谱,或者compaction本身把IO打满导致正常读写被拖慢。这就是为什么在建表或者调整表结构之前,必须先想清楚这张表的写入模式和读取模式。

1.2 三种策略的核心差异一览

下面这张对比表是我自己习惯用的速查表,不涉及具体版本细节,但在选型时足够用了。

策略合并逻辑空间放大读放大写放大典型场景
STCS按SSTable大小分层,把差不多大小的合并成更大的高高低写多读少、吞吐优先、不追求读延迟
LCS把SSTable分成固定大小的level,逐层向下压实低低高读多写少、追求稳定读延迟、热数据表
TWCS按时间窗口分组,窗口内合并,窗口间不再合并低低低时序数据、日志数据、按时间过期清理

这张表只是前菜。真正决定你能不能用好它们的是背后的触发条件、参数细节和切换时机。

2. 核心细节解析与实操要点

2.1 SizeTiered:适合写多读少的“老实人”

STCS是Cassandra默认的策略,它的思想非常朴素:维护几个大小相近的SSTable组,当一个组里的文件数量达到阈值,就把整组合并成一个更大的SSTable。这个“大小相近”由bucket_high和bucket_low定义,比如默认bucket_high=1.5、bucket_low=0.5,意思是两个SSTable大小差异在0.5倍到1.5倍之间时,认为是同一层。

默认情况下,表里有4个同样量级的SSTable(min_threshold=4)就会触发合并,最多可以合并32个(max_threshold=32)。合并后文件变大,进入新的层级,等待下一次和同规模文件合并。所以STCS的SSTable体积会一路涨,最终形成“大文件堆叠”的局面。

STCS的优点是写入路径基本不额外折腾,落盘后就是后台慢慢合并,写入延迟低。缺点也很致命:空间放大严重。因为要留出相当于原始数据一段比例的临时空间,并且大合并时会把几十个文件读出来再写回去,瞬间IO和磁盘占用都拉满。

我给一个实际体验:某日志分析系统使用默认配置,数据量大概每天200GB,跑了两个月后磁盘占用比实际数据多出了将近一倍。排查询时发现大量大合并正在同时进行,单个节点临时SSTable峰值达到30GB,磁盘被打满。后来改成LCS才缓解。

如果你确定要用STCS,建表时可以这样显式声明:

CREATE TABLE user_events ( user_id uuid, event_time timestamp, event_type text, payload text, PRIMARY KEY ((user_id), event_time) ) WITH compaction = { 'class': 'SizeTieredCompactionStrategy', 'min_threshold': 4, 'max_threshold': 32, 'min_sstable_size': 50 };

min_sstable_size的默认单位是MB,小于这个值的SSTable不会参与分层合并。实际上,官方文档里STCS还有sstable_size_in_mb参数,但某些版本没启用。我建议不要纠结这些细节,把握住“文件越多、合并越猛”这个本质就行。

2.2 Leveled:用写放大换读性能的“强迫症”

LCS的设计目标很直接:让任意一条数据在读的时候最多只接触少数几个SSTable。它把SSTable固定成level,每个level里又分成固定大小(默认160MB)的小文件。Level 0是刚落盘的文件,L1大小上限是一个文件大小,L2是10个文件大小,L3是100个文件大小,以此类推,每下一层容量放大10倍。

LCS保证每个level内不重叠,除非Level 0有重叠。这样查询时最多读一个L0的SSTable加上每个level各一个SSTable,读放大是“1 + level数量”,非常可控。读性能和延迟稳定性比STCS好得多。

但代价是写放大会显著提高。数据可能被反复搬运:先写到L0,再合并进L1,如果L1满了还要把里面的数据拆出来推到L2,一次写入可能被放大好几倍。高压写入场景下,后台compaction容易跟上不写入,表现为pending compaction长期不为零,CPU和磁盘都在燃。

所以LCS适合读多写少、对读延迟有要求的业务表,比如用户资料、订单状态、会话信息。对于日志流水类高写入场景,用LCS就是给自己找事。

配置示例:

CREATE TABLE order_status ( order_id text, updated_at timestamp, status int, PRIMARY KEY (order_id) ) WITH compaction = { 'class': 'LeveledCompactionStrategy', 'sstable_size_in_mb': 160, 'tombstone_threshold': 0.2 };

这里sstable_size_in_mb决定每层内SSTable多“宽”,默认160MB。调小这个值会让文件更碎、合并更频繁,调大则会减少合并次数但增加单次合并开销。一般保持默认即可,除非你有明确的性能测试结论。

2.3 TimeWindow:时序数据的“时间切片机”

TWCS是我个人最爱,也是目前处理时间序列数据的最优解,前提是你的数据真的符合时间序列特征。它的核心思路是:按照一个固定时间窗口(比如一天或一小时)来组织SSTable。同一窗口内的数据允许合并,跨窗口的SSTable互不干扰。

这样做的好处非常明显。首先,因为跨窗口不合并,老窗口的SSTable会一直保持原始形态,不会被反复读写,删数据的成本极低——直接把整个窗口的SSTable扔掉就行。同时,读放大也很低,查询某条时间点的数据只需要读取对应窗口内的文件。写放大也很低,因为窗口内合并只涉及当天的数据,不会拉扯历史数据。

TWCS对模型有硬性要求:表必须有一个时间戳列作为clustering key的一部分,且写入时该时间戳必须基本跟随系统当前时间。如果补写历史数据,那些数据会被归入旧窗口,导致旧窗口出现新的SSTable,破坏了“窗口封闭”的假设。严重时甚至会把大量跨窗口SSTable合并在一起,退化成STCS的恐怖场景。

我维护过一个设备上报表,数据会延迟几小时入库,用TWCS后经常看到旧窗口出现新合并,后来强制客户端把上报时间作为时间戳而不是入库时间,问题才解决。

TWCS建表示例:

CREATE TABLE device_metrics ( device_id uuid, recorded_at timestamp, temperature double, humidity double, PRIMARY KEY ((device_id), recorded_at) ) WITH compaction = { 'class': 'TimeWindowCompactionStrategy', 'compaction_window_unit': 'DAYS', 'compaction_window_size': 1, 'tombstone_threshold': 0.2 };

compaction_window_unit可以是MINUTES、HOURS、DAYS。compaction_window_size是窗口大小,比如1 DAY就是一天一个窗口。对于需要24小时以内清掉数据的短期缓表,甚至可以用HOURS级窗口,过期文件直接删除,非常干净。

3. 实操过程与核心环节实现

3.1 建表时如何声明compaction策略

上面已经给了三种策略的建表写法,这里我再补充两个容易忽略的细节。

第一,compaction参数是一个Map,键和值都必须是字符串。如果写数字不要加引号会直接报语法错误。比如写'min_threshold': 4正常,但'min_threshold': 4.0在某些版本也会被接受,不过统一写整数最稳。

第二,除了class之外,其他参数在Map里的顺序无所谓。Cassandra会忽略未知的参数,但不会报错。这意味着如果你写错了参数名,可能不会立刻发现,而是悄悄使用默认值。我曾经把compaction_window_size拼成compaction_window_sizes,建表成功了,但实际窗口大小一直用默认的1天,直到数据模型设计者发现窗口不对才排查出来。

如果你不确定当前生效的参数,直接查表定义:

DESCRIBE TABLE device_metrics;

返回结果里会带出完整的compaction配置,一目了然。

3.2 运行时如何平滑切换compaction策略

生产环境很多时候建表时没想清楚,跑了一段时间发现策略不合适。Cassandra支持用ALTER TABLE动态修改compaction策略,不需要重启节点。

比如想把某张日志表从默认的STCS改成TWCS:

ALTER TABLE raw_logs WITH compaction = { 'class': 'TimeWindowCompactionStrategy', 'compaction_window_unit': 'HOURS', 'compaction_window_size': 6 };

执行后,策略立即生效,后续新生成的SSTable都会按照新的逻辑管理。但要特别提醒,老SSTable不会被立刻重构。它们会继续存在,直到某个compaction触发时才会按新策略合并。所以切换后你可能会看到磁盘占用短暂不降反升,这是正常的过渡期。

另外,从STCS或LCS切换到TWCS前,务必确认表结构里有合适的时间戳列,并且数据写入模式确实是随当前时间追加的。否则切换后旧窗口会一直积累不合并的数据,历史文件越堆越多,最终比原来还糟。

3.3 参数配置建议与踩坑笔记

这里整理一份我在不同场景下实际采用的参数参考。注意这不是黄金标准,只是给你一个起点,最终要结合集群磁盘IO能力和读延迟目标调优。

场景策略推荐配置
高吞吐日志、流水、事件表,不追求点查STCSmin_threshold=4, max_threshold=32, min_sstable_size=50
用户信息、订单状态、点查延迟敏感LCSsstable_size_in_mb=160, tombstone_threshold=0.2
时序指标、设备上报、按时间过期TWCScompaction_window_unit=DAYS, compaction_window_size=1, tombstone_threshold=0.1

参数选择有几个原则:

  • STCS的min_threshold调高(比如6)能减少合并频率,但会让SSTable数量涨得更高,读放大更大。调低则合并更频繁,写放大上升。追求写入稳定的场景建议保持默认4。
  • LCS的tombstone_threshold表示当SSTable里墓碑占比超过这个值就触发合并清理。对于频繁更新删除的表,设到0.2能更主动清理,避免读放大被墓碑拖垮。
  • TWCS的tombstone_threshold不要设太低,否则窗口内稍微有点删除就会频繁合并,相当于自废TWCS武功。建议0.1到0.2之间。

还有一个经常被忽略的参数是compaction_throughput_mb_per_sec,在cassandra.yaml里,默认是16或64,建议根据磁盘能力调大。否则在高写入场景下,compaction吞吐被限制,pending任务越积越多。这个参数是整个节点级的,不是表级。

4. 常见问题与排查技巧实录

4.1 问题速查表

下面几个问题是我被问得最多的,也是我自己真实踩过的,整理成表方便对照。

现象可能原因快速处理
磁盘空间突然暴涨,SSTable数量翻倍STCS大合并产生临时文件等合并结束或改用LCS
nodetool compactionstats显示pending任务长期高写入速度超过compaction吞吐调大compaction_throughput_mb_per_sec,或临时增加节点
使用TWCS后旧窗口仍然出现合并写入时间戳严重偏离当前时间检查客户端时间戳生成逻辑,强制使用业务时间
查询越来越慢,但SSTable数量不多墓碑过多或跨分区宽行检查tombstone_threshold配置,手动触发nodetool compact
切换策略后磁盘占用变大旧SSTable需要时间重新合并观察一段时间,不要误判为策略错误

4.2 我遇到的三个真实观察

第一个观察是STCS在混合负载下的“大合并风暴”。某统计表每天凌晨都有大批数据处理任务,触发了几十个SSTable同时合并,现场IO延迟从3毫秒飙到200毫秒,线上读写全部卡顿。后来改成LCS,虽然写放大高了一些,但读延迟稳定多了,也没有再出现瞬间IO峰值。

第二个观察是LCS在纯写入压力下的“过度工作”。某项目直接把默认的STCS改成LCS,结果日志表写入速率高,后台compaction线程忙到起飞,CPU占用持续90%以上,写入还是跟不上。最后切成TWCS才解脱。

第三个观察是TWCS的“时间戳陷阱”。有一批IoT数据会延迟数天入库,业务方一直没意识到时间戳用的是入库时间还是采集时间。忽略这个区别的后果是,旧时间窗口不断有新SSTable生成,整个窗口的“不可变性”失效,空间放大逐渐逼近STCS。后来我们统一采集时间为时间戳,才恢复正常。

4.3 监控compaction是否健康的实用命令

日常巡检我建议至少盯这几个信息:

  • nodetool compactionstats:实时查看正在运行的compaction数量、任务类型、完成百分比。
  • nodetool tablestats <keyspace.table>:查看每个表的SSTable数量和压缩比率。SSTable数量持续增长说明compaction跟不上。
  • nodetool tpstats -- CompactionExecutor:如果CompactionExecutor线程池的Pending任务持续不为0,说明compaction积压了。
  • nodetool netstats:查看节点间流传输状态,有时候compaction带来的磁盘压力会影响跨机房传输。

我习惯在监控大盘上放三个指标:每张表的SSTable数量变化、CompactionExecutor的Pending数、磁盘IO util。这三个指标组合起来基本能提前暴露compaction问题。

如果发现SSTable数量异常上升,先不要急着改策略。先看是不是墓碑过多、写入峰值、或者冷热数据不均衡。只有确认当前策略确实不适配负载模型,才值得做切换。

写在最后的一点体会

我见过太多同学在Cassandra建表时随手粘贴一段模板配置,然后半年后和compaction问题搏斗。其实选一个好策略不需要多复杂,只要先问自己三个问题:这张表的写入是持续追加还是频繁更新?读路径是否对延迟特别敏感?数据是否有明确的时间过期语义?

如果让我说一条最实用的经验,那就是:能复用默认配置就别自己发明,能用TWCS的别用STCS,能用LCS的别让它承担高压写入。但最重要的是,一定要在测试环境用真实的SSTable增长速率模拟几天,再看nodetool tablestats里的SSTable数量曲线是否稳定。你以为合理的配置,跑几天后往往会暴露真实问题。我后来养成的习惯是:每张新表上线前,都会专门跑到生产前置环境写一版压力数据,盯着compaction跑一个完整周期。这个习惯帮我提前拦下了好几次磁盘危机,也推荐你试试。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 12:03:03

GPU容器化与AI训练平台实践:从Kubernetes调度到资源治理

1. 从裸机乱局到容器化&#xff1a;我们为什么非要重建一套训练平台 我所在的团队负责为整个公司的算法部门提供 GPU 算力支撑。两年前的状态&#xff0c;说实话&#xff0c;和很多公司一样&#xff1a;几台 8 卡的 GPU 服务器散落在不同机柜&#xff0c;算法同学申请到一台机器…

作者头像 李华
网站建设 2026/10/16 12:02:03

正整数划分经典解法:从动态规划到完全背包的笔试题实战

写笔试题有个很有意思的现象&#xff1a;越是看起来简单的数学题&#xff0c;越容易把人卡在笔试现场。正整数划分就是典型代表。题目往往只有一句话&#xff1a;“把一个正整数拆成若干个正整数之和&#xff0c;求方案数。”看完题目觉得不难&#xff0c;真到动手写状态转移方…

作者头像 李华
网站建设 2026/10/16 12:01:42

BiLSTM-CRF与BERT融合:序列标注面试核心原理拆解

1. 从原理到面试题&#xff0c;一次把双向序列模型的“底裤”拆干净今年校招和社招的算法岗面试&#xff0c;NLP方向绕不开的一个「全家桶」就是标题里这四个概念&#xff1a;BiRNN、BiLSTM、BiLSTM-CRF 以及 BERT-BiLSTM-CRF。每次面到序列标注、命名实体识别、分词或关系抽取…

作者头像 李华
网站建设 2026/10/16 11:58:23

KNN算法详解:从原理到实战,手写实现与调参优化指南

KNN算法大概是机器学习里最适合拿来手写的算法了。它思路直观、实现门槛低、不需要训练过程&#xff0c;天然支持多分类&#xff0c;甚至能直接用来做回归——听起来很简单对吧&#xff1f;但我在实际做项目的时候踩过不少坑&#xff0c;距离度量怎么选、K值设多少、特征要不要…

作者头像 李华
网站建设 2026/10/16 11:58:01

Qt QCustomPlot 瀑布图实现:从频谱到实时滚动可视化

简介&#xff1a;一份基于Qt框架和QCustomPlot绘图库实现的瀑布图示例项目&#xff0c;面向需要掌握自定义图表开发的C桌面应用开发者&#xff0c;适用于学习图形界面与数据可视化。压缩包共36个文件&#xff0c;以cpp、h源码和pro工程文件为主&#xff0c;附带ui界面描述以及r…

作者头像 李华
网站建设 2026/10/16 11:56:01

SQL Server随机查询一条表记录:自定义函数封装与性能优化

最近在整理一个抽奖小功能的时候&#xff0c;又绕回了那个老朋友&#xff1a;随机查询一条表记录。需求一句话就能说清&#xff1a;一张用户表&#xff0c;每次抽奖要从里面捞一个人&#xff0c;不能偏袒&#xff0c;不能重复。很多人第一反应就是 SELECT TOP 1 * FROM Users …

作者头像 李华