7个版本 (破坏性更新)
新 0.27.0 | 2024年8月5日 |
---|---|
0.26.0 | 2024年6月20日 |
0.25.0 | 2024年2月24日 |
0.23.0 | 2023年3月14日 |
0.19.0 | 2023年1月30日 |
#180 在 数据库接口
每月80 次下载
3.5MB
5K SLoC
Apache Cassandra的轻量级基准测试工具
针对Cassandra集群运行自定义的CQL工作负载,并测量吞吐量和响应时间
为何还需要另一个基准测试程序?
- Latte在Apache Cassandra基准测试方面显著优于其他基准测试工具。请参阅基准测试。
- Latte旨在提供定义工作负载最灵活的方式。
性能
与NoSQLBench、Cassandra Stress 和 tlp-stress 不同,Latte是用Rust编写的,并使用Scylla的原生Cassandra驱动程序。它具有全异步、每个核心一个线程的执行引擎,能够从单个线程每秒运行数千个请求。
Latte有以下独特的性能特点
- 在多核机器上具有出色的可扩展性。
- 比NoSQLBench大约高10倍的CPU效率。这意味着您可以使用少量客户端测试大型集群。
- 比基于Java的工具低50-100倍内存占用。
- 对操作系统资源的低影响——低数量的系统调用、上下文切换和页面错误。
- 无需客户端代码预热。客户端代码从第一次基准测试周期开始即可以最大性能运行。即使是30秒的运行也能给出准确的结果。
- 测试过程中没有GC暂停或HotSpot重新编译。您想要测量服务器的中断,而不是基准测试工具。
出色的性能使其成为快速实验不同工作负载时的探索性基准测试的理想工具。
灵活性
其他基准测试工具通常使用配置文件来指定工作负载配方。虽然这使定义简单工作负载变得容易,但当您想要编写更现实的场景时,例如发出多个查询或以不同于工具直接构建的方式生成数据,它很快就会变得繁琐。
Latte不是试图将流行的配置文件格式弯曲成图灵完备的脚本语言,它简单地嵌入了一种真实、功能齐全、图灵完备的现代脚本语言。我们选择Rune,因为它与Rust无缝集成、一流的异步支持、令人满意的表现力和维护者提供的出色支持。
Rune提供了与Rust类似的结构和功能,尽管具有动态类型和易于自动内存管理。因此,您不仅可以发出自定义CQL查询,还可以编写
任何您想做的事情。这里有变量、条件语句、循环、模式匹配、函数、lambda函数、用户定义的数据结构、对象、枚举、常量、宏等等。
特性
- 与Apache Cassandra 3.x、4.x、DataStax Enterprise 6.x和ScyllaDB兼容
- 具有强大脚本引擎的自定义工作负载
- 异步查询
- 预定义查询
- 可编程数据生成
- 工作负载参数化
- 准确测量吞吐量和响应时间,并具有误差范围
- 无协调缺失
- 可配置的连接和线程数
- 速率和并发限制器
- 进度条
- 美观的文本报告
- 可以以JSON格式导出报告
- 两次运行的并列比较
- 对自相关校正的差异的统计分析
局限性
Latte仍然是处于早期阶段、正在积极开发中的软件。
- 尚未支持绑定某些CQL数据类型,例如用户定义的类型、映射或小于64位的整数类型。
- 查询结果集尚未公开。
- 数据生成函数集很小,但很快将扩展。
- 向后兼容性可能会经常破坏。
安装
从deb包安装
dpkg -i latte-<version>.deb
从源码安装
- 安装Rust工具链
- 运行
cargo install latte-cli
用法
在某处启动一个Cassandra集群(可以是本地节点)。然后运行
latte schema <workload.rn> [<node address>] # create the database schema
latte load <workload.rn> [<node address>] # populate the database with data
latte run <workload.rn> [-f <function>] [<node address>] # execute the workload and measure the performance
您可以在workloads
文件夹中找到一些示例工作负载文件。为了方便起见,您可以将工作负载文件放置在/usr/share/latte/workloads
或.local/share/latte/workloads
下,这样latte可以在任何当前工作目录下找到它们。您还可以通过设置环境变量LATTE_WORKLOAD_PATH
来设置自定义工作负载位置。
Latte在标准输出上生成文本报告,但也将所有数据保存到工作目录中的json文件。文件名会根据运行参数和时间戳自动创建。
您可以使用latte show
显示之前运行的输出。
latte show <report.json>
latte show <report.json> -b <previous report.json> # to compare against baseline performance
运行latte --help
以显示有关可用选项的帮助信息。
工作负载
Latte的工作负载可以使用嵌入的脚本语言Rune进行完全自定义。
工作负载脚本定义了一组由Latte自动调用的公共函数。一个最小可行的工作负载脚本必须至少定义一个公共异步函数run
,它有两个参数
ctx
– 会话上下文,提供对Cassandra的访问i
– 64位整数类型的当前唯一周期数,从0开始
以下脚本将基准测试查询system.local
表
pub async fn run(ctx, i) {
ctx.execute("SELECT cluster_name FROM system.local LIMIT 1").await
}
在ctx
上的实例函数是异步的,因此您应该在这些函数上调用await
。
工作负载脚本可以为运行基准测试提供多个功能。在这种情况下,您可以随意命名这些功能,然后使用 -f /
--function
参数选择其中一个。
模式创建
您可以在 schema
函数中创建(重新)创建基准测试所需的自己的键空间和表。如果存在,schema
函数还应删除旧模式。通过运行 latte schema
命令来执行 schema
函数。
pub async fn schema(ctx) {
ctx.execute("CREATE KEYSPACE IF NOT EXISTS test \
WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 }").await?;
ctx.execute("DROP TABLE IF NOT EXISTS test.test").await?;
ctx.execute("CREATE TABLE test.test(id bigint, data varchar)").await?;
}
预处理语句
调用 ctx.execute
不是最佳选择,因为它不使用预处理语句。您可以在 prepare
函数中准备语句并将它们注册到上下文对象上。
const INSERT = "my_insert";
const SELECT = "my_select";
pub async fn prepare(ctx) {
ctx.prepare(INSERT, "INSERT INTO test.test(id, data) VALUES (?, ?)").await?;
ctx.prepare(SELECT, "SELECT * FROM test.test WHERE id = ?").await?;
}
pub async fn run(ctx, i) {
ctx.execute_prepared(SELECT, [i]).await
}
查询参数也可以通过名称绑定和传递
const INSERT = "my_insert";
pub async fn prepare(ctx) {
ctx.prepare(INSERT, "INSERT INTO test.test(id, data) VALUES (:id, :data)").await?;
}
pub async fn run(ctx, i) {
ctx.execute_prepared(INSERT, #{id: 5, data: "foo"}).await
}
填充数据库
当读取查询返回非空结果集时,它们更有趣。
要能够使用 latte load
将数据加载到表中,您需要在上下文对象上设置加载周期数并定义 load
函数。
pub async fn prepare(ctx) {
ctx.load_cycle_count = 1000000;
}
pub async fn load(ctx, i) {
ctx.execute_prepared(INSERT, [i, "Lorem ipsum dolor sit amet"]).await
}
我们还建议定义 erase
函数以在加载前清除数据,这样无论数据库中之前存在什么数据,您都可以始终获得相同的数据集。
pub async fn erase(ctx) {
ctx.execute("TRUNCATE TABLE test.test").await
}
生成数据
Latte 附带一组数据生成函数库。这些函数在 latte
包中可用。通常,这些函数接受一个整数 i
周期号,因此您可以生成一致的数字。数据生成函数是纯函数,即多次使用相同的参数调用它们会始终产生相同的结果。
latte::uuid(i)
– 生成一个随机(类型 4)UUIDlatte::hash(i)
– 生成一个非负整数哈希值latte::hash2(a, b)
– 生成两个整数的非负整数哈希值latte::hash_range(i, max)
– 在范围0..max
内生成一个整数值latte::hash_select(i, vector)
– 根据哈希选择向量中的一个元素latte::blob(i, len)
– 生成一个长度为len
的随机二进制 bloblatte::normal(i, mean, std_dev)
– 从正态分布生成一个浮点数latte::uniform(i, min, max)
– 从均匀分布生成一个浮点数
类型转换
Rune使用64位表示法来处理整数和浮点数。从版本0.28开始,Rune数字会自动转换为正确的目标查询参数类型,因此您无需进行显式转换。例如,您可以将整数作为Cassandra类型的参数传递smallint
。如果数字太大,无法适应目标类型的允许范围,则会引发运行时错误。
以下方法可用
x.to_integer()
– 将浮点数转换为整数x.to_float()
– 将整数转换为浮点数x.to_string()
– 将浮点数或整数转换为字符串x.clamp(min, max)
– 将整数或浮点数值的范围限制在给定的范围内
您也可以通过调用实例函数to_integer
或to_float
来在浮点数和整数之间进行转换。
文本资源
可以使用fs
模块中的函数从文件或资源中加载文本数据
fs::read_to_string(file_path)
– 返回文件内容作为字符串fs::read_lines(file_path)
– 将文件行读入字符串向量fs::read_resource_to_string(resource_name)
– 返回内置资源内容作为字符串fs::read_resource_lines(resource_name)
– 返回内置资源行作为字符串向量
资源嵌入在程序二进制文件中。您可以在源树中的resources
文件夹中找到它们。
为了减少内存分配的成本,最好在prepare
函数中只加载一次资源,并将它们存储在上下文的data
字段中,以便在load
和run
中未来使用。
pub async fn prepare(ctx) {
ctx.data.last_names = fs::read_lines("lastnames.txt")?;
// ... prepare queries
}
pub async fn run(ctx, i) {
let random_last_name = latte::hash_select(i, ctx.data.last_names);
// ... use random_last_name in queries
}
参数化工作负载
可以通过从命令行调用提供的参数来参数化工作负载。使用宏latte::param!(param_name, default_value)
来初始化从命令行参数来的脚本常量
const ROW_COUNT = latte::param!("row_count", 1000000);
pub async fn prepare(ctx) {
ctx.load_cycle_count = ROW_COUNT;
}
然后您可以使用-P
来设置参数
latte run <workload> -P row_count=200
错误处理
在执行工作负载脚本期间出现的错误分为三类
- 编译错误 – 在脚本加载时检测到的错误;例如,语法错误或引用未定义的变量。这些错误会立即发出信号,并在连接到数据库之前终止基准测试。
- 运行时错误/恐慌 – 例如,除以零或数组越界访问。它们会立即终止基准测试。
- 错误返回值 - 例如当查询执行返回错误结果时。这些值只有在实际从函数返回时才有效(使用
?
将它们传播到调用链)。除Cassandra超载错误外的所有错误都会立即终止基准测试。
在主运行阶段发生的超载错误(例如超时)将被计数并在基准测试报告中报告。
其他函数
ctx.elapsed_secs()
- 返回自开始工作负载以来经过的秒数,作为浮点数
依赖关系
~34–48MB
~789K SLoC