ScyllaDB 中计数器列的替代方案

An Alternative to Counter Columns in ScyllaDB

根据 the official documentationScyllaDB 中对计数器列的限制包括:

The only other columns in a table with a counter column can be columns of the primary key (which cannot be updated). No other kinds of column can be included. This limitation safeguards correct handling of counter and non-counter updates by not allowing them in the same operation.

All non-counters columns in the table must be part of the primary key.

Counter columns, on the other hand, may not be part of the primary key.

在我的 table 设计中,我有数百行,其中每一行都需要链接到一个计数器列。

table的结构如下:

K1 K2 C1 C2 C3 V1 V2 V3 COUNTER

其中 K 是分区键,C 是簇键,V 是值,COUNTER 是计数器列。

一般上面的table查询如下:

SELECT * from table WHERE K1 = A & K2 = B & C1 = X & C2 = Y & C3 = Z. 

这导致每个查询返回大约 500 行。如图所示,每一行都链接到一个 COUNTER 列。具体来说,K1, K2, C1, C2, C3 的每个组合都链接到一个 不同的 COUNTER 值。

如果必须将 COUNTER 移动到完全不同的 table,我应该如何建模这个 table?

如果我理解正确,唯一的方法是定义另一个 table -> table_counter 没有任何值 (V):

K1 K2 C1 C2 C3 COUNTER

但是,我对这种方法有几个问题:

1) 像这样把一个逻辑上有凝聚力的table拆散似乎极其不雅

2) 这意味着每当我想执行上一个查询时,我基本上需要执行两个查询,而不是一个,只是为了获取链接到每个查询的计数器信息K1 K2 C1 C2 C3组合

3) 我也将被迫在客户端将上面两个查询的结果组合成一个数据结构(为了有用)

这是正确的吗?如果是,是否有 COUNTER 列的替代方案,我可以将 COUNTER 列添加到第一个 table?

我想到的一种方法是使用常规 INTEGER 作为计数器列。每当需要更新计数器列时,我都可以读取当前计数器(整数)值并在客户端递增它,然后将新值写回数据库。我知道我不会受到并发 reads/writes 的保护,所以如果两个客户端同时读取计数器值,并且他们都 increment/update 它,一个写入(更新)将丢失(例如,只会保留最后一次写入)。然而,我可以忍受偶尔丢失的写入,因为我们没有跟踪任何关键的东西,其中单个(或少数)丢失的写入会产生重大影响。我也知道每次我想更新计数器列时都需要读取然后写入(两次操作),但是,它允许我将计数器列保留为原始 table 的一部分以及将查询从两个 table 减少到一个。此外,无需将在客户端查询两个 table 的结果与此设计相结合。似乎比使用计数器列更有效,也更优雅。

这种方法是 COUNTER 列的可行替代方法吗?有没有我错过的陷阱?在我的示例中是否有其他方法可能效果更好?

谢谢

“经典”的替代方案 DRDT-inspired(but not quite) counter column is to use Scylla's lightweight transactions (LWT) - 基本上你的计数器变成了一个普通的整数列,如果你愿意,你可以正常读取它,但是写入使用条件更新(UPDATE ... IF ...)。例如,要修改值 V 同时增加计数器,您可以:

  1. 读取行的当前值
  2. 确定V的新值并增加计数器,最后
  3. 用新的 V 和递增的计数器写回行,条件是 (IF) 当前计数器仍然是计数器的原始值。
  4. 如果第 3 步失败,即 IF 条件不为真,转到第 1 步。

这种模式被称为“乐观锁定”——第 1-2 步是“乐观的”假设他们构建的新项目将能够被写入,但如果其他一些并发更新击败了我们,第 1- 步- 2 需要重复。这与悲观锁定方法形成对比,在悲观锁定方法中,客户端“获取锁”,并且只有在知道它持有锁之后,才会费心计算项目的新值(步骤 2)。

LWT 比计数器强大得多,你可以用它做的事情比用计数器做的要多得多。读取可以像常规读取一样高效,但请注意,写入确实变慢了。 Scylla 正在研究基于 Raft 的 next-generation LWT 实现(目前的实现是基于 Paxos),因此您可以期待未来 LWT 写入性能的改进。