跳到主要内容
英文版本为准

本页译自英文,由模型翻译。若与英文原文存在差异,以英文原文为准。

数字

ruststream-sqlx 的基准测试数字测量什么,一条消息仍然要付出什么,以及这些数字不能用来得出什么结论。

与裸 sqlx 循环的对照​

同一行中的三个循环可以互相比较,因为它们在同一个进程中交替运行,连接同一个数据库。一行不能与其他 Broker crate 公布的行比较:表中的队列在每条消息上做的工作与消息 Broker 不同,它的每条消息都是一次领取、一次结算和一次提交。

在三个循环中,每个数字都包含这些到数据库的往返。在 受数据库限制 的行中,往返至少占一条消息所用时间的一半,因此循环之间的差值是本 crate 和运行时所做工作的下限,而不是对它的测量。大多数 inbox 的行都是 无法区分:本 crate 的工作小于一个等待服务器的循环各轮之间的波动。

outbox 的行测量的是另一件事:跟踪的开销。一条被跟踪的消息在发布前要付出一次插入,在消费者一侧要付出一次取出和一次标记,每一项都是在自己的连接上执行的一条语句。因此按设计,outbox 比不用 outbox 慢好几倍。本 crate 负责的是与手写 outbox 之间的差距,后者执行相同的语句。

吞吐量网格​

只有一个 worker 时,三个循环按同样的顺序做同样的工作。worker 更多时,裸循环运行 n 个领取者,每个领取者是一个独立领取和删除的任务;本 crate 的循环运行 n 个订阅,每个订阅使用自己的 Broker。服务为它的 n 个 worker 运行一个订阅,在 PostgreSQL 和 MySQL 上为每个空闲的 worker 保持一次进行中的领取。因此,本 crate 一列与裸循环一列并排,显示本 crate 在相同并发度下的开销;四个和八个 worker 时本 crate 与服务之间的差距,反映的是一个订阅的领取方式,而不是分发。一个批每次领取最多它的大小那么多行,一次领取服务那么多次投递,因此三个循环的结果很接近。

在 SQLite 上,订阅同一时间只保持一次进行中的领取,因为 SQLite 同一时间只接受一个写入者,而这次领取为每个 worker 各取一行。裸循环在一个 SQLite 文件上的 n 个领取者每次领取只取一行,所以在四个和八个 worker 时,服务领取同样多的行,所用的领取次数比裸循环少。

代码的开销​

指令计数包括运行时、本 crate,以及 sqlx 驱动程序在服务线程上的工作:为每条语句编码、与服务器交换数据、为每一行解码。旁边裸循环的计数是驱动程序所占的份额,因此两者之差就是本 crate 和运行时。同样的计数在缓存未命中时耗时更多,所以计数不等于时间。

每个数字都是稳态值,即 1000 次投递的运行与 2000 次投递的运行之差。打开连接池的连接、Broker 的启动检查和第一次投递属于冷启动,只付出一次。读指令数时精确到几个百分点即可,不必精确到每一位:与真实服务器通信的服务,在多次运行之间会有波动。

仍然存在的开销​

本 crate 遵守整个框架家族的规则:在热路径上,除 sqlx 自身的开销外,每条消息没有内存分配、动态调用、额外的原子操作或复制。仍然存在的开销列在这里,各自注明适用的场合:

  • 读取投递事务 Tx 的处理器,每次投递付出一次引用计数递增;通过 Ctx<keys::Pool> 请求连接池的处理器也付出一次。不使用它们的订阅不付出任何开销。
  • 通过 Repository 或路由的发布,读取 Broker 的关闭标志,即一次原子加载,并唤醒本进程中订阅了该表的订阅,每个订阅一次原子操作。
  • Routed 查找每条消息的名称,精确名称是一次哈希查找,并对行的 Publish 做一次动态调用。Repository 在编译期确定它的表,两者都不需要。
  • 按名称订阅、而行的事件由服务自己定义时,每条消息付出一次装箱的投递和一次装箱的结算 future。
  • outbox 通过 OnceLock 读取启动后才设置的连接池,每条被跟踪的消息一次原子加载,并为每条被跟踪消息的 id 消息头文本分配内存。
  • 行锁方式和咨询锁方式为每条处理中的消息或每个处理中的批占用一个池连接:这是持有者终止时由数据库释放的锁的代价。
  • 从处理器之外、通过运行中应用的发布者发布时,每条消息编码进一个单独的缓冲区;裸循环则为所有消息保留一个缓冲区。

它们不意味着什么​

测试环境在运行基准测试的机器的主机网络上,以 Docker 运行 PostgreSQL 和 MySQL,不启用持久性:没有 fsync,提交时也不刷新日志。SQLite 运行在 WAL 模式、synchronous = NORMAL 的本地文件上。测量的是一条消息的开销,而不是服务器下面的磁盘。启用持久性的服务要为此付出代价,在每个循环中付出的代价都相同。位于另一台主机上的服务器回答的是关于网络的问题,而不是关于本 crate 的问题。

一行就是一张表、一个小的 JSON 消息体和回环地址上的一台服务器。它测量的是一条消息在本 crate 中的开销,而不是 PostgreSQL、MySQL 或 SQLite 能承载多少。在三个循环中,计时窗口都在最后一次投递被计数时、在它结算之前关闭,因此每次运行都有一次结算在所有循环中都不计入数字。

这些数字是一台机器在某一天的快照,构建信息记录在它们旁边。它们在专供这次运行使用的机器上手动重新测量:它们所显示的差异,比共享机器上的噪声还小。