3.3 查询驱动建模


3.3 查询驱动建模

本节摘要:Cassandra 铁律:先列出所有查询,再建表——通常一查询一表,必要时物化视图或双写。本节用订单/Feed/IoT 三案例对照 Mongo 聚合管道与 PostgreSQL 范式化。

读前必看

阅读完本节,你应当能够:

  1. 用「查询清单 → 表清单」完成一个小域建模
  2. 说明 Materialized View 与双表同步的取舍
  3. 列举 ALLOW FILTERING、高基数二级索引、跨 partition 事务三类反模式
  4. 判断何时应退回 Mongo 或 PostgreSQL

一、问题与直觉

关系库 ER 图先定实体再查——JOIN 帮你在运行时组合。Cassandra 没有 JOIN:运行时组合 = 全环扫描。Netflix 播放历史按用户查、Apple 消息按对话查——表结构直接 mirror 访问路径。Mongo 可在应用层 $lookup,但大流量下同样不推荐;HBase 多表 Join 走 MapReduce 离线。

二、核心原理

四步建模法(SOURCE 3.3):

  1. 列业务查询(含排序、分页、CL)
  2. 为每条查询设计 PRIMARY KEY
  3. 去重合并表(接受冗余)
  4. 应用层或 CDC 保持派生表一致

案例:社交 Feed

查询 表设计
用户时间线 PRIMARY KEY (user_id, post_time)
帖子 by id PRIMARY KEY (post_id)
粉丝读作者 同上 user_id 表,写 fan-out 或 pull 模型

Fan-out on write:发帖时写每个粉丝 timeline——写放大,读快。Pull:读时聚合——读放大。Cassandra 偏 fan-out 因写吞吐便宜。

与 MongoDB

维度 Cassandra MongoDB
多维查询 多表/SAI 单文档嵌套 + 索引
事务 LWT 单行/小范围 4.x 多文档 TX
模式变更 ALTER 阻塞 schema-less

与 HBase:HBase 常一张宽表多 Column Family;Cassandra 倾向多表窄模型 + CQL 类型安全。

三、工程实践要点

物化视图:自动维护派生表,底层 Paxos;3.x 后谨慎用于高写表——写放大 ×2。

双写一致性:应用写主表 + 派生表,失败用 Kafka 补偿;别指望跨 partition ACID。

反模式清单

  • ALLOW FILTERING 上生产
  • 二级索引 on 高基数列(如 email)
  • 单 partition 存整个 BLOB 树
  • 用 Cassandra 做 OLAP GROUP BY

⚠️ 常见坑:从 PostgreSQL 迁移只搬表结构不搬查询清单——上线第一周全员 ALLOW FILTERING

💡 关键直觉:表数量变多是 feature 不是 debt——在 Cassandra 里,少表往往意味着查询被牺牲。

本节速览

  • 查询驱动 是唯一正确建模顺序
  • 冗余与 fan-out 是换读延迟的货币
  • MV/双表 需显式一致性故事
  • Mongo 适合文档多变;PG 适合事务关联
  • Cassandra 适合已知路径的海量写读

第4章讨论表结构上线后的部署、repair 与备份——建模决策的运维后果。

案例:IoT 时序建模完整过程

以「设备温度监控」为例,完整走一遍查询驱动建模:

业务查询清单: Q1. 某设备最近 24 小时温度曲线(按时间倒序) Q2. 某设备某时间点的原始读数 Q3. 全厂今日超过阈值的设备列表(低频,可接受秒级延迟)
-- Q1/Q2 用同一张表:设备分区 + 时间聚类 CREATE TABLE temp_by_device ( device_id text, ts timestamp, temp double, PRIMARY KEY (device_id, ts) ) WITH CLUSTERING ORDER BY (ts DESC) AND compaction = {'class': 'TimeWindowCompactionStrategy'}; -- Q3 是低频全表过滤,不硬建模,用应用层定时扫描 + 缓存告警

这张表体现了三个决策:分区键选设备(Q1 等值条件)、聚类键选时间(Q1 排序)、Compaction 选 TWCS(时序 TTL 语义)。对比 MongoDB 用 $lt/$gt 范围查询同场景——文档模型查询更灵活,但无法把「设备+时间」物理聚簇,海量扫描时性能不可控。

案例:社交 Feed 双表一致性

CREATE TABLE post_by_id ( post_id uuid PRIMARY KEY, author_id uuid, content text, ts timestamp ); CREATE TABLE feed_by_user ( user_id uuid, post_id uuid, author uuid, ts timestamp, PRIMARY KEY (user_id, ts, post_id) ) WITH CLUSTERING ORDER BY (ts DESC);
# 发帖时双写:post_by_id 保证"按 id 取全文",feed_by_user 保证"首页时间线" def publish(post): session.execute("INSERT INTO post_by_id ...", (post.id, ...)) for follower in followers(post.author): session.execute("INSERT INTO feed_by_user ...", (follower, post.ts, post.id, ...)) # 失败补偿:写失败的任务进队列,由 worker 重试补齐

写放大明显(一个粉丝一条写),但读路径全是单分区点查。Cassandra 的定价逻辑是「写便宜、读贵」,所以 fan-out on write 是社区默认推荐;反过来如果粉丝列表巨大,则改 pull 模型读时聚合,用读放大换写放大。

反模式自查清单

反模式 症状 修正
全表 ALLOW FILTERING 查询扫全环,P99 恶化 把过滤字段挪进 PRIMARY KEY
高基数列建二级索引 索引写放大、扫描低效 建反查表
单分区存百万行 Compaction/repair 退化 时间桶 / 加盐
应用层 GROUP BY 读全量在内存聚合 CDC 到 OLAP
BATCH 跨分区 协调者串行处理 拆单条 / 同分区批量

对照自查一遍,你的建模是否踩中了至少一条?Cassandra 的建模评审会(schema review)就是拿着这份清单逐表过——这也是 4.x 之后大型团队的标准流程。

物化视图与双表的取舍

-- 物化视图:由主表自动派生,无需应用层双写 CREATE MATERIALIZED VIEW temp_by_device_daily AS SELECT device_id, ts, temp FROM temp_by_device WHERE device_id IS NOT NULL AND ts IS NOT NULL PRIMARY KEY ((device_id, toDate(ts)), ts);
维度 Materialized View 应用层双表
一致性 Paxos 保障主表/视图原子 靠重试/队列补偿
写放大 每次写多写一份 显式控制
调试成本 视图逻辑隐蔽 代码可读
高写表 不推荐(Paxos 开销) 推荐

物化视图的维护走 Paxos,高写入表上每行写都会翻倍并引入串行化成本;社区共识是 3.x 后谨慎用于高频写表,优先应用层双表。选择时先量写入吞吐:每秒几万的表不碰 MV,每秒几百的表用 MV 省代码。

查询驱动建模的演进与 Schema 变更

-- 新增列:ALTER 支持,但无默认值回填 ALTER TABLE temp_by_device ADD humidity double; -- 变更聚类键顺序:不支持,只能建新表 + 迁移

Cassandra 的 schema 变更能力有限:加列可以,改主键、改聚类排序都必须建新表、双写迁移、再切流。这反向强化了「先列查询、后建表」纪律——建模期的一次偷懒,上线后要用一次迁移偿还。MongoDB 的 schema-less 看似无痛,实则把结构约束推给应用层,查询错误在运行时才暴露;Cassandra 把约束前置到建表,代价是演进必须规划。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U