在不同的基准测试中,Rust 驱动程序被证明比其他驱动程序具有更高的性能,这让我们产生了将它用作其他驱动程序的统一核心的想法。
这篇博文基于 ScyllaDB 大学 Rust 课程。在这篇文章中,我将介绍课程的要点。您将了解准备好的语句、分页和重试,并查看使用 ScyllaDB Rust 驱动程序的示例。最终目标是演示一些微小的更改如何显着提高应用程序的性能。
从 git 下载示例:
git clone https://github.com/scylladb/scylla-code-samples.git
cd scylla-code-samples/Rust_Scylla_Driver/chat/
要快速启动并运行 ScyllaDB,请使用官方 Docker 镜像:
docker run \
-p 9042:9042/tcp \
--name some-scylla \
--hostname rust-scylla \
-d scylladb/scylla:4.5.0 \
--smp 1 --memory=750M --overprovisioned 1
在此示例中,您将创建一个控制台应用程序,该应用程序从标准输入读取消息并将它们放入 ScyllaDB 中的表中。首先,创建键空间和表:
docker exec -it some-scylla cqlsh
CREATE KEYSPACE IF NOT EXISTS log WITH REPLICATION = {
'class': 'SimpleStrategy',
'replication_factor': 1
};
CREATE TABLE IF NOT EXISTS log.messages (
id bigint,
message text,
PRIMARY KEY (id)
);
现在,看看应用程序的主要代码:
应用程序连接到数据库,从控制台读取一些行,并将它们存储在表中log.messages
。然后它从表中读取这些行并打印出来。
到目前为止,这与您在 Rust 入门课程中看到的非常相似。使用此应用程序,您将看到一些小的更改如何提高应用程序的性能。
在 while 循环的每次迭代中,我们都希望将新数据插入log.messages
表中。天真地这样做是低效的,因为每次调用session.query
都会将整个查询字符串发送到数据库,然后数据库对其进行解析。可以使用会话提前准备查询以避免不必要的数据库端calculations.prepare
方法。对该方法的调用将返回一个PreparedStatement
对象,稍后可以使用该对象session.execute()
来执行所需的查询。
准备好的陈述是由 ScyllaDB 解析的查询,然后保存以备后用。使用准备好的语句的一个重要好处是,您可以继续重用相同的查询,同时修改查询中的变量以匹配名称、地址和位置等参数。
当要求准备 CQL 语句时,客户端库将向 ScyllaDB 发送 CQL 语句。然后,ScyllaDB 将通过 MD5 哈希为该 CQL 语句创建一个唯一的指纹。ScyllaDB 然后使用这个散列来检查它的查询缓存,看看它是否已经看到它。如果是这样,它将返回对该缓存的 CQL 语句的引用。如果 ScyllaDB 在其缓存中没有该唯一查询哈希,它将继续解析查询并将解析后的输出插入其缓存中。
然后,客户端将能够发送并执行指定语句 ID(封装在PreparedStatement
对象中)并提供(绑定)变量的请求,如下所示。
查看上面的示例代码并将其修改为使用准备好的语句。session.prepare
第一步是在 while 循环之前创建一个准备好的语句(借助)。接下来,您需要在 while 循环内 替换session.query
with 。session.execute
在这两个步骤之后,应用程序将重用准备好的语句 insert_message
而不是发送原始查询。这显着提高了性能。
查看应用程序的最后几行:
调用了该Session::query
方法,并发送了未准备好的选择查询。由于此查询只执行一次,因此不值得准备。但是,如果我们怀疑结果会很大,使用分页可能会更好。
分页是一种以可管理的块的形式返回大量数据的方法。在没有分页的情况下,协调器节点准备一个包含所有数据的结果实体并将其返回。如果结果很大,这可能会对性能产生重大影响,因为它可能会在客户端和 ScyllaDB 服务器端占用大量内存。
为避免这种情况,请使用分页,以便将结果以有限大小的块传输,一次传输一个块。传输每个块后,数据库停止并等待客户端请求下一个。重复此操作,直到传输整个结果集。
客户端可以根据它可以包含的行数来限制页面的大小。如果页面在达到客户端提供的行限制之前达到大小限制,则称为短页面或短读取。
正如您现在可能已经猜到的那样,Session::query
不使用分页。它一次性将整个结果提取到内存中。另一种 Session 方法在后台使用分页—— Session::query_iter
(Session::execute_iter
是另一种适用于准备好的语句的方法)。该Session::query_iter
方法将一个查询和一个值列表作为参数,并在结果行上返回一个异步迭代器。这是它的使用方式:
调用后query_iter
,驱动程序启动后台任务以获取后续行。调用者任务(调用的任务query_iter
)使用类似迭代器的流接口来消耗新获取的行。调用者和后台任务同时运行,因此其中一个可以获取新行,而另一个使用它们。
通过向应用程序添加分页,您可以减少内存使用并提高应用程序的性能。
查询失败后,驱动程序可能会根据重试策略和查询本身决定重试。可以为整个会话或单个查询配置重试策略。
驱动程序提供两种策略可供选择:
可以通过实施 RetryPolicy 和 RetrySession 来提供自定义重试策略。
享受重试策略好处的关键是提供更多关于查询幂等性的信息。如果查询可以多次应用而不改变初始应用的结果,则该查询是幂等的。如果驱动程序不是幂等的,则它不会重试失败的查询。将查询标记为幂等应由用户完成,因为驱动程序不解析查询字符串。