cassandra - 使用 Cassandra 获得最新的独特结果
问题描述
我有一项服务可以处理不同服务的用户状态。多个 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)
并以完全一致的方式读取以检查是否未写入较旧的更新。但这放弃了 Cassandra 的可用性和反模式。
有没有办法在没有写前读/物化视图的情况下做到这一点?
编辑
写入不一定按顺序进行,因此时间戳是在外部提供的。
我不需要保留历史记录,只需保留最后一次更新(通过外部时间戳)。
解决方案
对于您的选择:
- 使用 PRIMARY KEY (service, user) 创建主表并使用 PRIMARY KEY (service, user, updated_at) 创建物化视图。但这会损害性能。
物化视图并不会真正对性能造成太大影响,并且写入路径非常快,所以我不会担心,但目前 MV 存在很多问题,并且出于某种原因标记为实验性 - 我不会推荐它们,否则您将面临很多一致性当前版本中的问题。
- 使用 PRIMARY KEY (service, user) 创建表并在写入前以完全一致性读取以检查是否未写入较旧的更新。但这放弃了 Cassandra 的可用性和反模式。
也许我错过了一些你没有解释的要求,但你不需要在写之前先读。在我看来,这似乎是迄今为止对我来说最好的解决方案。就在您有更新时,将更改推送到 (service, user) 表,然后当您从表中读取时,您将获得每个用户的最新更新。IF EXISTS
在使用 paxos 的插入/更新中也有always或 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 之类的)。然后在边界时间附近查询当前和最后一个存储桶。否则,分区将增长,直到它们引起问题。
推荐阅读
- php - 错误:无法向服务器发送请求。节点 php-fpm 本地主机
- sqlite - 将 LocalDB 转换为 SQLite
- java - Solr 从站是否重新加载核心?
- go - 有没有办法将 redigo 与数据文本文件以及 redis-cli 管道一起使用?
- c# - 检查 ITypeSymbol 是否是委托类型
- java - Gradle - Spring Boot托管项目依赖项的“无法解析类”
- python - 内部联接在熊猫数据框中不起作用
- php - laravel 动态关系属性的表现和替代方式
- flutter - 谷歌地图错误
- javascript - SharePoint REST 查询 SP.UserProfiles.PeopleManager.getPropertiesFor 未获取用户