通过Cassandra获得最新的独特结果

时间:2019-06-26 07:06:10

标签: 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行。

我考虑了两个都有问题的解决方案:

  1. 使用PRIMARY KEY (service, user)创建主表,然后 使用PRIMARY KEY (service, user, updated_at)创建实例化视图。但这会影响性能。
  2. 使用PRIMARY KEY (service, user)创建表并读取 在写入之前具有完全一致性,以检查未写入较旧的更新。但是,这放弃了可用性和针对 卡桑德拉。

有没有写之前读/物化视图的方法?


编辑

写入不一定按顺序进行,因此时间戳是从外部提供的。
我不需要保留历史记录,只需保留最后一次更新(通过外部时间戳记即可)。

1 个答案:

答案 0 :(得分:1)

供您选择:

  
      
  1. 使用PRIMARY KEY(服务,用户)创建主表,并使用PRIMARY KEY(服务,用户,updated_at)创建实例化视图。但   这会损害性能。
  2.   

材料化视图并没有真正影响性能,并且写入路径非常快,因此我不必担心,但是MV当前存在很多问题,并且标记为实验性是有原因的-我不推荐使用它们,否则您将面对当前版本中存在很多一致性问题。

  
      
  1. 使用PRIMARY KEY(服务,用户)创建表并完整读取   写入之前保持一致,以检查未写入较旧的更新。   但这放弃了Cassandra的可用性和反模式。
  2.   

也许我遗漏了一些您没有解释的要求,但是您不需要在写之前进行阅读。在我看来,这似乎是迄今为止最好的解决方案。仅当有更新时,才将更改推送到(服务,用户)表,然后从表中读取时,您将获得每个用户的最新更新。使用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之类)。然后,在边界时间附近,您将查询当前和最后一个存储桶。否则,分区会不断增长,直到引起问题为止。