Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
slater — Speicherschonende graphdb mit Bolt+tls-Unterstützung, Verschlüsselung im Ruhezustand und Vektoren, entwickelt für lokale Replikat-Graph-Anwendungsfälle. | Kitploit
Tools/GitHubGitHub/hikari-systems/slater
Authentifizierung & AutorisierungVerschlüsselungs-/EntschlüsselungstoolsNetzwerksicherheitCloud-SicherheitDienstprogramme & FrameworksDatenbanksicherheit
GitHubhikari-systems/slater

slater

Speicherschonende graphdb mit Bolt+tls-Unterstützung, Verschlüsselung im Ruhezustand und Vektoren, entwickelt für lokale Replikat-Graph-Anwendungsfälle.

Repository anzeigen
10023vor 24 TagenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

Slater

CI Release

Aktuelle Version: v0.25.2 — alle Releases.

In einem Satz: Slater bedient Graphen, die nicht in den Speicher passen — Hunderte Millionen Knoten und Milliarden Kanten in wenigen hundert MB RAM — über standardmäßiges Bolt, sodass jeder neo4j-Treiber einfach funktioniert, mit disk-nativem Vektor-Search direkt neben dem Graphen, und es nimmt live, dauerhafte Schreibvorgänge entgegen, ohne darauf zu verzichten. Der residente Speicher wird durch ein Cache-Budget bestimmt, das Sie wählen, nicht durch die Größe des Graphen.


Verknüpfungen

Warum Slater existiertLesen und SchreibenWas Sie bekommenFunktionen
Ausführen mit DockerSo funktioniert esDie beschreibbare SchichtSpeicher-Backends
MountsKonfigurationACLHealth Check
ArbeitsbeispielEntwicklungLeistungLizenz
Graphiti-Speicher📖 Vollständiges Handbuch

Warum Slater existiert

Eine Graphdatenbank speichert Daten als Dinge (Knoten) und die Beziehungen zwischen ihnen (Kanten), wobei die Beziehungen erstklassige Bürger sind. Das ist genau das, was Sie brauchen, wenn Ihre Fragen sich um Verbindungen drehen und nicht um Zeilen — „Wer ist innerhalb von drei Hops von diesem Konto entfernt?“, „Was ist die vollständige Abhängigkeitskette hinter diesem Build?“, „Welche Konten teilen sich ein Gerät, eine Adresse und eine Karte?“ — die Abfragen, die in SQL zu einem Sumpf rekursiver Joins werden, sich in einem Graphen aber natürlich ergeben.

Die häufigste Beschwerde über Graphdatenbanken ist, dass sie nicht über das hinaus skalieren, was Sie im RAM halten können. Viele von ihnen (z. B. neo4j, Memgraph, FalkorDB usw.) halten den gesamten Graphen resident: Ein 40 GB-Graph benötigt 40 GB Speicher — pro Instanz. Möchten Sie eine Replik pro Region, pro Mandant oder pro Pod? Multiplizieren Sie die Rechnung. Und ab einer bestimmten Größe laden sie einfach nicht mehr: Der Wikidata-Graph mit 90 Millionen Knoten / 1,5 Milliarden Kanten benötigt ~64–128 GiB resident, sodass die In-Memory-Engines ihn überhaupt nicht öffnen können.

Slater ist die Widerlegung. Anstatt den Graphen in den Speicher zu laden, kompiliert es ihn einmal, offline: slater-build verwandelt Ihre Daten in ein content-addressed, unveränderliches On-Disk-Image, und beliebig viele Slater-Server bedienen dieses Image dann über Bolt (sodass Ihre vorhandenen neo4j-Treiber einfach funktionieren), pagen Blöcke bei Bedarf ein und halten nur ein festes Cache-Budget resident. So wird derselbe 90M-Knoten-Graph aus ein paar hundert MB RAM bedient — Graphgröße und Speicherrechnung sind entkoppelt. Ein 4 GB-Graph und ein 400 GB-Graph kosten denselben RAM zum Bedienen, sodass Sie günstige, zustandslose Lese-Repliken ausrollen und den Store, nicht den Heap, den Graphen halten lassen.

Das macht ihn zu einer natürlichen Wahl für Wissensgraphen hinter RAG, Empfehlungs- und Identitätsgraphen, Abhängigkeitsgraphen — alles Große und Verbundene, das Sie günstig und häufig abfragen möchten. Disk-nativer Vektor-Search lebt direkt neben dem Graphen, sodass dieselbe Engine auch die Retrieval-Schicht für Embeddings ist.

Einmal kompiliert bedeutet jedoch nicht eingefroren. Dieses Image ist eine Basis, kein Endzustand: Eine optionale Schreibschicht sitzt darüber, sodass ein Live-Graph korrigiert und erweitert werden kann, ohne etwas neu zu bauen.

Lesen und Schreiben

Der Kern ist unveränderlich; der Graph ist es nicht. Aktivieren Sie die beschreibbare Schicht (delta.enabled) und Sie schreiben über Bolt — korrigieren Sie eine Eigenschaft, fügen Sie einen Knoten hinzu, ziehen Sie eine Kante zurück — und die Änderung landet dauerhaft, ohne Neubau des Images. Was es auf der Leseseite günstig hält, ist wo die Schreibvorgänge leben.

Schreibvorgänge sammeln sich in einer Log-Structured-Merge (LSM)-Schicht über dem unveränderlichen Kern: ein Write-Ahead-Log und eine In-Memory-Tabelle, die in unveränderliche Delta-Segmente überläuft und durch eine periodische Konsolidierung in einen frischen Kern zurückgefaltet wird. Was Ihnen das bringt:

  • Lesevorgänge über einen unbeschriebenen Graphen kosten genau das, was sie vorher gekostet haben. Ein leeres Delta ist ein einzelner vorhersehbarer Zweig, kein Merge — der Lesepfad ist byte-identisch, ob die beschreibbare Schicht nun an ist oder nicht.
  • Die Lesekosten eines Schreibvorgangs skalieren mit der Größe des Deltas, nicht mit der Größe des Graphen. Ganz-Graph-Antworten — count(*), die Label- und Beziehungstyp-Marginalien — bleiben Metadaten-Lesevorgänge, selbst bei ausstehenden Schreibvorgängen: Das Delta führt seine eigenen Zähler, sodass ein count(*) über einen 91,6M-Knoten-Kern mit einer halben Million ausstehender Schreibvorgänge immer noch in zig Millisekunden antwortet, ohne einen einzigen Block zu berühren.
  • Bestätigt bedeutet dauerhaft. Ein einzelner Schreiber leert die Warteschlange und gibt SUCCESS erst nach dem fsync zurück, das den Schreibvorgang abdeckt. Bündeln Sie Ihre Schreibvorgänge und sie sind günstig — ein Schreib-UNWIND committet ein fsync pro Batch statt pro Zeile.
  • Business-Key-Schreibvorgänge, in beiden Dialekten. MERGE / MATCH … SET / DELETE (und CREATE / , Detach-Delete, Beziehungsschreibvorgänge), die auf die Identitätseigenschaft eines Knotens schlüsseln — oder die äquivalenten ISO-GQL-Datenmodifikationsanweisungen ( / / / ), die auf denselben Pfad herunterbrechen. Korrigieren, einfügen, upserten und zurückziehen, über Knoten und Kanten, adressiert so, wie Ihre Daten bereits sind.

Bei deaktivierter Schicht — dem Standard — bedient Slater den reinen unveränderlichen Kern und verweigert Schreibvorgänge. Siehe Die beschreibbare Schicht für das vollständige Modell.

Zum Namen. Slater ist nach dem CIA-Agenten in Archer (einer großartigen Serie) benannt, der darauf besteht, nur mit einem Namen genannt zu werden — „Just… Slater“ — und einer meiner Lieblingsfiguren darin. Siehe die Charakter-Wiki-Seite.

Was Sie bekommen

  • RAM bestimmt durch Ihr Cache-Budget, nicht durch Ihre Graphgröße — rollen Sie so viele Lese-Repliken aus, wie Sie möchten; der Graph muss nie in den Speicher passen.
  • Ein Drop-in für den Graphen — spricht Bolt, sodass jeder Standard-neo4j-Treiber (JS, Python, Go…) unverändert funktioniert. Es ist Cypher (plus ein Teil von ISO GQL, Lesen und Schreiben); nichts Neues zu lernen.
  • Live, dauerhafte Schreibvorgänge — eine optionale LSM-Schicht über dem unveränderlichen Kern: Business-Key-MERGE / SET / DELETE über Knoten und Kanten, gruppen-committet und fsync-dauerhaft, durch Konsolidierung in einen frischen Kern zurückgefaltet. Lesevorgänge zahlen nicht dafür.
  • Bereitstellung durch Dateiaustausch — bauen Sie eine neue content-gehashte Generation offline, schalten Sie den current-Zeiger atomar um, und Server nehmen sie auf. Jeder Block ist checksummiert, sodass ein halb kopiertes Image verweigert statt bedient wird.
  • Integrierter Vektor-Search — disk-natives Approximate-Nearest-Neighbour (Cosinus, L2 oder Dot KNN) sitzt direkt neben Ihrem Graphen, für den Fall, dass dies die Retrieval-Schicht hinter einer RAG-Pipeline ist, und Embeddings sind an Ort und Stelle beschreibbar — kein Offline-Neubau, um einen Vektor hinzuzufügen oder zu ändern.
  • Von Grund auf abgesichert — Lese- und Schreibberechtigungen sind unabhängig, plus optionale Verschlüsselung im Ruhezustand, TLS Bolt, argon2id-gehashte ACLs und ein schreibgeschütztes Container-Rootfs für Lese-Repliken. Konfigurieren Sie einen Master-Key und das On-Disk-Image ist authentifiziert sowie verschlüsselt — sein Manifest trägt einen keyed MAC, sodass ein Angreifer mit Schreibzugriff auf das Datenverzeichnis, aber ohne Schlüssel, kein Manifest fälschen kann, das der Server akzeptiert. Ohne Schlüssel erhalten Sie weiterhin den Content-Hash, der ein halb kopiertes oder beschädigtes Image erkennt — aber kein absichtliches. Welche Konfiguration was kauft.

