首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >TriviumDB 在非向量场景下的稳定性实测

TriviumDB 在非向量场景下的稳定性实测

原创
作者头像
晨星成焰
发布2026-08-30 04:06:29
发布2026-08-30 04:06:29
40
举报
文章被收录于专栏:AI开发相关AI开发相关

引言

前两篇我们一直在盯它的向量检索。但"三位一体"的另外两条路——文档查询和图遍历——到底能不能真的用起来?我用 1000 多个测试、65 项故障注入、5 分钟长稳和 100 万节点规模跑了一遍,并对图遍历的结论做了 5 种图型的交叉验证。

本文基于 TriviumDB v0.8.2,Windows 11 / x86_64 / Rust stable,全部数据都是本地实跑出来的。


一、这次测什么

之前对TDB的探索聊的都是向量检索,缺少非向量场景下的稳定性实测

这次我们只测非向量库的稳定性

用法

对应能力

本文关注点

文档数据库

TQL FIND,类 MongoDB 过滤语法

过滤语义是否正确、能撑到什么规模

图数据库

TQL MATCH,类 Cypher 模式匹配

多跳遍历、可变长路径、环检测

事务型嵌入式库

TQL DML + 事务 WAL

写入原子性、崩溃后数据是否还在

嵌入式基础设施

WAL / compaction / 只读代际

断电、位翻转、扇区撕裂之后能不能活

如果你的场景根本没有向量,纯用它当文档库或图库,会有问题吗?


二、当文档库用

TriviumDB 的 FIND 语法是 MongoDB 风格,支持等值、比较、$in/$nin、$exists/$size/$all/$type、$and/$or 组合、排序分页,还能对属性建二级索引。

通过构造 10k / 100k / 1M 三档节点,每档跑三类典型查询(单条件等值、等值+范围组合、$or 逻辑组合),每类重复采样并统计尾延迟来测试。

v0.8.2 实测数据

规模

查询

avg

p95

p99

QPS

10k

FIND {type: "person"}

0.091 ms

0.104 ms

0.125 ms

~10,988

10k

FIND {region: "cn", age: {$gte: 30}}

0.121 ms

0.129 ms

0.140 ms

~8,280

10k

FIND {$or: [{type: "event"}, {age: {$lt: 10}}]}

0.082 ms

0.084 ms

0.114 ms

~12,213

100k

FIND {type: "person"}

0.275 ms

0.330 ms

0.359 ms

~3,639

100k

FIND {region: "cn", age: {$gte: 30}}

0.291 ms

0.278 ms

0.280 ms

~3,439

100k

FIND {$or: [...]}

0.252 ms

0.277 ms

0.294 ms

~3,971

1M

FIND {type: "person"}

7.51 ms

6.05 ms

6.05 ms

~133

1M

FIND {region: "cn", age: {$gte: 30}}

7.36 ms

5.97 ms

5.97 ms

~136

1M

FIND {$or: [...]}

5.88 ms

5.82 ms

5.82 ms

~170

全部带 LIMIT 100。测试代码在 tests/non_vector_stability.rs

几个观察:

延迟随规模近似线性增长。10k → 100k 涨约 3 倍,100k → 1M 涨约 26 倍。最后这一跳涨幅偏大,说明 1M 规模下全表扫描的成本开始主导。

尾延迟很稳。p99 与 avg 的比值在 10k 档是 1.1–1.4 倍,1M 档反而缩小到 0.8–1.0 倍。没有出现那种"平均 5ms 但偶尔飙到 50ms"的长尾。

8 维向量 + 1M 节点下,QPS 约 130–170。但要意识到这是无索引全表过滤。上文的 create_index() 是另一条路——上游 benches/bench_queries.rs 里专门有一组 "index vs no index" 的对比,10k 规模下建索引后 FIND 有量级差异。

结论:10 万节点以内当文档库用,单查询亚毫秒,完全够用。百万级也能跑,但如果查询模式固定,记得建索引。


