本节摘要:写优化靠 UNLOGGED BATCH(同 partition)、async 驱动、
LOCAL_ONE+hint 场景;读优化靠 CL 降级、Key Cache、SAI 索引节制、LCS/TWCS。SOURCE 5.2:concurrent_writes与磁盘 I/O 需联动调。
阅读完本节,你应当能够:
BEGIN UNLOGGED BATCH 与 LOGGED BATCH 边界DefaultConsistencyLevel 与 speculative retryDropped Mutations 与 Pending Compactions 处置日志采集每秒 20 万条,单条 INSERT——CPU 耗在 RPC 与 MemTable 插入开销。改成 同 partition 的 UNLOGGED BATCH 可降 round-trip(须同 partition key,否则 BATCH 仍多协调)。读侧用户会话允许 200ms 陈旧,却用 QUORUM 跨 DC——白等远洋 RTT。Mongo bulkWrite、HBase BufferedMutator 同类思路;Cassandra BATCH 语义更窄,误用跨 partition LOGGED BATCH 反而慢。
写优化:
BEGIN UNLOGGED BATCH INSERT INTO events (device_id, ts, val) VALUES ('d1', toTimestamp(now()), 1.0); INSERT INTO events (device_id, ts, val) VALUES ('d1', toTimestamp(now()), 2.0); APPLY BATCH;
| 手段 | 效果 | 注意 |
|---|---|---|
| UNLOGGED BATCH | 减协调 | 必须同 partition |
| async executeAsync | 吞吐 | 背压控制 |
| CL ONE / LOCAL_ONE | 低延迟 | 容忍丢写 |
| TWCS + TTL | IoT 清理 | 时序专用 |
读优化:
LOCAL_QUORUM 替代 QUORUM(多 DC)read_repair_chance 调低读路径副作用Compaction 与性能:nodetool compactionstats 看 pending;STCS 写多读少,LCS 读稳,TWCS 时序。写放大与读放大不可兼得(见 2.3)。
驱动 4.x:
session.executeAsync(SimpleStatement.builder( "INSERT INTO ...").setConsistencyLevel(DefaultConsistencyLevel.LOCAL_QUORUM).build());
反模式:跨 partition LOGGED BATCH(Paxos 级开销);高基数 SAI on 邮箱。
监控:Prometheus cassandra_table_write_latency、MCAC;Dropped Mutations>0 查 GC/磁盘/CL。
磁盘:Compaction 与 streaming 共享 I/O;NVMe 与 compaction_throughput_mb_per_sec 限速。
⚠️ 常见坑:BATCH 里混不同 partition——Coordinator 仍逐条路由,无性能收益。
💡 关键直觉:写优化在 batch 与 CL;读优化在 cache 与 compaction 策略——别只调 JVM。
下一节用三维契约完成 Mongo/HBase/PG 选型表。
以日志采集为例,从「逐条 INSERT」到「批量+异步」的完整改造:
-- 改造前:每条日志一次 RPC,吞吐受限 INSERT INTO logs (device_id, ts, level, msg) VALUES ('d-1', now(), 'INFO', 'a'); -- 改造后:同分区多行合并为一次协调 BEGIN UNLOGGED BATCH INSERT INTO logs (device_id, ts, level, msg) VALUES ('d-1', now(), 'INFO', 'a'); INSERT INTO logs (device_id, ts, level, msg) VALUES ('d-1', now(), 'WARN', 'b'); INSERT INTO logs (device_id, ts, level, msg) VALUES ('d-1', now(), 'INFO', 'c'); APPLY BATCH;
# 驱动侧:异步 + 背压,不让写入队列无限膨胀 from cassandra.cluster import Cluster cluster = Cluster(['10.0.1.10']) session = cluster.connect() session.execute("USE telemetry") # 使用 execute_async 批量提交,同时控制 in-flight 数量
# 客户端连接池建议:每节点 8-16 连接,复用长连接
批量优化的三条边界:BATCH 必须同分区、每条 BATCH 控制在几十条内、异步队列必须有背压。突破任一条,优化就会变成新的故障源——跨分区 BATCH 更慢,超大 BATCH 撑爆请求体,无背压异步会 OOM。
# cassandra.yaml 缓存配置 key_cache_size_in_mb: 100 key_cache_save_period: 14400 row_cache_size_in_mb: 0 # 生产默认关闭 row_cache_class_name: org.apache.cassandra.cache.OHCProvider
# 观察缓存命中率 nodetool info | grep -i "Key Cache" # Key cache hit rate 偏低时,优先查访问模式是否局部化
| 缓存 | 命中收益 | 代价 | 适用 |
|---|---|---|---|
| Key Cache | 省读盘定位 | 堆外内存 | 点查为主 |
| Row Cache | 省整个查询 | 内存贵、一致性 | 极热小数据集 |
| OS Page Cache | 文件系统层兜底 | 无需配置 | 始终有效 |
读优化的顺序是「先看建模对不对(分区内查询),再看 Bloom 过滤,最后才谈缓存」——缓存只能锦上添花,救不了建模错误带来的全环扫描。
# 用 cassandra-stress 建立基准(首次跑出 P50/P99 基线) cassandra-stress write n=1000000 -rate threads=64 \ -schema "replication(factor=3)" -node 10.0.1.10
| 指标 | 健康阈值 | 告警动作 |
|---|---|---|
| 写 P99 | < 2× 基准 | 查 GC / 磁盘 / CL |
| Dropped Mutations | 0 | 立即查写路径 |
| Pending Compactions | < 100 | 限速或扩容 |
| Key Cache Hit | > 90% | 低于则查访问模式 |
| Heap Used | < 70% 长期 | 超限查对象泄漏 |
基准的意义在于「有据可依」:没有基线,就无法区分「正常波动」与「性能劣化」。建议每次发版、每次扩容后重跑一次 stress,把基线偏差控制在 20% 内作为验收线。
# 观察 Compaction 是否拖累读写 nodetool compactionstats # 若 pending 长期积压,读写延迟必然抬头,因为磁盘 I/O 被占
# cassandra.yaml:Compaction 与 streaming 各自限速,防互相踩踏 compaction_throughput_mb_per_sec: 16 stream_throughput_outbound_megabits_per_sec: 200
| 场景 | 策略 | 读写效果 |
|---|---|---|
| 写多读少 | STCS | 写放大最小,读放大容忍 |
| 读敏感 + 点查 | LCS | 读稳定,写放大换 |
| 时序 + TTL | TWCS | 窗口化压缩,读写双优 |
Compaction 是「后台作业」,但它的节奏直接决定前台 SLA:积压时先限速保业务,低谷期再提速清积压;比「追着 Compaction 调参」更省力的,是设计阶段选对策略——读写比与时间序列形态,在建模时就已经决定了。