Cassandra LWT 写入产生不正确的数据
Cassandra LWT writes producing incorrect data
我们有一个 3 节点 Cassandra 集群(单个 DC),以及一个 table 应用程序 运行 在单独的节点上访问(对于 read/writes)。
在重负载下,当应用程序的两个实例都试图更新相同的用户条目(例如添加属性)时,我们观察到来自 app1
的更新(运行 on node1
成功 - 即 ResultSet#wasApplied
returns true
)。但是,当 app2
(在 node2
上读取数据时,它在 app1
更新之前获取过时数据)。
我想知道为什么会这样,因为具有串行一致性的 LTW 应该可以防止这种类型的不一致。非常感谢任何帮助!
提前致谢!
示例:(基于应用程序日志)
- 最初用户的属性
A
的值为 1
app1
和 app2
都在添加新属性; app1
添加 B:2
和 app2
添加 C:3
app1
将正确的数据读入内存,添加新属性 B
并成功写入 Cassandra。日志显示具有 A:1
和 B:2
元组的最终属性列表。
app2
读取数据,但只看到 A:1
(app1
和 app2
的日志之间的时间差只有 2ms;因此可能发生以任何顺序)。
- 一旦
app2
的写入完成,结束状态只有A:1
和C:2
,这是不正确的。
更多信息
对 table 的(准备好的语句)读写具有以下特征:
- 写入是一个 LWT(在 IF EXISTS 用户标识上)
- table 上的写入以 LOCAL_QUORUM
的一致性级别执行
- 对 table 的读取以 LOCAL_SERIAL
的一致性级别执行
table 看起来像:
CREATE TABLE my_table(userid ascii, username ascii, attributes map);
更新属性映射时应用内的逻辑是:
User user = dao.getUser(userid);
user.addNewAttribute("key", "value");
dao.update(user);
因为 read/writes 发生在 2 个不同的节点上,我们没有在应用程序级别同步操作。
app2
在 app1
开始写入之前正在执行 read
。因此,app1
得到了旧数据(不是旧数据)。 update
操作设置整个地图而不是 add/removing 元素 to/from 地图。
我们更改了逻辑,因此 add/remove 操作现在执行添加(并分别删除)而不是执行 "read and then set"。
新准备好的语句类似于:
QueryBuilder.update("my_table")
.onlyIf(QueryBuilder.eq("username", QueryBuilder.bindMarker()))
.with(QueryBuilder.putAll("attributes", QueryBuilder.bindMarker()))
.and(QueryBuilder.set("lastUpdatedOn", QueryBuilder.bindMarker()))
.where(QueryBuilder.eq("userid", QueryBuilder.bindMarker()));
在使用 set
设置属性之前
我们有一个 3 节点 Cassandra 集群(单个 DC),以及一个 table 应用程序 运行 在单独的节点上访问(对于 read/writes)。
在重负载下,当应用程序的两个实例都试图更新相同的用户条目(例如添加属性)时,我们观察到来自 app1
的更新(运行 on node1
成功 - 即 ResultSet#wasApplied
returns true
)。但是,当 app2
(在 node2
上读取数据时,它在 app1
更新之前获取过时数据)。
我想知道为什么会这样,因为具有串行一致性的 LTW 应该可以防止这种类型的不一致。非常感谢任何帮助!
提前致谢!
示例:(基于应用程序日志)
- 最初用户的属性
A
的值为1
app1
和app2
都在添加新属性;app1
添加B:2
和app2
添加C:3
app1
将正确的数据读入内存,添加新属性B
并成功写入 Cassandra。日志显示具有A:1
和B:2
元组的最终属性列表。app2
读取数据,但只看到A:1
(app1
和app2
的日志之间的时间差只有 2ms;因此可能发生以任何顺序)。- 一旦
app2
的写入完成,结束状态只有A:1
和C:2
,这是不正确的。
更多信息
对 table 的(准备好的语句)读写具有以下特征:
- 写入是一个 LWT(在 IF EXISTS 用户标识上)
- table 上的写入以 LOCAL_QUORUM 的一致性级别执行
- 对 table 的读取以 LOCAL_SERIAL 的一致性级别执行
table 看起来像:
CREATE TABLE my_table(userid ascii, username ascii, attributes map);
更新属性映射时应用内的逻辑是:
User user = dao.getUser(userid);
user.addNewAttribute("key", "value");
dao.update(user);
因为 read/writes 发生在 2 个不同的节点上,我们没有在应用程序级别同步操作。
app2
在 app1
开始写入之前正在执行 read
。因此,app1
得到了旧数据(不是旧数据)。 update
操作设置整个地图而不是 add/removing 元素 to/from 地图。
我们更改了逻辑,因此 add/remove 操作现在执行添加(并分别删除)而不是执行 "read and then set"。
新准备好的语句类似于:
QueryBuilder.update("my_table")
.onlyIf(QueryBuilder.eq("username", QueryBuilder.bindMarker()))
.with(QueryBuilder.putAll("attributes", QueryBuilder.bindMarker()))
.and(QueryBuilder.set("lastUpdatedOn", QueryBuilder.bindMarker()))
.where(QueryBuilder.eq("userid", QueryBuilder.bindMarker()));
在使用 set