Funktionen

Zwei Binaries bilden den Workspace:

BinaryRolle
slaterDer Online-Bolt-Server (der Container-ENTRYPOINT): bedient Lesevorgänge und, mit delta.enabled, den Single-Writer-dauerhaften Schreibpfad.
slater-buildDer Offline-Compiler: verwandelt einen primitiven Cypher-Dump in ein unveränderliches, content-gehashtes Generationsverzeichnis.

Slater trennt Massen-Building vom Bedienen: slater-build erledigt die schwere Arbeit offline — nimmt Ihre Daten auf und kompiliert sie in eine unveränderliche Generation — sodass ein kalter Graph nie auf dem heißen Serving-Pfad zusammengesetzt wird. Innerhalb des Servers beantwortet die Leseoberfläche eine breite Cypher-Scheibe — Pattern-Matching, WITH/UNION/CALL {…}- Subqueries, 70+ Skalar- & Aggregatfunktionen, temporale & geospatiale Werte, Graph- Algorithmen (algo.*) und disk-natives Vektor-KNN (db.idx.vector.queryNodes) — während das Delta-Overlay der beschreibbaren Schicht unter dieser Oberfläche sitzt und bei Leere null Kosten hat, sodass Lesevorgänge nie die Schreibseiten-Mechanik tragen. Sie können einen Graphen auf zwei Arten aktualisieren: live über Bolt darauf schreiben (siehe Die beschreibbare Schicht), oder eine neue Generation offline bauen und den current-Zeiger atomar tauschen, den der laufende Server über seine Generationswache aufnimmt (siehe Generationswache).

Dokumentation

Das vollständige Benutzerhandbuch lebt in docs/manual/ — ein funktionsweiser Leitfaden, der für jede Fähigkeit erklärt, was sie ist, warum sie existiert und wie man sie nutzt, mit Arbeitsbeispielen, die Sie gegen einen gebündelten Beispielgraphen ausführen können. Beginnen Sie dort für alles über diese Übersicht hinaus.

  • Neu hier? Quickstart baut und bedient einen Graphen in fünf Schritten.
  • Abfragen schreiben? Abfragen, Funktionen & Ausdrücke, Prozeduren & Algorithmen, Vektor-Search, Daten schreiben.
  • Graphen bauen? Graphen bauen und die Build-CLI-Referenz.
  • Slater betreiben? Bereitstellung, Speicher, Konfigurationsreferenz, Sicherheit, Leistungsoptimierung.

Verwendung von Slater als Graphiti-Speicher

graphiti-slater ist ein Adapter, der es Graphiti ermöglicht, seinen temporalen Wissens- graphen in Slater zu speichern, mit einem ausführbaren docker-example/ — einschließlich der Bereitstellung für Claude Code als MCP-Server. Siehe dieses Repository für wie es funktioniert und wie man es ausführt.

Ausführen mit Docker

Slater ist dafür ausgelegt, als Docker-Bereitstellung ausgeführt zu werden — das ist der erwartete Weg, es zu nutzen. Vorgebaute Multi-Arch-Images (linux/amd64 + linux/arm64) werden auf Docker Hub unter hikarisystems/slater veröffentlicht, mit :latest und :vX.Y.Z bei jedem Release getaggt:```sh docker pull hikarisystems/slater:latest

root@kitploit:~
Eine Anleitung zur reinen Docker-Befehlsnutzung, -konfiguration und zum Betrieb findest du in
[`DOCKERHUB.md`](https://github.com/hikari-systems/slater/blob/main/DOCKERHUB.md) (und wird auf der Docker-Hub-Übersichtsseite gespiegelt) —
**beginne dort, wenn du bereitstellst.** Kurz gesagt:```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

Um das Image stattdessen lokal zu bauen (z. B. für die Entwicklung):```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:~
Der Builder-Stage installiert `cmake`, `clang` und `libclang-dev` für das rustls-`aws-lc-rs`-Backend; `git` (bereits im Basis-Image enthalten) wird für die `hs-utils`-git+tag-Abhängigkeit benötigt, die `.cargo/config.toml` über die git-CLI abruft.

Die folgenden Abschnitte behandeln das On-Disk-Format, die Konfiguration, ACLs und ein lokales (nicht-Docker-)Arbeitsbeispiel.

## So funktioniert es```
            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>
  • Eine Generation ist ein unveränderliches Verzeichnis: eine MANIFEST.json (Symboltabellen, Indexdeskriptoren, ein optionaler Verschlüsselungsheader), spaltenbasierte Blockdateien (node_props.blk, node_labels.blk, edge_props.blk, topology.csr.blk, vectors.f32.blk), Bereichsindizes (range/<name>.isam), ANN-Indizes über dem Schwellenwert (vector/<label>.<prop>.{vamana,pq}) sowie ein current-Textzeiger.
  • Jeder Block ist zstd-komprimiert und mit BLAKE3-Prüfsumme versehen; mit --encrypt wird jeder Block zusätzlich mit XChaCha20-Poly1305 versiegelt (AEAD im Ruhezustand).
  • Der Server öffnet eine Generation, indem er jede Datei erneut hasht und gegen das Manifest prüft, sodass ein halb kopiertes / abgeschnittenes Abbild — eine zerrissene Kopie im Datenverzeichnis, das sich auf Remote-/Netzwerkspeicher befinden kann — abgelehnt statt ausgeliefert wird.
  • Lesezugriffe laufen über drei begrenzte Cache-Pools — einen LRU für dekomprimierte Blöcke, einen Vektorindex-Pool (residente PQ-Codes + ein Vamana-Block-LRU) und einen Ergebnis-LRU — jeweils mit eigenem Byte-Budget. Jeder Pool wiegt, was er hält, und verdrängt, um unter seinem Budget zu bleiben, sodass der RSS die Budgets innerhalb begrenzter Pro-Eintrag- und Allokator-Overheads verfolgt, statt mit dem Graphen zu wachsen.

Die beschreibbare Schicht