三、当图库用

MATCH 走的是类 Cypher 语法,支持节点/边模式、多标签、可变长路径 *1..2、方向 INCOMING/BOTH,还有环检测和步数熔断。

先测试构造 root → mid*M → leaf*L 的星形扇出图,测 1 跳和 2 跳(两条查询都带 {type:"root"} 锚点):

规模

查询

avg

QPS

10k

MATCH (a {type:"root"})-[:NEXT]->(b)(1 跳)

0.745 ms

~1,342

10k

MATCH (a {type:"root"})-[:NEXT]->(b)-[:NEXT]->(c)(2 跳)

0.428 ms

~2,338

100k

1 跳

14.09 ms

~71

100k

2 跳

9.22 ms

~108

1M

1 跳

141.0 ms

~7

1M

2 跳

140.5 ms

~7

用 5 种图型交叉验证

不过上面这张表全部来自同一种图型(星形),不足以支撑普适结论——星形的扇出极度不均,而真实业务的图拓扑千差万别。

所以我补了对照:统一 5 万节点、统一只有 1 个锚点,只改拓扑结构。

图型

平均出度

锚点扫描

1 跳

2 跳

3 跳

星形

248.75

4.27 ms

6.00 ms

4.36 ms

6.30 ms(0 行)

链式

1.00

4.32 ms

4.30 ms

4.43 ms

3.78 ms

随机图 G(n,k=4)

4.00

4.13 ms

4.47 ms

4.05 ms

4.51 ms

无标度 BA

4.00

4.57 ms

4.13 ms

4.64 ms

4.05 ms

二维网格

1.99

4.72 ms

4.44 ms

4.22 ms

4.68 ms

除星形外,四种拓扑完全不同的图(出度 1 / 4 / 4 / 2),1、2、3 跳的耗时都几乎等于锚点扫描本身的耗时——跳数增加带来的成本被锚点扫描完全盖住了。

星形是明确的边界情况:它的平均出度 248,2 跳就要访问海量边,所以波动明显;而且 3 跳返回 0 行(图上只有两层),却花了 6.30 ms——无结果的深遍历要穷尽整个搜索空间,反而更贵

这也顺带解释了开头那张表里"2 跳比 1 跳快"的现象:星形图里 1 跳返回的是高扇出的 mid(每个 1000 条出边),2 跳返回的是零出边的 leaf,成本差异来自访问到的边数,不是跳数本身。换到上面其他四种拓扑,这个差异就消失了。

跳数之外,还有五个维度

只测 1 跳和 2 跳显然不够——图查询的成本受很多因素影响。所以我又补测了五个维度(随机图 5 万节点、单锚点、LIMIT 100):

维度

对比项

结果

锚点数量

1 / 10 / 100 / 1000 个

1 跳耗时 3.68 → 3.82 ms,几乎不变

遍历方向

出边 -> vs 入边 <-

3.55 vs 3.56 ms,无差异

遍历深度

1 / 2 / 3 / 5 跳,以及可变长 *1..8

3.56 → 3.75 ms,只增加 5%

图中是否有环

双向边构成的环形图

各跳耗时正常,环路检测没有引发异常

边标签

指定 [:NEXT] / 通配 [] / 多标签 [:NEXT|OTHER]

3.58 / 3.58 / 3.64 ms,无差异

几个值得单独说的点:

锚点数量从 1 涨到 1000,耗时几乎不变。因为锚点定位是全表扫描一遍,成本跟"扫出多少条"无关,只跟总节点数有关。多出来的锚点只增加展开次数,而 LIMIT 100 把展开成本封顶了。

深度到 5 跳、可变长到 8 跳,也只涨了 5%。 这比前面 1–3 跳的结论更有说服力:访问面从几条边扩到几百条边,成本仍被锚点扫描盖住。

