首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >KWDB 3.1.0 单节点读写性能实测:不跑 benchmark,只用脚本说话

KWDB 3.1.0 单节点读写性能实测:不跑 benchmark,只用脚本说话

原创
作者头像
一只牛博
发布2026-03-23 16:53:52
发布2026-03-23 16:53:52
2220
举报
文章被收录于专栏:数据库数据库

摘要:本文记录了在 CentOS 7.6 / 4核 / 8GB 内存的 Docker 单节点环境中,对 KWDB 3.1.0 进行的一次读写性能测试。没有使用任何第三方 benchmark 工具,全程用 Python + Shell 脚本模拟批量写入,用 KWDB 自带的查询计时测量查询响应。测试内容包括单条写入延迟、不同批次大小的写入吞吐量、四种典型查询的响应时间,以及连续30轮写入的稳定性。文章也记录了测试过程中遇到的时间戳格式校验问题,以真实数据说话。


为什么不用 benchmark 工具

做 KWDB 的性能测试时,首先碰到一个选择:用 sysbenchFIO 还是 KWDB 内置的 workload 工具?

考虑了一下,决定都不用。benchmark 工具的跑分数字太"漂亮",参数调一调就能高出一大截,对实际工程参考意义有限。我更想知道的是:在接近真实业务场景的情况下,KWDB 能跑到什么水平。

所以这次的方式是:Python 脚本生成传感器测试数据,不同批次大小循环写入,每种重复5次取平均,然后用 SQL 客户端直接测查询延迟。这是 KWDB 在正常使用条件下的表现,不经过任何调优。

测试环境:

  • 操作系统:CentOS 7.6,内核 3.10
  • CPU:4核,内存:8GB
  • KWDB:3.1.0,Docker 单节点,无副本
  • 数据目录:宿主机 /data/kwdb 挂载,宿主机系统盘(未单独挂SSD)

建立测试数据库

重新建一套独立的测试数据库,不复用其他场景的库。

代码语言:sql
复制
CREATE TS DATABASE perf_ts;
CREATE DATABASE perf_mgmt;

两个库分别耗时:

  • CREATE TS DATABASE perf_ts:<font color="green">6.044ms</font>
  • CREATE DATABASE perf_mgmt:<font color="green">5.134ms</font>

然后在 perf_ts 里建时序表 perf_metrics,6个指标列,2个 TAG 列:

代码语言:sql
复制
USE perf_ts;
CREATE TABLE perf_metrics (
  k_timestamp   TIMESTAMPTZ NOT NULL,
  temp_c        FLOAT,
  pressure_bar  FLOAT,
  vibration_g   FLOAT,
  current_a     FLOAT,
  voltage_v     FLOAT,
  power_kw      FLOAT
) TAGS (
  device_id   INT NOT NULL,
  line_id     INT NOT NULL
) PRIMARY TAGS(device_id)
ACTIVETIME 90 DAY;

时序表建表耗时 26.690ms,比关系表(6.874ms)慢了约4倍。ACTIVETIME 90 DAY 是 KWDB 时序表的特有语法,指定热存储时间窗口,超出窗口的数据会被自动转储——这也是 KWDB 区别于普通关系数据库的地方之一。

建库建表截图
建库建表截图
时序表建表截图
时序表建表截图
关系表建表截图
关系表建表截图

单条写入延迟:均值约 1.17ms

先测最基础的数字:不带批量,直接在 SQL 客户端里单条 INSERT 三次:

代码语言:sql
复制
INSERT INTO perf_metrics(k_timestamp,device_id,line_id,temp_c,pressure_bar,vibration_g,current_a,voltage_v,power_kw)
VALUES ('2026-05-01 00:00:01+08',1001,1,72.5,1.82,2.31,5.5,220.3,18.5);

INSERT INTO perf_metrics(k_timestamp,device_id,line_id,temp_c,pressure_bar,vibration_g,current_a,voltage_v,power_kw)
VALUES ('2026-05-01 00:00:02+08',1002,1,68.4,1.75,2.10,4.8,219.8,16.2);

INSERT INTO perf_metrics(k_timestamp,device_id,line_id,temp_c,pressure_bar,vibration_g,current_a,voltage_v,power_kw)
VALUES ('2026-05-01 00:00:03+08',1003,2,75.0,2.10,3.12,6.2,221.0,22.8);

三次实测结果:

代码语言:txt
复制
INSERT 1  Time: 1.252041ms
INSERT 1  Time: 1.156214ms
INSERT 1  Time: 1.089314ms

<font color="green">三次均值约 1.165ms</font>,计算方式:(1.252 + 1.156 + 1.089) / 3 = 1.165ms。这是 KWDB SQL 客户端在容器内直接执行的延迟,包含了 SQL 解析、写入时序引擎、WAL 落盘的全部链路。

