Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
slater — 低内存图数据库,支持 Bolt+TLS、静态加密与向量,专为本地副本图(local replica graph)使用场景设计。 | Kitploit
工具/GitHubGitHub/hikari-systems/slater
身份验证与授权加密/解密工具网络安全云安全实用工具与框架数据库安全
GitHubhikari-systems/slater

slater

低内存图数据库,支持 Bolt+TLS、静态加密与向量,专为本地副本图(local replica graph)使用场景设计。

查看仓库
1002325天前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

Slater

CI Release

当前版本:v0.25.2 — 所有发布。

一句话概括: Slater 服务于无法放入内存的图——数亿个节点和数十亿条边仅占用几百 MB 内存——通过标准 Bolt 协议提供服务,因此任何 neo4j 驱动都能直接使用;磁盘原生的向量搜索紧邻图而生,并且它在不放弃这一切的同时接受实时、持久的写入。常驻内存由你选择的缓存预算决定,而不是由图的大小决定。


快捷入口

为什么存在 Slater读取与写入你会得到什么功能特性
使用 Docker 运行工作原理可写层存储后端(文件系统 / S3 / GCS)
挂载环境与配置ACL健康检查
实操示例开发性能许可证
Graphiti 记忆存储📖 完整手册

为什么存在 Slater

图数据库将数据存储为事物(节点)以及它们之间的关系(边),关系是一等公民。当你关注的是连接而非行时,这正是你想要的——“谁在这个账户三跳之内?”、“这次构建背后的完整依赖链是什么?”、“哪些账户共享设备、地址和银行卡?”——这些查询在 SQL 中会成为递归连接的泥潭,但在图中却自然呈现。

关于图数据库最常见的抱怨是:它们无法扩展到超出内存能容纳的规模。 其中许多(如 neo4j、Memgraph、FalkorDB 等)将整个图常驻内存:一个 40 GB 的图就需要 40 GB 内存——每个实例。想按区域、按租户或按 Pod 各放一个副本?账单成倍增加。而且一旦超过一定规模,它们根本无法加载:例如 9000 万节点 / 15 亿边的 Wikidata 图需要约 64–128 GiB 常驻内存,因此内存引擎根本无法打开它。

Slater 是对此的反驳。它不把图加载到内存,而是离线一次性编译:slater-build 将你的数据转换为内容寻址、不可变的磁盘映像,任意数量的 Slater 服务器随后通过 Bolt 提供该映像(因此你现有的 neo4j 驱动可以直接使用),按需分页加载块,并仅保留固定的缓存预算常驻内存。正是这样,同一个 9000 万节点的图只需几百 MB 内存即可服务——图的大小与内存账单解耦。一个 4 GB 的图和一个 400 GB 的图服务所需内存相同,因此你可以廉价地横向扩展无状态只读副本,让存储而不是堆来持有图。

这使它天然适合 RAG 背后的知识图谱、推荐与身份图、依赖图——任何庞大且相互关联、你想廉价而频繁查询的数据。磁盘原生的向量搜索紧邻图而生,因此同一引擎也可作为嵌入向量的检索层。

不过,一次性编译并不意味着冻结。该映像是一个基础,而不是最终状态:一个可选的写入层位于其上,因此在线图可以被修正和扩展,而无需重建任何东西。

读取与写入

核心是不可变的,但图不是。启用可写层(delta.enabled)后,你可以通过 Bolt 进行写入——修正一个属性、添加一个节点、撤回一条边——变更持久生效,无需重建映像。让读取端保持廉价的关键是写入的存放位置。

写入会累积在不可变核心之上的日志结构合并(LSM)层中:一个预写日志和一张内存表,溢出为不可变的增量段,并通过定期合并(consolidation) 折回成全新的核心。这带来以下好处:

  • 对未写入过的图进行读取的成本与此前完全相同。 空的增量是一个可预期的单一分支,而不是合并——无论可写层是否开启,读取路径都逐字节一致。
  • 写入的读取成本随增量的大小而非图的大小扩展。 全图答案——count(*)、标签与关系类型的边际统计——即使存在未完成的写入,也仍然是元数据读取:增量维护自己的计数器,因此对 9160 万节点核心且带有 50 万待处理写入执行 count(*),仍可在几十毫秒内返回结果,而无需触碰任何数据块。
  • 收到确认即表示持久化。 单个写入者清空队列,并且只有在覆盖该次写入的 fsync 完成之后才返回 SUCCESS。将写入分组可以降低成本——一次写入 UNWIND 每个批次只提交一次 fsync,而不是每行一次。
  • 两种方言中的业务键写入。 以节点标识属性为键的 MERGE / MATCH … SET / DELETE(以及 CREATE / REMOVE、分离删除、关系写入)——或等价的 ISO GQL 数据修改语句(INSERT / SET / REMOVE / ),它们降级到同一条路径。对节点和边进行修正、插入、upsert(插入或更新)和撤回,以你数据已有的方式来寻址。

在关闭该层(默认情况)时,Slater 只提供纯粹的不可变核心,并拒绝写入。完整模型请参阅 可写层。

关于名字。 Slater 得名于 Archer(一部很棒的美剧)中的 CIA 特工, 他坚持只用一个名字——“就叫我……Slater”——也是我在这部剧中最喜欢的 角色之一。参见 角色维基页面。

你会得到什么

  • 内存由你的缓存预算决定,而非图的大小——想扩展多少只读副本都可以;图永远不需要放入内存。
  • 即插即用的图替代——讲 Bolt 协议,因此任何标准 neo4j 驱动(JS、Python、Go……)都能原样工作。它使用 Cypher(外加一部分 ISO GQL,支持读写);无需学习新东西。
  • 实时、持久的写入——不可变核心之上可选启用的 LSM 层:对节点和边进行业务键 MERGE / SET / DELETE,组提交并由 fsync 确保持久化,通过合并折回成全新核心。读取无需为此付出代价。
  • 通过文件切换部署——离线构建新的内容哈希代次(generation),原子地切换 current 指针,服务器即会拾取。每个块都有校验和,因此半拷贝的映像会被拒绝而不是被服务。
  • 内置向量搜索——磁盘原生的近似最近邻(cosine、L2 或点积 KNN)紧邻你的图,适用于作为 RAG 流水线背后的检索层;嵌入向量可原地写入——添加或修改向量无需离线重建。
  • 设计上即安全——读与写授权相互独立,此外还有可选的静态加密、Bolt TLS、argon2id 哈希的 ACL,以及用于只读副本的只读容器根文件系统。配置主密钥后,磁盘映像既是加密的也是可认证(authenticated) 的——其清单携带带密钥的 MAC,因此对数据目录有写权限但没有密钥的攻击者无法伪造一个服务器会接受的清单。没有密钥时你仍然得到内容哈希,它能发现半拷贝或损坏的映像——但无法发现蓄意伪造的。哪种配置提供什么保障。

功能特性

工作区由两个二进制文件组成:

二进制角色
slater在线 Bolt 服务器(容器 ENTRYPOINT):提供读取,并在启用 delta.enabled 时提供单写入者持久写入路径。
slater-build离线编译器:将基础 Cypher 转储转换为不可变、内容哈希的代次目录。

