Cassandra DataStax driver slow?
本文关键字:slow driver DataStax Cassandra | 更新日期: 2023-09-27 18:06:51
我刚刚开始用Cassandra做实验,我使用的是c#和DataStax驱动程序(v3.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行。
下面是我正在运行的测试(根据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 ms,十进制的时间最差,每个字段大约为3 ms。其他字段(text, int, date和timestamp)每个字段大约占用0.5 ms。
看看我测量的时间,我应该怀疑这一点:响应中的行越多,所需的时间就越长,而且几乎是线性的。
@xmas79突出了一个伟大的点。您不应该创建太多的会话实例(每个键空间最好使用1个会话实例),但是还有另一个指导原则可以帮助您。遵循以下指南并参考:
- 每个(物理)集群(每个应用程序生命周期)使用一个集群实例
- 每个键空间最多使用一个Session,或者在查询中使用单个Session并显式指定键空间
- 如果你多次执行一个语句,考虑使用PreparedStatement
- 你可以减少网络往返的次数,也有使用batch 进行原子操作
编辑
另外,再看一下你的代码,你正在为你正在执行的每个相同的查询创建一个准备好的语句。准备好的语句应该只创建一次,并且应该使用它的引用来执行查询。准备好的语句所做的是向服务器发送您将经常执行的CQL,以便服务器已经解析字符串并返回给用户一个标识。所以,我给你的建议是不要使用它,如果你不打算为每个查询共享preparedstatement对象。或者把你的代码改成这样:
[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();
}
简而言之,您希望保持Cassandra的集群对象打开并在多个请求中重用它。
集群对象本身的创建成本很高,但它提供了诸如自动负载平衡、令牌感知、自动故障转移等好处。
为什么执行
using (var session = _cluster.Connect("dm"))
在每个查询上?您应该构建一次Cluster
实例,连接到集群并获得一次Session
,并在任何地方重用它们。我认为Cluster
对象配置了重要的参数,如故障转移,负载平衡等。Session
对象为您管理它们。每次连接都会给你带来性能损失。
编辑
看起来您正在执行SELECT
,每个延迟为10ms-15ms。您是否在每次查询中都获得相同的跟踪编号(例如1.4ms) ?你们的存储IO系统是什么?如果您使用的是旋转磁盘,则可能会导致磁盘子系统的寻道时间损失。