【问题标题】:Partition DELETE/INSERT concurrency issue in CassandraCassandra 中的分区删除/插入并发问题
【发布时间】:2016-11-11 12:46:17
【问题描述】:

我在 Cassandra 中有一个存储 csv 文件版本的表。它使用具有唯一 id 的版本(分区键)和行号(集群键)的主键。当我插入新版本时,我首先对要插入的分区键执行删除语句,以清理任何不完整的数据。然后插入数据。

现在问题来了。即使删除和后续插入在应用程序中一个接一个地同步执行,Cassandra 中似乎仍然存在某种程度的并发性,因为当我事后阅读时,我插入的行偶尔会丢失 - 大约 3 次中有 1 行。以下是一些事实:

  • 卡桑德拉 3.0
  • 一致性 ALL (R+W)
  • 使用 Java 驱动程序删除
  • 使用 Spark-Cassandra 连接器插入
  • 节点数:2
  • 复制因子:2

我执行的删除语句是这样的:

“从 myTable 中删除版本 = 'id'”

如果我省略它,问题就会消失。如果我在删除和插入之间插入延迟,则问题会减少(丢失的行更少)。最初我使用限制较少的一致性级别,我确信这是问题所在,但它并没有影响问题。我的假设是,出于某种原因,尽管 ALL 的一致性级别,删除语句仍被异步发送到副本,但我不明白为什么会出现这种情况或如何避免这种情况。

【问题讨论】:

  • 一般来说,C* 是一个最终一致的数据库。这意味着很多不同的东西,但你不能依赖操作顺序。永远不要尝试连续做一些相关的操作。最好重新设计您的架构并改变您的方法。如果您发布您的数据模型并解释您希望实现的目标,我们可能会为您提供帮助。据我所知,在您的情况下,最好在客户端进行数据检索后过滤旧版本。
  • 我的期望是最终的一致性程度会受到我选择的一致性级别的限制。现在,为了简单起见,我们在原型中使用 ALL,因为它不是瓶颈。如果我不能依赖以最保守的一致性级别按顺序完成的操作序列,那么我可以将一致性级别用于什么?
  • ALL 保证所有节点都对查询达成共识(因此在单个查询之后,所有节点都将具有相同版本的受影响行),但不保证任何关于查询顺序的内容。我强烈建议您重新设计您的方法并停止将 C* 视为关系数据库。那是行不通的。
  • 根据文档 ALL 将保证它被写入所有节点上的提交日志,这将保证给定相关时间戳的顺序(尽管罗素在下面对这些时间戳的分配有一个很好的观点,但这可以在查询中解决)。
  • 是的,但你不能保证处理顺序......无论如何,除非你有非常少量的写入,否则以所有一致性执行所有写入是一个坏主意。您可以通过重新设计方法来解决问题并提高效率。

标签: apache-spark cassandra


【解决方案1】:

默认情况下,所有突变都将获取协调器的写入时间。来自文档

TIMESTAMP:设置操作的时间戳。 如果没有指定, 协调器将使用当前时间(以微秒为单位) 语句执行的开始作为时间戳。 这通常是 合适的默认值。

http://cassandra.apache.org/doc/cql3/CQL.html

由于不同突变的协调器可能不同,因此协调器之间的时钟偏差可能最终导致一台机器发生突变,从而相对于另一台机器产生偏差。

由于写入时间控制 C* 历史,这意味着您可以拥有一个同步插入和删除的驱动程序,但取决于协调器,删除可能发生在插入“之前”。

示例

假设两个节点 A 和 B,B 在 A 之后以 5 秒的时钟偏差运行。

在时间 0:您将数据插入集群并且选择 A 作为协调器。突变到达 A 并且 A 分配了一个时间戳 (0)

现在集群中有一条记录

INSERT VALUE AT TIME 0

两个节点都包含此消息,并且请求返回确认写入成功。

在时间 2:您对先前插入的数据发出删除操作,并选择 B 作为协调器。 B 分配的时间戳为 (-3),因为它比 A 中的时间晚了 5 秒。这意味着我们最终会得到如下语句

DELETE VALUE AT TIME -3

我们承认所有节点都收到了这条记录。

现在全局一致时间线是

DELETE VALUE AT TIME -3
INSERT VALUE AT TIME 0

由于插入发生在删除之后,该值仍然存在。

【讨论】:

  • 但令我惊讶的是,考虑到删除是以一致性 ALL 执行的,我希望它在返回给调用者之前确认所有副本上的删除。是否会出现此问题,因为它在返回之前没有在内部执行 DELETE,而是仅使用时间戳记录它,然后在下一个插入语句中出现故障?我相信插入是分批发生的,所以如果每个批次都有自己的时间戳,这将与我的观察结果非常吻合。
  • 我可以看到您支持在基于 rdd 的连接器 API 上设置写入时间,但该功能似乎已从基于数据帧的 API 中消失。这是有原因的吗?
  • 删除没有“确认”它是“确认”的语义。时间戳应用于协调器(一个节点)处的突变,整个突变将看起来像“DELETE X AT Time 5”这将发送到所有节点如果然后插入并且另一个节点是协调器并且该节点是时钟倾斜,您可能会看到“INSERT X at 2”即使这两条消息都得到了所有副本的确认,并且通过您的驱动程序,看起来这两件事的发生顺序是 Delete -> INSERT 确认的日志是“DELETE INSERT”
  • 更新示例,我们只是还没有添加数据帧的功能
【解决方案2】:

我遇到了类似的问题,我已经通过为 INSERT 和 DELETE 请求(实际上是所有查询,包括 UPDATE)启用 Light-Weight-Transaction 来解决它。它将确保对该分区的所有查询都通过一个“线程”进行序列化,因此 DELETE 不会覆盖 INSERT。例如(假设 instance_id 是主键):

INSERT INTO myTable (instance_id, instance_version, data) VALUES ('myinstance', 0, 'some-data') IF NOT EXISTS;
UPDATE myTable SET instance_version=1, data='some-updated-data' WHERE instance_id='myinstance' IF instance_version=0;
UPDATE myTable SET instance_version=2, data='again-some-updated-data' WHERE instance_id='myinstance' IF instance_version=1;
DELETE FROM myTable WHERE instance_id='myinstance' IF instance_version=2
//or:
DELETE FROM myTable WHERE instance_id='myinstance' IF EXISTS

IF 子句为每一行启用轻量级事务,因此它们都被序列化。警告:LWT 比普通调用更昂贵,但有时需要它们,例如在这种并发问题的情况下。

【讨论】:

    猜你喜欢
    • 2017-11-25
    • 2015-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-16
    • 2022-11-24
    • 2012-09-28
    相关资源
    最近更新 更多