参考
性能
体积、内存、CPU、响应速度的实测数字。
实测数字,不是估算。测试机是 3 核 3.8 GiB 的 Xeon E5-2699 v4,Debian 12,
release 构建(LTO + opt-level=z + strip)。负载来自一个模拟 agent 程序。
装机体积
| 可执行文件 | 5.4 MiB |
| 冷启动到能响应请求 | 63 ms |
| 30 天历史数据(7 个节点) | 15.7 MiB |
| 源码行数 | 3,132 |
一个二进制加一个 .db 文件就是全部:没有配置文件,没有额外目录。
内存
| 场景 | RSS |
|---|---|
| 空转 | 6.1 MiB |
| 200 个节点在线,每 2 秒上报一次 | 8.4 MiB |
| 每秒 1000 次上报 | 8.6 MiB |
| 每秒 1000 次上报 + 8 个人同时刷状态页 | 18.2 MiB |
每秒 1000 次上报大约等于 2000 个节点按默认的 2 秒间隔在报。负载上去基本不涨—— Rust 没有 GC,也没有语言运行时。
密码登录是例外
上面量的是 agent 负载,不含面板的密码登录。密码校验走 argon2,一次要 19 MiB, 而 glibc 不把这一块还给操作系统——它留在那个线程的分配器里。
所以真实部署的常驻内存是「有没有人用密码登录过」的函数,不是负载的函数:
| RSS | |
|---|---|
| 从没登录过 | 5.9 MiB |
| 串行密码登录若干次后(稳态封顶) | ~103 MiB |
| 生产实例(用 GitHub SSO,跑了 1 天 16 小时) | 12 MiB |
SSO 不走 argon2,所以生产那台是 12 MiB。并发登录曾能把内存抬到 570 MiB,
现在被并发闸门压回 104–143 MiB,都在单元文件给的 MemoryMax=256M 之内。
CPU
干同样的活烧掉多少 CPU 秒:
| 场景 | |
|---|---|
| 200 节点每 2 秒上报,跑 90 秒 | 2.3 秒 |
| 每秒 1000 次上报,跑 60 秒 | 16.1 秒 |
| 上报 + 刷状态页混合,跑 90 秒 | 33.0 秒 |
换算一下:200 个节点在线时,占一颗核的 2.5%。
状态页响应速度
在每秒 1000 次上报的同时,8 个并发不停地刷状态页:
| 每秒处理的请求数 | 723 |
| 中位延迟 | 9.4 ms |
| p99 延迟 | 33 ms |
723 这个数打到了压测客户端自己的上限,真实上限更高。
这些数字是调出来的
光换语言只拿得到体积和内存的优势。同样的混合负载,调优前后:
| 调优前 | 调优后 | ||
|---|---|---|---|
| 内存 | 43.3 MiB | 18.2 MiB | 降 58% |
| CPU(90 秒) | 99.3 秒 | 33.0 秒 | 降 67% |
| 状态页每秒请求数 | 26 | 723 | 高 28 倍 |
| p99 延迟 | 842 ms | 33 ms | 快 26 倍 |
改动只有四处,都很小:
- WebSocket 读缓冲 128 KiB → 4 KiB。 库的默认值是按大文件传输定的,一个上报包只有几百字节。 200 个连接就是 25 MiB 白占的内存
- 节点列表不再逐个查流量。 原来渲染 200 个节点要查 200 次数据库,现在一次查完
- 状态页的 HTTP 接口复用已有的 2 秒快照缓存。 这个缓存本来就在, 但只有 WebSocket 那条路走它
- 调了三个 SQLite 参数:8 MiB page cache、WAL 检查点阈值和大小上限
会随时间变慢的那一类
上面调的是并发下的常数开销。另一类更难发现——今天量不出来、每天都更慢一点。
探测记录表的主键顺序原来把 task_id 卡在节点和时间戳中间,于是「某节点某时间窗」的查询
只能定位到节点,然后把这个节点保留期内的全部记录扫一遍。
按默认 30 天保留期造 120 万行,同一个「1 小时延迟图」请求:
| 主键顺序 | 单次耗时 |
|---|---|
(node_id, task_id, ts) | 41.91 ms |
(node_id, ts, task_id) | 0.82 ms |
线上那天只有 4 万行、1.4 ms,肉眼看不出任何问题——这正是它值得单独记一笔的原因。
再下一轮拆开看,发现贵的是排序不是读盘。而主键读出来的行本来就是时间序, 所以聚合桶改成在内存里边读边折,同时只持有一个桶:
| 窗口 | 之前 | 之后 | |
|---|---|---|---|
hours=1 | 4.7 ms | 2.8 ms | 1.7× |
hours=24 | 61.7 ms | 16.5 ms | 3.7× |
hours=168 | 284.0 ms | 54.1 ms | 5.2× |
这条路匿名可达,而且代价落在 agent 上报用的那条唯一的写连接上—— 所以它同时是一个性能问题和一个安全问题。
量过但没做的
temp_store = MEMORY:实测更慢- 把清理的两条全表删除换成按节点定位:27 ms → 3 ms,但一小时才跑一次
- 给历史表砍掉不画的五列:省 585 KB,而库总共才 5.7 MB
都是量完判定边际收益不够的。 记在这里是为了下次不用再量一遍。