唯一明显影响成本的是结果行数。 锚点 FIND 返回 1 行时 3.70 ms,返回 1000 行时涨到 4.72 ms——多出来的约 1 ms 就是结果物化的成本,跟遍历无关。

顺带一条能力边界MATCH 目前只支持显式有向模式((a)-[:X]->(b)(a)<-[:X]-(b)),OUTGOING / INCOMING / BOTH 这三个关键字是 SEARCH ... EXPAND 的能力,MATCH 侧在官方文档里标注为「后续扩展」。要查入边,得把箭头反过来写。

真正站得住的结论

MATCH 成本 = 锚点属性全表扫描(主导) + 实际访问到的边数 × 单位成本 + 结果物化。跳数本身不直接计费,但跳数增加通常会扩大访问面。

这就是为什么链式、网格、随机图里看不出跳数差异(每跳只访问个位到几十条边),而星形里 2 跳就开始吃力(要访问几万条边)。

锚点定位在没有索引时是全表扫描——1M 节点就是 100 万次 payload 匹配,这是 7 QPS 的根源。

优化方向也因此非常明确:给锚点属性建索引。上游的 create_index() / find_by_property_index() 就是干这个的,benches/bench_queries.rs 里有专门的"索引 vs 无索引"对比组。如果你的图查询模式固定(比如总是从某类节点出发),建索引的收益是量级级别的。

反过来说,如果需要"从任意节点出发做多跳扩散"、节点量又在百万级,当前版本要谨慎评估 —— 但瓶颈是锚点扫描,不是扩散本身。另外要留意高扇出节点:星形图那 6 ms 的教训说明,扇出大的节点会显著放大遍历成本。


四、当事务型嵌入式库用:崩溃之后数据还在吗

TriviumDB 自带的故障注入测试套件相当完整,用一个统一脚本就能跑完 9 个子套件:

结果:65 passed / 0 failed / 0 ignored,输出 FAULT_INJECTION_SUITE_OK

子套件

测试数

模拟的故障

recovery

17

各种崩溃后的恢复路径

io_fault

13

磁盘 IO 错误、文件锁、权限

hw_crash

11

进程硬崩溃(非优雅退出)

hw_intrusion

7

数据被外部篡改/入侵

wal_midwrite

6

WAL 写到一半断电

emi_bitflip

4

电磁干扰导致比特翻转

power_cycle

4

逐字节断电模拟

oom_intercept

2

内存分配失败

sector_tearing

1

扇区撕裂(部分写入)

这几个套件的设计质量让我有点意外。wal_midwrite 不是简单地在"写之前"或"写之后"中断,而是在 WAL 记录的任意字节位置切断,验证恢复时能否正确识别不完整记录。sector_tearing 模拟的是磁盘只写了一半扇区这种物理层故障。emi_bitflip 直接翻转文件中的比特位,验证 CRC 能否检出。

65 项全通过->在写入过程的任何一个时点断电,重启后数据库要么完整恢复到某个一致状态,要么明确报错——不会出现"看起来打开了,但数据是半新半旧"的静默损坏。

对嵌入式场景来说,这个保证了稳定性


五、长跑 5 分钟:内存和文件会不会失控

嵌入式库经常要跑在长时间不重启的进程里,内存泄漏和文件膨胀是两个慢性病。

结果:2 passed / 0 failed,耗时 302.91 秒。

测试

内容

结果

SOAK_01

连续混合写删查 4536 轮

RSS 4 MB → peak 9 MB → final 9 MB

SOAK_02

500 轮 flush / compact

文件 41 KB → 61 KB(1.49×)

判定阈值是 RSS 不超过初始值的 3 倍(12 MB),实际稳定在 9 MB。文件膨胀 1.49 倍,远低于 5 倍阈值。

需要注意口径:这是 8 维向量 + 小规模 payload 的场景,绝对内存占用很小。它证明的是"没有泄漏趋势",不是"大负载下内存占用低"。如果你的真实场景是 768 维向量 + 大 JSON,绝对数值会完全不同,但观察趋势的方法是一样的


