LatticeDB —— 图数据库界的 SQLite
文章摘要
LatticeDB 是一个用 Zig 写的、零依赖、MIT 协议的嵌入式属性图数据库,把图遍历、向量相似度检索(HNSW)和 BM25 全文检索塞进同一个引擎、同一套查询语言、同一个文件里。作者 Jeff Hajewski(HN 用户 smiths1999)在 Show HN 的自述里说明了动机:「我们在工作中越来越多地用图数据库。我发现它们在本地用起来很痛苦,于是决定试着做个更好的。」
项目的自我定位是四个「一」:一个文件(整个数据库就是一个可移植的单文件,无服务端、零配置)、一套查询层(图遍历、HNSW 向量相似度、BM25 全文检索用同一种查询语言)、一份事件日志(持久化的具名流和内建的图变更订阅流与图写入共享同一条事务/WAL 路径)、本地优先(单机单写入进程,WAL 保证持久性)。README 的招牌示例是一条同时用上三种检索的 Cypher:从与查询向量距离小于阈值的 Chunk 出发,沿 PART_OF 走到 Document、再沿 AUTHORED_BY 走到 Person,同时用全文操作符筛选文档内容,最后按向量距离排序——向量距离操作符是 <=>,全文检索操作符是 @@。
查询语言是 Cypher 的一个子集:MATCH / WHERE / RETURN / CREATE / DELETE / SET / REMOVE、ORDER BY / LIMIT / SKIP / DETACH DELETE、MERGE / WITH / UNWIND 和聚合函数(count、sum、avg、min、max、collect)、变长路径(*1..3)、参数。功能面还包括:带标签和任意属性的节点与边、可持久化的显式等值索引、ACID 事务与崩溃恢复;向量侧支持可配置 M/ef 的 HNSW、内建哈希嵌入或对接 Ollama/OpenAI 的 HTTP 客户端、批量向量插入;全文侧是带分词与词干还原的 BM25 倒排索引,外加可配置 Levenshtein 距离的模糊搜索。运维面有:热备份(lattice backup,不需要关库)、持续备份(把变更推送到一个目录并支持时间点恢复)、把数据库序列化成字节流以及从字节流打开(方便把大量小库放进对象存储)、:memory: 内存库、在线空闲列表复用加 lattice compact 物理回收。绑定覆盖 CLI、Python、TypeScript/Node、Go、Java(JDK 21+,JNI)和一套干净的 C API。
性能数字是 README 的重头戏(Apple M1、单线程):节点查找 0.13 μs(790 万次/秒)、节点创建 0.65 μs、边遍历 9 μs、100 篇文档的全文检索 19 μs、100 万向量的 10-NN 检索 0.83 ms 且召回率 100%。向量部分给了完整的规模曲线(1K 到 1M,延迟从 65 μs 到 832 μs,召回 99–100%,内存 1 MB 到 1040 MB),并说明用了 HNSW 论文算法 4 的启发式邻居选择、连接页打包(内存约降为 1/4.5)、预归一化点积。与 SQLite 递归 CTE 的头对头对比是最扎眼的一组:10 万节点/50 万边规模下,1 跳快 36 倍、2 跳快 14 倍、3 跳快 6 倍、变长路径(1..5)快 75 倍;而深度受限遍历随深度拉开差距——深度 50 时 500 μs 对 1.4 s,即 2819 倍。作者在这里做了很克制的方法论声明:只有 SQLite 那几行是在同一台机器、同一套测试框架里头对头测出来的,Kuzu 和 Neo4j 的数字来自第三方博客、硬件和方法都不受他控制,「只能当量级参考,不能当基准测试结果」。
README 里被评论区反复称赞的是「什么时候该用别的东西」这一节,坦率程度罕见:需要多个应用同时写同一个库时用 Neo4j 或 PostgreSQL(LatticeDB 是单写入者模型,一个进程打开文件就占有它);数据本质上是表格型(销售记录、用户账号、时间序列)时用 SQLite 或 PostgreSQL 更简单也一样快,「图数据库的优势在于关系本身就是重点,而不是事后补的东西」;需要跨机器扩展时看 Neo4j 集群、Dgraph 或 Neptune——LatticeDB 能持续把单文件的变更推送到别处,「但那是备份而不是集群」;需要完整 Cypher 时用 Neo4j(OPTIONAL MATCH 和 CALL 过程尚未实现);需要成熟工具链和生态时也别选它,「新和精简对嵌入式是优点,对需要丰富运维生态的场景就是缺点」。
HN 评论精华
这条 Show HN 拿到 187 分。作者全程在场,回帖密度极高,而且在讨论进行中就现场改了两次代码,这是本帖最有意思的部分。
- vorpalhex 问「你测过多大规模」和「设计过程中最有意思的部分是什么」。作者答:性能基准做到 100 万节点,但日常主要在更小的规模上用,服务于另一个探索智能体记忆的项目。最有意思的部分他觉得难选——从学习角度看是开头那段,因为花了大量时间去了解别的数据库怎么工作,「哪怕是『把数据写到磁盘』这种相对简单的事,复杂度也远超我最初的预期」。然后他补了一段关于 LLM 的观察:「我大量使用了 LLM 来构建这个项目,另一个有意思的部分是看它们如何失败。我一直认为测试不能保证代码质量,跟 LLM 一起工作只是强化了这个看法。它们常常写出流于表面的测试。有时一整套测试都通过了,但我真去玩那个功能时它明显是坏的。LLM 确实让我能做出这种规模的东西,但离『帮我建个图数据库,做完通知我』还差得远。」
- tescreal 注意到贡献者列表里有 Claude,追问具体怎么用、用了多少。作者给了本帖最长也最值得读的一条回复,按阶段拆开讲:早期是大量来回讨论想法——现有方案是什么、怎么做才有意思、作为用户他想要哪些关键功能、怎么围绕这些组织成一个说得通的产品。构建初期是一块一块来:比如做文件系统交互时,他让 Claude 实现一个功能并用教科书的口吻解释它是如何工作的(「像是《LatticeDB 内部原理》里的一节」),然后他自己通读代码——「这是一种边学边建、同时进行的绝佳方式」。到后期功能复杂起来,他花更多时间在讨论、下指令和验证上,花更少时间去理解具体实现。他举了个具体例子:他已经很久没手写过 SIMD 代码了,可以试着评审 Claude 的输出,但他确信自己会漏掉微妙的 bug;于是他改变策略——假定代码是对的,把精力放在「我该如何验证」上。基准测试和实际把玩就是他的主要验证工具。「我跑了个基准,看到性能很好。然后我去翻测试向量,才发现它们是平凡的,这直接让基准结果完全作废。」于是回到白板,做一套新的基准集,看到结果不理想,再去评估实现哪里错了。「有时候一个功能拖上好几天,纯粹是因为这个迭代循环。」
- adsharma 在自己的 M4 mac mini(基础款)上跑了作者提供的
zig build sqlite-benchmark,贴出了一组和官方页面明显不同的数字:10 万节点规模下,1 跳 5.7 μs vs SQLite 16.1 μs(2.8 倍)、2 跳 30.1 vs 59.4(2.0 倍)、3 跳 171.1 vs 228.8(1.3 倍)、变长路径 82.2 μs vs 5.8 ms(70.4 倍)。作者的回应很坦诚:「lattice 的数字挺接近,但我的 sqlite 数字差得离谱。我换过电脑了,会重新测。」adsharma 还给了一个战略判断:既然磁盘上的数据结构与 SQLite 类似,他预计其他「在 SQLite 上做图」的项目一旦吸收 LatticeDB 的技巧就会形成竞争。 - 现场修 bug 的两条线索。itissid 问有没有类似 litestream 的东西可以给生产场景做备份(单台 web 服务器足够、能容忍几分钟停机)。作者:「基于你的评论,我正在收尾这个功能。文档更新一完成,今晚就推一个带热拷贝功能的新版本。」(moostee 一句:「冠军。」)vladigtr 问单文件上怎么处理并发写入者——「这正是嵌入式数据库通常会变棘手的地方,而图模型让加锁更有意思」。作者回:同进程内在 db 层强制;再仔细看了代码后发现跨进程没有任何保护机制,「今晚会加上文件锁机制。指出得好!」 稍后他又追加一条:「刚刚加上了文件锁,避免跨进程并发写入,已在最新版本里。」总体设计目标本来就是单写入者、多读取者。
- 对手图谱是本帖另一条主线。anentropic 指出 LadybugDB(自称「图数据库界的 DuckDB」)并找到了官方的 vs-kuzu 对比页。LadybugDB 维护者 adsharma 亲自来做了两点更正:LadybugDB 已经重做了 Kuzu 的 WAL 设计,基于 WAL 的复制不难做;19 ms 对 39 μs 这组数字正如作者自己说的,系统差异巨大、方法论未必可比。他们主要发力在查询计划优化而非微算子优化,月底的 0.20.x 会带来预备语句缓存查询计划与结果向量(省 malloc)和 filter 的 SIMD 优化。AgharaShyam 感慨 Kuzu 被苹果收购后停摆,Ladybug 看着有希望,但很怀念 Kuzu 团队的 YouTube 内容。lmeyerov 介绍了 gfql/PyGraphistry:CPU 列式向量化引擎,外加目前唯一的开源 GPU 引擎模式,不需要数据库或文件,是纯计算层。
- nrjames 直接问:为什么不 fork Kuzu 在上面做?作者两条理由:一是他想从零造一个自己的东西而不是给已成型的大项目做贡献,「对我来说这是更好的学习方式。要是最好的结局只是我做了个好玩的项目,那也完全没问题」;二是数据布局不同——lattice 是事务型、行式的,Kuzu 是列式的。lvca 来推 ArcadeDB(其 Graph OLAP 图查询比 Kuzu 更快),作者的回答是同一个逻辑:「为什么不用别的现成的?因为我想从零造。」问到与嵌入式 SurrealDB 的对比时他也很坦率:不具可比性——Surreal 支持嵌入模式但据他理解不是单文件,支持的数据模型数量庞大,背后还有公司,「Lattice 就我一个人」;要轻量简单选 Lattice,要企业托管和支持选 SurrealDB——「不过在你评论之前我都没听说过 SurrealDB,所以我说的话你打个折听」。
- srameshc 问 duckpgq 怎么样。作者给了一句很精炼的划分:如果你有一堆表,只是有时想把它们当图来遍历,用 duckpgq;如果图遍历本身就是主要的查询方式,用 latticedb。 他的动机用例就是智能体记忆——用 latticedb 做后端存储,「找到相关记忆就是在遍历图,有点像 graph RAG」。
- tomComb 问能不能把 RDF 数据(比如 Wikidata)映射过来,RDF 的谓词大概就对应边。作者说这正是他最近几周在想的方向,工作中也在往这边走。zvr 顺势要求支持 SPARQL,作者当天认真想了一下,回复说「不会像我最初以为的那么直接,会放在雷达上」。这条支线还引出两个好问题:ebolyen 问 SPARQL 相对于普通 SQL、或者相对于 Prolog/Datalog 的目标究竟是什么——语义网工具似乎从没迎来自己的爆发时刻,一直与关系型和逻辑编程的「主流」历史平行运行;LunaSea 问 LLM 出现之后,本体和三元组(RDF、OWL)相比于「直接喂给 LLM」还有没有用武之地。adsharma 补充:Wikidata 并不等于必须用 RDF/SPARQL,Cypher 也行。
- k9294 问了一个很实际的建模问题:他在做个人知识图谱服务(Notion 式的 JSON schema 自定义实体 + Obsidian 的 markdown 双链),如何在图数据库里建模层级访问控制——用户拿到一个文档的权限后,应自动拥有该工作区内所有子文档的权限。作者的方案是
hasAccessTo/accessibleBy加childOf边,权限检查就是往上遍历到第一个带权限的根节点;但他也点出缺点:这全是业务逻辑,光看数据库看不出规则是这样的。infogulch 补充了更成熟的路径:现代授权系统大多是基于图的,去看 ReBAC 和 ABAC 模型,以及 apache/casbin、authzed/spicedb 的实现——「这些 schema 的图定义往往简单得出奇,在 LatticeDB 里复现应该不难」。 - anigbrowl 的称赞点得很准:他一直在找兼具 Neo4j 灵活性与 SQLite 低认知负担的东西,「README 极好——除了清晰的解释和示例,那节『什么时候该用/什么时候不该用它』尤其令人佩服,很多有价值但不友好的工具恰恰缺了这个」。作者回应说他就是想让 README 成为那种扫一眼就能判断项目是什么、是不是你要找的东西,然后就结束的文档。
- 其他被顺手挂出来的同类项目:SparrowDB、marsdb、以及 Qbix 那套架在 SQLite/MySQL/Postgres 之上的图层。cjlm 说会把 LatticeDB 加进 gdb-engines.com。