整理代码

This commit is contained in:
LIm1tZz7 committed 2026-10-13 14:09:36 +08:00
1 parent e87c4ee6a8
commit a45e33f94b
39 files changed
+264 -917

No files matched your search

+28 -47
View File
@@ -1,5 +1,9 @@
# 性能基准
[← 返回 README](../README.md)
**本页导航:** [测试方法与复现](#测试方法与复现) · [实测环境](#实测环境) · [性能测试结果](#性能测试结果) · [事务基准](#事务基准) · [瓶颈与使用范围](#瓶颈与使用范围)
## 测试方法与复现
`benchmark/` 中的应用名为 `benchmark4csql`。它启动真实 `chusql-server`,通过本机 TCP 使用共享客户端协议,服务端调用 Rust 存储;不以纯内存引擎测量代替端到端性能。每次运行创建独立临时数据库,结束后停止服务并清理数据。任一 SQL、结果校验或并发请求失败会使运行失败。
@@ -7,15 +11,18 @@
在 `benchmark/` 中构建优化版本,并在 PATH 中放置 release 存储动态库:
```powershell
$env:TMP = 'D:\Workspace\ChuSQL\logs\cli-test-tmp'
$env:TEMP = $env:TMP
$env:PATH = 'D:\Workspace\ChuSQL\chusql-core\storage\target\release;' + $env:PATH
$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。
首次运行前从 `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 次点查,同步开始;吞吐以整个并发阶段的墙上耗时计算,连接与登录不计入该阶段。
@@ -33,7 +40,7 @@ stack exec -- benchmark4csql 10000 50000
## 性能测试结果
完整运行返回退出码 0,输出 `PRESSURE_OK all result checks passed` 和 `BENCHMARK_OK all scenarios completed`。原始日志保存于本机 `logs/benchmark-performance.log`。下面保留该次成功运行的数据;首次失败运行不参与统计。
下表为 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 命中。
@@ -80,7 +87,6 @@ stack exec -- benchmark4csql 10000 50000
| 订单加载,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 |
| writes 事务中 200 条 INSERT,含 BEGIN / COMMIT | 200 | 67,549.539 | 2.96 |
| batch_t 加载,100 行/提交 | 10,000 | 1,309.4 | 7,637.09 |
| items 加载,100 行/提交 | 50,000 | 6,336.8 | 7,890.42 |
@@ -101,45 +107,9 @@ stack exec -- benchmark4csql 10000 50000
回滚后校验 `writes.id = 1` 的 age 仍为 2,事务中写入的 0 没有落入正式数据。删除后 writes 行数为 200。加载、连接及并发点查的结果校验全部通过。
## 性能瓶颈分析
## 事务基准
本节描述首次基准时的实现;事务差分优化后的结果见文末复测。
### 1. 事务提交的整库差分是首要排查点
本次 200 条事务 INSERT 比 200 次独立持久化 INSERT 慢约 60 倍。事务阶段开始时,数据库已有 10,000 用户、50,000 订单及 5,000 宽行,事务只修改 writes 表。事务内一次 400 行 UPDATE 仅耗时 0.5 ms,说明不能将整个事务阶段的 67.55 秒简单归因于 SQL 更新计算或 fsync。
代码依据:服务端 [Session.hs](../chusql-server/src/ChuSQL/Server/Session.hs) 的 BEGIN 和 COMMIT 都获取整库快照。`transactionDiffs` 对具有整数 id 的表先走 keyedDiff,即使 staged 与 base 完全相同,也会建立关联列表并对每行执行线性 `lookup` / `any`;50,000 行未修改表仍可能进行平方级比较。COMMIT 分别调用冲突检查和操作生成,可能重复计算差分。这是与实测异常最吻合的瓶颈候选。
当前没有 BEGIN、逐条语句、COMMIT 的独立计时或 CPU profile,不能给各部分分摊比例。下一步应先拆分计时,再对未修改表快速跳过、以 Map / HashMap 做按 id 差分、复用一次差分计算结果,并考虑记录实际修改表与键。整库快照及内存统计重算也需要测量。不能据当前实现假设把 Web 编辑放进一个大事务就会更快。
### 2. 不同绑定的相关子查询仍重复扫描
20 个 EXISTS 绑定平均 2.06 秒,而单次 50,000 行订单扫描约 114 ms;两者量级相符。缓存只能合并相同绑定,这里的 20 个用户 id 都不同,因此仍需多次读取订单。执行路径 [Eval.hs](../chusql-core/engine/src/ChuSQL/Core/Engine/Algebra/Eval.hs) 将子查询逐绑定执行,没有半连接,EXISTS 也先得到子查询结果再判断是否为空。
优先验证 `orders.user_id` 二级索引及基于统计的点查选择,再评估半连接、EXISTS 找到首行即可停止、或单次物化内层匹配键。这个推断有查询结构和耗时量级支持,但本次没有对索引版相关子查询做对照,不把预期收益当作已测结果。
### 3. 扫描、结果传输与全量排序限制大结果查询
50,000 行扫描比 10,000 行扫描慢约 4.8 倍。无索引 name 点查只返回一行,耗时仍接近用户全表扫描;索引后均值下降约 64 倍,说明扫描工作量具有决定性影响。宽行加载吞吐降至约 4,609 行/秒,低于短行的约 1.3 万行/秒。
`sortRows` 使用完整 `sortBy` 后再 LIMIT,返回 100 行并不代表只处理 100 行。当前全表结果会经过存储解码、引擎求值及 TCP JSON 编码。下一步可测 Top-K 排序、分页或流式返回,以及存储、引擎、编码各阶段的时间和分配量。本次没有冷缓存、磁盘裸吞吐、分阶段 CPU 或内存峰值测量,无法把扫描耗时全部归到磁盘。
### 4. 批量提交有效,读并发扩展低于线性
批量加载达到约 1.3 万行/秒,200 次独立提交只有约 179 行/秒。数据和已打开表数量不同,这不是严格的单变量对照;但与 WAL 提交、表刷盘和检查点固定成本被摊薄的执行路径一致。Rust 的 `group_via_wal` 还会刷所有已打开表,应单独测量并评估只刷本组实际修改表的收益及恢复正确性。
8 客户端点查总吞吐约为 1 客户端的 4.8 倍,单请求均值从 0.290 ms 上升至 0.481 ms。表明只读请求有并发收益,也存在共享资源、调度或协议开销。本次没有混合读写或并发写压测,不据此推断写扩展能力;当前每库写独占的限制仍在。
## 使用范围
这是单机回环、主要为热缓存、管理员身份的单次基准,不是生产容量承诺。未覆盖远程网络、鉴权角色开销、崩溃恢复耗时、并发写冲突或大型宽表。七次读采样和单次写测量不足以建立统计置信区间,后续优化应使用相同参数、多次独立运行和分阶段 profile 比较。
## 2026-10-05 事务差分优化复测
使用相同机器、优化构建及 10,000 用户、50,000 订单规模运行完整基准,所有场景及结果校验通过(`BENCHMARK_OK`)。日志为 `logs/repair-txn-performance.log`。
COMMIT 跳过未修改表,以 IntMap / IntSet 查询整数键差分,冲突检查与操作生成共享差分;保持原行顺序及重复 id 的首行查找语义。
同日、相同机器和 10,000 用户 / 50,000 订单规模的独立测量,结果校验通过。日志为本机 `logs/repair-txn-performance.log`;以下数据与上面的场景不是同一次采样。
| 指标 | 耗时(ms) |
| --- | ---: |
@@ -149,8 +119,19 @@ COMMIT 跳过未修改表,以 IntMap / IntSet 查询整数键差分,冲突
| 事务内 200 条 INSERT | 41.129 |
| COMMIT | 376.345 |
事务总耗时从首次的 67,549.539 ms 降至 687.355 ms,约快 98 倍,吞吐从 2.96 升至 290.97 行/秒;本次事务写入约比逐条持久化快 1.65 倍。两次独立运行未建立统计置信区间,倍数仅描述这两次测量。
事务写入约 290.97 行/秒。BEGIN 与 COMMIT 合计约占总耗时的 94%;COMMIT 还包含差分、冲突检查和持久化,不能全部归因于快照。提交跳过未修改表,整数键差分使用 `IntMap` / `IntSet`,冲突检查与操作生成复用差分。
BEGIN 与 COMMIT 仍获取整库快照,合计约占事务总耗时的 94%;COMMIT 计时也包含差分、冲突检查和持久化,不能全部归因于快照。整库快照传输、修改表比较及刷盘仍需进一步分阶段测量。
## 瓶颈与使用范围
20 个不同绑定的相关 EXISTS 平均 2,135.218 ms(首次 2,061.770 ms),该瓶颈仍存在,本轮没有改变子查询执行方式。
| 观察 | 当前边界与测量方向 |
| --- | --- |
| 整库事务快照成本高 | BEGIN / COMMIT 仍读取整库,需分别测传输、修改表比较与刷盘 |
| 不同外层绑定重复扫描 | 20 个不同绑定的相关 EXISTS 在事务基准运行中均值为 2,135.218 ms;相同绑定缓存不等于半连接或通用物化 |
| 大结果与排序有全量成本 | 扫描包含解码、执行、TCP JSON;完整排序后 LIMIT,可分别评估 Top-K、分页和流式返回 |
| 批量提交摊薄固定开销 | 场景的数据库大小和已打开表数量不同,不能当作严格单变量对照;刷盘优化仍须验证恢复 |
| 读并发低于线性扩展 | 8 客户端吞吐约为 1 客户端的 4.8 倍;未测混合读写或并发写 |
> [!IMPORTANT]
> 这是单机回环、主要为热缓存、管理员身份的基准样本,不是生产容量承诺。未测远程网络、权限目录规模、冷缓存、内存峰值、崩溃恢复耗时或并发写冲突;七次读采样和单次写测量不足以建立统计置信区间。
后续比较应保持机器、数据分布和构建参数一致,多次独立运行,并记录各阶段时间与分配量。