Files
ChuSQL/docs/performance.md
2026-10-13 14:09:36 +08:00

138 lines
9.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 性能基准
[← 返回 README](../README.md)
**本页导航:** [测试方法与复现](#测试方法与复现) · [实测环境](#实测环境) · [性能测试结果](#性能测试结果) · [事务基准](#事务基准) · [瓶颈与使用范围](#瓶颈与使用范围)
## 测试方法与复现
`benchmark/` 中的应用名为 `benchmark4csql`。它启动真实 `chusql-server`,通过本机 TCP 使用共享客户端协议,服务端调用 Rust 存储;不以纯内存引擎测量代替端到端性能。每次运行创建独立临时数据库,结束后停止服务并清理数据。任一 SQL、结果校验或并发请求失败会使运行失败。
在 `benchmark/` 中构建优化版本,并在 PATH 中放置 release 存储动态库:
```powershell
$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]
> 这是单机回环、主要为热缓存、管理员身份的基准样本,不是生产容量承诺。未测远程网络、权限目录规模、冷缓存、内存峰值、崩溃恢复耗时或并发写冲突;七次读采样和单次写测量不足以建立统计置信区间。
后续比较应保持机器、数据分布和构建参数一致,多次独立运行,并记录各阶段时间与分配量。