我有一个服务可以处理不同服务上的用户状态。
多个DC上的流量可能非常高,所以我认为Cassandra将适合存储此数据。
我只需要保留每个服务和每个用户的最新更新。
我考虑过创建此表:
CREATE TABLE db.state (
service uuid,
user uuid,
updated_at timestamp,
data varchar,
PRIMARY KEY (service, user, updated_at)
) WITH CLUSTERING ORDER BY (updated_at DESC);
问题是如何查询最近的100个唯一身份用户状态。
使用此查询:
SELECT service, user, data, updated_at FROM db.state WHERE service = :service LIMIT 100
。
如果某个用户有很多更新,那么我不会获得最近的100个用户,但更少。 我不想合并客户端中的唯一用户,因为为了获得100个用户,有时我需要获得10000行。
我考虑了两个都有问题的解决方案:
PRIMARY KEY (service, user)
创建主表,然后
使用PRIMARY KEY (service, user,
updated_at)
创建实例化视图。但这会影响性能。PRIMARY KEY (service, user)
创建表并读取
在写入之前具有完全一致性,以检查未写入较旧的更新。但是,这放弃了可用性和针对
卡桑德拉。有没有写之前读/物化视图的方法?
写入不一定按顺序进行,因此时间戳是从外部提供的。
我不需要保留历史记录,只需保留最后一次更新(通过外部时间戳记即可)。
答案 0 :(得分:1)
供您选择:
- 使用PRIMARY KEY(服务,用户)创建主表,并使用PRIMARY KEY(服务,用户,updated_at)创建实例化视图。但 这会损害性能。
材料化视图并没有真正影响性能,并且写入路径非常快,因此我不必担心,但是MV当前存在很多问题,并且标记为实验性是有原因的-我不推荐使用它们,否则您将面对当前版本中存在很多一致性问题。
- 使用PRIMARY KEY(服务,用户)创建表并完整读取 写入之前保持一致,以检查未写入较旧的更新。 但这放弃了Cassandra的可用性和反模式。
也许我遗漏了一些您没有解释的要求,但是您不需要在写之前进行阅读。在我看来,这似乎是迄今为止最好的解决方案。仅当有更新时,才将更改推送到(服务,用户)表,然后从表中读取时,您将获得每个用户的最新更新。使用paxos的插入/更新操作中也总是有IF EXISTS
或IF子句。
如果您需要历史记录(而不仅仅是最新记录),并且又不想使用第二张表,则可以使用group by:
CREATE TABLE state ( // simplified a little
service int,
user int,
updated_at timeuuid,
data text,
PRIMARY KEY (service, user, updated_at)
) WITH CLUSTERING ORDER BY (user ASC, updated_at DESC);
INSERT INTO state (service, user, updated_at, data) VALUES ( 1, 1, now(), '1');
INSERT INTO state (service, user, updated_at, data) VALUES ( 1, 1, now(), '2');
INSERT INTO state (service, user, updated_at, data) VALUES ( 1, 1, now(), '3');
INSERT INTO state (service, user, updated_at, data) VALUES ( 1, 2, now(), '1');
INSERT INTO state (service, user, updated_at, data) VALUES ( 1, 2, now(), '2');
INSERT INTO state (service, user, updated_at, data) VALUES ( 2, 1, now(), '1');
INSERT INTO state (service, user, updated_at, data) VALUES ( 1, 3, now(), '2');
INSERT INTO state (service, user, updated_at, data) VALUES ( 1, 3, now(), '3');
INSERT INTO state (service, user, updated_at, data) VALUES ( 1, 3, now(), '1');
INSERT INTO state (service, user, updated_at, data) VALUES ( 1, 3, now(), '2');
SELECT * FROM state WHERE service = 1 GROUP BY service, user;
service | user | updated_at | data
---------+------+--------------------------------------+------
1 | 1 | 7c2bd900-981e-11e9-a27a-7b01c564a3f0 | 3
1 | 2 | 7c2d1180-981e-11e9-a27a-7b01c564a3f0 | 2
1 | 3 | 7c88c610-981e-11e9-a27a-7b01c564a3f0 | 2
它的效率不是很出色,但是只要您不让单个服务分区变得太大,它就可以工作。实际上,我会 强 向其中添加日期组件/存储桶,例如:
CREATE TABLE state (
bucket text
service int,
user int,
updated_at timeuuid,
data text,
PRIMARY KEY ((bucket, service), user, updated_at)
) WITH CLUSTERING ORDER BY (user ASC, updated_at DESC);
其中bucket是YYYY-MM-DD字符串(或YYYY-WEEKOFYEAR之类)。然后,在边界时间附近,您将查询当前和最后一个存储桶。否则,分区会不断增长,直到引起问题为止。