单条写入截图
单条写入截图

批次大小对比:吞吐量差距 929 倍

单条 1.17ms 听起来快,但工业 IoT 场景下多台设备同时上报,怎么组织写入请求对吞吐量的影响非常大。

用 Python 脚本测了 4 种批次大小,每种重复 5 次取平均。关键代码逻辑:

代码语言:python
复制
for batch in [1, 10, 100, 1000]:
    times = []
    for _ in range(REPEAT):
        rows = [make_row(idx + i) for i in range(batch)]
        # 每次 subprocess 调用 docker exec 执行一次 INSERT
        times.append(run_insert(rows))
    avg = sum(times) / len(times)

结果(每种批次大小都重复5次,取均值):

批次大小

每次请求均耗时

吞吐量

单行耗时

1行/次

239.22ms

4 行/秒

239,222μs

10行/次

239.34ms

42 行/秒

23,934μs

100行/次

236.86ms

422 行/秒

2,369μs

1000行/次

269.05ms

<font color="green">3,717 行/秒</font>

269μs

这组数据揭示的规律: 1行和1000行的单次调用耗时相差不多,都在 240~270ms 左右。这段时间不是 KWDB 写入导致的,而是每次 docker exec 启动子进程 + 建立网络连接的固定开销(约 235ms),跟写多少行无关。

真正体现差距的是吞吐量:同样一次调用的固定成本,携带 1000 行数据,吞吐量从 4行/秒 提升到 3717行/秒,差距 <font color="red">929倍</font>。这是"每次连接成本固定"的底层逻辑决定的,在实际 IoT 接入层做数据聚合批量写入,比逐条写入效率上有质的差别。

批量对比截图
批量对比截图

写入约 10 万行数据(以及一次真实的踩坑)

脚本分批写入,每批 1000 行,计划写 100 批共 10 万行。结果遇到了一个实际问题。

脚本里时间戳的生成方式是按秒数递增:第0秒是 00:00:00,第86400秒就变成了 24:00:00——小时数超过了 23。KWDB 对时间戳格式有严格校验,这些批次被完整拒绝,没有静默写入或截断处理。

最终入库的有效数据:91,555 行(86个完整批次 × 1000行,加上前面批次对比测试写入的 5,555 行)。

这个行为值得记录一下:KWDB 选择了"拒绝整批"而不是"忽略无效行继续写"。对于时序数据来说,这是合理的做法——时间戳错乱会导致时间范围查询结果不可信。后来稳定性测试额外写入了 <font color="green">15,000 行</font>,测试结束时总量达 <font color="green">106,558 行</font>。


查询性能:四种典型场景

说明: KWDB 不需要手动开启计时,每条 SQL 执行后自动打印 Time: xxx ms,以下所有耗时均来自截图中的实测数据。


COUNT 总行数

代码语言:sql
复制
SELECT COUNT(*) AS total_rows FROM perf_metrics;
-- 结果:106558(1 row)
-- Time: 3.991612ms

<font color="green">3.99ms</font> 完成 106,558 行的全表计数。


Q1:时间范围过滤聚合

代码语言:sql
复制
SELECT COUNT(*), AVG(temp_c), MAX(temp_c), MIN(temp_c)
FROM perf_metrics
WHERE k_timestamp > '2026-03-01 22:00:00+08';
代码语言:txt
复制
count | avg                | max | min
------+--------------------+-----+-----
12354 | 74.4880200744698   |  89 |  60
(1 row)
Time: 7.089083ms

从 106K 行里按时间范围筛出 12,354 行并同时计算 AVG/MAX/MIN,耗时 <font color="green">7.089ms</font>。时序表的 k_timestamp 作为主键,时间范围过滤直接走主键定位,不是全扫。


Q2:全表 GROUP BY 聚合

代码语言:sql
复制
SELECT device_id, COUNT(*) AS data_points, AVG(temp_c), MAX(pressure_bar)
FROM perf_metrics
GROUP BY device_id
ORDER BY device_id;
代码语言:txt
复制
device_id | data_points | avg_temp          | max_press
----------+-------------+-------------------+----------
     1001 |       18311 | 72.49822511058926 |       1.9
     1002 |       18311 | 73.49822511058926 |       1.9
     1003 |       18311 | 74.49822511058926 |       1.9
     1004 |       18311 | 75.49822511058926 |       1.9
     1005 |       18311 | 76.49822511058926 |       1.9
(5 rows)
Time: 4.842201ms

5台设备,各 18,311 条,全表聚合耗时 <font color="green" size="4">4.842ms</font>。

18311 × 5 = 91,555,正好是稳定性测试开始前的总行数,证明数据分布完全均匀,没有倾斜。


Q3:多条件复合过滤

