Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

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

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

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
slater — Low-memory graphdb with Bolt+tls support, at-rest encryption & vectors designed for local replica graph use cases. | Kitploit
工具/GitHubGitHub/hikari-systems/slater
Authentication & AuthorizationEncryption/Decryption ToolsNetwork SecurityCloud SecurityUtilities & FrameworksDatabase Security
GitHubhikari-systems/slater

slater

Low-memory graphdb with Bolt+tls support, at-rest encryption & vectors designed for local replica graph use cases.

查看仓库
10023419小时33分前Kitploit 审核通过

最受欢迎

查看全部 →

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

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
内容在请求的语言中不可用。显示英文版本。
# Slater

[![CI](https://github.com/Hikari-Systems/slater/actions/workflows/ci.yml/badge.svg)](https://github.com/Hikari-Systems/slater/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/Hikari-Systems/slater?sort=semver)](https://github.com/Hikari-Systems/slater/releases/latest)

<!-- current-version: source of truth for the release number. Keep in sync with the git tag and Cargo.toml; enforced by .githooks/pre-push and the release CI. -->
**Current version: `v0.25.4`** — [all releases](https://github.com/Hikari-Systems/slater/releases).

> **In one line:** Slater serves **graphs that don't fit in memory** — hundreds of millions of nodes and billions of edges in low hundreds of MB of RAM — over standard **Bolt**, so any neo4j driver just works, with disk-native vector search sitting next to the graph, and it takes **live, durable writes** without giving that up. Resident memory is set by a cache budget you choose, **not** by the size of the graph.

---

**Shortcuts**

|  |  |  |  |
|:--|:--|:--|:--|
| [Why Slater exists](#why-slater-exists) | [Reads and writes](#reads-and-writes) | [What you get](#what-you-get) | [Features](#features) |
| [Running with Docker](#running-with-docker) | [How it works](#how-it-works) | [The writable layer](#the-writable-layer) | [Storage backends](#storage-backends-filesystem--s3--gcs) |
| [Mounts](#mounts) | [Configuration](#environment--configuration) | [ACL](#acl) | [Health check](#health-check) |
| [Worked example](#worked-example) | [Development](#development) | [Performance](#performance) | [License](#license) |
| [Graphiti memory store](#using-slater-as-a-graphiti-memory-store) | [📖 Full manual](https://github.com/hikari-systems/slater/blob/main/docs/manual/README.md) |  |  |

## Why Slater exists

A **graph database** stores data as *things* (nodes) and the *relationships between them* (edges), with the relationships as first-class citizens. That's what you want when your questions are about connections rather than rows — "who's within three hops of this account?", "what's the full dependency chain behind this build?", "which accounts share a device, an address, and a card?" — the queries that become a swamp of recursive joins in SQL but fall out naturally in a graph.

**The most common complaint about graph databases is that they don't scale past what you can hold in RAM.** Many of them (eg neo4j, Memgraph, FalkorDB, etc) keep the whole graph resident: a 40&nbsp;GB graph wants 40&nbsp;GB of memory — *per instance*. Want a replica per region, per tenant, or per pod? Multiply the bill. And past a certain size they simply won't load: eg the 90 million node / 1.5B-edge Wikidata graph needs ~64–128&nbsp;GiB resident, so the in-memory engines can't open it at all.

Slater is the rebuttal. Rather than loading the graph into memory, it **compiles it once, offline**: `slater-build` turns your data into a content-addressed, immutable on-disk image, and any number of Slater servers then serve that image over **Bolt** (so your existing neo4j drivers just work), paging blocks in on demand and holding only a fixed cache budget resident. That is how the same 90M-node graph serves from **a few hundred MB of RAM** — graph size and memory bill are decoupled. **A 4&nbsp;GB graph and a 400&nbsp;GB graph cost the same RAM to serve**, so you fan out cheap, stateless read replicas and let the store, not the heap, hold the graph.

That makes it a natural fit for knowledge graphs behind RAG, recommendation and identity graphs, dependency graphs — anything large and connected you want to query cheaply and often. Disk-native vector search lives right next to the graph, so the same engine is the retrieval layer for embeddings too.

Compiled once does not mean frozen, though. That image is a **base**, not a final state: an opt-in write layer sits above it, so a live graph can be corrected and extended without rebuilding anything.

### Reads and writes

The core is immutable; the graph is not. Turn the writable layer on (`delta.enabled`) and you write over Bolt — correct one property, add a node, retract an edge — and the change lands durably, with no rebuild of the image. What keeps it cheap on the read side is *where* the writes live.

Writes accumulate in a **log-structured-merge (LSM) layer over the immutable core**: a write-ahead log and an in-memory table, spilling to immutable delta segments, folded back into a fresh core by a periodic **consolidation**. What that buys you:

- **Reads over an unwritten graph cost exactly what they did before.** An empty delta is a single predictable branch, not a merge — the read path is byte-identical whether or not the writable layer is on.
- **The read cost of a write scales with the size of the delta, not the size of the graph.** Whole-graph answers — `count(*)`, the label and relationship-type marginals — stay *metadata reads* even with writes outstanding: the delta keeps its own counters, so a `count(*)` over a 91.6M-node core with half a million pending writes still answers in tens of milliseconds without touching a single block.
- **Acknowledged means durable.** A single writer drains the queue and returns `SUCCESS` only after the `fsync` that covers the write. Group your writes and they're cheap — a write-`UNWIND` commits one `fsync` per batch rather than per row.
- **Business-key writes, in either dialect.** `MERGE` / `MATCH … SET` / `DELETE` (and `CREATE` / `REMOVE`, detach delete, relationship writes) keyed on a node's identity property — or the equivalent ISO GQL data-modifying statements (`INSERT` / `SET` / `REMOVE` / `DELETE`), which lower onto the same path. Correct, insert, upsert and retract, over nodes and edges, addressed the way your data already is.

With the layer off — the default — Slater serves the pure immutable core and refuses writes. See [The writable layer](#the-writable-layer) for the full model.

> **On the name.** Slater is named after the CIA agent in *Archer* (a great show)
> who insists on going by a single name — "Just… Slater" — and one of my favourite
> characters in it. See the
> [character wiki page](https://archer.fandom.com/wiki/Slater).

### What you get

- **RAM set by your cache budget, not your graph size** — fan out as many read replicas as you like; the graph never has to fit in memory.
- **A drop-in for the graph** — speaks Bolt, so any standard neo4j driver (JS, Python, Go…) works unchanged. It's Cypher (plus a slice of ISO GQL, reads and writes); nothing new to learn.
- **Live, durable writes** — an opt-in LSM layer over the immutable core: business-key `MERGE` / `SET` / `DELETE` over nodes and edges, group-committed and `fsync`-durable, folded back into a fresh core by consolidation. Reads don't pay for it.
- **Deployment by file swap** — build a new content-hashed *generation* offline, atomically flip the `current` pointer, and servers pick it up. Every block is checksummed, so a half-copied image is refused rather than served.
- **Vector search built in** — disk-native approximate-nearest-neighbour (cosine, L2, or dot KNN) sits right next to your graph, for when this is the retrieval layer behind a RAG pipeline, and embeddings are writable in place — no offline rebuild to add or change a vector.
- **Locked down by design** — read and write grants are independent, plus optional at-rest encryption, TLS Bolt, argon2id-hashed ACLs, and a read-only container rootfs for read replicas. Configure a master key and the on-disk image is *authenticated* as well as encrypted — its manifest carries a keyed MAC, so an attacker with write access to the data directory but no key cannot forge a manifest the server will accept. Without a key you still get the content hash, which catches a half-copied or corrupted image — but not a deliberate one. [Which configuration buys what](https://github.com/hikari-systems/slater/blob/main/THREAT_MODEL.md#what-integrity-means-in-each-configuration).

## Features

| Feature | What it means for you |
|---|---|
| **Bounded, predictable memory** | Resident memory tracks three cache budgets *you* set, to within bounded per-entry and allocator overhead — it does **not** grow with graph size; you tune the performance/RAM trade-off instead of provisioning for the whole graph. A jemalloc allocator with background purge returns freed memory to the OS after heavy query bursts, so resident size falls back toward its idle floor rather than staying pinned at the post-burst high-water mark. |
| **Multi-tenant out of the box** | One server hosts many graphs with per-user read grants — multi-database isolation that most graph DBs reserve for a paid/enterprise tier. |
| **Encryption at rest & in transit** | Per-block XChaCha20-Poly1305 sealing (the key is never written to disk) plus optional TLS (`bolt+s://`). GDPR-friendly by construction. Encryption is also what buys **authenticated** integrity: the builder seals the manifest with a keyed MAC, and a server holding the key verifies it and refuses to serve a generation whose manifest is forged, altered, or has had its MAC stripped. An unkeyed (plaintext) image is guarded by the unkeyed content hash only — completeness and corruption, not tampering. See [What integrity means in each configuration](https://github.com/hikari-systems/slater/blob/main/THREAT_MODEL.md#what-integrity-means-in-each-configuration). |
| **Tiny install** | A small stripped binary on a distroless glibc base (no shell/apt) — the multi-arch (amd64/arm64) image pulls at ~22 MB, or ~12 MB for the server-only `slater:latest-lite` tag; pure-Rust TLS, no OpenSSL. Pull and run. |
| **Built for periodic publish** | Build a graph offline, serve it immutable, then atomically swap in a new version with zero downtime — ideal for data-warehouse / scheduled-refresh workloads. |
| **Rugged under load** | The server and offline builder both compile with `#![forbid(unsafe_code)]` — the engine's only `unsafe` lives in the audited jemalloc allocator crate. The core is immutable, so reads take no locks and never wait on a writer; a single writer serialises mutations behind the write path alone. No GC pauses, no data races. One bad query can't take the server down. |
| **Works with your neo4j tools** | Speaks Bolt 5.4 / 4.4 / 4.1 — use the standard neo4j drivers (JS, Python, Go, Java…), `cypher-shell`, or graph browsers unchanged. |
| **Rich Cypher query surface** | A broad read surface: `MATCH`/`WHERE`/`WITH`/`UNION`, `CALL {…}` subqueries, 70+ functions & aggregations, temporal & geospatial values, and regex. |
| **Live, durable writes** | An opt-in single-writer LSM layer over the immutable core (`delta.enabled`): business-key `MERGE` / `SET` / `DELETE` / `CREATE` / `REMOVE` over nodes and relationships, batched write-`UNWIND` (one `fsync` per batch), and `CALL slater.consolidate()` — group-committed, `fsync`-durable, and folded back into a fresh core by consolidation. The read path is byte-identical when the delta is empty. |
| **ISO GQL, read and write** | Speaks a subset of **ISO GQL** (ISO/IEC 39075) over the same Bolt connection — quantified paths, path restrictors, shortest-path selectors, label/type boolean expressions, `FOR`, `CAST`, an optional `GQL`/`CYPHER` dialect prefix — and, with the writable layer on, GQL's data-modifying statements (`INSERT` / `SET` / `REMOVE` / `[DETACH] DELETE`) lower onto the same durable write path. Cypher and GQL, reads and writes, in one engine. |
| **Vectors + graph in one engine** | Disk-native ANN vector search (Vamana + PQ; cosine / L2 / dot) for embeddings/RAG, plus graph algorithms (PageRank, BFS, betweenness, WCC…) — bounded memory even with millions of vectors. Embeddings are **writable** (a FreshDiskANN-style write ladder): insert / update / delete a vector, KNN-visible at once, folded into the base without a rebuild. |
| **Safe on network storage** | Every file is BLAKE3 content-hashed and verified on open; torn or half-copied images are refused, not served. Designed for NFS/remote volumes (no mmap surprises). |
| **Pluggable storage backends** | Serve the same generation format from a local filesystem, an S3 (S3-compatible) bucket, **or** a Google Cloud Storage bucket — publish once, fan out to stateless replicas — with an optional local-SSD cache tier in front of the object store. See [Storage backends](#storage-backends-filesystem--s3--gcs). |

Two binaries make up the workspace:

| Binary | Role |
| --- | --- |
| `slater` | The online Bolt server (the container ENTRYPOINT): serves reads and, with `delta.enabled`, the single-writer durable write path. |
| `slater-build` | The offline compiler: turns a primitive-Cypher dump into an immutable, content-hashed generation directory. |

Slater splits *bulk building* from *serving*: `slater-build` does the heavy lifting
offline — ingesting your data and compiling it into an immutable generation — so a
cold graph is never assembled on the serving hot path. Within the server, the read
surface answers a broad Cypher slice — pattern matching, `WITH`/`UNION`/`CALL {…}`
subqueries, 70+ scalar & aggregate functions, temporal & geospatial values, graph
algorithms (`algo.*`), and disk-native vector KNN (`db.idx.vector.queryNodes`) —
while the writable layer's delta overlay sits *below* that surface and is zero-cost
when empty, so reads never carry the write-side machinery. You can update a graph
two ways: write to it live over Bolt (see [The writable layer](#the-writable-layer)),
or build a new generation offline and atomically swap the `current` pointer, which
the running server picks up via its generation guard (see
[Generation guard](#generation-guard)).

## Documentation

The complete **user manual** lives in **[`docs/manual/`](https://github.com/hikari-systems/slater/blob/main/docs/manual/README.md)** —
a feature-by-feature guide that explains, for every capability, what it is, why it
exists, and how to use it, with worked examples you can run against a bundled
sample graph. Start there for anything beyond this overview.

- **New here?** [Quickstart](https://github.com/hikari-systems/slater/blob/main/docs/manual/02-quickstart.md) builds and serves a
  graph in five steps.
- **Writing queries?** [Querying](https://github.com/hikari-systems/slater/blob/main/docs/manual/07-querying.md),
  [Functions & expressions](https://github.com/hikari-systems/slater/blob/main/docs/manual/08-functions-and-expressions.md),
  [Procedures & algorithms](https://github.com/hikari-systems/slater/blob/main/docs/manual/09-procedures-and-algorithms.md),
  [Vector search](https://github.com/hikari-systems/slater/blob/main/docs/manual/10-vector-search.md),
  [Writing data](https://github.com/hikari-systems/slater/blob/main/docs/manual/11-writing-data.md).
- **Building graphs?** [Building graphs](https://github.com/hikari-systems/slater/blob/main/docs/manual/05-building-graphs.md) and the
  [Build CLI reference](https://github.com/hikari-systems/slater/blob/main/docs/manual/06-build-cli-reference.md).
- **Operating Slater?** [Deployment](https://github.com/hikari-systems/slater/blob/main/docs/manual/13-deployment.md),
  [Storage](https://github.com/hikari-systems/slater/blob/main/docs/manual/12-storage.md),
  [Configuration reference](https://github.com/hikari-systems/slater/blob/main/docs/manual/14-configuration-reference.md),
  [Security](https://github.com/hikari-systems/slater/blob/main/docs/manual/15-security.md),
  [Performance tuning](https://github.com/hikari-systems/slater/blob/main/docs/manual/16-performance-tuning.md).

## Using Slater as a Graphiti memory store

[**graphiti-slater**](https://github.com/Hikari-Systems/graphiti-slater) is an adapter
that lets [Graphiti](https://github.com/getzep/graphiti) store its temporal knowledge
graph in Slater, with a runnable
[`docker-example/`](https://github.com/Hikari-Systems/graphiti-slater/tree/develop/docker-example)
— including exposing it to Claude Code as an MCP server. See that repository for how it
works and how to run it.

## Running with Docker

Slater is designed to be run as a **Docker deployment** — that's the expected way
to use it. Prebuilt multi-arch images (`linux/amd64` + `linux/arm64`) are
published to **Docker Hub** at
[**`hikarisystems/slater`**](https://hub.docker.com/r/hikarisystems/slater),
tagged `:latest` and `:vX.Y.Z` on every release:

```sh
docker pull hikarisystems/slater:latest
```

A Docker-command-only usage, configuration, and operations guide lives in
[`DOCKERHUB.md`](https://github.com/hikari-systems/slater/blob/main/DOCKERHUB.md) (and is mirrored to the Docker Hub overview page) —
**start there if you're deploying.** In short:

```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
```

To build the image locally instead (e.g. for development):

```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
```

The builder stage installs `cmake`, `clang` and `libclang-dev` for the rustls
`aws-lc-rs` backend; `git` (already in the base image) is required for the
`hs-utils` git+tag dependency, which `.cargo/config.toml` fetches via the git CLI.

The sections below cover the on-disk format, configuration, ACLs, and a
local (non-Docker) worked example.

## How it works

```
            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>
```

* A **generation** is one immutable directory: a `MANIFEST.json` (symbol tables,
  index descriptors, an optional encryption header), columnar block files
  (`node_props.blk`, `node_labels.blk`, `edge_props.blk`, `topology.csr.blk`,
  `vectors.f32.blk`), range indexes (`range/<name>.isam`), above-threshold ANN
  indexes (`vector/<label>.<prop>.{vamana,pq}`), and a `current` text pointer.
* Every block is zstd-compressed and BLAKE3-checksummed; with `--encrypt` each
  block is additionally sealed with XChaCha20-Poly1305 (AEAD at rest).
* The server opens a generation by **re-hashing every file** against the manifest,
  so a half-copied / truncated image — a torn copy onto the data dir, which may be
  remote/network storage — is refused rather than served.
* Reads flow through **three bounded cache pools** — a decompressed-block LRU, a
  vector-index pool (resident PQ codes + a Vamana-block LRU), and a result LRU —
  each with its own byte budget. Each pool weighs what it holds and evicts to stay
  under its budget, so RSS tracks the budgets to within bounded per-entry and
  allocator overhead instead of growing with the graph.

### The writable layer

With `delta.enabled`, the immutable generation becomes the **fully-compacted bottom
level (the "core")** of a small log-structured-merge tree, and live writes ride on
top of it:

```
   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)
                                 └─────────────┘
```

* **Durability floor — the WAL.** Each mutation is serialised behind a single
  writer per graph, appended to a per-graph write-ahead log, and `fsync`ed before
  the Bolt `SUCCESS` is returned — so *acknowledged ⇒ durable*, and a torn tail is
  dropped on replay. A batched write-`UNWIND` appends its rows and commits **one**
  `fsync` for the whole batch. The WAL is **local disk only** (it is not routed
  through the storage backend), which makes a *writer* node stateful: it needs a
  durable local volume at `delta.walDir`. Read replicas stay stateless.
* **Memtable → L0 → consolidation.** Writes accumulate in an in-RAM memtable
  (bounded by `delta.memtableBytes`); when it fills it flushes to an immutable L0
  delta segment. A **consolidation** folds `{core + delta}` into a fresh core by
  serialising the merged view back through `slater-build` and swapping `current`
  atomically — the same content-hash guard as any published generation. Trigger it
  by hand with `CALL slater.consolidate()`, automatically at `delta.deltaCorePercent`
  of the core's size (optionally gated to an off-peak `delta.consolidateWindow`), or
  let the `delta.deltaHardBytes` throttle backstop runaway growth.
* **The overlay sits below the read surface.** The executor reads through a
  `ReadView` that is either the bare core (delta always empty) or a merged
  `(core, delta)` view; the engine is monomorphised over it, so an empty delta
  compiles to a single predictable branch and the read-only path is byte-identical.
  Whole-graph counters (`count(*)`, label/reltype marginals) are served from the
  delta's own live counters, so they stay metadata reads even with writes pending.
* **A query sees a stable snapshot.** It pins one `(core, delta)` tuple for its
  whole life. There are no multi-statement transactions and no rollback — a write is
  a durable, business-key-addressed correction, not an OLTP transaction.

The exact write grammar and knobs are in the [Configuration](#environment--configuration)
table (`delta.*`) and the [Worked example](#worked-example) below.

### Range indexes (ISAM)

A range index (`range/<name>.isam`, one per indexed `(label, property)`) lets a
`MATCH (n:Label {prop: v})` or `WHERE n.prop <op> v` resolve to the matching node
ids **without scanning the label**. It is an
**[ISAM](https://en.wikipedia.org/wiki/ISAM)** (Indexed Sequential Access Method)
structure — the classic *static, sorted, block-structured* index, which is exactly
the right shape for an immutable generation: there are no inserts to rebalance, so
the simplicity of ISAM buys what a B-tree's mutation machinery would only
complicate.

* Entries `(value, entity_id)` are sorted by value and packed into the same
  zstd-compressed 256 KiB blocks as everything else.
* A small **resident top-level** holds the first key of each block (a sparse
  index). A lookup binary-searches that in-memory top level to find the *one* block
  a key can be in, reads + decompresses that block, and scans it — so an equality
  lookup is **one block read**, and a range scan walks the contiguous run of blocks
  it spans. (This is why a `meshUi`-indexed lookup is single-digit milliseconds
  while the same match on an unindexed property scans the whole label.)
* The planner picks it via `NodeScan::RangeEq` / `RangeRange`; an unindexed
  predicate falls back to a label sweep or full scan, with the executor re-checking
  every predicate either way.

### Vector search (Vamana + PQ) — cosine, L2 and dot, read *and* write

Vector KNN (`db.idx.vector.queryNodes`) runs over **cosine, L2, or dot-product (MIPS)**
indexes. The base index is built offline with two execution paths, chosen per index by
the `--ann-threshold` (default 50 000 vectors):

* **Below the threshold — brute force.** The full `f32` vectors live in
  `vectors.f32.blk`; a query scans the index's group and computes the exact distance in
  the index's metric. Simple and exact; fine when the vector set is small.
* **At or above the threshold — Vamana + PQ**, the disk-native ANN path that keeps
  resident memory bounded regardless of how many vectors there are:
  * **[Vamana](https://arxiv.org/pdf/2401.11324)** is the graph index from the
    DiskANN line of work: a single proximity graph whose edges are pruned (the
    `--vamana-r` out-degree and `--vamana-alpha` long-edge factor) so a *greedy
    beam search* — start at the medoid, repeatedly hop toward the query, keeping a
    candidate list of width `vectorQuery.beamWidth` — reaches a node's true
    neighbours in few hops, i.e. **few random block reads per query**. The graph
    blocks (`vector/<label>.<prop>.vamana`) are paged in through the vector cache,
    not held wholesale.
  * **[Product quantisation (PQ)](https://medium.com/aiguys/product-quantization-k-nn-for-big-datasets-12431d764c4e)**
    compresses each vector into a short code (`--pq-subspaces` × `--pq-bits`): the
    dimensions are split into subspaces, each independently k-means-clustered, and
    the vector is stored as the tuple of nearest-centroid ids. These codes
    (`vector/<label>.<prop>.pq`) are small enough to keep **resident**, so the
    beam search scores candidates from RAM and only the chosen few full vectors are
    read from disk. That resident PQ set is what the `cache.vectorCacheBytes` pool
    pins.

**Writable embeddings — the vector write ladder ([FreshDiskANN](https://arxiv.org/abs/2105.09613)-style).**
An indexed embedding is a first-class writable value. `SET n.embedding = vecf32([…])` (and `REMOVE`) lands in the
write delta and is **immediately KNN-visible with exact rank**, then survives a segment
flush, a merge, and a consolidation. A query merges up to three levels — the sealed base
index, a sealed per-segment index, and an in-memory **RW-index** (a live mutable Vamana
over the write delta) — so latency stays flat as writes accumulate rather than growing
with the count of pending writes. A delete leaves a *hole*: the node stops being returned
but stays a navigational waypoint until a background **delete-consolidation** splices it
out of the graph, so deletes stop costing query IO. And because the on-disk graph
addresses its neighbours by layout position rather than node id, `CALL slater.consolidate()`
carries the Vamana **by reference** — hard-linked, byte-identical — and rewrites only a
small id column, folding vector writes into the base **without** the O(N·R·L) graph
rebuild. Measured numbers, with caveats, are in the [performance report](https://github.com/hikari-systems/slater/blob/main/docs/PERF-REPORT.md).

## Storage backends (filesystem / S3 / GCS)

Every generation file is opened through an **`ObjectStore`** abstraction rather
than `std::fs` directly, so the *same* on-disk byte format — blocks, indexes,
manifest, `current` pointer — is served unchanged from any backend; only *where
the bytes come from* differs, never the readers, the query engine, or the
integrity checks. The hot path is positional reads (`read_exact_at`), which map
onto a `pread` on a local file and an HTTP byte-range request on an object store
— Slater never mmaps, so the explicit, bounded-read model is identical
everywhere.

**Three first-class backends**, selected by `dataBackend.kind`. The filesystem is
the simple default; **Amazon S3 and Google Cloud Storage are equal, fully
supported object-store backends** — the published image ships with both compiled
in, so each is configuration-only, and a generation built once can be served from
any of them (even migrated `fs` → S3 → GCS) without a rebuild.

| `dataBackend.kind` | Positional read | Integrity at open | Credentials |
| --- | --- | --- | --- |
| `fs` *(default)* | `pread` | full BLAKE3 re-hash of each file | — |
| `s3` | HTTP `Range` GET | server **SHA-256** via `HEAD` (→ BLAKE3 body re-hash if absent) | config keys, AWS chain, or IAM role |
| `gcs` | HTTP range read | server **CRC32C** via `get_object` (→ BLAKE3 body re-hash if absent) | ADC / Workload Identity, or service-account JSON |

Both object stores verify integrity from the **checksum the store already
computes and keeps**, fetched as object metadata: `slater-build` sends the
checksum on upload (the store validates the bytes against it and stores it), and
the server reads it back at open and compares it to the manifest — one metadata
request per file, no body download. It is content-grade and identical in spirit
across S3 (SHA-256) and GCS (CRC32C). When an object carries **no** server-stored
checksum (copied in out-of-band, or uploaded with a different default), the server
**re-hashes the object body against the manifest BLAKE3** rather than trusting its
byte length — a requested integrity check is never silently downgraded to a size
comparison. Slater-published generations always carry the checksum, so they stay on
the cheap metadata path.

What this column checks, on every backend, is that the files **match the manifest**.
Whether the manifest itself can be trusted is a separate question, and it is the
master key that answers it: with a key configured the manifest carries a keyed MAC
that the server verifies before it trusts any field (including these hashes), so a
manifest rewritten to describe tampered files is refused; without a key the comparison is unkeyed throughout, and
someone who can write to the data directory can rewrite a file and the manifest
together. See
[What integrity means in each configuration](https://github.com/hikari-systems/slater/blob/main/THREAT_MODEL.md#what-integrity-means-in-each-configuration).
The check itself can be turned off with `dataBackend.verifyIntegrity: false`, which
trades it for a faster open.

### Filesystem (`fs`)

The default, rooted at `dataBackend.fs.dir`. The right choice for most
deployments: a generation on a local SSD (or an NFS/EBS mount) served read-only.
Integrity is a full BLAKE3 re-hash of every file at open.

### Amazon S3 (`s3`)

An S3 or S3-compatible bucket (AWS, MinIO, localstack). Credentials come **first**
from config (`dataBackend.s3.awsAccessKey` / `awsSecretKey`, plus
`awsSessionToken` for temporary STS credentials) and fall back to the standard AWS
chain (`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` env, shared profile, or
instance/IRSA role) when left empty.

```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
```
```sh
# 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`)

A GCS bucket, reached over the JSON API. Authorization is GCP-native: by default
it resolves **Application Default Credentials** — GKE Workload Identity, the GCE
metadata server, or a `gcloud` / `GOOGLE_APPLICATION_CREDENTIALS` key. Set
`dataBackend.gcs.credentialsPath` (a service-account JSON key file) or inline
`credentialsJson` for an explicit key. `dataBackend.gcs.endpoint` points at a
`fake-gcs-server` emulator, and `dataBackend.gcs.anonymous=true` enables
unauthenticated access **for that emulator only** — never against real GCS.

```sh
# serve from GCS (env-var form; see the config table for every key)
dataBackend__kind=gcs
```

---

[Read more](https://github.com/hikari-systems/slater)
下载工具