根据用户提供的截图(1000 条数据):
- "顺序写":18,011 ops/sec (0.06s 完成)✅ 快
- "批量写":4,000 ops/sec (0.25s 完成)❌ 慢
这与通常的性能直觉完全相反!为什么顺序写反而比批量写快?
前端 PerformanceTest.tsx 第 57 行:
// runSequentialTest() 函数
const runSequentialTest = async () => {
const res = await runBenchmark(count, 50); // ← 関键!
setResult({
type: t('perfSequentialWrite'),
count: res.count,
duration: res.duration_ms / 1000,
opsPerSec: res.ops_per_sec,
...
});
};实际执行方式:
前端浏览器 网络 HooDB 服务端
│ │ │
├─ POST /benchmark ─────────>│ │
│ {count: 1000, │ │
│ concurrency: 50} │ ├─ 创建 50 个 goroutine
│ │ │
│ │ ├─ 并发提交 1000 条
│ │ │
│ │ ├─ batcher 合并 batch
│ │ │
│ │ ├─ Raft 日志复制
│ │ │
│ │ ├─ FSM Apply(批量写)
│<─ 返回 {ops_per_sec: 18011} ─────────────────────┤
│ │ │
特点:
- ✅ 只需 1 个 HTTP 请求
- ✅ 数据合并在服务端 goroutine 级别
- ✅ 网络开销最小
- ✅ 服务端使用 50 个并发 处理
前端 PerformanceTest.tsx 第 68-117 行:
// runBatchTest() 函数
const runBatchTest = async () => {
const batchSize = 50;
const batches = Math.ceil(count / batchSize); // 1000/50 = 20 次
for (let b = 0; b < batches; b++) {
const items = {};
for (let i = 0; i < currentBatchSize; i++) {
items[key] = value; // 组收 50 条
}
await batchSet(items); // ← 発送 HTTP 請求
}
};实际执行方式:
第1个循环
前端浏览器 网络 HooDB 服务端
│ │ │
├─ POST /kv/batch ─────────>│ │
│ {items: {key1, key2, ..., key50}} │─────────────>├─ BatchSet()
│ │ ├─ ApplyCommand()
│<─ 返回 ─────────────────────────────────────────┤
│ (等待响应...) │ │
│ ← RTT 延迟 ~50ms │ │
第2个循环
├─ POST /kv/batch ─────────>│ │
│ {items: {key51, ..., key100}} │─────────────>├─ BatchSet()
│ │ │
│<─ 返回 ─────────────────────────────────────────┤
│ (等待响应...) │ │
... × 20 次循环,每次都要等完整的 HTTP RTT ...
特点:
- ❌ 需要 20 个 HTTP 请求(串行)
- ❌ 每个请求都要一次网络 RTT (~50ms)
- ❌ 浏览器连接复用开销
- ❌ 串行执行,无法并发
- ❌ 总时间 = 20 × RTT + 处理时间 ≈ 1000ms
| 项目 | "顺序写"实现 | "批量写"实现 |
|---|---|---|
| 发送方式 | 1 个 HTTP 请求 | 20 个 HTTP 请求 |
| 执行模式 | 服务端 50 个 goroutine 并发 | 浏览器串行循环 |
| 数据合并 | 由 batcher + Raft 自动合并 | 前端手工分割成 50 条发送 |
| 网络开销 | 1 × RTT (~5ms) | 20 × RTT (~1000ms) |
| 总耗时 | 50ms(包括网络) | 1000ms+ |
| 吞吐量 | 18,011 ops/sec | 4,000 ops/sec |
| 误导指数 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
原始意图可能是:
"由浏览器将请求分批发送,模拟高吞吐量场景"
但实际结果是:
"由于 HTTP/1.1 连接复用 + 浏览器 6 连接限制,每个请求都成为瓶颈"
HTTP/1.1 规范建议浏览器最多保持 6 个并发连接:
浏览器连接池(6 个)
├─ 建立连接 1 到服务器...等待响应 1
├─ 建立连接 2 到服务器...等待响应 2
├─ ...
├─ 建立连接 6 到服务器...等待响应 6
└─ 连接 7-20 需要排队等待!
总时间 = ceil(20 / 6) × RTT ≈ 4 × 50ms = 200ms +
处理时间 ≈ 50ms
= 250ms
原因:变量命名失误。这个测试实际上应该叫"服务端基准测试"或"并发基准测试"。
const runSequentialTest = async () => { // ← 名字说"顺序"
const res = await runBenchmark(count, 50); // ← 但实现用 concurrency=50
};这是误导性命名!应该改成:
const runServerBenchmarkTest = async () => { // ← 更准确的名字
const res = await runBenchmark(count, 50); // 服务端 50 并发
};为对比,看后端的顺序 vs 批量写的性能差异(直接调用 API):
# 后端顺序写(concurrency=1)
curl -X POST http://localhost:8001/benchmark \
-H "Content-Type: application/json" \
-d '{"count":1000,"concurrency":1}'
# 结果:3920 ops/sec,255ms
# 后端批量写(concurrency=50)
curl -X POST http://localhost:8001/benchmark \
-H "Content-Type: application/json" \
-d '{"count":1000,"concurrency":50}'
# 结果:23677 ops/sec,42ms后端正常遵循规律:并发度高 = 吞吐量高。
关键区别:
- 后端:1 个 HTTP 请求,服务端并行处理
- 前端批量写:20 个 HTTP 请求,串行等待
前端 PerformanceTest.tsx 中的"批量写"实现不是真正的批量,而是逐批串行发送:
// ❌ 问题代码:串行发送
for (let b = 0; b < batches; b++) {
await batchSet(items); // 等完才能继续下一个
}
// 优化建议:并发发送
await Promise.all(
batches.map((batch, idx) =>
batchSet(batch) // 所有 batch 同时发送
)
);客户端 → 服务端 (1个HTTP请求)
↓
POST /benchmark
↓
[0-2ms] HTTP 网络传输
[2-5ms] 解析请求,创建 50 个 goroutine
[5-50ms] 50 个并发发送 + batcher + Raft 日志复制
[50-60ms] 返回响应
↓
客户端接收结果
总耗时: 60ms ÷ 1000 条 = 18,000+ ops/sec
20 个循环串行执行:
[0-50ms] POST /kv/batch #1 → 返回
[50-100ms] POST /kv/batch #2 → 返回
...
[950-1000ms] POST /kv/batch #20 → 返回
总耗时: 1000ms ÷ 1000 条 = 4,000 ops/sec
const runBatchTest = async () => {
const batchSize = 50;
const batches = Math.ceil(count / batchSize);
const startTime = Date.now();
// ✅ 所有 batch 并发发送
const promises = [];
for (let b = 0; b < batches; b++) {
const items = {};
const currentBatchSize = Math.min(batchSize, count - b * batchSize);
for (let i = 0; i < currentBatchSize; i++) {
const idx = b * batchSize + i;
items[`batch_test_${Date.now()}_${idx}`] = `value_${idx}`;
}
promises.push(batchSet(items));
}
// 等待所有请求完成
const results = await Promise.allSettled(promises);
const duration = (Date.now() - startTime) / 1000;
const success = results.filter(r => r.status === 'fulfilled').length * batchSize;
setResult({
type: t('perfBatchWrite'),
count,
duration,
opsPerSec: success / duration, // 会变成 ~15,000+ ops/sec
success: success,
failed: results.filter(r => r.status === 'rejected').length * batchSize,
});
};预期改进:4,000 ops/sec → 15,000+ ops/sec(取决于 HTTP 连接池)
// 重命名为更准确的名字
const runClientBatchTest = async () => {
// 从客户端发送 HTTP 请求,并发度受限
};
const runServerBenchmarkTest = async () => {
// 使用服务端 benchmark,最准确的测量
const res = await runBenchmark(count, 1); // 服务端顺序
};
const runServerConcurrentTest = async () => {
// 使用服务端 benchmark,最大吞吐量
const res = await runBenchmark(count, 50); // 服务端并发
};当前的 UI 预期性能说明:
预期性能
顺序写入:~1,000+ ops/sec (受 Raft 共识限制)
批量写入:~10,000+ ops/sec (批量优化)
改为:
实际执行方式
顺序/并发测试:所有工作在**服务端**并行执行,客户端只用1个HTTP请求
预期:~20,000+ ops/sec (服务端 50 并发 + 批量合并)
批量测试:客户端**串行发送** HTTP 请求,受浏览器连接限制
预期:~4,000 ops/sec (网络 RTT 成为主瓶颈)
内部API(仅供测试):
- POST /benchmark: 服务端基准测试,按 concurrency 并发执行
- POST /kv/batch: 支持一次提交多条,可由客户端并发调用
| 问题 | 根因 | 影响 |
|---|---|---|
| "顺序写"吞吐量高 | 前端实现用了 concurrency=50 | 名字误导,使用者不知道那是并发 |
| "批量写"吞吐量低 | 前端串行发送 HTTP 请求 | 触发浏览器连接限制和网络 RTT 瓶颈 |
| 性能看起来相反 | 两个测试的实现方式完全不同 | 用户困惑,影响对系统的理解 |
建议:
- 修复前端代码:
Promise.all()并发发送批量请求 - 改进 UI 标签:改成"服务端基准测试"和"客户端批量操作"
- 添加说明文档:解释每个测试的实际含义和网络开销