代码语言:sql
复制
SELECT k_timestamp, device_id, temp_c, vibration_g
FROM perf_metrics
WHERE k_timestamp BETWEEN '2026-03-01 10:00:00+08' AND '2026-03-01 12:00:00+08'
  AND temp_c > 86
ORDER BY temp_c DESC LIMIT 10;
代码语言:txt
复制
k_timestamp                | device_id | temp_c | vibration_g
---------------------------+-----------+--------+-------------
2026-03-01 02:04:59+00:00  |      1005 |     89 |         2.5
2026-03-01 02:04:29+00:00  |      1005 |     89 |         2.5
...(共10行,均为 device 1005)
Time: 5.824193ms

2小时时间窗口 + temp_c > 86 双条件过滤,<font color="green">5.824ms</font>。结果全部来自 device 1005,因为测试数据生成时 device_id 越大,温度基线越高(1001基线72°, 1005基线76°),超过86°的自然都在1005。


Q4:跨模 JOIN 聚合

代码语言:sql
复制
SELECT d.device_name, COUNT(*) AS total_points,
       ROUND(AVG(m.temp_c), 2) AS avg_temp,
       MAX(m.temp_c) AS peak_temp,
       ROUND(AVG(m.power_kw), 2) AS avg_power
FROM perf_ts.perf_metrics m
JOIN perf_mgmt.perf_device d ON m.device_id = d.device_id
GROUP BY d.device_name
ORDER BY avg_temp DESC;
代码语言:txt
复制
device_name  | total_points | avg_temp | peak_temp | avg_power
-------------+--------------+----------+-----------+----------
传感器-1005   |        18311 |     76.5 |        89 |     18.77
传感器-1004   |        18311 |     75.5 |        88 |     18.71
传感器-1003   |        18311 |     74.5 |        87 |     18.65
传感器-1002   |        18311 |     73.5 |        86 |     18.59
传感器-1001   |        18311 |     72.5 |        85 |     18.53
(5 rows)
Time: 41.811912ms

跨两个数据库(时序引擎的 perf_ts.perf_metrics + 关系引擎的 perf_mgmt.perf_device),JOIN 后全量聚合,<font color="orange">41.812ms</font>。

与 Q2 的纯时序聚合(4.84ms)相比,跨模 JOIN 慢了约 8.6 倍。这个额外开销来自跨引擎的数据交换,对于这次查询(10万行时序 JOIN 5行关系表)来说 41ms 仍在实用范围内。

查询测试截图
查询测试截图
跨模查询截图
跨模查询截图

稳定性测试:30轮连续写入

稳定性测试的关注点不是某一次的峰值,而是连续运行期间延迟是否会逐渐漂移。30轮,每轮写 500 行,每轮间隔 5 秒,总时间约4分钟:

代码语言:txt
复制
轮次 | 时间     | 本批耗时 | 累计行数
-----+----------+---------+---------
   1 | 16:20:12 |    309ms |     500
   2 | 16:20:21 |    267ms |    1000
   3 | 16:20:29 |    300ms |    1500
   4 | 16:20:38 |    261ms |    2000
   5 | 16:20:46 |    259ms |    2500
   6 | 16:20:55 |    271ms |    3000
   7 | 16:21:03 |    268ms |    3500
   8 | 16:21:11 |    247ms |    4000
   9 | 16:21:20 |    254ms |    4500
  10 | 16:21:28 |    246ms |    5000
  11 | 16:21:36 |    287ms |    5500
  12 | 16:21:44 |    263ms |    6000
  13 | 16:21:53 |    306ms |    6500
  14 | 16:22:01 |    252ms |    7000
  15 | 16:22:10 |    235ms |    7500
  16 | 16:22:18 |    254ms |    8000
  17 | 16:22:26 |    255ms |    8500
  18 | 16:22:35 |    285ms |    9000
  19 | 16:22:43 |    259ms |    9500
  20 | 16:22:51 |    268ms |   10000
  21 | 16:22:59 |    236ms |   10500
  22 | 16:23:08 |    273ms |   11000
  23 | 16:23:16 |    241ms |   11500
  24 | 16:23:25 |    261ms |   12000
  25 | 16:23:33 |    239ms |   12500
  26 | 16:23:41 |    236ms |   13000
  27 | 16:23:49 |    258ms |   13500
  28 | 16:23:58 |    260ms |   14000
  29 | 16:24:06 |    252ms |   14500
  30 | 16:24:14 |    263ms |   15000
=== 稳定性测试结束,共写入 15000 行 ===

30轮耗时范围:<font color="green">235ms ~ 309ms</font>,第1轮 309ms 是最大值,从第二轮起维持在 235~306ms 区间,没有随时间增长的趋势

从时间戳也能看出来:16:20:12 开始,16:24:14 结束,每轮约 9 秒(5秒等待 + 4分钟写入),节奏均匀。对于通过 shell 脚本 + docker exec 方式调用的测试,这个抖动是正常水平。