Slater 将批量构建与服务分离:slater-build 在离线状态下完成繁重工作——摄取你的数据并将其编译为不可变代次——因此冷图永远不会在服务热路径上组装。在服务器内部,读取面回答广泛的 Cypher 子集——模式匹配、WITH/UNION/CALL {…} 子查询、70+ 标量与聚合函数、时间与地理空间值、图算法(algo.*)以及磁盘原生向量 KNN(db.idx.vector.queryNodes)——而可写层的增量覆盖层位于该读取面之下,为空时零成本,因此读取永远不会携带写入侧的机制。你可以通过两种方式更新图:通过 Bolt 实时写入(参见 可写层),或离线构建新代次并原子地切换 current 指针,运行中的服务器通过其代次守卫(generation guard)拾取该指针(参见 代次守卫)。

文档

完整的用户手册位于 docs/manual/——这是一份逐功能指南,针对每项能力解释它是什么、为什么存在以及如何使用,并带有可在随附示例图上运行的实操示例。凡超出本概述的内容,请从那里开始。

  • 刚接触? 快速开始 用五个步骤构建并服务一个图。
  • 编写查询? 查询、函数与表达式、过程与算法、向量搜索、写入数据。
  • 构建图? 构建图 和 构建 CLI 参考。
  • 运维 Slater? 部署、存储、配置参考、安全、性能调优。

将 Slater 用作 Graphiti 记忆存储

graphiti-slater 是一个适配器,让 Graphiti 将其时间知识图谱存储在 Slater 中,并附带一个可运行的 docker-example/——包括将其作为 MCP 服务器暴露给 Claude Code。其工作原理及运行方式请参阅该仓库。

使用 Docker 运行

Slater 设计为以 Docker 部署方式运行——这是预期的使用方式。预构建的多架构镜像(linux/amd64 + linux/arm64)发布到 Docker Hub:hikarisystems/slater,每次发布都会打上 :latest 和 :vX.Y.Z 标签:```sh docker pull hikarisystems/slater:latest

root@kitploit:~
一份仅使用 Docker 命令的用法、配置和操作指南位于 [`DOCKERHUB.md`](https://github.com/hikari-systems/slater/blob/main/DOCKERHUB.md)(并镜像到 Docker Hub 概览页)——**如果你要部署,请先阅读该指南。** 简而言之:```sh
# Build a graph generation with the offline writer:
docker run --rm -v slater-data:/data -v "$PWD/dumps:/dumps:ro" \
  --entrypoint /app/slater-build hikarisystems/slater:latest \
  --input /dumps/people.cypher --graph people --data-dir /data

# Serve it over Bolt on 7687 (read-only unless `delta.enabled`):
docker run -d --name slater -p 7687:7687 \
  -v slater-data:/data:ro -v "$PWD/acl.json:/config/acl.json:ro" \
  hikarisystems/slater:latest

