使用二级索引更新 Cassandra 2.1 中的行

Using secondary indexes to update rows in Cassandra 2.1

我正在使用 Cassandra 2.1 并且有一个大致如下所示的模型:

CREATE TABLE events (
  client_id bigint,
  bucket int,
  timestamp timeuuid,
  ...
  ticket_id bigint,
  PRIMARY KEY ((client_id, bucket), timestamp)
);
CREATE INDEX events_ticket ON events(ticket_id);

如您所见,我在 ticket_id 上创建了二级索引。该索引工作正常。 events 包含大约 1 亿行,而其中只有 500 万行具有大约 50,000 张不同的票证。所以一张票 - 平均 - 有 100 个事件。

无需提供分区键即可查询二级索引,这在我们的情况下很方便。由于 bucket 列有时很难事先确定(即您应该知道事件的日期,bucket 是当前日期)。

cqlsh> select * from events where ticket_id = 123;

 client_id | bucket | timestamp | ... | ticket_id
-----------+--------+-----------+-----+-----------

(0 rows)

一张工单的所有事件都应该移动到另一张工单的问题如何解决? IE。以下查询将不起作用:

cqlsh> UPDATE events SET ticket_id = 321 WHERE ticket_id = 123;
InvalidRequest: code=2200 [Invalid query] message="Non PRIMARY KEY ticket_id found in where clause"

这是否意味着二级索引不能用于 UPDATE 查询?

我应该使用什么模型来支持这些变化?

您可以使用二级索引查询旧票的事件,然后使用检索到的事件中的主键来更新事件。

我不确定您为什么需要手动执行此操作,似乎 Cassandra 应该能够在后台执行此操作。

首先,UPDATEINSERT 操作在 Cassandra 中被视为相同。他们通俗地称为 "UPSERTs."

Does this imply secondary indexes cannot be used in UPDATE queries?

正确。如果不指定完整的 PRIMARY KEY,则无法在 Cassandra 中执行 UPSERT。即使是带有部分 PRIMARY KEY 的 UPSERT 也不起作用。并且(正如您所发现的)通过索引值进行 UPSERTing 也不起作用。

How do I solve the problem when all events of a ticket should be moved to another ticket?

不幸的是,完成此操作的唯一方法是查询 events 中每一行的键(具有特定的 ticket_id)并通过这些键进行 UPSERT ticket_id。好处是,您不必首先 DELETE 它们,因为 ticket_id 不是 PRIMARY KEY 的一部分。

How do I solve the problem when all events of a ticket should be moved to another ticket?

我认为您最好的计划是完全放弃二级索引,并创建一个查询 table 与您的 events table:

一起工作
CREATE TABLE eventsbyticketid (
  client_id bigint,
  bucket int,
  timestamp timeuuid,
  ...
  ticket_id bigint,
  PRIMARY KEY ((ticket_id), timestamp)
) WITH CLUSTERING ORDER BY (timestamp DESC);

这将允许您通过 ticket_id 快速查询(以获取您的 client_idbuckettimestamp。这将为您提供所需的信息在你的 events table.

上更新新的 ticket_id

您还可以通过 ticket_id(在 eventsbyticketid table 上)执行 DELETE。只要您拥有完整的分区键 (ticket_id),Cassandra 就允许使用部分主键进行 DELETE 操作。因此,从查询 table 中删除旧的 ticket_id 会很容易。为了确保写入原子性,您可以将 UPSERT 批处理在一起:

BEGIN BATCH
  UPDATE events SET ticket_id = 321 WHERE client_id=2112 AND bucket='2015-04-22 14:53' AND timestamp=4a7e2730-e929-11e4-88c8-21b264d4c94d;
  UPDATE eventsbyticketid SET client_id=2112, bucket='2015-04-22 14:53' WHERE ticket_id=321 AND timestamp=4a7e2730-e929-11e4-88c8-21b264d4c94d
APPLY BATCH;

其实和表演是一样的:

BEGIN BATCH
  INSERT INTO events (client_id,bucket,timestamp,ticketid) VALUES(2112,'2015-04-22 14:53',4a7e2730-e929-11e4-88c8-21b264d4c94d,321);
  INSERT INTO eventsbyticketid (client_id,bucket,timestamp,ticketid) VALUES(2112,'2015-04-22 14:53',4a7e2730-e929-11e4-88c8-21b264d4c94d,321);
APPLY BATCH;

旁注:timestamp 实际上是 Cassandra 中的一种(保留字)数据类型。这使得它成为 timeuuid 列的一个非常糟糕的名称。