稳定性测试截图
稳定性测试截图

资源消耗

测试后查了一下容器资源占用(测试完毕、无额外负载状态下):

代码语言:txt
复制
NAME   CPU%    MEM USAGE / LIMIT     MEM%
kwdb   1.65%   634.6MiB / 7.64GiB   8.11%

<font color="green">CPU 1.65%,内存 634.6MiB(约8.11%)</font>。

这是静止状态的数字(测试已结束,没有并发查询),不代表峰值。但单节点跑完约10万行写入测试之后,内存只用了 634MB,对于一台 8GB 内存的机器来说,还有大量余量,基本不影响同台宿主机上的其他服务。

资源占用截图
资源占用截图

数据连续性验证

最后跑了一次按小时分桶的连续性检查,看数据有没有缺行或重复:

代码语言:sql
复制
SELECT DATE_TRUNC('hour', k_timestamp) AS hour_bucket, COUNT(*) AS rows_in_hour
FROM perf_metrics
GROUP BY hour_bucket ORDER BY hour_bucket LIMIT 10;
代码语言:txt
复制
hour_bucket              | rows_in_hour
-------------------------+-------------
2026-02-28 16:00:00+00:00 |         3600
2026-02-28 17:00:00+00:00 |         3600
2026-02-28 18:00:00+00:00 |         3600
...(后续各小时均为3600)
(10 rows)
Time: 51.552382ms

每个小时桶正好 3600 行(5台设备轮流写入,每秒1行,一小时写3600秒 = 3600行),分布完全均匀,没有缺失。这条查询耗时 <font color="orange">51.55ms</font>,因为 DATE_TRUNC 要对每一行时间戳做函数运算,比直接聚合慢,这是预期内的行为。

最终验证截图
最终验证截图

测试结果汇总

测试项目

实测结果

单条写入延迟(SQL直连)

<font color="green">均值约 1.165ms</font>(三次实测均值)

批次写入吞吐(1行/次)

4 行/秒

批次写入吞吐(1000行/次)

<font color="green">3,717 行/秒</font>(提升 929×)

COUNT 全表 106,558 行

<font color="green">3.991ms</font>

时间范围过滤+聚合

<font color="green">7.089ms</font>(过滤出12,354行)

全量 GROUP BY 聚合(10万行)

<font color="green">4.842ms</font>

多条件复合过滤

<font color="green">5.824ms</font>

跨模 JOIN 全量聚合

<font color="orange">41.812ms</font>

稳定性(30轮每轮500行)

<font color="green">235~309ms</font>,无漂移趋势

资源占用(测试后空闲)

<font color="green">634.6MiB 内存,1.65% CPU</font>


几点实际感受

关于批次写入: 批次大小从1增到1000,吞吐量差距接近1000倍,但单次调用耗时几乎不变。原因是外部调用链路(进程启动 + 网络连接)是固定开销,KWDB 的实际写入时间只是其中一部分。实际应用中客户端必须做连接复用和批量攒发,单条写入的模式在高频场景下会把大部分时间耗在连接上而不是 DB 操作上。

关于聚合性能: <font color="green">4.842ms 完成 10 万行 GROUP BY</font> 是这次测试最直观的数字。这背后是时序列式存储的基本特性——同一列的数据在存储上相邻,聚合时不需要读取整行,只扫目标列。这种存储结构特别适合传感器类数据:写入频繁、每次查询只关心少数几个指标字段的聚合。

关于时间戳校验: KWDB 拒绝了小时数超出 0~23 范围的时间戳,不做降级处理。导致原定10万行最终只入库 91,555 行。这对我们来说是一次踩坑,但从数据库的角度来说,强校验比静默写入更安全——时序数据的时间线一旦错乱,后续所有基于时间的查询结果都会受影响。

关于跨模查询: Q4 的 41ms 比纯时序聚合慢8.6倍,但考虑到这是跨两套存储引擎的 JOIN,41ms 在单节点无调优的条件下是可以接受的查询延迟。如果业务上需要频繁做这类关联查询,做好查询计划分析和必要的缓存策略会更有帮助。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么不用 benchmark 工具
  • 建立测试数据库
  • 单条写入延迟:均值约 1.17ms
  • 批次大小对比:吞吐量差距 929 倍
  • 写入约 10 万行数据(以及一次真实的踩坑)
  • 查询性能:四种典型场景
    • COUNT 总行数
    • Q1:时间范围过滤聚合
    • Q2:全表 GROUP BY 聚合
    • Q3:多条件复合过滤
    • Q4:跨模 JOIN 聚合
  • 稳定性测试:30轮连续写入
  • 资源消耗
  • 数据连续性验证
  • 测试结果汇总
  • 几点实际感受
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档