Rust 和 ScyllaDB NoSQL:提高性能的 3 种方法

在不同的基准测试中,Rust 驱动程序被证明比其他驱动程序具有更高的性能,这让我们产生了将它用作其他驱动程序的统一核心的想法。 

这篇博文基于 ScyllaDB 大学 Rust 课程。在这篇文章中,我将介绍课程的要点。您将了解准备好的语句、分页和重试,并查看使用 ScyllaDB Rust 驱动程序的示例。最终目标是演示一些微小的更改如何显着提高应用程序的性能。 

在 Docker 中启动 ScyllaDB

从 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.querywith 。session.execute

在这两个步骤之后,应用程序将重用准备好的语句 insert_message而不是发送原始查询。这显着提高了性能。

分页

查看应用程序的最后几行: 

调用了该Session::query方法,并发送了未准备好的选择查询。由于此查询只执行一次,因此不值得准备。但是,如果我们怀疑结果会很大,使用分页可能会更好。

什么是分页?

分页是一种以可管理的块的形式返回大量数据的方法。在没有分页的情况下,协调器节点准备一个包含所有数据的结果实体并将其返回。如果结果很大,这可能会对性能产生重大影响,因为它可能会在客户端和 ScyllaDB 服务器端占用大量内存。 

为避免这种情况,请使用分页,以便将结果以有限大小的块传输,一次传输一个块。传输每个块后,数据库停止并等待客户端请求下一个。重复此操作,直到传输整个结果集。 

客户端可以根据它可以包含的行数来限制页面的大小。如果页面在达到客户端提供的行限制之前达到大小限制,则称为短页面或短读取。

将分页添加到我们的应用程序

正如您现在可能已经猜到的那样,Session::query不使用分页。它一次性将整个结果提取到内存中。另一种 Session 方法在后台使用分页——  Session::query_iterSession::execute_iter是另一种适用于准备好的语句的方法)。该Session::query_iter方法将一个查询和一个值列表作为参数,并在结果行上返回一个异步迭代器。这是它的使用方式: 

调用后query_iter,驱动程序启动后台任务以获取后续行。调用者任务(调用的任务query_iter)使用类似迭代器的流接口来消耗新获取的行。调用者和后台任务同时运行,因此其中一个可以获取新行,而另一个使用它们。 

通过向应用程序添加分页,您可以减少内存使用并提高应用程序的性能。

重试

查询失败后,驱动程序可能会根据重试策略和查询本身决定重试。可以为整个会话或单个查询配置重试策略。

可用的重试策略

驱动程序提供两种策略可供选择:

  • Fallthrough Retry Policy:从不重试并将所有错误直接返回给用户
  • 默认重试策略:默认使用,如果成功几率很高,可能会重试

可以通过实施 RetryPolicy 和 RetrySession 来提供自定义重试策略。

使用重试策略

享受重试策略好处的关键是提供更多关于查询幂等性的信息。如果查询可以多次应用而不改变初始应用的结果,则该查询是幂等的。如果驱动程序不是幂等的,则它不会重试失败的查询。将查询标记为幂等应由用户完成,因为驱动程序不解析查询字符串。 

你可能感兴趣的:(apache,kafka)