9.7 KiB
性能基准
本页导航: 测试方法与复现 · 实测环境 · 性能测试结果 · 事务基准 · 瓶颈与使用范围
测试方法与复现
benchmark/ 中的应用名为 benchmark4csql。它启动真实 chusql-server,通过本机 TCP 使用共享客户端协议,服务端调用 Rust 存储;不以纯内存引擎测量代替端到端性能。每次运行创建独立临时数据库,结束后停止服务并清理数据。任一 SQL、结果校验或并发请求失败会使运行失败。
在 benchmark/ 中构建优化版本,并在 PATH 中放置 release 存储动态库:
$repoRoot = (Resolve-Path '..').Path
$benchmarkTemp = Join-Path $repoRoot 'logs/benchmark-tmp'
New-Item -ItemType Directory -Force -Path $benchmarkTemp | Out-Null
$env:TEMP = $benchmarkTemp
$env:TMP = $benchmarkTemp
$env:PATH = (Join-Path $repoRoot 'chusql-core/storage/target/release') + ';' + $env:PATH
$env:GHC_CHARENC = 'UTF-8'
stack build chusql-core-model chusql-core-engine chusql-interface chusql-server chusql-bootstrap benchmark4csql --force-dirty --ghc-options=-O2
stack exec -- benchmark4csql 10000 50000
首次运行前从 chusql-core/storage/ 执行 cargo build --release。Linux 使用 LD_LIBRARY_PATH 指向 release 存储目录。两个参数分别为用户数、订单数,默认 10,000 / 50,000,均需至少 100。
读取场景先预热一次,再采样七次,记录均值、p50、p95、最大延迟与请求数/秒。p95 使用最近秩;七次采样的 p95 等于最大值,只适合比较本次场景,不能作为稳定的生产尾延迟。并发场景使用独立的已认证 TCP 会话,1、4、8 个客户端各执行 100 次点查,同步开始;吞吐以整个并发阶段的墙上耗时计算,连接与登录不计入该阶段。
加载主表每条 INSERT 包含 500 行。另测逐条持久化提交、200 条 INSERT 的单次事务提交、更新、删除和事务回滚。写入保留正常 WAL、数据刷盘与检查点路径,没有关闭持久化。查询耗时包含 TCP、SQL 解析、语义检查、引擎执行与响应解码;不包含 UI 渲染。读测试主要校验结果行数,加载 COUNT、并发点查值和回滚后值另行校验。
实测环境
- 测量日期:2026-10-05,Asia/Shanghai。
- CPU:AMD Ryzen 7 7840H,8 核 / 16 逻辑处理器。
- 可见物理内存:31.2 GiB。
- 系统:Windows 11 专业版,10.0.26200。
- 数据目录:D 盘 NTFS 上的仓库
logs/临时子目录;未测存储设备裸吞吐。 - 编译:GHC 9.10.3,Haskell 本地包
-O2;Rust release 动态库。 - 规模:10,000 用户、50,000 订单、5,000 条 512 字节字符串宽行;订单按用户循环分配,年龄按 100 个值循环分布。
性能测试结果
下表为 2026-10-05 的成功基准样本,不代表当前提交的重新测量。完整运行须通过结果校验并输出 BENCHMARK_OK;原始日志为本机 logs/benchmark-performance.log。
启动、引导、认证及创建基准库合计 0.62 秒。纯 TCP Ping 200 次平均 0.100 ms;schema catalog 200 次平均 0.267 ms。主键点查另外连续执行 100 次,平均 0.300 ms,100/100 命中。
读取场景
每项七个测量样本,单位为毫秒。请求吞吐由总采样耗时计算,为串行吞吐。
| 场景 | 返回行数 | 均值 | p50 | p95 / 最大值 | 请求/秒 |
|---|---|---|---|---|---|
| 用户全表扫描 | 10,000 | 23.768 | 18.961 | 36.309 | 42.07 |
| 订单全表扫描 | 50,000 | 114.094 | 118.676 | 131.074 | 8.76 |
| 主键点查 | 1 | 0.415 | 0.278 | 0.742 | 2,408.23 |
| name 点查,无二级索引 | 1 | 21.776 | 21.908 | 23.257 | 45.92 |
| name 点查,有二级索引 | 1 | 0.338 | 0.312 | 0.466 | 2,961.71 |
| 主键闭区间范围,100 行 | 100 | 0.675 | 0.644 | 0.985 | 1,482.33 |
| 按年龄排序,LIMIT 100 | 100 | 19.361 | 20.748 | 21.720 | 51.65 |
| 100 个年龄值分组计数 | 100 | 18.139 | 17.843 | 22.200 | 55.13 |
| 用户与订单过滤连接 | 4,500 | 144.929 | 142.598 | 158.723 | 6.90 |
| 过滤派生表与订单连接 | 4,500 | 141.797 | 141.535 | 148.438 | 7.05 |
| CTE 后按年龄分组 | 100 | 28.539 | 29.146 | 30.921 | 35.04 |
| 无关联 COUNT 标量子查询,外层 LIMIT 100 | 100 | 30.777 | 29.844 | 34.692 | 32.49 |
| EXISTS,20 个不同外层绑定 | 20 | 2,061.770 | 2,070.403 | 2,123.953 | 0.49 |
| 512 字节字符串宽行全表扫描 | 5,000 | 22.969 | 22.791 | 24.485 | 43.54 |
相关 EXISTS 用派生表先限定外层为前 20 个用户,每次子查询按 orders.user_id = u.id 匹配;orders.user_id 没有二级索引。每个绑定具有五条订单,外层与内层都读取真实存储。
并发点查
每客户端 100 次主键点查。单请求延迟合并所有客户端样本计算;总吞吐用并发阶段墙上时间计算,不能用单请求延迟均值的倒数替代。
| 客户端 | 请求数 | 墙上耗时 ms | 总吞吐 请求/秒 | 单请求均值 ms | p50 ms | p95 ms | 最大 ms |
|---|---|---|---|---|---|---|---|
| 1 | 100 | 29.094 | 3,437.10 | 0.290 | 0.250 | 0.544 | 1.183 |
| 4 | 400 | 37.862 | 10,564.79 | 0.374 | 0.335 | 0.574 | 1.796 |
| 8 | 800 | 48.877 | 16,367.72 | 0.481 | 0.437 | 0.735 | 2.027 |
写入、索引及事务
这些写入阶段各测量一次,不报告分位数。场景按顺序执行,数据库大小、已打开表数量和索引状态会变化。
| 场景 | 处理行数 | 总耗时 ms | 行/秒 |
|---|---|---|---|
| 用户加载,500 行/提交 | 10,000 | 762.419 | 13,116.15 |
| 订单加载,500 行/提交 | 50,000 | 3,915.697 | 12,769.12 |
| 512 字节宽行加载,500 行/提交 | 5,000 | 1,084.740 | 4,609.40 |
| writes 逐条持久化 INSERT | 200 | 1,119.0 | 178.73 |
| batch_t 加载,100 行/提交 | 10,000 | 1,309.4 | 7,637.09 |
| items 加载,100 行/提交 | 50,000 | 6,336.8 | 7,890.42 |
其他单次耗时:
| 操作 | 耗时 ms |
|---|---|
| 10,000 行 users 建 name 索引 | 239.7 |
| UPDATE writes 的 400 行 | 16.4 |
| 事务内 UPDATE writes 的 400 行 | 0.5 |
| ROLLBACK 上述修改 | 0.3 |
| DELETE writes 的 200 行 | 10.3 |
| DELETE users 单行 | 48.1 |
| DELETE users 全部剩余行 | 765.7 |
| 50,000 行 items 无索引 code 点查 | 135.2 |
| 50,000 行 items 建 code 索引 | 1,089.8 |
| 建索引后 code 点查 | 0.4 |
回滚后校验 writes.id = 1 的 age 仍为 2,事务中写入的 0 没有落入正式数据。删除后 writes 行数为 200。加载、连接及并发点查的结果校验全部通过。
事务基准
同日、相同机器和 10,000 用户 / 50,000 订单规模的独立测量,结果校验通过。日志为本机 logs/repair-txn-performance.log;以下数据与上面的场景不是同一次采样。
| 指标 | 耗时(ms) |
|---|---|
| 200 次独立持久化 INSERT | 1,134.7 |
| 事务 200 条 INSERT 总计 | 687.355 |
| BEGIN | 269.806 |
| 事务内 200 条 INSERT | 41.129 |
| COMMIT | 376.345 |
事务写入约 290.97 行/秒。BEGIN 与 COMMIT 合计约占总耗时的 94%;COMMIT 还包含差分、冲突检查和持久化,不能全部归因于快照。提交跳过未修改表,整数键差分使用 IntMap / IntSet,冲突检查与操作生成复用差分。
瓶颈与使用范围
| 观察 | 当前边界与测量方向 |
|---|---|
| 整库事务快照成本高 | BEGIN / COMMIT 仍读取整库,需分别测传输、修改表比较与刷盘 |
| 不同外层绑定重复扫描 | 20 个不同绑定的相关 EXISTS 在事务基准运行中均值为 2,135.218 ms;相同绑定缓存不等于半连接或通用物化 |
| 大结果与排序有全量成本 | 扫描包含解码、执行、TCP JSON;完整排序后 LIMIT,可分别评估 Top-K、分页和流式返回 |
| 批量提交摊薄固定开销 | 场景的数据库大小和已打开表数量不同,不能当作严格单变量对照;刷盘优化仍须验证恢复 |
| 读并发低于线性扩展 | 8 客户端吞吐约为 1 客户端的 4.8 倍;未测混合读写或并发写 |
Important
这是单机回环、主要为热缓存、管理员身份的基准样本,不是生产容量承诺。未测远程网络、权限目录规模、冷缓存、内存峰值、崩溃恢复耗时或并发写冲突;七次读采样和单次写测量不足以建立统计置信区间。
后续比较应保持机器、数据分布和构建参数一致,多次独立运行,并记录各阶段时间与分配量。