1. 概述

Cassandra 是一款 NoSQL 数据库,在保证高性能的同时提供了高可用性和水平扩展能力。

要充分发挥 Cassandra 的性能优势,我们需要根据具体业务场景的查询模式精心设计数据模型。简单粗暴地说,数据建模是 Cassandra 性能优化的核心

本文将深入探讨 Cassandra 数据建模的关键概念和实践方法。如果对 Cassandra 基础操作不熟悉,建议先阅读我们的 Cassandra 与 Java 集成 文章。

2. 分区键

Cassandra 作为分布式数据库,数据会被分区并存储在集群的多个节点上。

分区键由一个或多个数据字段组成,分区器通过哈希分区键生成令牌,从而将数据均匀分布到整个集群。⚠️ 分区键设计不当会导致数据倾斜,这是常见的性能踩坑点。

3. 聚合键

聚合键由一个或多个字段组成,用于将具有相同分区键的行进行分组,并按指定顺序存储。

举个典型场景:存储时间序列数据时,如果需要按时间顺序检索数据,包含时间字段的聚合键就能大幅提升查询效率。

关键点:分区键 + 聚合键 = 主键,唯一标识 Cassandra 集群中的任何记录。

4. 查询模式设计原则

开始 Cassandra 数据建模前,必须先明确查询模式,并遵循以下原则:

  1. 单分区查询:每个查询应只从单个分区获取数据
  2. 分区大小控制:需监控单个分区的数据量(Cassandra 对单分区列数有限制)
  3. 数据反规范化:为支持不同查询模式,允许数据冗余存储

基于以上原则,我们通过实际案例说明如何设计 Cassandra 数据模型。

5. 实战数据建模案例

5.1. Facebook 帖子存储

假设我们要存储不同用户的 Facebook 帖子,常见查询是获取指定用户的最新 N 条帖子。

根据设计原则:

  • 必须将单个用户的所有数据存储在同一个分区
  • 使用帖子时间戳作为聚合键,可高效获取最新帖子

表结构设计如下:

CREATE TABLE posts_facebook (
  user_id uuid,
  post_id timeuuid, 
  content text,
  PRIMARY KEY (user_id, post_id) )
WITH CLUSTERING ORDER BY (post_id DESC);

查询用户 Anna 的最新 20 条帖子:

SELECT content FROM posts_facebook WHERE user_id = "Anna_id" LIMIT 20

5.2. 全国健身房信息

假设要存储不同国家、州/省、城市的健身房信息,需求包括:

  • 查询指定城市的所有健身房
  • 按开业日期排序返回结果

根据设计原则:

  • 将同一国家、州/省、城市的健身房数据放在单个分区
  • 使用开业日期和健身房名称作为聚合键

表结构设计:

CREATE TABLE gyms_by_city (
 country_code text,
 state text,
 city text,
 gym_name text,
 opening_date timestamp,
 PRIMARY KEY (
   (country_code, state_province, city), 
   (opening_date, gym_name)) 
 WITH CLUSTERING ORDER BY (opening_date ASC, gym_name ASC);

查询美国亚利桑那州菲尼克斯市最早开业的 10 家健身房:

SELECT * FROM gyms_by_city
  WHERE country_code = "us" AND state = "Arizona" AND city = "Phoenix"
  LIMIT 10

查询最近开业的 10 家健身房(注意性能问题):

SELECT * FROM gyms_by_city
  WHERE country_code = "us" and state = "Arizona" and city = "Phoenix"
  ORDER BY opening_date DESC 
  LIMIT 10

⚠️ 踩坑提示:最后查询的排序方向与表定义相反,Cassandra 需要先获取数据再内存排序,性能较差。

5.3. 电商客户与商品

假设运营电商系统,需要存储客户和商品信息,常见查询包括:

  1. 获取客户信息
  2. 获取商品信息
  3. 查询喜欢某商品的所有客户
  4. 查询某客户喜欢的所有商品

基础方案:

  • 使用单独表存储客户和商品信息

为支持查询 3 和 4,需引入数据冗余:

  • 创建 Customer_By_Liked_Product
  • 创建 Product_Liked_By_Customer

完整表结构:

CREATE TABLE Customer (
  cust_id text,
  first_name text, 
  last_name text,
  registered_on timestamp, 
  PRIMARY KEY (cust_id));

CREATE TABLE Product (
  prdt_id text,
  title text,
  PRIMARY KEY (prdt_id));

CREATE TABLE Customer_By_Liked_Product (
  liked_prdt_id text,
  liked_on timestamp,
  title text,
  cust_id text,
  first_name text, 
  last_name text, 
  PRIMARY KEY (prdt_id, liked_on));

CREATE TABLE Product_Liked_By_Customer (
  cust_id text, 
  first_name text,
  last_name text,
  liked_prdt_id text, 
  liked_on timestamp,
  title text,
  PRIMARY KEY (cust_id, liked_on));

设计技巧:使用 liked_on 作为聚合键,可高效获取"最近喜欢"的数据。

查询最近喜欢 "Pepsi" 的 10 位客户:

SELECT * FROM Customer_By_Liked_Product WHERE title = "Pepsi" LIMIT 10

查询客户 Anna 最近喜欢的 10 件商品:

SELECT * FROM Product_Liked_By_Customer 
  WHERE first_name = "Anna" LIMIT 10

6. 低效查询模式

由于 Cassandra 的分布式存储特性,以下查询模式性能较差:

  • 跨分区查询
    需要协调器从多个节点获取数据,在堆内存中临时存储并聚合,严重影响性能

  • 关联查询(JOIN)
    Cassandra 不支持关系型数据库的 JOIN 操作,强制使用会导致:

    • 查询速度极慢
    • 数据一致性问题
    • 可用性风险

7. 总结

本文系统介绍了 Cassandra 数据建模的最佳实践:

  1. 核心原则:基于查询模式设计数据模型,而非实体关系
  2. 关键要素:合理设计分区键和聚合键
  3. 实战技巧:通过数据反规范化支持多查询模式
  4. 避坑指南:避免跨分区查询和 JOIN 操作

记住:Cassandra 数据建模的本质是用空间换时间,通过数据冗余换取查询效率。对于有经验的开发者来说,理解并应用这些原则是发挥 Cassandra 强大性能的关键。


原始标题:Data Modeling in Cassandra