Mit delta.enabled wird die unveränderliche Generation zur vollständig kompaktierten untersten Ebene (dem „Kern") eines kleinen log-strukturierten Merge-Baums, und Live-Schreibvorgänge liegen darüber:``` 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:~
* **Haltbarkeits-Boden — das WAL.** Jede Mutation wird hinter einem einzelnen
  Writer pro Graph serialisiert, an ein pro-Graph-Write-Ahead-Log angehängt und
  `fsync`-ed, bevor der Bolt-`SUCCESS` zurückgegeben wird — also *bestätigt ⇒
  dauerhaft*, und ein abgerissener Schwanz wird beim Replay verworfen. Ein
  gebatchtes Write-`UNWIND` hängt seine Zeilen an und committet **ein**
  `fsync` für den gesamten Batch. Das WAL ist **nur lokaler Datenträger** (es wird
  nicht über das Storage-Backend geleitet), was einen *Writer*-Knoten zustandsbehaftet
  macht: Er benötigt ein dauerhaftes lokales Volume unter `delta.walDir`.
  Read-Replicas bleiben zustandslos.
* **Memtable → L0 → Konsolidierung.** Schreibvorgänge sammeln sich in einer
  In-RAM-Memtable (begrenzt durch `delta.memtableBytes`); wenn sie voll ist,
  spült sie in ein unveränderliches L0-Delta-Segment. Eine **Konsolidierung**
  faltet `{core + delta}` in einen frischen Core, indem sie die zusammengeführte
  Ansicht zurück durch `slater-build` serialisiert und `current` atomar austauscht
  — derselbe Content-Hash-Schutz wie bei jeder veröffentlichten Generation. Löse
  sie von Hand mit `CALL slater.consolidate()` aus, automatisch bei
  `delta.deltaCorePercent` der Core-Größe (optional begrenzt auf ein
  Off-Peak-`delta.consolidateWindow`), oder lass den
  `delta.deltaHardBytes`-Drossel als Rückhalt gegen unkontrolliertes Wachstum
  dienen.
* **Das Overlay sitzt unterhalb der Leseoberfläche.** Der Executor liest durch
  eine `ReadView`, die entweder der nackte Core ist (Delta immer leer) oder eine
  zusammengeführte `(core, delta)`-Ansicht; die Engine ist darüber monomorphisiert,
  sodass ein leeres Delta zu einem einzigen vorhersagbaren Zweig kompiliert und der
  Nur-Lese-Pfad byte-identisch ist. Graphweite Zähler (`count(*)`,
  Label-/Reltype-Marginalien) werden aus den eigenen Live-Zählern des Deltas
  bedient, sodass sie auch bei ausstehenden Schreibvorgängen Metadaten-Lesevorgänge
  bleiben.
* **Eine Abfrage sieht einen stabilen Snapshot.** Sie pinnt ein `(core, delta)`-Tupel
  für ihre gesamte Lebensdauer. Es gibt keine Multi-Statement-Transaktionen und kein
  Rollback — ein Schreibvorgang ist eine dauerhafte, über den Business-Key
  adressierte Korrektur, keine OLTP-Transaktion.

Die genaue Schreibgrammatik und die Stellschrauben stehen in der Tabelle
[Konfiguration](#environment--configuration) (`delta.*`) und im
[Arbeitsbeispiel](#worked-example) unten.

### Bereichsindizes (ISAM)

Ein Bereichsindex (`range/<name>.isam`, einer pro indiziertem `(label, property)`)
ermöglicht es einem `MATCH (n:Label {prop: v})` oder `WHERE n.prop <op> v`, die
passenden Knoten-IDs **ohne Scannen des Labels** aufzulösen. Es ist eine
**[ISAM](https://en.wikipedia.org/wiki/ISAM)**-Struktur (Indexed Sequential Access
Method) — der klassische *statische, sortierte, blockstrukturierte* Index, der
genau die richtige Form für eine unveränderliche Generation ist: Es gibt keine
Einfügungen, die neu ausbalanciert werden müssten, sodass die Einfachheit von ISAM
das erkauft, was die Mutationsmechanik eines B-Baums nur verkomplizieren würde.

* Einträge `(value, entity_id)` sind nach Wert sortiert und in dieselben
  zstd-komprimierten 256-KiB-Blöcke gepackt wie alles andere.
* Eine kleine **residente oberste Ebene** hält den ersten Schlüssel jedes Blocks
  (ein Sparse-Index). Eine Suche binärsucht diese In-Memory-Oberebene, um den
  *einen* Block zu finden, in dem ein Schlüssel sein kann, liest + dekomprimiert
  diesen Block und scannt ihn — eine Gleichheitssuche ist also **ein Block-Lesen**,
  und ein Bereichsscan läuft durch den zusammenhängenden Blocklauf, den er
  überspannt. (Deshalb ist eine `meshUi`-indizierte Suche im einstelligen
  Millisekundenbereich, während derselbe Match auf einer nicht indizierten
  Eigenschaft das gesamte Label scannt.)
* Der Planner wählt ihn über `NodeScan::RangeEq` / `RangeRange`; ein nicht
  indiziertes Prädikat fällt auf einen Label-Sweep oder Vollscan zurück, wobei der
  Executor jedes Prädikat in beiden Fällen erneut prüft.

### Vektorsuche (Vamana + PQ) — Kosinus, L2 und Punktprodukt, Lesen *und* Schreiben

Vektor-KNN (`db.idx.vector.queryNodes`) läuft über **Kosinus-, L2- oder
Punktprodukt- (MIPS)**-Indizes. Der Basisindex wird offline mit zwei
Ausführungspfaden erstellt, die pro Index durch den `--ann-threshold` (Standard
50 000 Vektoren) gewählt werden:

* **Unterhalb des Schwellenwerts — Brute Force.** Die vollständigen `f32`-Vektoren
  liegen in `vectors.f32.blk`; eine Abfrage scannt die Gruppe des Index und
  berechnet die exakte Distanz in der Metrik des Index. Einfach und exakt; in
  Ordnung, wenn der Vektorsatz klein ist.
* **Bei oder oberhalb des Schwellenwerts — Vamana + PQ**, der
  disk-native-ANN-Pfad, der den residenten Speicher unabhängig von der Anzahl der
  Vektoren begrenzt hält:
  * **[Vamana](https://arxiv.org/pdf/2401.11324)** ist der Graphindex aus der
    DiskANN-Arbeitslinie: ein einzelner Näherungsgraph, dessen Kanten beschnitten
    sind (der `--vamana-r`-Ausgangsgrad und der `--vamana-alpha`-Langfaktor), sodass
    eine *gierige Beam-Suche* — Start am Medoid, wiederholtes Springen in Richtung
    der Abfrage, wobei eine Kandidatenliste der Breite `vectorQuery.beamWidth`
    gehalten wird — die wahren Nachbarn eines Knotens in wenigen Sprüngen erreicht,
    d. h. **wenige zufällige Block-Lesevorgänge pro Abfrage**. Die Graphblöcke
    (`vector/<label>.<prop>.vamana`) werden über den Vektor-Cache eingepagert,
    nicht vollständig gehalten.
  * **[Produktquantisierung (PQ)](https://medium.com/aiguys/product-quantization-k-nn-for-big-datasets-12431d764c4e)**
    komprimiert jeden Vektor in einen kurzen Code (`--pq-subspaces` × `--pq-bits`):
    Die Dimensionen werden in Unterräume aufgeteilt, jeder wird unabhängig
    k-means-geclustert, und der Vektor wird als Tupel der nächsten
    Zentroid-IDs gespeichert. Diese Codes (`vector/<label>.<prop>.pq`) sind klein
    genug, um **resident** zu bleiben, sodass die Beam-Suche Kandidaten aus dem RAM
    bewertet und nur die ausgewählten wenigen vollständigen Vektoren von der Platte
    gelesen werden. Diese residente PQ-Menge ist es, die der
    `cache.vectorCacheBytes`-Pool pinnt.

**Beschreibbare Einbettungen — die Vektor-Schreibleiter (im
[FreshDiskANN](https://arxiv.org/abs/2105.09613)-Stil).** Eine indizierte
Einbettung ist ein erstklassiger beschreibbarer Wert. `SET n.embedding = vecf32([…])`
(und `REMOVE`) landet im Schreib-Delta und ist **sofort KNN-sichtbar mit exaktem
Rang**, überlebt dann einen Segment-Flush, einen Merge und eine Konsolidierung.
Eine Abfrage führt bis zu drei Ebenen zusammen — den versiegelten Basisindex, einen
versiegelten Pro-Segment-Index und einen In-Memory-**RW-Index** (ein lebendiges
mutierbares Vamana über dem Schreib-Delta) — sodass die Latenz flach bleibt, wenn
Schreibvorgänge sich ansammeln, statt mit der Anzahl ausstehender Schreibvorgänge
zu wachsen. Ein Löschen hinterlässt ein *Loch*: Der Knoten wird nicht mehr
zurückgegeben, bleibt aber ein Navigations-Wegpunkt, bis eine Hintergrund-
**Lösch-Konsolidierung** ihn aus dem Graph herausspleißt, sodass Löschvorgänge
keine Abfrage-IO mehr kosten. Und weil der On-Disk-Graph seine Nachbarn über die
Layout-Position statt über die Knoten-ID adressiert, trägt
`CALL slater.consolidate()` das Vamana **per Referenz** — hart-verlinkt,
byte-identisch — und schreibt nur eine kleine ID-Spalte neu, wodurch
Vektor-Schreibvorgänge **ohne** den O(N·R·L)-Graph-Rebuild in die Basis gefaltet
werden. Gemessene Zahlen, mit Einschränkungen, stehen im
[Leistungsbericht](https://github.com/hikari-systems/slater/blob/main/docs/PERF-REPORT.md).

## Storage-Backends (Dateisystem / S3 / GCS)

Jede Generationsdatei wird über eine **`ObjectStore`**-Abstraktion geöffnet und
nicht direkt über `std::fs`, sodass *dasselbe* On-Disk-Byteformat — Blöcke,
Indizes, Manifest, `current`-Zeiger — unverändert von jedem Backend bedient wird;
nur *woher die Bytes kommen* unterscheidet sich, niemals die Leser, die
Abfrage-Engine oder die Integritätsprüfungen. Der heiße Pfad sind positionelle
Lesevorgänge (`read_exact_at`), die auf ein `pread` auf einer lokalen Datei und
eine HTTP-Byte-Range-Anfrage auf einem Objekt-Store abgebildet werden — Slater
verwendet nie mmap, sodass das explizite, begrenzte Lesemodell überall identisch
ist.

**Drei erstklassige Backends**, ausgewählt durch `dataBackend.kind`. Das
Dateisystem ist die einfache Standardoption; **Amazon S3 und Google Cloud Storage
sind gleichwertige, vollständig unterstützte Objekt-Store-Backends** — das
veröffentlichte Image wird mit beiden kompiliert ausgeliefert, sodass jedes nur
Konfiguration ist, und eine einmal erstellte Generation kann von jedem von ihnen
bedient werden (sogar migriert `fs` → S3 → GCS) ohne Neubuild.

| `dataBackend.kind` | Positionelles Lesen | Integrität beim Öffnen | Anmeldedaten |
| --- | --- | --- | --- |
| `fs` *(Standard)* | `pread` | vollständiger BLAKE3-Re-Hash jeder Datei | — |
| `s3` | HTTP `Range` GET | Server-**SHA-256** über `HEAD` (→ BLAKE3-Body-Re-Hash, falls nicht vorhanden) | Konfig-Schlüssel, AWS-Kette oder IAM-Rolle |
| `gcs` | HTTP-Bereichslesen | Server-**CRC32C** über `get_object` (→ BLAKE3-Body-Re-Hash, falls nicht vorhanden) | ADC / Workload Identity oder Service-Account-JSON |

Beide Objekt-Stores verifizieren die Integrität über die **Prüfsumme, die der Store
bereits berechnet und aufbewahrt**, abgerufen als Objektmetadaten: `slater-build`
sendet die Prüfsumme beim Upload (der Store validiert die Bytes dagegen und
speichert sie), und der Server liest sie beim Öffnen zurück und vergleicht sie mit
dem Manifest — eine Metadatenanfrage pro Datei, kein Body-Download. Sie ist
inhaltsgradig und im Geiste identisch über S3 (SHA-256) und GCS (CRC32C). Wenn ein
Objekt **keine** serverseitig gespeicherte Prüfsumme trägt (out-of-band kopiert
oder mit einem anderen Standard hochgeladen), **re-hasht der Server den Objekt-Body
gegen das Manifest-BLAKE3**, statt seiner Bytelänge zu vertrauen — eine angeforderte
Integritätsprüfung wird nie stillschweigend zu einem Größenvergleich herabgestuft.
Von Slater veröffentlichte Generationen tragen immer die Prüfsumme, sodass sie auf
dem günstigen Metadatenpfad bleiben.

Was diese Spalte auf jedem Backend prüft, ist, dass die Dateien **mit dem Manifest
übereinstimmen**. Ob das Manifest selbst vertrauenswürdig ist, ist eine separate
Frage, und der Master-Schlüssel ist es, der sie beantwortet: Mit einem
konfigurierten Schlüssel trägt das Manifest einen Schlüssel-MAC, den der Server
verifiziert, bevor er irgendeinem Feld vertraut (einschließlich dieser Hashes),
sodass ein umgeschriebenes Manifest, das manipulierte Dateien beschreibt,
abgelehnt wird; ohne Schlüssel ist der Vergleich durchgehend ungeschlüsselt, und
jemand, der in das Datenverzeichnis schreiben kann, kann eine Datei und das
Manifest zusammen umschreiben. Siehe
[Was Integrität in jeder Konfiguration bedeutet](https://github.com/hikari-systems/slater/blob/main/THREAT_MODEL.md#what-integrity-means-in-each-configuration).
Die Prüfung selbst kann mit `dataBackend.verifyIntegrity: false` deaktiviert
werden, was sie gegen ein schnelleres Öffnen eintauscht.

### Dateisystem (`fs`)

Der Standard, verwurzelt in `dataBackend.fs.dir`. Die richtige Wahl für die meisten
Bereitstellungen: eine Generation auf einer lokalen SSD (oder einem NFS/EBS-Mount),
die schreibgeschützt bedient wird. Integrität ist ein vollständiger BLAKE3-Re-Hash
jeder Datei beim Öffnen.

### Amazon S3 (`s3`)

Ein S3- oder S3-kompatibler Bucket (AWS, MinIO, localstack). Anmeldedaten kommen
**zuerst** aus der Konfiguration (`dataBackend.s3.awsAccessKey` / `awsSecretKey`,
plus `awsSessionToken` für temporäre STS-Anmeldedaten) und fallen auf die
Standard-AWS-Kette (`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`-Env,
gemeinsames Profil oder Instanz-/IRSA-Rolle) zurück, wenn sie leer gelassen werden.```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)

Ein GCS-Bucket, der über die JSON-API erreicht wird. Die Autorisierung erfolgt GCP-nativ: Standardmäßig werden Application Default Credentials aufgelöst — GKE Workload Identity, der GCE- Metadatenserver oder ein gcloud- / GOOGLE_APPLICATION_CREDENTIALS-Schlüssel. Setze dataBackend.gcs.credentialsPath (eine Service-Account-JSON-Schlüsseldatei) oder inline credentialsJson für einen expliziten Schlüssel. dataBackend.gcs.endpoint zeigt auf einen fake-gcs-server-Emulator, und dataBackend.gcs.anonymous=true ermöglicht unauthentifizierten Zugriff nur für diesen Emulator — niemals gegen echtes 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

In allen Fällen schreibt slater-build die fertige Generation zuerst in --data-dir (seinen lokalen Staging-Bereich) und lädt sie zusätzlich in den Bucket hoch; der entfernte current-Zeiger wird zuletzt geschrieben, sodass ein Serving-Knoten nie eine halb veröffentlichte Generation sieht.

Wann ein Objekt-Store (S3 oder GCS) verwendet werden sollte

Greifen Sie zu s3 oder gcs, wenn Sie Generationen in dauerhaftem, zentralem Objekt- Speicher statt auf dem Datenträger eines Knotens wünschen — typischerweise: einmal veröffentlichen und an viele zustandslose, plattenlose Server-Replikate verteilen, die alle denselben Bucket lesen; den Build-Host von den Serve-Hosts entkoppeln; oder sich auf die Haltbarkeit/Versionierung/Lebenszyklus des Stores verlassen, statt Volumes zu verwalten. Der Kompromiss ist Latenz: Ein kalter Block ist ein Netzwerk-Roundtrip (~10–50 ms) statt eines lokalen Lesevorgangs (~0,1 ms). Slater verdeckt den Großteil davon mit dem In-Memory-Block-Cache, konkurrierendem Read-Ahead und dem optionalen Disk-Cache darunter. Wenn Ihre Generationen bereits auf schnellem lokalem Speicher liegen und Sie das zentrale Bucket-Modell nicht benötigen, ist fs einfacher und schneller.

Lokaler Disk-Block-Cache (zweite Ebene des Objekt-Stores)

Der In-Memory-BlockCache ist bewusst klein gehalten (begrenzter RSS ist das Hauptversprechen), sodass bei einem Arbeitsdatensatz, der größer als der RAM ist, dieselben Blöcke bei jedem Spill erneut aus dem Objekt-Store geholt würden. Ein optionaler lokaler SSD-Zweit-Cache behebt das: Ein aus dem RAM verdrängter Block wird vom lokalen Datenträger (~0,1 ms) statt über einen frischen Objekt-GET bedient, übersteht die In-Memory-Verdrängung und reduziert Anzahl/Kosten der Objekt-Store- Anfragen — und bringt einen Objekt-Store-gestützten Knoten nach dem Aufwärmen nahe an die Leistung eines lokalen Dateisystems. Er ist für beide s3 und gcs opt-in und wird durch Setzen von dataBackend.<s3|gcs>.diskCacheBytes > 0 und ein beschreibbares diskCacheDir aktiviert.

  • Er cached die versiegelten Bytes exakt wie abgerufen — bereits komprimiert und (bei --encrypt-Generationen) weiterhin AEAD-versiegelt — unterhalb von Entschlüsselung/Dekomprimierung. Die Cache-Ebene hält nie den Verschlüsselungsschlüssel und verschlüsselt nie erneut, sodass der Ruhezustand kostenlos erhalten bleibt: Eine verschlüsselte Generation landet weiterhin versiegelt auf dem Datenträger.
  • Schreibvorgänge sind Write-behind: Ein Fehlgriff gibt die abgerufenen Bytes sofort an die Abfrage zurück, dann erledigt ein Hintergrund-Thread den Datenträger-Schreibvorgang und das LRU-Trimmen, sodass der Abfragepfad nie auf Datenträger-I/O wartet. Die Verdrängung hält den Cache innerhalb seines Byte-Budgets; eine pro Datei geprüfte Prüfsumme bei jedem Lesevorgang heilt eine beschädigte Cache-Datei selbst zu einem Fehlgriff (→ erneuter Abruf aus dem Objekt-Store).
  • diskCacheDir muss auf ein echtes beschreibbares Volume zeigen — niemals tmpfs (tmpfs ist RAM und würde die begrenzte-RSS-Garantie zunichtemachen). Der In-Memory- Index, der ihn verfolgt, kostet etwas RAM (~zig Bytes pro gecachtem Block), was gegen Ihre RSS-Obergrenze zählt — dimensionieren Sie das Verzeichnis ≫ den In-Memory-Block- Cache.
  • Die andere RAM-Kosten der Ebene ist die Write-behind-Warteschlange, die Blöcke auf ihrem Weg zum Datenträger zwischenspeichert. Sie ist auf blockCacheBytes / 8 begrenzt (durch diskCacheBytes nach unten begrenzt) — 8 MiB beim Standardwert — und verwirft statt zu wachsen, sodass ein kalter Scan sie nicht aufblähen kann; ein verworfener Block wird einfach beim nächsten Fehlgriff erneut abgerufen. Sie benötigt keine Konfiguration: Sie skaliert mit blockCacheBytes, sodass die Datenträger-Ebene dem RSS-Budget außer ihrem Index keine neue Zahl hinzufügt.

Mounts

Ein Read-Replica läuft mit einem schreibgeschützten Root-Dateisystem und einem Nicht-Root-Benutzer (appuser:1000) — alles, was es benötigt, ist schreibgeschützt gemountet. Ein Writer (delta.enabled) benötigt zusätzlich ein dauerhaftes, beschreibbares Volume für sein WAL.

Umgebung / Konfiguration

Die Konfiguration wird vom hausüblichen geschichteten Loader geladen: das eingebackene config.json, dann /sandbox/config.json tief darüber gemergt, dann KEY__sub- Umgebungs-Overrides (doppelter Unterstrich für Verschachtelung; Schlüssel entsprechen der camelCase-Konfiguration).

Jeder Konfigurationsknopf — sein camelCase-Schlüssel, das KEY__sub-Umgebungs-Override, sein Standardwert und was er tut — ist in der Konfigurationsreferenz tabellarisch aufgeführt. Die am häufigsten abgestimmten Knöpfe sind die Cache-Budgets (cache.*), die Abfrage-Guards (query.*), die Verbindungs-Obergrenzen (server.*), das Storage-Backend (dataBackend.*) und die beschreibbare Ebene (delta.*).

Residenter Speicher verfolgt blockCacheBytes + vectorCacheBytes + resultCacheBytes bis auf begrenzten Pro-Eintrag- und Allokator-Overhead — jeder Pool wiegt seinen eigenen Inhalt (Strings und Container nach allokierter Kapazität) und verdrängt, um unter dem Budget zu bleiben, aber die Pro-Eintrag-Buchhaltung und die Größenklassen-Rundung des Allokators sitzen obendrauf auf der von Ihnen gesetzten Zahl — plus ein kleiner fester Overhead (und bis zu degreeColumnBytes für die lazy-Grad-Spalte, sobald der Grad-Summen-count(endpoint)- Schnellpfad ausgeführt wird). Er ist unabhängig von der Graphgröße — das ist das Hauptversprechen, das vom Integrationstest rss_stays_bounded_under_sustained_knn_load ausgeführt wird, der das Peak-vs.-Warm-RSS-Wachstum gut innerhalb der summierten Budgets hält. Pro-Verbindungs-Puffer liegen außerhalb der Cache-Budgets, sodass die Garantie unter widriger Last nur gilt, weil server.maxConnections begrenzt, wie viele gleichzeitig existieren können.

Netzwerk-Posture

Slater ist ein Read-Replica-Handle; die primäre Verbindungssicherheitskontrolle ist das Netzwerk, nicht das Binary. Binden Sie es an eine private Schnittstelle, beschränken Sie Quellbereiche auf der Netzwerkebene (Security Groups / NetworkPolicy) und — wenn es etwas anderem als vertrauenswürdigen Clients gegenübersteht — stellen Sie ihm einen verbindungsbegrenzenden L4-Proxy voran (HAProxy maxconn + eine Pro-Quelle-stick-table, oder nftables connlimit + hashlimit). Das sitzt vor dem Dateideskriptor, der jemals an den Prozess übergeben wird, und ist daher die robusteste Begrenzung.

Die oben genannten In-Binary-Begrenzungen (maxConnections, maxPreAuthConnections, maxConnectionsPerIp, die differenziellen Byte-Obergrenzen und loginTimeoutMs) sind Defence-in-Depth: Sie sind standardmäßig aktiviert und großzügig, sodass sie für eine legitime Client-Population unsichtbar sind, aber sie lassen die begrenzte-RSS-Garantie auch dann gelten, wenn der Proxy vergessen wird. Siehe docs/HARDENING.md für die vollständige defensive Posture und THREAT_MODEL.md / SECURITY_WORKLIST.md für die kanonischen Details.

Generations-Guard

Slater pollt den current-Zeiger jedes Graphs alle generationPollMs (Poll, nicht inotify — das Datenverzeichnis kann entfernt/Netzwerk-Speicher wie NFS sein, wo Dateisystem-Änderungsereignisse unzuverlässig sind). Wenn er sich ändert:

  • reloadStrategy=exit (Standard): Der Server protokolliert fatal und beendet sich mit Nicht-Null-Exitcode, sodass der Orchestrator ihn sauber gegen die neue Generation neu startet.
  • reloadStrategy=swap: Der Server öffnet und validiert die neue Generation (derselbe Content-Hash-Guard wie beim Boot), tauscht sie atomar ein und lässt laufende Abfragen auf der alten beenden. Ein beschädigtes/unvollständiges neues Image wird abgelehnt und die alte Generation bedient weiter.

ACL

acl.json ordnet Benutzern argon2id-Passwort-Hashes und Pro-Graph-read-/write- Grants zu. Prägen Sie einen Hash (speichern Sie niemals Klartext) mit:```sh slater hash-password 's3cret' # prints a $argon2id$… string for acl.json

root@kitploit:~
A starter `acl.json` ships at the repo root; its shape is:```json
{
  "users": {
    "reporting": {
      "passwordArgon2id": "$argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>",
      "grants": {
        "people": ["read"],
        "products": ["read", "write"]
      }
    }
  }
}
  • users — ein Eintrag pro Login, nach Benutzername verschlüsselt.

  • passwordArgon2id — der $argon2id$…-String aus slater hash-password (niemals im Klartext; die Datei selbst ist einfaches JSON und liegt auf gemeinsamem Speicher).

  • grants — Fähigkeitslisten pro Graph. Zwei Berechtigungen sind von Bedeutung:

    • read — den Graph abfragen. Ein Graph, der in den Grants eines Benutzers fehlt, ist für ihn unsichtbar.
    • write — den Graph über die beschreibbare Ebene (delta.enabled) verändern: die MERGE / SET / DELETE-Anweisungen und CALL slater.consolidate().

Mounten Sie sie schreibgeschützt unter dem Pfad, der durch aclPath benannt ist (Standard /config/acl.json). Der Server lädt sie bei jedem Generations-Hot-Swap neu, und der ACL-Stempel im Ruhezustand wird bei jedem Neuladen erneut geprüft (siehe requireAclStamp).

Health-Check

Die slater-Binärdatei dient zugleich als eigene Liveness-Sonde: slater healthcheck [host] [port] führt einen Bolt-Handshake (keine HTTP-Anfrage) gegen den Server durch und beendet sich mit 0, wenn eine Protokollversion ausgehandelt wird, andernfalls mit 1 — standardmäßig localhost und dem konfigurierten Bolt-Port. Das ist es, was der Container- HEALTHCHECK ausführt, sodass Orchestratoren einen wirklich Bolt-bereiten Server sehen, nicht nur einen offenen Socket:```sh slater healthcheck localhost 7687 # exit 0 = healthy docker exec slater /app/slater healthcheck # inside the container

root@kitploit:~
## One-shot-Abfrage

Für Skripting, CI-Prüfungen und schnelle Nachschlagevorgänge mountet `slater query` die
aktuelle Generation eines Graphen, führt eine einzelne schreibgeschützte Cypher-Abfrage prozessintern aus, gibt das
Ergebnis als JSON-Objekt aus und beendet sich — ohne Server, ohne Bolt-Verbindung. Es berücksichtigt
dieselbe Konfiguration wie der Server (Speicher-Backend, Verschlüsselungsschlüssel, Abfragebudgets):```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"]]}

Knoten und Beziehungen werden zu ihren Labels/Typen und Eigenschaften erweitert. Verwenden Sie -q, wenn Sie maschinenlesbare Ausgabe wünschen (das Ergebnis-JSON ist das einzige, was auf stdout ausgegeben wird); lassen Sie es weg für einen operator-orientierten Lauf mit Logs. Ohne -q wird nach jedem Lauf eine nur-Metriken-Zusammenfassung protokolliert — z. B.```text INFO query executed cost=2389 resultCount=10 execMs=441 limitRowCount=10

root@kitploit:~
carrying the query `cost` (elements charged), `resultCount`, `execMs`, and
`limitRowCount` (only when the query specifies a `LIMIT`) — never the query text
or any result value. Exit status is `0` on success, `1` on a parse/open/execute
error (message on stderr).

## Export a graph (`slater dump`)

`slater dump` exports a graph from a **running** server as business-key `MERGE`
Cypher — the same dialect `slater-build` ingests — so a graph round-trips
(dump → `slater-build` → new generation) for migration or text backup. Unlike
`slater query`, it connects over **Bolt**, authenticates, and honours per-graph
ACLs, so it needs no disk access to the server. The password is read from
`SLATER_DUMP_PASSWORD` or stdin (never a flag, keeping it out of `ps`/history).```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

Jeder Identitätsschlüssel eines Labels ist die Eigenschaft, die sein Range-Index trägt; überschreiben mit --key Label=prop (wiederholbar) oder einem globalen --pk <Feld>. CREATE INDEX DDL wird zuerst ausgegeben, damit der Rebuild die Indizes neu erstellt. Ein Multi-Label-Knoten behält jedes Label — er wird als MERGE (n:Ident:Other {key: v}) ausgegeben, mit dem Identitätslabel (dem, das den Geschäftsschlüssel liefert) zuerst und dem Rest sortiert; der Merge ist nur auf das Identitätslabel gestützt, sodass die nachfolgenden Labels auf den Knoten geschrieben werden, ohne einen weiteren zu erzeugen. Labels, Beziehungstypen und Eigenschaftsschlüssel mit Sonderzeichen werden bei der Ausgabe in Backticks gesetzt, damit ungewöhnliche Namen originalgetreu hin- und zurückübertragen werden und kein Cypher in den Rebuild injiziert werden kann. Vektoren (und andere Werte ohne Cypher-Literal-Schreibweise) können keinen MERGE-Dump nutzen und werden mit einer Warnung auf stderr verworfen. Der Exit-Status ist 0 bei Erfolg, 1 bei Fehler.

Ausgearbeitetes Beispiel

Ein vollständiger, ausführbarer Durchlauf — einen Graphen erstellen, ihn bereitstellen, mit den neo4j JavaScript- und Python-Treibern verbinden und in ihn schreiben — befindet sich in den Seiten Quickstart und Daten schreiben des Handbuchs, unter Verwendung des gebündelten Beispielgraphen in docs/manual/examples/.

Entwicklung```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:~
### Objekt-Store-Backends sind optionale Cargo-Features

Ein einfaches `cargo build` erzeugt ein **nur-Dateisystem**-Binary — die `s3`- und `gcs`-Backends
sind hinter Cargo-Features verborgen, sodass der Standard-Build klein bleibt (kein AWS-
oder Google-SDK, keine asynchrone Laufzeit). Aktiviere das jeweilige Backend, das du benötigst, auf **beiden** `slater`
(serve) und `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

Jede Crate stellt passende s3-/gcs-Features bereit, die an graph-format/{s3,gcs} weiterreichen. Wird ein Backend zur Laufzeit angefordert (dataBackend.kind=s3|gcs oder slater-build --publish-{s3,gcs}-*), ohne dass das Feature einkompiliert ist, schlägt dies schnell mit einer klaren „built without the … feature“-Fehlermeldung fehl. Das veröffentlichte Docker-Image aktiviert beide (Dockerfile CARGO_FEATURES), daher benötigen vorgefertigte Images keine zusätzlichen Flags – das ist nur beim Bauen aus dem Quellcode relevant. Die Integrationstests sind ebenso abgesichert: --features s3 --test s3_minio, --features gcs --test gcs_emulator (ein fake-gcs-server) und --features gcs --test gcs_real (echtes GCS über ADC); jeder wird übersprungen, sofern die jeweiligen SLATER_*-Umgebungsvariablen nicht gesetzt sind.

Siehe docs/PLAN.md, docs/PROGRESS.md und docs/DECISIONS.md für das Design, das Meilenstein-Register und das Entscheidungsprotokoll.

Leistung

Bis zu sechs Engines, eine Single-Client-Suite, Graphen von einem 62k-Knoten-Spielzeug bis zu Wikidata 91,6 Mio. Knoten / 1,5 Mrd. Kanten. Jede Engine wird isoliert gemessen (jeder andere Container gestoppt – RSS und Latenz sind ihr eigener Fußabdruck). Die Latenztabellen unten wurden auf Slater 0.21.0 neu gemessen (der beschreibbare Build): die kleinen/mittleren Graphen (MeSH, EU-AI-Act) frisch und der 91,6-Mio.-Graph als frischer Same-Box-, Shared-Anchor- Slater-gegen-Neo4j-Durchlauf (siehe diese Tabelle). Die Resident-Memory-Werte werden aus dem früheren Durchlauf übernommen (gemessen über Container-Cgroup; der Lesepfad ist byte-identisch, während die beschreibbare Schicht im Leerlauf ist). Die Zahlen der anderen Engines stammen aus dem etablierten Cross-Engine-Lauf (ihre Versionen/Leistung sind unverändert). Alle Werte sind Mediane (ms) oder Spitzen-Resident-Speicher (MiB). Überall gilt: niedriger ist besser; fett = bester Wert in der Zeile. Slater wurde auf seinem lokalen Dateisystem- (fs) Backend ausgeführt; die S3- und GCS-Backends tauschen lokale Lese-Latenz gegen Objektstore-Roundtrips (abgemildert durch die In-Memory-Caches und die optionale lokale Disk-Cache-Ebene), daher charakterisieren diese Werte die Engine, nicht eine Netzwerk-Speicher-Bereitstellung.

Die drei Engines, die von der Platte auslagern – slater, Neo4j 5 und LadybugDB – laden alle fünf Graphen. Das In-Memory-Trio (Memgraph · FalkorDB · ArcadeDB) kann den 1,5-Mrd.-Kanten-Graphen überhaupt nicht halten (er benötigt ~64–128 GiB resident), und ArcadeDBs Importer kann ihn ebenfalls nicht fertigstellen.

Resident-Speicher (MiB) – begrenzt, während der Graph ~1.500× wächst

Jede Zahl ist zugesagter Arbeitsspeicher – was das Betriebssystem nicht zurückfordern kann. Jede Engine außer slater hält ihren Graphen im zugesagten anonymen Speicher (eigener Heap, Neo4js Off-Heap-Page-Cache oder ein Buffer-Pool), daher ist ihr Spitzen-RSS ihr zugesagter Fußabdruck. Nur slater bedient aus dem zurückforderbaren OS-Page-Cache seines On-Disk-Stores, daher ist seine Zahl der anonyme Arbeitssatz; der Page-Cache des Stores (unter Druck entfernbar – slater bedient weiter) ist ausgeschlossen und für den 91,6-Mio.-Graphen als gesamt in Klammern angegeben. Fett = niedrigster Wert.

slater ist auf jeder Skala am niedrigsten und wächst ~50×, während der Graph ~1.500× wächst – sein Fußabdruck folgt dem Abfrage-Arbeitssatz, nicht dem Graphen (im Leerlauf durchgehend ~16–71 MiB). Das In-Memory-Trio wächst ~linear und kann den 1,5-Mrd.-Graphen nicht laden; Neo4j sagt einen ~2-GiB-Heap unabhängig von der Abfrage zu. († LadybugDB nur auf den begrenzten Formen – seine Hub-/var-length-/shortestPath-Traversalen bei 1,5 Mrd. Kanten benötigen einen auf ≥2 GiB erhöhten Lesepool, gegenüber slaters automatischer maxIntermediate-Begrenzung.) Die Build-Zeit-Wert→Zähler-Histogramme fügen vernachlässigbaren Resident-Speicher hinzu – ein paar KB für eine indizierte Spalte mit niedriger Kardinalität und null für Unique-Key-Graphen wie Wikidata (wikidata_id überschreitet die Kardinalitätsgrenze des Histogramms, daher wird keines gespeichert) – daher bleiben diese Werte durch dieses Feature unverändert.

Latenz (Median ms) – Graph passt in den RAM (MeSH, 341k / 469k)

slater besitzt die Metadaten-/Index-/Scan- Formen (count, label, idx-eq, scan – ~0,4 ms, 10–200× die Service-Engines), den indizierten Punkt-Lookup (0,43 ms, nun knapp vor den 0,48 ms des In-Memory-Paars), den unverankerten Multi-Hop (2-Hop 1,40 ms über den Beziehungstyp-Scan, am schnellsten im Feld) und – über ein Build-Zeit-Wert→Zähler-Histogramm auf dem indizierten Gruppierungsschlüssel – das Label-weite group-by / count(DISTINCT) (0,45 ms, vor LadybugDBs spaltenorientierten 5,3 ms). Die In-Memory-Server behalten nur rohen 1-Hop (Memgraph 1,21 ms gegenüber slaters 1,28 ms). (pole 62k/106k sieht gleich aus: slater allein am schnellsten bei count/scan ~0,4 ms, ~1,3–2,6 ms bei Hops.)

Latenz (Median ms) – Vektoren (EU-AI-Act kNN, 15k × 1024-dim)

slater beantwortet kNN mit einem exakten Brute-Force- Scan (diese Mengen liegen unter seiner 50k-Vektor-ANN-Schwelle), während die anderen ein approximatives residentes HNSW verwenden – daher sind slaters Ergebnisse exakt (Recall 1,0). Ein SIMD-Distanz-Kernel plus eine residente, vor-normalisierte Vektormatrix brachte Concept von ~23 → ~2,9 ms und Chunk von ~10 → ~2,4 ms, sodass slater nun Neo4j und LadybugDB schlägt und innerhalb von ~1,4× von Memgraph liegt, nur hinter FalkorDB – und dabei exakt.

Vektor-Schreib-Leiter – Einfügen / Aktualisieren / Löschen ohne Rebuild

Die obigen Tabellen sind Cross-Engine-Lese-Vergleiche. Der Vektor-Schreib-Pfad (die FreshDiskANN-artige Schreib-Leiter über der statischen Vamana-Basis) hat kein Cross-Engine-Pendant – keine andere Engine hier macht disk-natives, beschreibbares ANN – daher sind die Zahlen unten Single-Engine-Komponenten-Benchmarks über einer synthetischen, embedding-artigen Fixture (eine Low-Rank-Mannigfaltigkeit, dim 768, ungleiche Normen), festgehalten unter crates/slater/benches/ und vollständig – mit Methodik und allen Einschränkungen – in docs/PERF-REPORT.md beschrieben. Recall wird immer gegen eine exakte Brute Force über die Live-Menge gemessen, nie ein Index gegen einen anderen. Die Skala hier ist repräsentativ und nur dort extrapoliert, wo die Metrik größenlinear ist.

Die eine Zahl, die die dedizierte Leistungsbox verdient, ist der Slow-Path-Konsolidierungs-Rewrite-Durchsatz – wenn eine Konsolidierung Löschungen oder neue Vektoren trägt statt einer reinen Permutation, ist es eine sequenzielle Rekompression, begrenzt durch Single-Thread-zstd und lokale Platte, daher ist das absolute MiB/s umgebungsspezifisch (der Bericht zeigt die Form und erklärt die Umgebungsrange).

Latenz (Median ms) – Graph ≫ RAM (Wikidata 91,6 Mio. / 1,5 Mrd.)

Die In-Memory-Engines (Memgraph / FalkorDB / ArcadeDB) können diesen Graphen überhaupt nicht laden (~64–128 GiB resident). Nur slater und Neo4j 5 schaffen das. Dies ist ein frischer Same-Box-, Same-Day-Durchlauf gegen einen gemeinsamen, festen Anker-Satz – jede Abfrage trifft auf beiden Engines die identischen Knoten, daher ist der direkte Vergleich Äpfel-mit-Äpfeln (ein gemeinsamer wikidata_id-Pool aus Ankern mit moderatem Grad; siehe die Anmerkung unten, warum das wichtig ist). slater wird bei beiden Fanouts gezeigt (query.maxFanout 1 = Durchsatz-Standard, 8 = der Latenz-Regler, der kalte Block-Reads überlappt). Fett = bester Wert in der Zeile.

Das ehrliche Bild: slater dominiert die Metadaten-/Index-Formen – count(*) wird über Metadaten bedient (0,41 ms gegenüber Neo4js 3,6-s-Disk-Scan, ~8800×), und Punkt-Lookup / Grad / 3-Hop laufen ~2–10× schneller – ist bei 1–2-Hop mit Neo4j gleichauf (Fanout 8 zieht bei kalten Reads voraus), verliert aber var-length *1..2 distinct deutlich (≈1 s gegenüber Neo4js 47 ms): slaters variable-length-distinct-Expansion ist hier materiell langsamer, eine echte Schwäche, die eine eigene Untersuchung verdient. All das bei ein paar hundert MB RSS gegenüber Neo4js zugesagtem ~2-GiB-Heap.

Zu den Ankern. Diese Traversal-Zahlen hängen stark davon ab, von welchen Knoten man startet – ein Knoten eine Verbindung von einem Wikidata-Mega-Hub („human“, „country“) hat eine Millionen-starke 2-Hop-Nachbarschaft, daher schwanken var-length/Hop-Kosten um Größenordnungen mit der Ankerwahl. Die frühere Ausgabe dieser Tabelle sampelte die eigene „ersten N per Scan“ jeder Engine, was weder stabil noch vergleichbar ist; dieser Durchlauf fixiert einen einzigen gemeinsamen, grad-begrenzten Anker-Satz für beide Engines. (shortestPath ist in diesem Durchlauf weggelassen – zwischen zwei beliebigen Ankern ist er pfad-existenzabhängig und zu stark schwankend, um sinnvoll einen Median zu bilden.)

Multi-Hop count(*) – Speicher vom Ergebnisumfang entkoppelt

Unbegrenztes Multi-Hop RETURN count(*) zählt während der Expansion, statt die übereinstimmenden Zeilen zu materialisieren. Gleiche Hub-Anker auf dem 91,6-Mio.-Graphen, maxIntermediate=20M:

3-Hop count(*) @ 91,6 Mio.fanout=1fanout=8
Latenz / Spitzen-Arbeitssatz554 ms / 0,66 GiB298 ms / 1,9 GiB

Die Zählung hält O(1) Zeilen. Die Abrechnung ist unverändert, daher stößt eine Mega-Hub-Zählung weiterhin maxIntermediate auf Compute (Adjazenz-Reads), wie zuvor begrenzt.

Parallelität pro Abfrage (maxFanout)

Das Erhöhen von query.maxFanout überlappt die kalten, I/O-gebundenen Block-Reads einer Abfrage über Kerne – es hilft bei plattengebundenen Formen mit großem kaltem Arbeitssatz und ist bei warmen Formen flach. Auf dem 1,5-Mrd.-Graphen: shortestPath ≤6 918 → 608 ms (1,5×, größte Suche 6.269 → 2.350 ms, 2,7×); 3-Hop-Zählung 547 → 298 ms. maxFanout=1 ist der Standard (durchsatzorientiert); 8 ist der Latenz-Regler, bei mehr transientem Worker-Speicher.

Wo slater gewinnt / zurückliegt

Vollständige Per-Engine-Tabellen (pole, MeSH, EU-AI-Act + der blockCacheBytes-RAM↔Latenz-Regler, Wikidata 1M & 91,6 Mio.) finden sich in perf/cross-engine-hs/README.md; der frische Slater-only-Durchlauf (beide Fanouts, jeder Datensatz) in perf/PERF_CURRENT_STATUS.md.

Nebenläufigkeit & Brown-out (Lasttests)

Die obigen Benchmarks sind Single-Client. Die komplementäre Achse – Verhalten unter vielen gleichzeitigen Clients – hat ihre eigene Testumgebung, perf/loadtest/: ein Locust-Treiber über Bolt plus ein Koordinator, der die Last hochfährt, CALL slater.diagnostics() liest, das Kapazitäts-Knie findet und den Begrenzer benennt (vollständige Methode in docs/LOAD-TESTING.md). Schlagzeilen aus einem Lauf mit 256-MiB-Cache auf dem Wikidata-1M-Graphen (eine 16-Kern-Box):

Beide Speicherprobleme, die der Lasttest aufdeckte, sind nun behoben; alles im Lasttest-Dokument nachverfolgt.

Lizenz

Lizenziert unter der Apache License, Version 2.0. Siehe LICENSE für den vollständigen Text und NOTICE für die Namensnennung. Sofern Sie nicht ausdrücklich etwas anderes angeben, wird jeder Beitrag, der absichtlich zur Aufnahme in dieses Werk eingereicht wird, wie in der Apache-2.0-Lizenz definiert, wie oben lizenziert, ohne zusätzliche Bedingungen.

SPDX-Lizenzkennung: Apache-2.0

Tool herunterladen
REMOVE
INSERT
SET
REMOVE
DELETE
FunktionWas sie für Sie bedeutet
Begrenzter, vorhersehbarer SpeicherResidenter Speicher folgt drei Cache-Budgets, die Sie festlegen, innerhalb begrenzter Pro-Eintrag- und Allokator-Overheads — er wächst nicht mit der Graphgröße; Sie stimmen den Leistungs-/RAM-Kompromiss ab, statt für den gesamten Graphen zu provisionieren. Ein jemalloc-Allokator mit Hintergrund-Purge gibt freigegebenen Speicher nach schweren Abfrage-Bursts an das OS zurück, sodass die residente Größe auf ihren Leerlauf-Boden zurückfällt, statt am Post-Burst-Hochwasserstand festzunageln.
Multi-Tenant out of the boxEin Server hostet viele Graphen mit Leseberechtigungen pro Benutzer — Multi-Datenbank-Isolation, die die meisten Graph-DBs für eine kostenpflichtige/Enterprise-Stufe reservieren.
Verschlüsselung im Ruhezustand & in TransitPro-Block-XChaCha20-Poly1305-Versiegelung (der Schlüssel wird nie auf die Festplatte geschrieben) plus optionales TLS (bolt+s://). Von Natur aus DSGVO-freundlich. Verschlüsselung ist auch das, was authentifizierte Integrität kauft: Der Builder versiegelt das Manifest mit einem keyed MAC, und ein Server, der den Schlüssel hält, verifiziert es und weigert sich, eine Generation zu bedienen, deren Manifest gefälscht, verändert oder dessen MAC entfernt wurde. Ein unverschlüsseltes (Klartext-)Image ist nur durch den unverschlüsselten Content-Hash geschützt — Vollständigkeit und Korruption, nicht Manipulation. Siehe Was Integrität in jeder Konfiguration bedeutet.
Winzige InstallationEin kleines gestripptes Binary auf einer distroless-glibc-Basis (keine Shell/apt) — das Multi-Arch (amd64/arm64)-Image zieht ~22 MB, oder ~12 MB für den Server-only-slater:latest-lite-Tag; reines Rust-TLS, kein OpenSSL. Ziehen und ausführen.
Gebaut für periodisches PublizierenBauen Sie einen Graphen offline, bedienen Sie ihn unveränderlich, dann tauschen Sie atomar eine neue Version mit null Ausfallzeit ein — ideal für Data-Warehouse-/geplante-Refresh-Workloads.
Robust unter LastServer und Offline-Builder kompilieren beide mit #![forbid(unsafe_code)] — das einzige unsafe der Engine lebt in der geprüften jemalloc-Allokator-Crate. Der Kern ist unveränderlich, sodass Lesevorgänge keine Sperren nehmen und nie auf einen Schreiber warten; ein einzelner Schreiber serialisiert Mutationen hinter dem Schreibpfad allein. Keine GC-Pausen, keine Datenrennen. Eine schlechte Abfrage kann den Server nicht herunterfahren.
Funktioniert mit Ihren neo4j-ToolsSpricht Bolt 5.4 / 4.4 / 4.1 — verwenden Sie die Standard-neo4j-Treiber (JS, Python, Go, Java…), cypher-shell oder Graph-Browser unverändert.
Reiche Cypher-AbfrageflächeEine breite Leseoberfläche: MATCH/WHERE/WITH/UNION, CALL {…}-Subqueries, 70+ Funktionen & Aggregationen, temporale & geospatiale Werte und Regex.
Live, dauerhafte SchreibvorgängeEine optionale Single-Writer-LSM-Schicht über dem unveränderlichen Kern (delta.enabled): Business-Key-MERGE / SET / DELETE / CREATE / REMOVE über Knoten und Beziehungen, gebatchtes Schreib-UNWIND (ein fsync pro Batch) und CALL slater.consolidate() — gruppen-committet, fsync-dauerhaft und durch Konsolidierung in einen frischen Kern zurückgefaltet. Der Lesepfad ist byte-identisch, wenn das Delta leer ist.
ISO GQL, Lesen und SchreibenSpricht eine Teilmenge von ISO GQL (ISO/IEC 39075) über dieselbe Bolt-Verbindung — quantifizierte Pfade, Pfad-Restriktoren, Shortest-Path-Selektoren, Label/Typ-Boolesche-Ausdrücke, FOR, CAST, ein optionales GQL/CYPHER-Dialekt-Präfix — und, mit aktivierter beschreibbarer Schicht, GQLs Datenmodifikationsanweisungen (INSERT / SET / REMOVE / [DETACH] DELETE), die auf denselben dauerhaften Schreibpfad herunterbrechen. Cypher und GQL, Lesen und Schreiben, in einer Engine.
Vektoren + Graph in einer EngineDisk-nativer ANN-Vektor-Search (Vamana + PQ; Cosinus / L2 / Dot) für Embeddings/RAG, plus Graph-Algorithmen (PageRank, BFS, Betweenness, WCC…) — begrenzter Speicher selbst mit Millionen von Vektoren. Embeddings sind beschreibbar (eine FreshDiskANN-artige Schreibleiter): Vektor einfügen / aktualisieren / löschen, sofort KNN-sichtbar, ohne Neubau in die Basis gefaltet.
Sicher auf NetzwerkspeicherJede Datei ist BLAKE3-content-gehasht und beim Öffnen verifiziert; zerrissene oder halb kopierte Images werden verweigert, nicht bedient. Entwickelt für NFS/Remote-Volumes (keine mmap-Überraschungen).
Plugbare Speicher-BackendsBedienen Sie dasselbe Generationsformat von einem lokalen Dateisystem, einem S3 (S3-kompatiblen) Bucket oder einem Google-Cloud-Storage-Bucket — einmal publizieren, auf zustandslose Repliken ausrollen — mit einer optionalen lokalen SSD-Cache-Stufe vor dem Objekt-Store. Siehe Speicher-Backends.
PfadZweckHinweise
/dataDie Graph-Generationen (<graph>/<uuid>/… + current).Schreibgeschützt für Replikate; von slater-build erzeugt. Kann auf entferntem/Netzwerk-Speicher (z. B. NFS) liegen, daher werden Lesevorgänge nicht als schnelle lokale SSD-Latenzen angenommen.
/sandboxPro-Umgebung-Konfigurations-Overlay + Geheimnisse./sandbox/config.json wird tief über das eingebackene config.json gemergt; enthält auch acl.json, TLS-PEM-Material, die At-Rest-Schlüsseldatei.
/tmp, /runScratch (tmpfs).Ein Read-Replica schreibt standardmäßig nie auf den Datenträger.
(Writer) delta.walDirDas Write-Ahead-Log + L0-Delta-Segmente, wenn delta.enabled.Beschreibbar und ein dauerhaftes, echtes Volume — niemals tmpfs (es ist die Haltbarkeits-Untergrenze). Ein relativer Pfad wird unter dem Datenverzeichnis aufgelöst; geben Sie einem Writer hier ein eigenes persistentes Volume.
(optional) Disk-CacheDer lokale Disk-Block-Cache, wenn dataBackend.s3.diskCacheBytes / dataBackend.gcs.diskCacheBytes > 0.Beschreibbar und ein echtes Volume — nicht tmpfs. Von den s3- und gcs-Backends verwendet; siehe Storage-Backends.

Sie sind unabhängig: eine read-Berechtigung gewährt keinen Schreibzugriff. Das Aktivieren der beschreibbaren Ebene kann daher Ihre bestehenden Leser nicht zu Schreibern machen. Ein Schreiber benötigt beides — ["read", "write"] — denn das Auflösen eines Geschäftsschlüssels zum Schreiben ist ein Lesevorgang. Nicht erkannte Berechtigungszeichenfolgen werden ignoriert (sie gewähren nichts).

EngineKlasseSpeichergrenze
slaterdiskgestützt, ausgelagertquery.maxIntermediate begrenzt den Arbeitssatz automatisch
Neo4j 5diskgestützt, JVM~2 GiB Heap + Off-Heap, unabhängig von der Abfrage zugesagt
Memgraph · FalkorDBIn-Memorygesamter Graph im RAM
ArcadeDBIn-Memory, JVMgesamter Graph resident; am schwersten
LadybugDBeingebettet, spaltenorientiertmanueller Buffer-Pool, der die Abfrage übertreffen muss
Graph (Knoten / Kanten)slaterNeo4j 5MemgraphFalkorDBArcadeDBLadybugDB
pole – 62k / 106k117461141401.556198
MeSH – 341k / 469k631.0833584551.631121
EU-AI-Act – 21k / 45k (+55 MiB vec)997292293121.948286
Wikidata – 91,6 Mio. / 1,5 Mrd.584 (4.595 gesamt)~2.900kann nicht ladenkann nicht ladenkann nicht laden~652 †
FormslaterNeo4j 5MemgraphFalkorDBArcadeDBLadybugDB
count(*) alle Knoten0,4115,023,816,482,02,2
Label-Zählung0,424,220,71,14,44,3
indizierter Punkt-Lookup0,433,90,480,480,658,8
idx-eq-Zählung0,424,95,02,03812,5
1-Hop (indizierter Anker)1,285,81,214,13904,9
2-Hop (unverankert)1,405,68,516,74446,4
group-by / count(DISTINCT)0,4547–5163–6431–394115,3
Vollscan CONTAINS0,435,424,11,716,34,1
FormslaterNeo4j 5MemgraphFalkorDBLadybugDB
kNN Top-10 Concept2,98,61,91,22,8
kNN Top-10 Chunk2,45,71,91,53,2
Eigenschaftgemessenwarum es wichtig ist
KNN-Latenz vs. ausstehende SchreibvorgängeRW-Index ~1,5–2 ms, flach bis 50k ausstehend; die vor-indizierte Brute-Force-Overlay 1,9 → 115 ms (linear im Delta) – 61× bei 50kAbfragelatenz verschlechtert sich nicht, während sich Schreibvorgänge zwischen Konsolidierungen ansammeln
Embedding-Einfügung~1,5–2 ms pro Vektor in den Live-Indexein Schreibvorgang ist sofort KNN-sichtbar; das Delta-Rebuild-Budget beträgt ≈ 2 ms × die Delta-Grenze
Lösch-IO bei Iso-Recall2,9× weniger Knoten-Abrufe pro Abfrage bei 67 % gelöscht, 5,2× bei 80 % (Recall ≥ 0,90)ein konsolidierter Graph zahlt keine Lesesteuer für gelöschte Vektoren
Konsolidierung, reine PermutationO(1) – die .vamana ist hart-verlinkt byte-identisch, nur die ID-Spalte wird neu geschriebendas Falten von Vektor-Schreibvorgängen in die Basis überspringt den O(N·R·L)-Rebuild
Recall über die Leiterkonsolidiert ≥ Basis für Kosinus, L2 und Punktproduktdie Schreib-Leiter erhält den Recall auf jeder Sprosse
Formslater (fan 1)slater (fan 8)Neo4j 5
count(*) alle Knoten0,410,413606
Punkt-Lookup (indiziert)0,720,496,3
Grad (1-Hop-Zählung)0,430,446,0
1-Hop-Nachbarn9,84,510,1
2-Hop372334,5
3-Hop322574
var-length *1..2 distinct985105647
DimensionslaterBestes des FeldesUrteil
Resident-Speicher, jede Skala11–584 MiB (62k → 91,6 Mio.)In-Memory 1,5–2,7 GiB; kann 1,5 Mrd. nicht ladenslater
count / Metadaten / Scan~0,4 msService-Engines 5–80 msslater (10–200×)
indizierter Punkt-Lookup0,43 ms (MeSH)Memgraph · FalkorDB 0,48 msslater (knapp vor dem In-Memory-Paar)
unverankerter Multi-Hop (Zeilen)1,40 ms (MeSH 2-Hop)Neo4j 5,6 msslater (Beziehungstyp-Scan)
Aggregation (group-by / DISTINCT)0,45 msLadybugDB 5 ms (spaltenorientiert)slater (Build-Zeit-Histogramm)
kNN2,4–2,9 ms (exakt)FalkorDB 1,2 ms (HNSW)schlägt Neo4j/Ladybug; ~1,4× hinter Memgraph; exakt
91,6 Mio. Metadaten / Punkt / Grad / 3-Hop0,4–32 msNeo4j 6–3.600 msslater (2–8800×)
91,6 Mio. 1–2-Hop4,5–23 ms (fan 8)Neo4j 10–35 ms~gleichauf
91,6 Mio. var-length *1..2 distinct~1 sNeo4j 47 msNeo4j (eine echte slater-Schwachstelle)
Multi-Hop count(*) bei Skala0,3–0,6 GiBIn-Memory-Engines materialisieren den Zeilensatzslater, begrenzt
ErgebnisMessung
Hält 1000 gleichzeitige Clients, null FehlerDurchsatz erreicht Spitze ~2,5k rps; das Latenz-Knie setzt bei etwa 750 Clients ein (p99 51 → 750 ms) – Warteschlangen unter Kern-Konkurrenz, keine harte Grenze (Einzellauf, WSL2)
Block-Cache begrenzt und wirksam100 % Trefferquote, 0 Verdrängungen, 50 MB resident für einen cache-passenden Arbeitssatz
RSS unter Dauerlast gehaltender jemalloc-Allokator hält RSS bei ~0,6 GB über eine 100→500-Client-wiki_cache_churn-Rampe – cache-gebunden und stabil, ohne MALLOC_*-Tuning (die frühere MALLOC_ARENA_MAX=2 + Trim-Schwelle ist ausgemustert); sein Hintergrund-Purge gibt auch den Hochwasserstand nach dem Burst zurück, statt ihn fixiert zu lassen
Aggregierter Speicher begrenztserverweites query.maxIntermediateGlobal + adjazenz-belastete Expansion halten die wiki_budget-2-Hop-Flut bei 1000 Clients ohne OOM (RSS ~0,6 GB; die Wache verwirft ~60 % der Hub-Abfragen als wiederholbare Budget-Fehler)