要在本地构建镜像(例如用于开发):```sh

Build the image (both binaries).

docker compose build

Serve (expects generations under the slater-data volume / your /data mount).

docker compose up slater

Build a generation with the offline writer (profile build):

docker compose run --rm builder
--input /dumps/people.cypher --graph people --data-dir /data

root@kitploit:~
构建阶段为 rustls 的 `aws-lc-rs` 后端安装了 `cmake`、`clang` 和 `libclang-dev`;`git`(基础镜像中已包含)是 `hs-utils` 的 git+tag 依赖所必需的,`.cargo/config.toml` 通过 git CLI 获取该依赖。

以下各节介绍磁盘上的格式、配置、ACL 以及一个本地(非 Docker)的实际示例。

## 工作原理```
            slater-build                         slater (Bolt server)
   dump.cypher ──────────▶ /data/<graph>/<uuid>/ ──────────▶ neo4j driver
   (offline, atomic)        MANIFEST.json, *.blk,            (bolt / bolt+s)
                            range/*.isam, vector/*.{vamana,pq},
                            current → <uuid>
  • 一个世代是一个不可变目录:一个 MANIFEST.json(符号表、 索引描述符、可选的加密头),列式块文件 (node_props.blk、node_labels.blk、edge_props.blk、topology.csr.blk、 vectors.f32.blk)、范围索引(range/<name>.isam)、超阈值 ANN 索引(vector/<label>.<prop>.{vamana,pq}),以及一个 current 文本指针。
  • 每个块都经过 zstd 压缩并带有 BLAKE3 校验和;使用 --encrypt 时,每个 块还会使用 XChaCha20-Poly1305(静态 AEAD)进行额外密封。
  • 服务器通过对照清单重新哈希每个文件来打开一个世代, 因此,半拷贝/截断的镜像——一个撕裂的、复制到数据目录(可能是远程/网络存储)的 副本——会被拒绝而不是被提供。
  • 读取流经三个有界缓存池——一个解压块 LRU、一个 向量索引池(常驻 PQ 码 + 一个 Vamana 块 LRU)和一个结果 LRU—— 每个池都有自己的字节预算。每个池会衡量其持有的内容并逐出, 以保持在预算之下,因此 RSS 会跟随预算,误差仅在有限的每条目和分配器 开销范围内,而不是随图增长。

可写层

当 delta.enabled 时,不可变世代成为一个小型日志结构化合并树的 完全压缩底层("core"),实时写入在其之上 进行:``` write (Bolt) read (Bolt) │ │ ▼ ▼ ┌──────────────┐ flush ┌──────────────┐ ┌──────────────────────┐ │ WAL + active │ ───────▶ │ L0 delta │ │ a query pins one │ │ memtable │ │ segments │ │ (core, delta) view │ └──────────────┘ └──────┬───────┘ │ and reads the merge │ (fsync = ack) │ └──────────────────────┘ consolidation │ (folds core + delta → fresh core) ▼ ┌─────────────┐ │ new core │ (atomic current swap) └─────────────┘

root@kitploit:~
* **持久性下限 — WAL。** 每个变更都被序列化到每个图对应的单个写入器之后,追加到每个图的预写日志中,并在返回 Bolt `SUCCESS` 之前执行 `fsync` —— 因此 *已确认 ⇒ 已持久化*,撕裂的尾部会在重放时被丢弃。批量写入 `UNWIND` 会追加其行,并为整批提交**一次** `fsync`。WAL **仅位于本地磁盘**(不经过存储后端路由),这使 *写入* 节点成为有状态的:它需要在 `delta.walDir` 处有一个持久化本地卷。只读副本保持无状态。
* **Memtable → L0 → 压缩(consolidation)。** 写入在内存中的 memtable 内累积(由 `delta.memtableBytes` 限制);当它填满时会刷写到一个不可变的 L0 delta 段。**压缩** 通过将合并后的视图重新经 `slater-build` 序列化并原子地替换 `current`,将 `{core + delta}` 折叠成一个新的 core —— 与任何已发布的 generation 相同的内容哈希保护。可以手动通过 `CALL slater.consolidate()` 触发,或在达到 core 大小的 `delta.deltaCorePercent` 时自动触发(可选择限制到非高峰 `delta.consolidateWindow`),或让 `delta.deltaHardBytes` 节流作为失控增长的兜底。
* **叠加层位于读表面之下。** 执行器通过 `ReadView` 读取,该视图要么是裸 core(delta 始终为空),要么是合并的 `(core, delta)` 视图;引擎在其上进行单态化,因此空 delta 可编译为单一可预测分支,且只读路径字节级一致。全图计数器(`count(*)`、标签/关系类型的边际计数)由 delta 自身的实时计数器提供,因此即使有待处理写入,它们仍是元数据读取。
* **查询看到稳定的快照。** 它在整个生命周期内固定一个 `(core, delta)` 元组。没有多语句事务,也没有回滚 —— 写入是持久的、按业务键寻址的更正,而不是 OLTP 事务。

确切的写入语法和调节项见下面的 [配置](#environment--configuration) 表(`delta.*`)和 [实际示例](#worked-example)。

### 范围索引(ISAM)

范围索引(`range/<name>.isam`,每个被索引的 `(label, property)` 对应一个)允许 `MATCH (n:Label {prop: v})` 或 `WHERE n.prop <op> v` 解析到匹配的节点 ID,**而无需扫描该标签**。它是一种 **[ISAM](https://en.wikipedia.org/wiki/ISAM)**(索引顺序存取方法,Indexed Sequential Access Method)结构 —— 经典的 *静态、排序、块结构* 索引,这正是不变 generation 的理想形态:没有需要重新平衡的插入,因此 ISAM 的简洁性所获得的效果,B-tree 的变更机制只会使其复杂化。

* 条目 `(value, entity_id)` 按值排序,并像其他所有内容一样打包进 zstd 压缩的 256 KiB 块中。
* 一个较小的**常驻顶层**保存每个块的第一个键(稀疏索引)。查找时在内存中的顶层进行二分搜索,找到键可能所在的*唯一*块,读取并解压该块并扫描 —— 因此等值查找只需**一次块读取**,范围扫描则遍历其覆盖的连续块序列。(这就是为什么 `meshUi` 索引查找只需个位数毫秒,而对未索引属性做同样匹配则要扫描整个标签。)
* 规划器通过 `NodeScan::RangeEq` / `RangeRange` 选择该索引;未索引的谓词会回退到标签扫描或全表扫描,无论哪种方式,执行器都会重新检查每个谓词。

### 向量搜索(Vamana + PQ)—— 余弦、L2 和点积,读 *和* 写

向量 KNN(`db.idx.vector.queryNodes`)运行在 **余弦、L2 或点积(MIPS)** 索引之上。基础索引离线构建,包含两条执行路径,由 `--ann-threshold`(默认 50 000 个向量)按索引选择:

* **低于阈值 —— 暴力搜索。** 完整的 `f32` 向量位于 `vectors.f32.blk`;查询会扫描该索引的组,并以索引的度量计算精确距离。简单且精确;在向量集较小时很合适。
* **达到或超过阈值 —— Vamana + PQ**,即磁盘原生的 ANN 路径,无论向量数量多少,都能将常驻内存保持有界:
  * **[Vamana](https://arxiv.org/pdf/2401.11324)** 是 DiskANN 系列工作中的图索引:一个单一邻近图,其边经过剪枝(`--vamana-r` 出度和 `--vamana-alpha` 长边因子),因此*贪婪束搜索* —— 从 medoid 开始,反复朝查询跳跃,并维护宽度为 `vectorQuery.beamWidth` 的候选列表 —— 能在少量跳跃内到达节点的真正邻居,即**每次查询只需少量随机块读取**。图块(`vector/<label>.<prop>.vamana`)通过向量缓存按页调入,而非整体驻留。
  * **[乘积量化(PQ)](https://medium.com/aiguys/product-quantization-k-nn-for-big-datasets-12431d764c4e)** 将每个向量压缩为短代码(`--pq-subspaces` × `--pq-bits`):维度被拆分为子空间,每个子空间独立进行 k-means 聚类,向量则存储为最近质心 ID 的元组。这些代码(`vector/<label>.<prop>.pq`)足够小,可以**常驻内存**,因此束搜索从 RAM 中为候选打分,只有选中的少数完整向量才从磁盘读取。这个常驻 PQ 集就是 `cache.vectorCacheBytes` 缓冲池所固定的内容。

**可写嵌入 —— 向量写入阶梯([FreshDiskANN](https://arxiv.org/abs/2105.09613) 风格)。** 被索引的嵌入是一等可写值。`SET n.embedding = vecf32([…])`(以及 `REMOVE`)落入写入 delta,并**立即以精确排序对 KNN 可见**,随后在段刷写、合并和压缩中存活。查询最多合并三个层级 —— 已封存的基础索引、已封存的每段索引,以及内存中的 **RW-index**(写 delta 之上的实时可变 Vamana)—— 因此延迟在写入累积时保持平稳,而不是随着待处理写入数量增长。删除会留下一个*空洞*:该节点不再被返回,但作为导航路点保留,直到后台**删除压缩**将其从图中拼接出去,因此删除不再耗费查询 IO。而且由于磁盘上的图按布局位置而非节点 ID 寻址邻居,`CALL slater.consolidate()` 会**按引用**携带 Vamana —— 硬链接、字节一致 —— 并且只重写一个小型 ID 列,将向量写入合并进基础索引,而**无需** O(N·R·L) 图重建。带有说明的实测数据见 [性能报告](https://github.com/hikari-systems/slater/blob/main/docs/PERF-REPORT.md)。

## 存储后端(文件系统 / S3 / GCS)

每个 generation 文件都通过 **`ObjectStore`** 抽象打开,而非直接使用 `std::fs`,因此 *相同* 的磁盘字节格式 —— 块、索引、清单、`current` 指针 —— 可从任何后端原样提供;只有 *字节来源* 不同,读取器、查询引擎或完整性检查从不因后端而异。热路径是位置读取(`read_exact_at`),映射到本地文件上的 `pread` 和对象存储上的 HTTP 字节范围请求 —— Slater 从不使用 mmap,因此显式、有界读取模型在所有地方都相同。

**三个一流的后端**,通过 `dataBackend.kind` 选择。文件系统是简单的默认项;**Amazon S3 和 Google Cloud Storage 是同等、完全受支持的对象存储后端** —— 发布镜像内置了两者,因此每个都只需配置即可,而且一次构建的 generation 可以从其中任何一个提供服务(甚至可以从 `fs` → S3 → GCS 迁移),无需重建。

| `dataBackend.kind` | 位置读取 | 打开时的完整性 | 凭据 |
| --- | --- | --- | --- |
| `fs` *(默认)* | `pread` | 对每个文件进行完整 BLAKE3 重新哈希 | — |
| `s3` | HTTP `Range` GET | 通过 `HEAD` 获取服务器 **SHA-256**(若缺失,则对正文进行 BLAKE3 重新哈希) | 配置密钥、AWS 链或 IAM 角色 |
| `gcs` | HTTP 范围读取 | 通过 `get_object` 获取服务器 **CRC32C**(若缺失,则对正文进行 BLAKE3 重新哈希) | ADC / Workload Identity,或服务账号 JSON |

两个对象存储都通过**存储端已经计算并保留的校验和**来验证完整性,该校验和作为对象元数据获取:`slater-build` 在上传时发送该校验和(存储端据此验证字节并存储),服务器在打开时读回并与清单比对 —— 每个文件一次元数据请求,不下载正文。它是内容级的,且与 S3(SHA-256)和 GCS(CRC32C)在本质上一致。当对象**没有**存储端校验和时(通过带外方式复制,或使用不同默认值上传),服务器会**对照清单 BLAKE3 重新哈希对象正文**,而不是信任其字节长度 —— 请求的完整性检查绝不会被静默降级为大小比较。Slater 发布的 generation 始终携带校验和,因此它们保持在廉价的元数据路径上。

这个列在每种后端上检查的是文件**与清单匹配**。清单本身是否可信是另一个问题,而主密钥就是答案:配置了密钥后,清单会携带带密钥的 MAC,服务器在信任任何字段(包括这些哈希)之前会先验证该 MAC,因此被重写以描述被篡改文件的清单会被拒绝;没有密钥时,整个比较过程都是无密钥的,能够写入数据目录的人可以同时重写文件和清单。请参阅
[每种配置下完整性的含义](https://github.com/hikari-systems/slater/blob/main/THREAT_MODEL.md#what-integrity-means-in-each-configuration)。
该检查本身可以通过 `dataBackend.verifyIntegrity: false` 关闭,以换取更快的打开速度。

### 文件系统(`fs`)

默认项,根目录为 `dataBackend.fs.dir`。适合大多数部署:位于本地 SSD(或 NFS/EBS 挂载)上的 generation 以只读方式提供服务。完整性是在打开时对每个文件进行完整的 BLAKE3 重新哈希。

### Amazon S3(`s3`)

S3 或兼容 S3 的存储桶(AWS、MinIO、localstack)。凭据**首先**来自配置(`dataBackend.s3.awsAccessKey` / `awsSecretKey`,以及用于临时 STS 凭据的 `awsSessionToken`),留空时回退到标准 AWS 链(`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` 环境变量、共享配置文件,或实例/IRSA 角色)。```sh
# serve from S3 (env-var form; see the config table for every key)
dataBackend__kind=s3
dataBackend__s3__bucket=slater
dataBackend__s3__region=eu-west-2
dataBackend__s3__awsAccessKey=…        # omit to use the AWS chain / instance role
dataBackend__s3__awsSecretKey=…
# S3-compatible (e.g. MinIO): also set
dataBackend__s3__endpoint=http://minio:9000
dataBackend__s3__pathStyle=true        # required by most S3-compatible servers
root@kitploit:~
# publish a generation into the bucket (remote `current` pointer written last)
slater-build --input people.cypher --graph people --data-dir /data \
  --publish-s3-bucket slater --publish-s3-region eu-west-2 --publish-s3-prefix prod
#   MinIO: add  --publish-s3-endpoint http://localhost:9000 --publish-s3-path-style

Google Cloud Storage (gcs)

一个 GCS bucket,通过 JSON API 访问。授权采用 GCP 原生方式:默认情况下会解析 Application Default Credentials——包括 GKE Workload Identity、GCE metadata server,或 gcloud / GOOGLE_APPLICATION_CREDENTIALS 密钥。可设置 dataBackend.gcs.credentialsPath(服务账号 JSON 密钥文件)或内联的 credentialsJson 来提供显式密钥。dataBackend.gcs.endpoint 指向 fake-gcs-server 模拟器,而 dataBackend.gcs.anonymous=true 仅对该模拟器启用未认证访问——切勿用于真实的 GCS。```sh

serve from GCS (env-var form; see the config table for every key)

dataBackend__kind=gcs dataBackend__gcs__bucket=slater dataBackend__gcs__prefix=prod dataBackend__gcs__credentialsPath=/secrets/sa.json # omit for ADC / Workload Identity

root@kitploit:~
```sh
# publish a generation into the bucket (remote `current` pointer written last)
slater-build --input people.cypher --graph people --data-dir /data \
  --publish-gcs-bucket slater --publish-gcs-prefix prod
#   explicit key: add  --publish-gcs-credentials /secrets/sa.json

在所有情况下,slater-build 都会先将完整的生成结果写入 --data-dir(其本地暂存区域),然后额外上传到存储桶;远程的 current 指针最后写入,因此服务节点永远不会看到半发布的生成结果。

何时使用对象存储(S3 或 GCS)

当你希望生成结果存放在持久、集中的对象存储中,而不是节点磁盘上时,选择 s3 或 gcs —— 典型场景包括:发布一次,然后扇出到许多无状态、无磁盘的服务器副本,它们都读取同一个存储桶;将构建主机与服务主机解耦;或者依靠存储的持久性/版本管理/生命周期,而不是自行管理卷。代价是延迟:冷块是一次网络往返(约 10–50 ms),而不是本地读取(约 0.1 ms)。Slater 通过内存块缓存、并发预读以及可选的磁盘缓存(见下文)隐藏了其中大部分延迟。如果你的生成结果已经位于快速本地存储上,并且不需要集中式存储桶模型,那么 fs 更简单、更快。

本地磁盘块缓存(对象存储第二层)

内存中的 BlockCache 刻意保持较小(有界 RSS 是首要保证),因此在大于 RAM 的工作集上,同样的块每次溢出时都会从对象存储重新获取。一个可选的本地 SSD 第二缓存层解决了这个问题:从 RAM 逐出的块从本地磁盘(约 0.1 ms)提供服务,而不是重新执行对象 GET,从而在内存逐出后仍然存活,并降低对象存储的请求次数/成本——一旦预热,可使基于对象存储的节点接近本地文件系统的性能。它对 s3 和 gcs 都是可选启用的,通过设置 dataBackend.<s3|gcs>.diskCacheBytes > 0 以及一个可写的 diskCacheDir 来开启。

  • 它缓存密封后的字节,完全按获取时的样子——已经压缩,并且(对于 --encrypt 生成结果)仍然是 AEAD 密封的——位于解密/解压缩之下。缓存层从不持有加密密钥,也从不重新加密,因此静态状态免费保留:加密的生成结果以仍密封的状态落到磁盘上。
  • 写入是写后置的:未命中时,获取到的字节立即返回给查询,然后由后台线程执行磁盘写入和 LRU 修剪,因此查询路径永远不会阻塞在磁盘 I/O 上。逐出使缓存保持在其字节预算内;每次读取时验证的按文件校验和可将损坏的缓存文件自愈为未命中(→ 从对象存储重新获取)。
  • diskCacheDir 必须指向真实可写的卷——绝不能是 tmpfs(tmpfs 是内存,会破坏有界 RSS 保证)。跟踪它的内存索引会占用少量 RAM(每个缓存块约几十字节),这部分计入你的 RSS 上限——目录大小应远大于内存块缓存。
  • 该层的另一项 RAM 成本是写后置队列,它在块写入磁盘前暂存这些块。它的上限是 blockCacheBytes / 8(下限由 diskCacheBytes 决定)——默认为 8 MiB——并且会丢弃而不是增长,因此冷扫描不会使其膨胀;被丢弃的块只会在下次未命中时重新获取。它无需配置:它随 blockCacheBytes 伸缩,因此磁盘层除了其索引外,不会在 RSS 预算中增加新数字。

挂载

只读副本以只读根文件系统和非 root 用户(appuser:1000)运行——它所需的一切都以只读方式挂载。写入者(delta.enabled)还需要一个持久、可写的卷来存放其 WAL。

环境 / 配置

配置由标准的仓库分层加载器加载:内置的 config.json,然后 /sandbox/config.json 深合并到其上,然后是 KEY__sub 环境变量覆盖(双下划线表示嵌套;键与 camelCase 配置匹配)。

每个配置旋钮——它的 camelCase 键、KEY__sub 环境变量覆盖、默认值及其作用——都列在 配置参考 的表格中。最常调整的旋钮是缓存预算(cache.*)、查询保护(query.*)、连接上限(server.*)、存储后端(dataBackend.*)以及可写层(delta.*)。

常驻内存跟踪的是 blockCacheBytes + vectorCacheBytes + resultCacheBytes 在有限的每条目和分配器开销以内——每个池都称量自己的内容(字符串和容器按分配的容量计算)并逐出以保持在预算内,但你设置的数字之上还有每条目记账和分配器的尺寸类别舍入——再加上少量固定开销(以及用于 lazy 度数列的 degreeColumnBytes,一旦度数总和 count(endpoint) 快速路径被占用)。它与图大小无关——这是首要保证,由 rss_stays_bounded_under_sustained_knn_load 集成测试验证,该测试将峰值与稳定 RSS 的增长严格控制在预算总和之内。每连接缓冲区位于缓存预算之外,因此在对抗性负载下保证仍然成立,只是因为 server.maxConnections 限制了同时存在的连接数量。

网络态势

Slater 是只读副本句柄;主要的连接安全控制是网络,而不是二进制。将其绑定到私有接口,在网络层限制源范围(安全组 / NetworkPolicy),并且——如果它面向任何非可信客户端——在其前面放置一个限制连接的 L4 代理(HAProxy maxconn + 按源的 stick-table,或 nftables connlimit + hashlimit)。这位于文件描述符交给进程之前,因此是最稳健的限制。

上述二进制内限制(maxConnections、maxPreAuthConnections、maxConnectionsPerIp、差分字节上限和 loginTimeoutMs)是纵深防御:它们默认开启且宽松,因此对合法客户端群体不可见,但即使在忘记代理的情况下也能使有界 RSS 保证成立。完整的防御态势请参阅 docs/HARDENING.md,权威细节请参阅 THREAT_MODEL.md / SECURITY_WORKLIST.md。

生成结果守护

Slater 每 generationPollMs 轮询一次每个图的 current 指针(轮询,而非 inotify——数据目录可能是远程/网络存储,如 NFS,其中的文件系统变更事件不可靠)。当它发生变化时:

  • reloadStrategy=exit(默认):服务器记录致命错误并以非零状态退出,以便编排器针对新生成结果干净地重启它。
  • reloadStrategy=swap:服务器打开并验证新的生成结果(与启动时相同的内容哈希保护),原子地将其换入,并让进行中的查询在旧生成结果上完成。损坏/不完整的新镜像会被拒绝,旧生成结果继续服务。

ACL

acl.json 将用户映射到 argon2id 密码哈希和按图的 read / write 授权。使用以下命令生成哈希(绝不要存储明文):```sh slater hash-password 's3cret' # prints a $argon2id$… string for acl.json

root@kitploit:~
仓库根目录附带一个入门用的 `acl.json`;其结构如下:```json
{
  "users": {
    "reporting": {
      "passwordArgon2id": "$argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>",
      "grants": {
        "people": ["read"],
        "products": ["read", "write"]
      }
    }
  }
}
  • users — 每位登录用户一条记录,以用户名作为键。

  • passwordArgon2id — 来自 slater hash-password 的 $argon2id$… 字符串 (绝不允许明文;该文件本身是纯 JSON,且存放在共享存储上)。

  • grants — 按图划分的能力列表。只有两种权限具有实际意义:

    • read — 查询图。用户 grants 中不存在的图,对该用户不可见。
    • write — 通过可写层(delta.enabled)变更图: MERGE / SET / DELETE 语句以及 CALL slater.consolidate()。

    两者是相互独立的:read 授权并不赋予任何写权限。 因此启用可写层 并不会把你现有的读者提升为写者。写者需要同时拥有两者 — — 因为解析业务键以便写入,本身也是一次读取。 无法识别的权限字符串会被忽略(它们不授予任何权限)。

以只读方式挂载到 aclPath 指定的路径(默认为 /config/acl.json)。 服务器会在每次生成热切换时重新加载该文件,并且每次重新加载时都会 重新检查静态 ACL 印记(参见 requireAclStamp)。

健康检查

slater 二进制本身兼作存活探针:slater healthcheck [host] [port] 会对服务器执行一次 Bolt 握手(不是 HTTP 请求), 若协商出协议版本则以 0 退出,否则以 1 退出 — 默认使用 localhost 和已配置的 Bolt 端口。这正是容器 HEALTHCHECK 所运行的内容,因此编排器看到的是一个真正 Bolt 就绪的服务器, 而不仅仅是开放的套接字:```sh slater healthcheck localhost 7687 # exit 0 = healthy docker exec slater /app/slater healthcheck # inside the container

root@kitploit:~
## 一次性查询

对于脚本编写、CI 检查和快速查找,`slater query` 挂载图的
当前世代,在进程内运行单个只读 Cypher 查询,打印结果
为 JSON 对象,然后退出——无需服务器,无需 Bolt 连接。它遵循
与服务器相同的配置(存储后端、加密密钥、查询预算):```sh
# GRAPH defaults to `defaultGraph`. Without -q, normal datestamped logging
# (config, "opened generation", …) is written to stdout alongside the result.
slater query mygraph 'MATCH (n) RETURN count(n) AS c'

# -q/--quiet ⇒ logging suppressed, so stdout is *only* the compact result JSON
slater query mygraph -q 'MATCH (c:Company) RETURN c.ticker AS t LIMIT 3' | jq
# {"columns":["t"],"rows":[["AUPH"],["KYMR"],["MREO"]]}

节点和关系会展开为其标签/类型和属性。使用 -q 当你需要机器可解析的输出时(结果 JSON 是唯一出现在 stdout 上的内容);省略它以进行面向操作员的带日志运行。不带 -q 时,一个 仅含指标的摘要会在每次运行后被记录 — 例如```text INFO query executed cost=2389 resultCount=10 execMs=441 limitRowCount=10

root@kitploit:~
返回查询的 `cost`(计费元素数)、`resultCount`、`execMs` 以及
`limitRowCount`(仅在查询指定了 `LIMIT` 时)—— 绝不返回查询文本
或任何结果值。退出状态为 `0` 表示成功,`1` 表示解析/打开/执行
错误(消息输出到 stderr)。

## 导出图(`slater dump`)

`slater dump` 从**运行中**的服务器将图导出为业务键 `MERGE` Cypher —— 即 `slater-build` 所摄入的同一种方言 —— 从而使图能够往返(dump → `slater-build` → 新生成),用于迁移或文本备份。与 `slater query` 不同,它通过 **Bolt** 连接、进行身份验证,并遵循每图 ACL,因此无需对服务器的磁盘访问。密码从 `SLATER_DUMP_PASSWORD` 或 stdin 读取(绝不用标志,使其不出现在 `ps`/历史中)。```sh
# List the graphs the authenticated user may read.
SLATER_DUMP_PASSWORD=pw slater dump --list -u reporting

# Dump a graph to a file (identity keys inferred from range indexes).
SLATER_DUMP_PASSWORD=pw slater dump people -u reporting -o people.cypher

# Rebuild it into a fresh generation.
slater-build --input people.cypher --graph people --data-dir ./data

每个标签的标识键是其范围索引所携带的属性; 可通过 --key Label=prop(可重复)或全局 --pk <field> 覆盖。 CREATE INDEX DDL 首先发出,以便重建时重新创建索引。 多标签节点保留 每个 标签 — 它以 MERGE (n:Ident:Other {key: v}) 形式发出, 标识标签(提供业务键的那个)在前,其余 按序排列;合并仅以标识标签为键,因此尾随标签 被写入该节点,而不会创建另一个节点。包含特殊字符的标签、关系 类型和属性键在发出时 用反引号引用,因此不寻常的名称可以忠实地往返, 且无法将 Cypher 注入重建。向量(以及其他 没有 Cypher 字面量拼写的值)无法随 MERGE 转储输出,会被丢弃 并在 stderr 上发出警告。成功时退出状态为 0,出错时为 1。

工作示例

一个完整、可运行的演练 — 构建图、为其提供服务、使用 neo4j JavaScript 和 Python 驱动程序连接并向其写入数据 — 位于手册的 快速入门 和 写入数据 页面,使用 docs/manual/examples/ 中捆绑的示例图。

开发```sh

export PATH="$HOME/.cargo/bin:$PATH" cargo build cargo test # unit + the bounded-RSS headline integration test cargo clippy --all-targets -- -D warnings cargo fmt --all -- --check

root@kitploit:~
### 对象存储后端是可选的 cargo 特性

普通的 `cargo build` 会生成一个 **仅文件系统** 的二进制文件 —— `s3` 和 `gcs`
后端由 cargo 特性控制,因此默认构建保持精简(无需 AWS
或 Google SDK,无需异步运行时)。请在 `slater`(serve)
和 `slater-build`(publish)**两者**上启用你需要的后端:```sh
# S3 only / GCS only / both
cargo build -p slater -p slater-build --features s3
cargo build -p slater -p slater-build --features gcs
cargo build -p slater -p slater-build --features s3,gcs

Each crate 暴露匹配的 s3 / gcs features,这些 feature 转发到 graph-format/{s3,gcs}。如果在运行时请求某个后端(dataBackend.kind=s3|gcs,或 slater-build --publish-{s3,gcs}-*),而该 feature 未编译进去,则会快速失败并给出清晰的“built without the … feature”错误。发布的 Docker 镜像同时启用了两者(Dockerfile CARGO_FEATURES),因此预构建镜像无需额外标志——这仅在从源码构建时才重要。集成测试也做了同样的门控:--features s3 --test s3_minio、--features gcs --test gcs_emulator(一个 fake-gcs-server),以及 --features gcs --test gcs_real(通过 ADC 访问真实 GCS);每一项测试都会在未设置对应 SLATER_* 环境变量时跳过。

设计说明、里程碑记录和决策日志分别见 docs/PLAN.md、docs/PROGRESS.md 和 docs/DECISIONS.md。

性能

最多六个引擎、一套单客户端测试套件,图规模从 62k 节点的玩具图到 Wikidata 91.6M 节点 / 1.5B 边。每个引擎都隔离测量(其他所有容器停止——RSS 和延迟都是其自身占用)。下面的延迟表是在 Slater 0.21.0(可写构建)上重新实测的:小/中规模图(MeSH、EU-AI-Act)为全新测量,91.6M 图则是全新的同机、共享锚点 slater-vs-Neo4j 对照(见该表)。常驻内存数据沿用较早一轮的测量(通过容器 cgroup 测量;读路径与可写层空闲时字节一致)。其他引擎的数字来自既有的跨引擎测试(它们的版本/性能未变)。所有数字均为中位数(ms)或峰值常驻内存(MiB)。每处数值越低越好;加粗 = 行内最佳。 slater 运行在其本地文件系统(fs)后端上;S3 和 GCS 后端用对象存储往返换取本地读延迟(由内存缓存和可选本地磁盘缓存层缓解),因此这些数字刻画的是引擎本身,而非网络存储部署。

三个从磁盘分页的引擎——slater、Neo4j 5 和 LadybugDB——能够加载全部五个图。内存三人组(Memgraph · FalkorDB · ArcadeDB)完全无法容纳 1.5B 边图(需要约 64–128 GiB 常驻),ArcadeDB 的导入器也无法完成该图。

常驻内存(MiB)——随图增长约 1,500× 时的内存界限

每个数字都是已提交工作内存——即操作系统无法回收的部分。除 slater 外的每个引擎都把图放在已提交的匿名内存中(各自堆、Neo4j 的堆外页缓存或缓冲池),因此其峰值 RSS 就是其已提交占用。只有 slater 从磁盘存储的可回收 OS 页缓存提供服务,因此其数字是匿名工作集;存储的页缓存(在压力下可被驱逐——slater 仍会继续服务)被排除在外,并在 91.6M 图中以括号里的 total 显示。加粗 = 最低。

slater 在每个规模下都是最低,且在图增长约 1,500× 时仅增长约 50×——其占用跟踪的是 查询工作集,而非图本身(全程空闲约 16–71 MiB)。内存三人组几乎线性增长,且无法加载 1.5B 图;Neo4j 无论查询如何都会提交约 2 GiB 堆。(† LadybugDB 仅在边界形状上——其在 1.5B 边上的 hub / 变长 / shortestPath 遍历需要把读池提高到 ≥2 GiB,而 slater 则有自动的 maxIntermediate 上限。)构建期的值→计数直方图增加的可忽略不计——低基数索引列仅几 KB,而像 Wikidata 这样的唯一键图则为 零(wikidata_id 超过直方图基数上限,因此不存储)——所以这些数字不受该 feature 影响。

延迟(中位数 ms)——图可装入 RAM(MeSH,341k / 469k)

slater 主导了元数据 / 索引 / 扫描类形状(count、label、idx-eq、scan——约 0.4 ms,是服务引擎的 10–200×)以及索引点查(0.43 ms,现在已追上内存二人组的 0.48 ms)、无锚点多跳(2 跳 1.40 ms,通过关系类型扫描实现,业内最快)——以及通过构建期对索引分组键建立的值→计数直方图——整标签 group-by / count(DISTINCT)(0.45 ms,领先 LadybugDB 的列式 5.3 ms)。内存服务器只在原始 1 跳上保持领先(Memgraph 1.21 ms 对 slater 的 1.28 ms)。(pole 62k/106k 情况相同:slater 在 count/scan 上唯一最快约 0.4 ms,跳数约 1.3–2.6 ms。)

延迟(中位数 ms)——向量(EU-AI-Act kNN,15k × 1024 维)

slater 用精确暴力扫描回答 kNN(这些集合低于其 50k 向量的 ANN 阈值),而其他引擎使用近似常驻 HNSW——因此 slater 的结果是精确的(召回率 1.0)。SIMD 距离内核 + 常驻、预归一化的向量矩阵将 Concept 从约 23 → 约 2.9 ms,Chunk 从约 10 → 约 2.4 ms,因此 slater 现在超越 Neo4j 和 LadybugDB,并达到 Memgraph 的约 1.4× 以内,仅落后于 FalkorDB——同时保持精确。

向量写入阶梯——无需重建的插入 / 更新 / 删除

上面的表是跨引擎的 读 对比。向量写路径(在静态 Vamana 基线上采用 FreshDiskANN 风格的写入阶梯)没有跨引擎对应物——此处的其他引擎都没有做磁盘原生、可写 ANN——所以下面这些数字是单一引擎的组件基准,运行在合成的、类 embedding 的测试数据(低秩流形,维度 768,范数不等)上,提交在 crates/slater/benches/ 下,并在 docs/PERF-REPORT.md 中完整记录——包括方法论和所有注意事项。召回率始终与对实时集合的精确暴力搜索比较,而非一个索引对另一个索引。这里的规模具有代表性,仅在指标按大小线性时才做外推。

唯一需要专门性能箱的指标是 慢路径 合并重写的吞吐量——当合并携带删除或新向量而非纯重排时,它是以单线程 zstd 和本地磁盘为界的顺序重压缩,因此绝对 MiB/s 与环境相关(报告展示了形态并解释了环境范围)。

延迟(中位数 ms)——图 ≫ RAM(Wikidata 91.6M / 1.5B)

内存引擎(Memgraph / FalkorDB / ArcadeDB)根本无法加载此图(约 64–128 GiB 常驻)。只有 slater 和 Neo4j 5 可以。这是一轮全新同机、当日、针对共享固定锚点集的对照——每个查询在两个引擎上都命中相同节点,因此这场正面交锋是公平的(一个共同的 wikidata_id 中等度数锚点池;下面有关于为何重要的说明)。slater 以两种扇出展示(query.maxFanout 1 = 吞吐量默认,8 = 用于重叠冷块读取的延迟旋钮)。加粗 = 行内最佳。

实话实说:slater 在元数据 / 索引形状上占绝对优势——count(*) 由元数据服务(0.41 ms 对 Neo4j 的 3.6 s 磁盘扫描,约 8800×),点查 / 度数 / 3 跳快约 2–10×——在 1–2 跳上与 Neo4j 相当(扇出 8 在冷读上领先),但在 var-length *1..2 distinct 上明显落败(约 1 s 对 Neo4j 的 47 ms):slater 的变长 distinct 展开在此明显更慢,这是值得单独调查的真实弱点。而所有这一切都只用了数百 MB RSS,对比 Neo4j 已提交的约 2 GiB 堆。

关于锚点。 这些遍历数字很大程度上取决于你从哪些节点出发——一个离 Wikidata 巨型 hub(“human”“country”)只有一跳的节点,其 2 跳邻域可达百万级,因此变长 / 跳数成本会随锚点选择而波动几个数量级。该表的旧版本采样的是各引擎自己的“按扫描的前 N 个”,既不稳定也不可比;本轮为两个引擎固定了一个共享的、度数受限的锚点集。(本轮省略了 shortestPath——在任意两个锚点之间它取决于路径是否存在,方差太高,无法有意义地取中位数。)

多跳 count(*)——内存与结果大小解耦

无上限的多跳 RETURN count(*) 在展开过程中计数,而不是物化匹配行。在 91.6M 图上使用相同 hub 锚点,maxIntermediate=20M:

91.6M 上的 3 跳 count(*)fanout=1fanout=8
延迟 / 峰值工作集554 ms / 0.66 GiB298 ms / 1.9 GiB

计数只持有 O(1) 行。计费不变,因此巨型 hub 计数仍会在计算(邻接读取)上触发 maxIntermediate,界限与之前相同。

每查询并行度(maxFanout)

提高 query.maxFanout 会在多核上重叠查询的冷、I/O 受限块读取——它有助于大冷工作集、磁盘受限的形状,在热形状上则持平。在 1.5B 图上:shortestPath ≤6 918 → 608 ms(1.5×,最大搜索 6,269 → 2,350 ms,2.7×);3 跳计数 547 → 298 ms。maxFanout=1 是默认值(面向吞吐量);8 是延迟旋钮,代价是更高的瞬时工作线程内存。

slater 的胜场 / 落后之处

各引擎完整表(pole、MeSH、EU-AI-Act + blockCacheBytes RAM↔延迟旋钮、Wikidata 1M 和 91.6M)见 perf/cross-engine-hs/README.md;最新的 slater 单独轮(两种扇出、所有数据集)见 perf/PERF_CURRENT_STATUS.md。

并发与降级(负载测试)

上面的基准是单客户端。互补的维度——多并发客户端下的行为——有自己专用的测试框架,perf/loadtest/:一个基于 Bolt 的 Locust 驱动,外加一个协调器,用于逐步加压、读取 CALL slater.diagnostics()、找到容量拐点并指出限制因素(完整方法见 docs/LOAD-TESTING.md)。以下是在 Wikidata-1M 图上、256 MiB 缓存的一次运行(一台 16 核机器)的要点:

负载测试暴露的两个内存问题现在都已关闭;全部跟踪记录见负载测试文档。

许可证

根据 Apache License 2.0 许可。完整文本见 LICENSE,署名信息见 NOTICE。除非你另有明确说明,否则任何有意提交以纳入本作品的贡献(按 Apache 2.0 许可证定义)均应按照上述条款许可,不附加任何额外条款或条件。

SPDX-License-Identifier: Apache-2.0

下载工具
DELETE
功能特性对你意味着什么
有界、可预知的内存常驻内存跟踪你设定的三个缓存预算,误差在有界的每条目与分配器开销之内——它不会随图的大小增长;你调整性能/内存权衡,而不是为整个图做资源预配。带有后台清理的 jemalloc 分配器会在大量查询突发后把释放的内存归还给操作系统,因此常驻内存回落到空闲基线,而不是停留在突发后的高水位。
开箱即用的多租户一台服务器托管多个图,并支持按用户的读取授权——这是大多数图数据库只在付费/企业版中提供的多数据库隔离能力。
静态与传输中加密逐块 XChaCha20-Poly1305 密封(密钥从不写入磁盘),外加可选的 TLS(bolt+s://)。按构造即符合 GDPR。加密同时也是获得可认证完整性的手段:构建器用带密钥的 MAC 密封清单,持有密钥的服务器会验证它,并拒绝服务清单被伪造、篡改或被剥离 MAC 的代次。未加密钥(明文)的映像仅由无密钥的内容哈希保护——能检测完整性与损坏,但无法检测篡改。参见 每种配置中的完整性含义。
极小的安装包基于 distroless glibc 的小型 stripped 二进制(无 shell/apt)——多架构(amd64/arm64)镜像拉取约 22 MB,仅服务器的 slater:latest-lite 标签约 12 MB;纯 Rust TLS,无 OpenSSL。拉取即可运行。
为定期发布而设计离线构建图,以不可变方式提供服务,然后零停机原子地切换新版本——非常适合数据仓库 / 定时刷新工作负载。
负载下依然坚固服务器和离线构建器都以 #![forbid(unsafe_code)] 编译——引擎中唯一的 unsafe 位于经过审计的 jemalloc 分配器 crate 中。核心不可变,因此读取无需加锁,也永远不必等待写入者;单个写入者只在写入路径上串行化变更。没有 GC 暂停,没有数据竞争。一个坏查询无法让服务器宕机。
与你现有的 neo4j 工具兼容讲 Bolt 5.4 / 4.4 / 4.1——可直接使用标准 neo4j 驱动(JS、Python、Go、Java……)、cypher-shell 或图浏览器,无需修改。
丰富的 Cypher 查询面广泛的读取面:MATCH/WHERE/WITH/UNION、CALL {…} 子查询、70+ 函数与聚合、时间与地理空间值,以及正则表达式。
实时、持久的写入不可变核心之上可选启用的单写入者 LSM 层(delta.enabled):对节点和关系进行业务键 MERGE / SET / DELETE / CREATE / REMOVE、批处理写入 UNWIND(每批一次 fsync),以及 CALL slater.consolidate()——组提交、fsync 持久化,并通过合并折回成全新核心。当增量为空时,读取路径逐字节一致。
ISO GQL,读写皆可在同一个 Bolt 连接上讲 ISO GQL(ISO/IEC 39075)子集——量化路径、路径限制器、最短路径选择器、标签/类型布尔表达式、FOR、CAST、可选的 GQL/CYPHER 方言前缀——并且当可写层开启时,GQL 的数据修改语句(INSERT / SET / REMOVE / [DETACH] DELETE)降级到同一条持久写入路径。Cypher 与 GQL,读取与写入,同一引擎内实现。
向量 + 图,同一引擎磁盘原生的 ANN 向量搜索(Vamana + PQ;cosine / L2 / dot),用于嵌入/RAG,另有图算法(PageRank、BFS、介数、WCC……)——即使有数百万向量,内存也保持有界。嵌入向量可写(FreshDiskANN 风格的写入阶梯):插入 / 更新 / 删除向量,KNN 立即可见,无需重建即可折回基础。
网络存储上安全每个文件都经过 BLAKE3 内容哈希并在打开时验证;撕裂或半拷贝的映像会被拒绝而不是被服务。专为 NFS/远程卷设计(没有 mmap 的意外)。
可插拔的存储后端从本地文件系统、S3(S3 兼容)存储桶或 Google Cloud Storage 存储桶提供相同的代次格式——发布一次,横向扩展到无状态副本——并可在对象存储前放置可选的本地 SSD 缓存层。参见 存储后端(文件系统 / S3 / GCS)。
路径用途说明
/data图生成结果(<graph>/<uuid>/… + current)。对副本只读;由 slater-build 生成。可能位于远程/网络存储(例如 NFS)上,因此不假定读取具有快速本地 SSD 的延迟。
/sandbox按环境的配置覆盖层 + 密钥。/sandbox/config.json 会深合并到内置的 config.json 之上;还存放 acl.json、TLS PEM 材料、静态加密密钥文件。
/tmp、/run临时空间(tmpfs)。只读副本默认从不写入磁盘。
(写入者) delta.walDir预写日志 + L0 delta 段,当 delta.enabled 时使用。可写,并且是持久、真实的卷——绝不能是 tmpfs(它是持久性的底线)。相对路径在数据目录下解析;在此处为写入者提供其自己的持久卷。
(可选) 磁盘缓存本地磁盘块缓存,当 dataBackend.s3.diskCacheBytes / dataBackend.gcs.diskCacheBytes > 0 时使用。可写,并且是真实卷——不是 tmpfs。由 s3 和 gcs 后端使用;参见 存储后端。
["read", "write"]
引擎类别内存界限
slater磁盘支撑、分页query.maxIntermediate 自动限定工作集
Neo4j 5磁盘支撑、JVM约 2 GiB 堆 + 堆外,无论查询如何都会提交
Memgraph · FalkorDB内存全图常驻 RAM
ArcadeDB内存、JVM全图常驻;最重
LadybugDB嵌入式、列式手动缓冲池,必须超过查询需求
图(节点 / 边)slaterNeo4j 5MemgraphFalkorDBArcadeDBLadybugDB
pole — 62k / 106k117461141401,556198
MeSH — 341k / 469k631,0833584551,631121
EU-AI-Act — 21k / 45k(+55 MiB 向量)997292293121,948286
Wikidata — 91.6M / 1.5B584 (total 4,595)~2,900无法加载无法加载无法加载~652 †
形状slaterNeo4j 5MemgraphFalkorDBArcadeDBLadybugDB
count(*) 所有节点0.4115.023.816.482.02.2
标签计数0.424.220.71.14.44.3
索引点查0.433.90.480.480.658.8
idx-eq 计数0.424.95.02.03812.5
1 跳(索引锚点)1.285.81.214.13904.9
2 跳(无锚点)1.405.68.516.74446.4
group-by / count(DISTINCT)0.4547–5163–6431–394115.3
全扫描 CONTAINS0.435.424.11.716.34.1
形状slaterNeo4j 5MemgraphFalkorDBLadybugDB
kNN top-10 Concept2.98.61.91.22.8
kNN top-10 Chunk2.45.71.91.53.2
性质实测为何重要
KNN 延迟 vs 待处理写入RW 索引 约 1.5–2 ms,持平至 5 万待处理;预索引暴力叠加层 1.9 → 115 ms(随增量线性)——在 5 万时 61×查询延迟不会随着写入在合并之间堆积而退化
嵌入插入每向量 约 1.5–2 ms 写入实时索引写入立即可被 KNN 看到;增量重建预算 ≈ 2 ms × 增量上限
等召回率下的删除 IO删除 67 % 时每次查询减少 2.9× 节点读取,删除 80 % 时减少 5.2×(召回率 ≥ 0.90)合并后的图不会为已删除向量付出读取税
合并、纯重排O(1) — .vamana 硬链接字节相同,只重写 id 列将向量写入并入基线可跳过 O(N·R·L) 重建
整条阶梯上的召回率合并后 ≥ 基线,适用于 cosine、L2 和 dot写入阶梯在每一级都保持召回率
形状slater(fan 1)slater(fan 8)Neo4j 5
count(*) 所有节点0.410.413606
点查(索引)0.720.496.3
度数(1 跳计数)0.430.446.0
1 跳邻居9.84.510.1
2 跳372334.5
3 跳322574
变长 *1..2 distinct985105647
维度slater场上最佳结论
任何规模下的常驻内存11–584 MiB(62k → 91.6M)内存型 1.5–2.7 GiB;无法加载 1.5Bslater
count / 元数据 / 扫描约 0.4 ms服务引擎 5–80 msslater(10–200×)
索引点查0.43 ms(MeSH)Memgraph · FalkorDB 0.48 msslater(略胜内存二人组)
无锚点多跳(行)1.40 ms(MeSH 2 跳)Neo4j 5.6 msslater(关系类型扫描)
聚合(group-by / DISTINCT)0.45 msLadybugDB 5 ms(列式)slater(构建期直方图)
kNN2.4–2.9 ms(精确)FalkorDB 1.2 ms(HNSW)胜 Neo4j/Ladybug;约 1.4× 于 Memgraph;精确
91.6M 元数据 / 点 / 度数 / 3 跳0.4–32 msNeo4j 6–3,600 msslater(2–8800×)
91.6M 1–2 跳4.5–23 ms(fan 8)Neo4j 10–35 ms大致持平
91.6M 变长 *1..2 distinct约 1 sNeo4j 47 msNeo4j(slater 的真实弱点)
大规模多跳 count(*)0.3–0.6 GiB内存引擎物化行集slater,有界限
结果测量
可承受 1000 个并发客户端、零失败吞吐量峰值约 2.5k rps;延迟拐点约在 750 个客户端时出现(p99 51 → 750 ms)——核心争用下的排队,而非硬上限(单次运行,WSL2)
块缓存有界且有效命中率 100 %,0 次驱逐,适合缓存的 50 MB 常驻工作集
持续负载下 RSS 保持稳定jemalloc 分配器在 100→500 客户端的 wiki_cache_churn 斜坡中将 RSS 保持在约 0.6 GB——缓存受限且稳定,无需 MALLOC_* 调优(此前的 MALLOC_ARENA_MAX=2 + trim 阈值已退役);其后台清理也会收回突发后的高位水位,而不是让它钉住
聚合内存有界服务器级 query.maxIntermediateGlobal + 按邻接计费的展开在 1000 个客户端下遏制了 wiki_budget 2 跳洪泛而不 OOM(RSS 约 0.6 GB;防护将约 60% 的 hub 查询以可重试的预算错误甩掉)