【问题标题】:Cassandra DataStax driver slow?Cassandra DataStax 驱动程序慢?
【发布时间】:2026-02-06 01:05:01
【问题描述】:

我刚刚开始尝试使用 Cassandra,我正在使用 C# 和 DataStax driver (v 3.0.8)。我想做一些性能测试,看看 Cassandra 处理时间序列数据的速度有多快。

结果令人震惊,因为它需要很长时间才能完成SELECT。所以我想我做错了什么。

我已在本地计算机上设置 Cassandra 并创建了一个表:

CREATE KEYSPACE dm WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}  AND durable_writes = true;

CREATE TABLE dm.daily_data_by_day (
    symbol text,
    value_type int,
    as_of_day date,
    revision_timestamp_utc timestamp,
    value decimal,
    PRIMARY KEY ((symbol, value_type), as_of_day, revision_timestamp_utc)
) WITH CLUSTERING ORDER BY (as_of_day ASC, revision_timestamp_utc ASC)
    AND bloom_filter_fp_chance = 0.01
    AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'}
    AND comment = ''
    AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'}
    AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}
    AND crc_check_chance = 1.0
    AND dclocal_read_repair_chance = 0.1
    AND default_time_to_live = 0
    AND gc_grace_seconds = 864000
    AND max_index_interval = 2048
    AND memtable_flush_period_in_ms = 0
    AND min_index_interval = 128
    AND read_repair_chance = 0.0
    AND speculative_retry = '99PERCENTILE';

我已经用大约 1500 万行填充了这张表,分为大约 10000 个分区,每个分区最多包含 10000 行。

这是我正在运行的测试(更新 on request by phact):

[Test]
public void SelectPerformance()
{
    _cluster = Cluster.Builder().AddContactPoint("127.0.0.1").Build();
    _stopwatch = new Stopwatch();
    var items = new[]
        {
            // 20 different items...
        };

    foreach (var item in items)
    {
        var watch = Stopwatch.StartNew();
        var rows = ExecuteQuery(item.Symbol, item.FieldType, item.StartDate, item.EndDate);
        watch.Stop();
        Console.WriteLine($"{watch.ElapsedMilliseconds}\t{rows.Length}");
    }

    Console.WriteLine($"Average Execute: {_stopwatch.ElapsedMilliseconds/items.Length}");
    _cluster.Dispose();
}

