Apache Cassandra中多对多实体关系的数据建模方案咨询
针对你的大数据场景(1000万订阅者、数百合作伙伴)和四个高频查询,直接按查询驱动建模是Cassandra的核心思路,下面给你具体的设计方案:
一、核心设计原则
Cassandra以空间换时间,反规范化是常规操作——你的第二个思路方向是对的,几百个Partner带来的数据冗余完全在Cassandra的处理能力范围内(1000万订阅者即使每人订阅2个Partner,总数据量也才2000万条,属于Cassandra的常规负载)。而第一个方案用IN partner_ids的查询方式绝对不能用在高频场景,因为IN会触发多分区扫描,性能极差,延迟不可控。
二、对应每个查询的表设计
1. 根据Partner名称获取所有Subscriber
建一张专门的查询表,分区键直接用partner_name,聚类键用subscriber_id保证每个订阅者在同一Partner下唯一:
CREATE TABLE partner_subscribers ( partner_name TEXT, subscriber_id UUID, subscriber_name TEXT, region TEXT, subscribe_date TIMESTAMP, PRIMARY KEY (partner_name, subscriber_id) );
- 查询语句:
SELECT * FROM partner_subscribers WHERE partner_name = 'XXX'; - 优势:单分区查询,性能拉满,完全匹配高频需求。
2. 根据Partner名称+指定地区获取所有Subscriber
同样建专属查询表,用复合分区键(partner_name, region),确保查询直接命中分区:
CREATE TABLE partner_region_subscribers ( partner_name TEXT, region TEXT, subscriber_id UUID, subscriber_name TEXT, subscribe_date TIMESTAMP, PRIMARY KEY ((partner_name, region), subscriber_id) );
- 查询语句:
SELECT * FROM partner_region_subscribers WHERE partner_name = 'XXX' AND region = 'XXX'; - 优势:避免用索引(索引在高基数字段上性能差),直接单分区查询,速度最快。
3. 获取指定日期之后创建的所有Partner
建Partner主表,用固定分区键(因为Partner总数才几百,全分区扫描无压力),聚类键按create_date倒序排列:
CREATE TABLE partners ( partition_fixed TEXT, partner_id UUID, partner_name TEXT, create_date TIMESTAMP, subscriber_count INT, -- 预存订阅数,为第四个查询做准备 PRIMARY KEY (partition_fixed, create_date) );
- 插入数据时
partition_fixed固定为'all_partners',保证所有Partner存在同一个分区里 - 查询语句:
SELECT * FROM partners WHERE partition_fixed = 'all_partners' AND create_date > '2024-01-01';
4. 获取拥有特定数量订阅者的Partner
建一张按订阅数分组的表,分区键用subscriber_count,聚类键用partner_name:
CREATE TABLE partners_by_subscriber_count ( subscriber_count INT, partner_name TEXT, partner_id UUID, create_date TIMESTAMP, PRIMARY KEY (subscriber_count, partner_name) );
- 查询语句:
SELECT * FROM partners_by_subscriber_count WHERE subscriber_count = 1000; - 注意:当订阅关系变化时,需要同步更新
partners表的subscriber_count和这张表的对应数据(用Cassandra的BATCH操作批量执行更新)。
三、Subscriber主表(维护基础信息与订阅关系)
还要建一张Subscriber主表,存储其基本信息,同时用集合字段记录订阅的Partner ID(仅用于维护订阅关系,比如取消订阅时快速找到需要更新的Partner):
CREATE TABLE subscribers ( subscriber_id UUID, subscriber_name TEXT, region TEXT, create_date TIMESTAMP, partner_ids SET<UUID>, PRIMARY KEY (subscriber_id) );
四、数据一致性维护
因为反规范化后同一数据会出现在多张表,所以需要在订阅/取消订阅时用BATCH操作同步更新所有相关表:
比如订阅操作的批量语句示例:
BEGIN BATCH INSERT INTO partner_subscribers (partner_name, subscriber_id, subscriber_name, region, subscribe_date) VALUES ('partnerA', uuid(), 'user1', 'CN', toTimestamp(now())); INSERT INTO partner_region_subscribers (partner_name, region, subscriber_id, subscriber_name, subscribe_date) VALUES ('partnerA', 'CN', uuid(), 'user1', toTimestamp(now())); UPDATE subscribers SET partner_ids = partner_ids + {uuid()} WHERE subscriber_id = uuid(); UPDATE partners SET subscriber_count = subscriber_count + 1 WHERE partition_fixed = 'all_partners' AND partner_id = uuid(); INSERT INTO partners_by_subscriber_count (subscriber_count, partner_name, partner_id, create_date) VALUES (new_count, 'partnerA', uuid(), '2024-01-01') IF NOT EXISTS; APPLY BATCH;
注意:BATCH操作不要包含过多语句,控制在10条以内,避免性能问题。
总结
- 放弃第一个方案的
IN查询,性能完全无法满足高频需求; - 接受反规范化的冗余,这是Cassandra高性能的代价,你的场景下冗余量完全可控;
- 每个高频查询对应一张专属表,用分区键直接命中查询条件,确保性能;
- 通过批量操作维护多表的数据一致性。
内容的提问来源于stack exchange,提问作者Ovezmyrat

