在Cassandra中,异步写入似乎被打破了

时间:2014-12-27 12:28:40

标签: java cassandra

当将9百万行批量写入12节点cassandra(2.1.2)群集时,我遇到了spark-cassandra-connector(1.0.4,1.1.0)的问题。我写的是一致性ALL并且读取一致性ONE,但读取的行数每次都不同于900万(8.865.753,8.753.213等)。

我检查了连接器的代码,发现没有问题。然后,我决定编写自己的应用程序,独立于spark和连接器,来调查问题(唯一的依赖是datastax-driver-code version 2.1.3)。

完整代码,启动脚本和配置文件现在可以found on github

在伪代码中,我写了两个不同版本的应用程序,同步一个:

try (Session session = cluster.connect()) {

    String cql = "insert into <<a table with 9 normal fields and 2 collections>>";
    PreparedStatement pstm = session.prepare(cql);

    for(String partitionKey : keySource) {
        // keySource is an Iterable<String> of partition keys

        BoundStatement bound = pstm.bind(partitionKey /*, << plus the other parameters >> */);
        bound.setConsistencyLevel(ConsistencyLevel.ALL);

        session.execute(bound);
    }

}

异步的那个:

try (Session session = cluster.connect()) {

    List<ResultSetFuture> futures = new LinkedList<ResultSetFuture>();

    String cql = "insert into <<a table with 9 normal fields and 2 collections>>";
    PreparedStatement pstm = session.prepare(cql);

    for(String partitionKey : keySource) {
        // keySource is an Iterable<String> of partition keys

        while(futures.size()>=10 /* Max 10 concurrent writes */) {
            // Wait for the first issued write to terminate
            ResultSetFuture future = futures.get(0);
            future.get();
            futures.remove(0);
        }

        BoundStatement bound = pstm.bind(partitionKey /*, << plus the other parameters >> */);
        bound.setConsistencyLevel(ConsistencyLevel.ALL);

        futures.add(session.executeAsync(bound));
    }

    while(futures.size()>0) {
        // Wait for the other write requests to terminate
        ResultSetFuture future = futures.get(0);
        future.get();
        futures.remove(0);
    }
}

最后一个类似于无批处理配置时连接器使用的那个。

除了负载很高外,应用程序的两个版本在所有情况下的工作方式都相同。

例如,当在9台机器(45个线程)上运行5个线程的同步版本时,向集群写入9百万行,我会在后续读取中找到所有行(使用spark-cassandra-connector)。

如果我运行异步版本,每台机器有1个线程(9个线程),执行速度要快得多,但我无法在后续读取中找到所有行(与spark-cassandra-connector相同的问题)。

执行期间代码没有抛出任何异常。

问题的原因是什么?

我添加了一些其他结果(感谢评论):

  • 9台机器上有9个线程的异步版本,每个线程有5个并发写入器(45个并发写入器):没有问题
  • 在9台计算机上同步90个线程的版本(每个JVM实例10个线程):没有问题

问题似乎开始于Async写入和许多并发写入器&gt; 45和&lt; = 90,所以我做了其他测试以确保结果是正确的:

  • 取代&#34; get&#34; ResultSetFuture的方法 &#34; getUninterruptibly&#34;:同样的问题。
  • 9台机器上有18个线程的异步版本,其中5个并发 每个线程的编写者(90个并发编写者):没有问题

最后的发现表明,大量并发写入器(90)不像第一次测试中预期的那样是个问题。问题是使用相同会话的大量异步写入。

在同一会话中有5个并发异步写入,问题不存在。如果我将并发写入的数量增加到10,则某些操作会在没有通知的情况下丢失。

如果您在同一会话中同时发出多个(&gt; 5)写入,似乎在Cassandra 2.1.2(或Cassandra Java驱动程序)中打破了异步写入。

2 个答案:

答案 0 :(得分:6)

一些可能性:

  • 您的异步示例是在9个线程上发出10次写入,因此每次执行90次同时您的同步示例一次只执行45次写入,因此我会尝试将异步降低到相同的速率,以便这是一个苹果对苹果的比较。

    您没有说明如何使用异步方法检查异常。我发现您使用的是future.get(),但建议您使用getUninterruptibly(),如文档中所述:

      

    等待查询返回并返回其结果。这个方法是   通常比Future.get()更方便,因为它:等待   结果不可中断,因此不会抛出InterruptedException。   返回有意义的异常,而不是必须处理   为ExecutionException。因此,它是获得未来的首选方式   结果

    因此,您可能没有看到异步示例中出现的写入异常。

  • 另一种不太可能的方法是,您的keySource由于某种原因返回重复的分区键,因此当您执行写操作时,其中一些最终会覆盖以前插入的行,并且不会增加行数。但这也应该影响同步版本,所以我说这不太可能。

    我会尝试以低于900万的速度编写较小的设备并以较慢的速度查看问题是否仅在特定数量的插入或特定插入速率下才会发生。如果插入的数量有影响,那么我怀疑数据中的行键有问题。如果插入速率有影响,那么我怀疑热点会导致写入超时错误。

  • 要检查的另一件事是Cassandra日志文件,看看是否有任何异常报告。

附录:2014年12月30日

我尝试使用Cassandra 2.1.2和驱动程序2.1.3的示例代码重现症状。我使用了一个带有递增数字键的单个表,这样我就可以看到数据中的空白。我做了很多异步插入(每个线程在10个线程中一次30个,全部使用一个全局会话)。然后我做了一个表的“select count(*)”,实际上它报告的表中行数少于预期。然后我做了一个“select *”并将行转储到一个文件中并检查是否缺少密钥。它们似乎是随机分布的,但是当我查询那些丢失的单个行时,事实证明它们实际存在于表中。然后我注意到每次做“选择计数(*)”时,它会返回一个不同的数字,所以它似乎给出了表格中行数的近似值而不是实际数字。

所以我修改了测试程序,在所有写入之后进行回读阶段,因为我知道所有的键值。当我这样做时,所有异步写入都出现在表中。

所以我的问题是,你在完成写作后如何检查表格中的行数?您是在查询每个单独的键值还是使用某种操作,例如“select *”?如果是后者,那似乎可以提供大部分行,但不是全部,所以也许您的数据实际存在。由于没有抛出异常,似乎表明写入都是成功的。另一个问题是,您确定您的密钥值对于所有900万行是唯一的。

答案 1 :(得分:6)

尼古拉和我本周末通过电子邮件进行了交流,并认为我会根据我现在的理论提供更新。我看了一下github project Nicola分享并在EC2上试验了一个8节点集群。

我能够重现2.1.2的问题,但确实观察到一段时间后我可以重新执行spark工作并返回所有900万行。

我似乎注意到的是,当节点处于压缩状态时,我没有获得所有900万行。我一时兴起看了change log for 2.1并观察了可以解释这个问题的问题CASSANDRA-8429 - "Some keys unreadable during compaction"

看到问题已经解决,目标是2.1.3,我重新对cassandra-2.1分支进行测试,并在压缩活动发生时运行计数工作并返回900万行。

我想更多地尝试这个,因为我对cassandra-2.1分支的测试相当有限,压缩活动可能纯属巧合,但我希望这可以解释这些问题。