private Row[] ExecuteQuery(string symbol, int fieldType, LocalDate startDate, LocalDate endDate)
{
    using (var session = _cluster.Connect("dm"))
    {
        var ps = session.Prepare(
@"SELECT
    symbol,
    value_type,
    as_of_day,
    revision_timestamp_utc,
    value
FROM
    daily_data_by_day
WHERE
    symbol = ? AND
    value_type = ? AND
    as_of_day >= ? AND as_of_day < ?");
        var statement = ps.Bind(symbol, fieldType, startDate, endDate);
        statement.EnableTracing();

        _stopwatch.Start();
        var rowSet = session.Execute(statement);
        _stopwatch.Stop();

        return rowSet.ToArray();
    }
}

秒表告诉我 session.Execute() 需要 20-30 毫秒来执行(更新:在更改代码以创建集群后,我只需要大约 15 毫秒)。所以我启用了一些跟踪并得到以下结果:

 activity                                                                  | source_elapsed 
--------------------------------------------------------------------------------------------
 Parsing SELECT symbol, value_type, as_of_day, revision_timestamp_utc,...; |             47 
                                                       Preparing statement |             98 
                     Executing single-partition query on daily_data_by_day |            922 
                                              Acquiring sstable references |            939 
 Skipped 0/5 non-slice-intersecting sstables, included 0 due to tombstones |            978 
                                   Bloom filter allows skipping sstable 74 |           1003 
                                   Bloom filter allows skipping sstable 75 |           1015 
                                   Bloom filter allows skipping sstable 72 |           1024 
                                   Bloom filter allows skipping sstable 73 |           1032 
                                              Key cache hit for sstable 63 |           1043 
                                 Merged data from memtables and 5 sstables |           1329 
                                       Read 100 live and 0 tombstone cells |           1353 

如果我正确理解此跟踪,Cassandra 执行我的查询所用的时间不到 1.4 毫秒。那么 DataStax 驱动程序剩下的时间在做什么呢?

(作为参考,我对本地 SQL Server 实例进行了相同的性能测试,结果从 C# 执行相同的查询大约需要 1-2 毫秒。)

更新:

我试图做一些分析,这对于你不拥有的异步代码来说并不容易......

我的结论是大部分时间都花在解析响应上。每个响应包含 2000 - 3000 行,每个响应的解析大约需要 9 毫秒。反序列化花费大部分时间,大约 6.5 毫秒,十进制最差,每个字段大约 3 毫秒。其他字段(文本、整数、日期和时间戳)每个字段大约需要 0.5 毫秒。

看看我测量的时间,我应该怀疑这一点:响应中的行越多,花费的时间越长,而且几乎是线性的。

【问题讨论】:

  • 您是否在本地 cassandra 环境中执行过这些测试?只有一个节点?我想分析你的代码。
  • @k0ner 这一切都是在我的本地机器上完成的,只有一个节点。它用于评估 Cassandra,学习如何使用它并查看它的性能。
  • 您是否尝试过分析您的代码?
  • @k0ner 不,我还没到那一步。
  • @TorbjörnKalin 您的初始代码,除了不遵循建议(例如在您的应用程序中重用Session 实例/重用准备语句/ ...)之外,仅测量单个执行的延迟多次同步查询。相反,您应该并行执行多个查询(通过异步方法或使用多个任务和调度程序)并分析延迟如何表现以及吞吐量是多少。这里有一个例子:github.com/riptano/csharp-driver-sut

标签: c# cassandra datastax


【解决方案1】:

@xmas79 强调了一个要点。您不应该创建太多会话实例(最好为每个键空间使用 1 个),但还有另一个指南可以帮助您。请遵循以下指南并参考:

  • 每个(物理)集群(每个应用程序生命周期)使用一个集群实例
  • 每个键空间最多使用一个 Session,或使用一个 Session 并在查询中明确指定键空间
  • 如果您多次执行语句,请考虑使用 PreparedStatement
  • 您可以减少网络往返次数,还可以 使用批处理的原子操作

http://www.datastax.com/dev/blog/4-simple-rules-when-using-the-datastax-drivers-for-cassandra

编辑

另外,再次查看您的代码,您正在为您正在执行的每个相同查询创建准备好的语句。准备好的语句应该只创建一次,您应该使用它的引用来执行查询。准备好的语句所做的是将您经常执行的 CQL 发送到服务器,以便服务器已经解析字符串并向用户返回一个标识。因此,如果您不打算为每个查询共享 PreparedStatment 对象,我对您的建议是不要使用它。或将您的代码更改为以下内容:

[Test]
public void SelectPerformance()
{
    _cluster = Cluster.Builder().AddContactPoint("127.0.0.1").Build();
    var session = _cluster.Connect("dm");
    var ps = session.Prepare(@"SELECT symbol, value_type, as_of_day, revision_timestamp_utc, value FROM daily_data_by_day WHERE symbol = ? AND  value_type = ? AND as_of_day >= ? AND as_of_day < ?");
    var items = new[]
    {
        // 20 different items...
    };
    foreach (var item in items)
    {
        var watch = Stopwatch.StartNew();
        var rows = ExecuteQuery(session, ps, item.Symbol, item.FieldType, item.StartDate, item.EndDate);
        watch.Stop();
        Console.WriteLine($"{watch.ElapsedMilliseconds}\t{rows.Length}");
    }

    Console.WriteLine($"Average Execute: {   _stopwatch.ElapsedMilliseconds/items.Length}");
    _cluster.Dispose();
}

private Row[] ExecuteQuery(Session session, PreparedStatement ps, string symbol, int fieldType, LocalDate startDate, LocalDate endDate)
{
     var statement = ps.Bind(symbol, fieldType, startDate, endDate);
     // Do not enable request tracing for latency benchmarking
     // statement.EnableTracing();
     var rowSet = session.Execute(statement);
     return rowSet.ToArray();
}

【讨论】:

  • 如果这是生产代码,我会同意你的看法。然而,这是一个测试,我正在尝试对类似生产的环境进行测量。
  • 我会更深入地了解准备好的语句的用法。看看我上面的评论。你也可以在这里看到datastax.github.io/java-driver/manual/statements/prepared准备好的语句是如何工作的
  • 我更改了我的代码以重用准备好的语句,现在每个请求快了大约 1 毫秒,所以这是一个很好的建议(虽然我不确定我是否能够做同样的事情后期生产)。剩余 10 毫秒的大部分时间都用于解析响应,请参阅我的问题的更新。
【解决方案2】:

简短的回答,您希望保持集群对象对 Cassandra 开放并跨请求重新使用它。

集群对象本身的创建成本很高,但会带来自动负载平衡、令牌感知、自动故障转移等好处。

【讨论】:

  • 为了检验您的假设,我按顺序运行了 20 个不同的查询,全部使用同一个集群。您是正确的,第一个查询需要更长的时间,但以下查询仍然需要大约 10-15 毫秒才能执行。
  • phact:我做了一些分析,结果可能会让您感兴趣,请参阅更新后的问题。在DecimalSerializer.Deserialize() 上花费了大量时间。
【解决方案3】:

为什么要执行

using (var session = _cluster.Connect("dm"))

在每个查询上?您应该构建一次Cluster 实例,连接到集群并获取一次Session,然后在任何地方重用它们。我认为Cluster 对象配置了重要参数,如故障转移、负载平衡等。Session 对象为您管理它们。每次连接都会给您带来性能损失。

编辑

看来您正在执行SELECT,每个延迟为 10 毫秒至 15 毫秒。您是否在每次查询时获得相同 跟踪号码(例如 1.4 毫秒)?您的存储 IO 系统是什么?如果您使用的是旋转磁盘,则可能会影响磁盘子系统的寻道时间。

【讨论】:

  • 这是一个性能测试,我想模拟一个真实的生产环境。另外,在phact's answer之后,我做了一个测试,会话只创建了一次,执行时间只从大约15毫秒减少到了12毫秒。
  • @TorbjörnKalin 您还需要保持连接打开。将保持资源繁忙,但有一个很好的理由。