六、绑定层的代价有多大

TriviumDB 的内核是 Rust 写的,对外提供三种用法,也就是三套绑定(binding)

用法

形式

说明

Rust

triviumdb::Database

直接调用,没有中间层

Python

import triviumdb

通过 PyO3 调用 Rust

Node.js

require("triviumdb")

通过 napi-rs 调用 Rust

后两种语言要调用 Rust 代码,得先跨过语言边界,这层就是 FFI(Foreign Function Interface,外部函数接口)。每次跨边界都要做参数转换(比如 Python 的 list 转成 Rust 的 Vec)、调用、再把结果转回来(Rust 结构转成 Python dict / JS 对象)。这部分成本是每次调用固定要付的,跟查询本身多重无关,习惯上叫它 FFI 税

我用同样的 100k / 1M 数据规模和同样的三类查询,在三套接口上都跑了一遍:

接口

100k avg

100k QPS

1M avg

1M QPS

Rust 核心

0.252–0.291 ms

3,439–3,971

5.88–7.51 ms

130–170

Python

0.316–0.343 ms

2,919–3,164

5.90–6.08 ms

165–170

Node.js

0.529–0.596 ms

1,679–1,890

6.82–7.69 ms

130–147

在 100k 规模,Python 比 Rust 慢约 20%,Node.js 慢约 100%。差距明显,因为查询本身只要 0.25 ms,FFI 开销占比很高。

但到了 1M 规模,查询本身涨到 6–7 ms,三者的差距几乎消失了

FFI 开销是固定的几微秒到几十微秒,查询越重,它被摊得越薄。如果你的查询都是毫秒级以上的,用 Python 绑定完全没问题;如果是大量微秒级的点查询,绑定层开销就需要考虑了。

七、结论

把这些数据放在一起,我的判断是:

适合用的场景

  • AI 应用里需要"顺带"做文档过滤和图遍历的部分。这是 TriviumDB 的舒适区:你已经用它存向量了,现在需要按元数据过滤、或者沿关系扩散,不需要再引入 MongoDB 和 Neo4j这些数据库
  • 10 万节点以内的嵌入式场景。FIND 查询亚毫秒,MATCH 两跳 10 ms 以内,崩溃恢复、断电、位翻转都有测试兜底。
  • 对数据安全性要求高于吞吐的场景
  • 百万级节点以内的重图遍历。1M 规模 7 QPS,可以做离线分析

一个实用建议:如果你的查询有固定的锚点模式(比如总是从某个类型的节点出发),先建索引。从 MATCH 的数据看,锚点全表扫描才是主要成本,建索引的收益是量级级别的。


  • 全部数据来自一台 Windows 11 机器(x86_64 / Rust stable / 16 逻辑处理器)。Linux 和 macOS 上没有跑过性能部分,文件系统和 mmap 行为的差异可能带来偏差。
  • 所有性能数字都是单次采集的绝对值,不同机器之间不可横向比较。要判断你自己的场景,最好在本机按本文的方法重跑一遍。

文中所有性能数字都是 v0.8.2 的单次采集绝对值,未做跨版本对比。不同机器之间不可横向比较。

测试环境:Windows 11 / x86_64 / Rust stable / 16 逻辑处理器。

前两篇:《三位一体的 AI 嵌入式数据库:TriviumDB 入门》、《TriviumDB 性能对比》。

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

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

目录
  • 引言
    • 一、这次测什么
    • 二、当文档库用
      • v0.8.2 实测数据
    • 三、当图库用
      • 用 5 种图型交叉验证
      • 跳数之外,还有五个维度
      • 真正站得住的结论
    • 四、当事务型嵌入式库用:崩溃之后数据还在吗
    • 五、长跑 5 分钟:内存和文件会不会失控
    • 六、绑定层的代价有多大
    • 七、结论
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档