Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
slater — Graphdb de baixo consumo de memória com suporte a Bolt+tls, criptografia em repouso e vetores, projetado para casos de uso de grafos de réplica local. | Kitploit
Ferramentas/GitHubGitHub/hikari-systems/slater
Autenticação e AutorizaçãoFerramentas de Criptografia/DescriptografiaSegurança de RedeSegurança na NuvemUtilitários e FrameworksSegurança de Banco de Dados
GitHubhikari-systems/slater

slater

Graphdb de baixo consumo de memória com suporte a Bolt+tls, criptografia em repouso e vetores, projetado para casos de uso de grafos de réplica local.

Ver Repositório
10023há 24 diasRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Slater

CI Release

Versão atual: v0.25.2 — todas as versões.

Em uma linha: o Slater serve grafos que não cabem em memória — centenas de milhões de nós e bilhões de arestas em poucas centenas de MB de RAM — via Bolt padrão, então qualquer driver neo4j funciona, com busca vetorial nativa em disco ao lado do grafo, e aceita gravações ao vivo e duráveis sem abrir mão disso. A memória residente é definida por um orçamento de cache que você escolhe, não pelo tamanho do grafo.


Atalhos

Por que o Slater existeLeituras e gravaçõesO que você obtémRecursos
Executando com DockerComo funcionaA camada gravávelBackends de armazenamento
MontagensConfiguraçãoACLVerificação de saúde
Exemplo práticoDesenvolvimentoDesempenhoLicença
Armazenamento de memória Graphiti📖 Manual completo

Por que o Slater existe

Um banco de dados de grafos armazena dados como coisas (nós) e as relações entre elas (arestas), com as relações como cidadãos de primeira classe. É isso que você quer quando suas perguntas são sobre conexões em vez de linhas — "quem está a três saltos desta conta?", "qual é a cadeia completa de dependências por trás deste build?", "quais contas compartilham um dispositivo, um endereço e um cartão?" — as consultas que viram um pântano de joins recursivos em SQL, mas que fluem naturalmente em um grafo.

A reclamação mais comum sobre bancos de dados de grafos é que eles não escalam além do que cabe na RAM. Muitos deles (por exemplo, neo4j, Memgraph, FalkorDB, etc.) mantêm o grafo inteiro residente: um grafo de 40 GB quer 40 GB de memória — por instância. Quer uma réplica por região, por locatário ou por pod? Multiplique a conta. E além de um certo tamanho eles simplesmente não carregam: por exemplo, o grafo Wikidata de 90 milhões de nós / 1,5 bilhão de arestas precisa de ~64–128 GiB residentes, então os mecanismos em memória não conseguem abri-lo de forma alguma.

O Slater é a refutação. Em vez de carregar o grafo na memória, ele o compila uma vez, offline: slater-build transforma seus dados em uma imagem imutável em disco, endereçada por conteúdo, e qualquer número de servidores Slater serve essa imagem via Bolt (então seus drivers neo4j existentes funcionam), paginando blocos sob demanda e mantendo apenas um orçamento fixo de cache residente. É assim que o mesmo grafo de 90M nós é servido a partir de algumas centenas de MB de RAM — o tamanho do grafo e a conta de memória são desacoplados. Um grafo de 4 GB e um grafo de 400 GB custam a mesma RAM para servir, então você distribui réplicas de leitura baratas e sem estado, deixando o armazenamento, não o heap, segurar o grafo.

Isso o torna um ajuste natural para grafos de conhecimento por trás de RAG, grafos de recomendação e identidade, grafos de dependência — qualquer coisa grande e conectada que você queira consultar de forma barata e frequente. A busca vetorial nativa em disco fica ao lado do grafo, então o mesmo mecanismo também é a camada de recuperação para embeddings.

Compilado uma vez não significa congelado, no entanto. Essa imagem é uma base, não um estado final: uma camada de gravação opcional fica acima dela, então um grafo ao vivo pode ser corrigido e estendido sem reconstruir nada.

Leituras e gravações

O núcleo é imutável; o grafo não é. Ative a camada gravável (delta.enabled) e você grava via Bolt — corrija uma propriedade, adicione um nó, retire uma aresta — e a mudança é registrada de forma durável, sem reconstrução da imagem. O que mantém isso barato no lado da leitura é onde as gravações vivem.

As gravações se acumulam em uma camada log-structured-merge (LSM) sobre o núcleo imutável: um write-ahead log e uma tabela em memória, que transbordam para segmentos delta imutáveis, dobrados de volta em um núcleo novo por uma consolidação periódica. O que isso compra para você:

  • Leituras sobre um grafo não gravado custam exatamente o que custavam antes. Um delta vazio é um único ramo previsível, não um merge — o caminho de leitura é byte-idêntico, esteja a camada gravável ativada ou não.
  • O custo de leitura de uma gravação escala com o tamanho do delta, não com o tamanho do grafo. Respostas de grafo inteiro — count(*), as marginais de rótulo e tipo de relacionamento — permanecem leituras de metadados mesmo com gravações pendentes: o delta mantém seus próprios contadores, então um count(*) sobre um núcleo de 91,6M nós com meio milhão de gravações pendentes ainda responde em dezenas de milissegundos sem tocar em um único bloco.
  • Confirmado significa durável. Um único gravador drena a fila e retorna SUCCESS somente após o fsync que cobre a gravação. Agrupe suas gravações e elas são baratas — um UNWIND de gravação confirma um fsync por lote, em vez de por linha.
  • Gravações por chave de negócio, em qualquer dialeto. MERGE / MATCH … SET / DELETE (e CREATE / REMOVE, delete com detach, gravações de relacionamento) com chave na propriedade de identidade de um nó — ou as declarações equivalentes de modificação de dados ISO GQL ( / / / ), que descem para o mesmo caminho. Corrija, insira, faça upsert e retire, sobre nós e arestas, endereçados da maneira que seus dados já estão.

Com a camada desativada — o padrão — o Slater serve o núcleo imutável puro e recusa gravações. Veja A camada gravável para o modelo completo.

Sobre o nome. Slater é nomeado em homenagem ao agente da CIA em Archer (um ótimo programa) que insiste em ser chamado por um único nome — "Apenas… Slater" — e um dos meus personagens favoritos nele. Veja a página da wiki do personagem.

O que você obtém

  • RAM definida pelo seu orçamento de cache, não pelo tamanho do seu grafo — distribua quantas réplicas de leitura quiser; o grafo nunca precisa caber em memória.
  • Substituto direto para o grafo — fala Bolt, então qualquer driver neo4j padrão (JS, Python, Go…) funciona sem alterações. É Cypher (mais uma fatia de ISO GQL, leituras e gravações); nada novo para aprender.
  • Gravações ao vivo e duráveis — uma camada LSM opcional sobre o núcleo imutável: MERGE / SET / DELETE por chave de negócio sobre nós e arestas, com commit em grupo e durável via fsync, dobradas de volta em um núcleo novo pela consolidação. As leituras não pagam por isso.
  • Implantação por troca de arquivo — construa uma nova geração com hash de conteúdo offline, alterne atomicamente o ponteiro current, e os servidores a captam. Cada bloco é verificado por checksum, então uma imagem copiada pela metade é recusada em vez de servida.
  • Busca vetorial integrada — busca aproximada de vizinhos mais próximos nativa em disco (cosseno, L2 ou dot KNN) fica ao lado do seu grafo, para quando este é a camada de recuperação por trás de um pipeline RAG, e os embeddings são graváveis no lugar — sem reconstrução offline para adicionar ou alterar um vetor.
  • Protegido por design — concessões de leitura e gravação são independentes, além de criptografia opcional em repouso, TLS Bolt, ACLs com hash argon2id e um rootfs de contêiner somente leitura para réplicas de leitura. Configure uma chave mestra e a imagem em disco é autenticada além de criptografada — seu manifesto carrega um MAC com chave, então um atacante com acesso de gravação ao diretório de dados, mas sem a chave, não pode forjar um manifesto que o servidor aceite. Sem uma chave, você ainda obtém o hash de conteúdo, que detecta uma imagem copiada pela metade ou corrompida — mas não uma deliberada. Qual configuração compra o quê.

Recursos

Dois binários compõem o workspace:

BinárioFunção
slaterO servidor Bolt online (o ENTRYPOINT do contêiner): serve leituras e, com delta.enabled, o caminho de gravação durável de gravador único.
slater-buildO compilador offline: transforma um dump de Cypher primitivo em um diretório de geração imutável com hash de conteúdo.

O Slater separa a construção em massa do serviço: slater-build faz o trabalho pesado offline — ingerindo seus dados e compilando-os em uma geração imutável — então um grafo frio nunca é montado no caminho quente do serviço. Dentro do servidor, a superfície de leitura responde a uma ampla fatia de Cypher — correspondência de padrões, subconsultas WITH/UNION/CALL {…}, 70+ funções escalares e de agregação, valores temporais e geoespaciais, algoritmos de grafo (algo.*) e KNN vetorial nativo em disco (db.idx.vector.queryNodes) — enquanto a sobreposição delta da camada gravável fica abaixo dessa superfície e tem custo zero quando vazia, então as leituras nunca carregam a maquinaria do lado de gravação. Você pode atualizar um grafo de duas maneiras: gravar nele ao vivo via Bolt (veja A camada gravável), ou construir uma nova geração offline e trocar atomicamente o ponteiro current, que o servidor em execução capta via seu guarda de geração (veja Guarda de geração).

Documentação

O manual do usuário completo vive em docs/manual/ — um guia recurso por recurso que explica, para cada capacidade, o que é, por que existe e como usá-la, com exemplos práticos que você pode executar contra um grafo de amostra incluído. Comece por lá para qualquer coisa além desta visão geral.

  • Novo aqui? Início rápido constrói e serve um grafo em cinco passos.
  • Escrevendo consultas? Consultas, Funções e expressões, Procedimentos e algoritmos, Busca vetorial, Gravação de dados.
  • Construindo grafos? Construção de grafos e a Referência da CLI de build.
  • Operando o Slater? Implantação, Armazenamento, Referência de configuração, Segurança, Ajuste de desempenho.

Usando o Slater como armazenamento de memória Graphiti

graphiti-slater é um adaptador que permite ao Graphiti armazenar seu grafo de conhecimento temporal no Slater, com um docker-example/ executável — incluindo expô-lo ao Claude Code como um servidor MCP. Veja esse repositório para saber como funciona e como executá-lo.

Executando com Docker

O Slater é projetado para ser executado como uma implantação Docker — essa é a forma esperada de usá-lo. Imagens multi-arquitetura pré-construídas (linux/amd64 + linux/arm64) são publicadas no Docker Hub em hikarisystems/slater, com as tags :latest e :vX.Y.Z em cada versão:```sh docker pull hikarisystems/slater:latest

root@kitploit:~
Um guia de uso, configuração e operações apenas com comandos Docker está disponível em
[`DOCKERHUB.md`](https://github.com/hikari-systems/slater/blob/main/DOCKERHUB.md) (e é espelhado na página de visão geral do Docker Hub) —
**comece por lá se você estiver implantando.** Em resumo:```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

Para construir a imagem localmente (por exemplo, para desenvolvimento):```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:~
A fase de construção instala `cmake`, `clang` e `libclang-dev` para o backend `aws-lc-rs` do rustls; `git` (já presente na imagem base) é necessário para a dependência git+tag do `hs-utils`, que `.cargo/config.toml` busca via CLI do git.

As seções abaixo cobrem o formato em disco, a configuração, as ACLs e um exemplo prático local (sem Docker).

## Como funciona```
            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>
  • Uma geração é um diretório imutável: um MANIFEST.json (tabelas de símbolos, descritores de índice, um cabeçalho de criptografia opcional), arquivos de blocos colunares (node_props.blk, node_labels.blk, edge_props.blk, topology.csr.blk, vectors.f32.blk), índices de intervalo (range/<name>.isam), índices ANN acima do limite (vector/<label>.<prop>.{vamana,pq}) e um ponteiro de texto current.
  • Cada bloco é comprimido com zstd e verificado com BLAKE3; com --encrypt, cada bloco é adicionalmente selado com XChaCha20-Poly1305 (AEAD em repouso).
  • O servidor abre uma geração recalculando o hash de cada arquivo em relação ao manifesto, de modo que uma imagem copiada pela metade / truncada — uma cópia rasgada no diretório de dados, que pode ser armazenamento remoto/de rede — é recusada em vez de servida.
  • As leituras fluem por três pools de cache limitados — um LRU de blocos descomprimidos, um pool de índices vetoriais (códigos PQ residentes + um LRU de blocos Vamana) e um LRU de resultados — cada um com seu próprio orçamento de bytes. Cada pool pesa o que contém e remove itens para permanecer abaixo do seu orçamento, de modo que o RSS acompanha os orçamentos dentro de uma sobrecarga limitada por entrada e do alocador, em vez de crescer com o grafo.

A camada gravável

Com delta.enabled, a geração imutável torna-se o nível inferior totalmente compactado (o "núcleo") de uma pequena árvore de mesclagem estruturada por logs, e as gravações ativas ficam por cima dela:``` 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:~
* **Piso de durabilidade — o WAL.** Cada mutação é serializada atrás de um único
  escritor por grafo, anexada a um log de write-ahead por grafo e submetida a `fsync`
  antes de o `SUCCESS` do Bolt ser retornado — portanto, *confirmado ⇒ durável*, e uma
  cauda rasgada é descartada na reprodução. Um `UNWIND` de escrita em lote anexa suas
  linhas e confirma **um** `fsync` para o lote inteiro. O WAL é **apenas disco local**
  (não é roteado pelo backend de armazenamento), o que torna um nó *escritor* com
  estado: ele precisa de um volume local durável em `delta.walDir`. Réplicas de leitura
  permanecem sem estado.
* **Memtable → L0 → consolidação.** As escritas acumulam-se numa memtable em RAM
  (limitada por `delta.memtableBytes`); quando enche, descarrega para um segmento
  delta L0 imutável. Uma **consolidação** dobra `{core + delta}` num core novo ao
  serializar a visão mesclada de volta através de `slater-build` e trocar `current`
  atomicamente — a mesma proteção de hash de conteúdo de qualquer geração publicada.
  Acione-a manualmente com `CALL slater.consolidate()`, automaticamente em
  `delta.deltaCorePercent` do tamanho do core (opcionalmente limitada a uma janela de
  consolidação fora de pico `delta.consolidateWindow`), ou deixe o limitador
  `delta.deltaHardBytes` conter o crescimento descontrolado.
* **A sobreposição fica abaixo da superfície de leitura.** O executor lê através de
  uma `ReadView` que é ou o core puro (delta sempre vazio) ou uma visão mesclada
  `(core, delta)`; o motor é monomorfizado sobre ela, então um delta vazio compila
  para um único ramo previsível e o caminho somente-leitura é byte-idêntico.
  Contadores de grafo inteiro (`count(*)`, marginais de label/reltype) são servidos
  dos próprios contadores ativos do delta, então permanecem leituras de metadados
  mesmo com escritas pendentes.
* **Uma consulta vê um snapshot estável.** Ela fixa uma tupla `(core, delta)` durante
  toda a sua vida. Não há transações multi-instrução nem rollback — uma escrita é uma
  correção durável, endereçada por chave de negócio, não uma transação OLTP.

A gramática exata de escrita e os parâmetros estão na tabela [Configuration](#environment--configuration)
(`delta.*`) e no [Worked example](#worked-example) abaixo.

### Índices de intervalo (ISAM)

Um índice de intervalo (`range/<name>.isam`, um por `(label, property)` indexado)
permite que um `MATCH (n:Label {prop: v})` ou `WHERE n.prop <op> v` resolva os ids
dos nós correspondentes **sem varrer o label**. É uma estrutura
**[ISAM](https://en.wikipedia.org/wiki/ISAM)** (Indexed Sequential Access Method) —
o clássico índice *estático, ordenado e em blocos*, que é exatamente a forma certa
para uma geração imutável: não há inserções para rebalancear, então a simplicidade
do ISAM compra o que a maquinaria de mutação de uma B-tree apenas complicaria.

* As entradas `(value, entity_id)` são ordenadas por valor e empacotadas nos mesmos
  blocos de 256 KiB comprimidos com zstd que todo o resto.
* Um pequeno **nível superior residente** guarda a primeira chave de cada bloco (um
  índice esparso). Uma busca faz uma pesquisa binária nesse nível superior em memória
  para encontrar o *único* bloco em que uma chave pode estar, lê + descomprime esse
  bloco e o varre — portanto, uma busca por igualdade é **uma leitura de bloco**, e
  uma varredura de intervalo percorre a sequência contígua de blocos que abrange.
  (É por isso que uma busca indexada por `meshUi` leva dígitos únicos de milissegundos
  enquanto a mesma correspondência numa propriedade não indexada varre o label inteiro.)
* O planejador a escolhe via `NodeScan::RangeEq` / `RangeRange`; um predicado não
  indexado recai numa varredura de label ou numa varredura completa, com o executor
  re-verificando cada predicado de qualquer forma.

### Busca vetorial (Vamana + PQ) — cosseno, L2 e produto escalar, leitura *e* escrita

KNN vetorial (`db.idx.vector.queryNodes`) roda sobre índices **cosseno, L2 ou
produto escalar (MIPS)**. O índice base é construído offline com dois caminhos de
execução, escolhidos por índice pelo `--ann-threshold` (padrão 50 000 vetores):

* **Abaixo do limite — força bruta.** Os vetores `f32` completos vivem em
  `vectors.f32.blk`; uma consulta varre o grupo do índice e calcula a distância exata
  na métrica do índice. Simples e exato; adequado quando o conjunto de vetores é pequeno.
* **No limite ou acima — Vamana + PQ**, o caminho ANN nativo de disco que mantém a
  memória residente limitada independentemente de quantos vetores existam:
  * **[Vamana](https://arxiv.org/pdf/2401.11324)** é o índice de grafo da linha de
    trabalho DiskANN: um único grafo de proximidade cujas arestas são podadas (o
    grau de saída `--vamana-r` e o fator de aresta longa `--vamana-alpha`) para que
    uma *busca gulosa por feixe* — comece no medoide, salte repetidamente em direção
    à consulta, mantendo uma lista de candidatos de largura `vectorQuery.beamWidth` —
    alcance os vizinhos verdadeiros de um nó em poucos saltos, ou seja, **poucas
    leituras aleatórias de bloco por consulta**. Os blocos do grafo
    (`vector/<label>.<prop>.vamana`) são paginados através do cache de vetores, não
    mantidos inteiros.
  * **[Quantização de produto (PQ)](https://medium.com/aiguys/product-quantization-k-nn-for-big-datasets-12431d764c4e)**
    comprime cada vetor num código curto (`--pq-subspaces` × `--pq-bits`): as
    dimensões são divididas em subespaços, cada um agrupado independentemente por
    k-means, e o vetor é armazenado como a tupla de ids dos centroides mais próximos.
    Esses códigos (`vector/<label>.<prop>.pq`) são pequenos o suficiente para
    permanecerem **residentes**, então a busca por feixe pontua candidatos a partir
    da RAM e apenas os poucos vetores completos escolhidos são lidos do disco. Esse
    conjunto PQ residente é o que o pool `cache.vectorCacheBytes` fixa.

**Embeddings graváveis — a escada de escrita vetorial (estilo [FreshDiskANN](https://arxiv.org/abs/2105.09613)).**
Um embedding indexado é um valor gravável de primeira classe. `SET n.embedding = vecf32([…])` (e `REMOVE`) cai no
delta de escrita e fica **imediatamente visível para KNN com classificação exata**, depois sobrevive a um
descarregamento de segmento, a uma mesclagem e a uma consolidação. Uma consulta mescla até três níveis — o índice
base selado, um índice por segmento selado e um **índice RW** em memória (um Vamana mutável ativo
sobre o delta de escrita) — então a latência permanece estável à medida que as escritas se acumulam, em vez de
crescer com a contagem de escritas pendentes. Uma exclusão deixa um *buraco*: o nó deixa de ser retornado,
mas permanece como ponto de navegação até uma **consolidação de exclusão** em segundo plano o remover
do grafo, então as exclusões deixam de custar IO de consulta. E como o grafo em disco
endereça seus vizinhos por posição de layout em vez de id de nó, `CALL slater.consolidate()`
carrega o Vamana **por referência** — hard-linked, byte-idêntico — e reescreve apenas uma
pequena coluna de ids, dobrando as escritas vetoriais no base **sem** a reconstrução
de grafo O(N·R·L). Números medidos, com ressalvas, estão no [relatório de desempenho](https://github.com/hikari-systems/slater/blob/main/docs/PERF-REPORT.md).

## Backends de armazenamento (filesystem / S3 / GCS)

Cada arquivo de geração é aberto através de uma abstração **`ObjectStore`** em vez
de `std::fs` diretamente, então o *mesmo* formato de bytes em disco — blocos, índices,
manifesto, ponteiro `current` — é servido inalterado de qualquer backend; apenas *de
onde os bytes vêm* difere, nunca os leitores, o motor de consulta ou as
verificações de integridade. O caminho quente são leituras posicionais (`read_exact_at`),
que mapeiam para um `pread` num arquivo local e uma solicitação de intervalo de bytes
HTTP num object store — o Slater nunca usa mmap, então o modelo explícito de leitura
limitada é idêntico em todos os lugares.

**Três backends de primeira classe**, selecionados por `dataBackend.kind`. O filesystem é
o padrão simples; **Amazon S3 e Google Cloud Storage são backends de object store
iguais e totalmente suportados** — a imagem publicada inclui ambos compilados,
então cada um é apenas configuração, e uma geração construída uma vez pode ser servida
de qualquer um deles (até migrada `fs` → S3 → GCS) sem reconstrução.

| `dataBackend.kind` | Leitura posicional | Integridade na abertura | Credenciais |
| --- | --- | --- | --- |
| `fs` *(padrão)* | `pread` | re-hash BLAKE3 completo de cada arquivo | — |
| `s3` | GET HTTP `Range` | **SHA-256** do servidor via `HEAD` (→ re-hash BLAKE3 do corpo se ausente) | chaves de configuração, cadeia AWS ou função IAM |
| `gcs` | leitura de intervalo HTTP | **CRC32C** do servidor via `get_object` (→ re-hash BLAKE3 do corpo se ausente) | ADC / Workload Identity ou JSON de conta de serviço |

Ambos os object stores verificam a integridade a partir do **checksum que o store já
computa e mantém**, buscado como metadados do objeto: `slater-build` envia o
checksum no upload (o store valida os bytes contra ele e o armazena), e
o servidor o lê de volta na abertura e o compara ao manifesto — uma solicitação de
metadados por arquivo, sem download do corpo. É de grau de conteúdo e idêntico em espírito
entre S3 (SHA-256) e GCS (CRC32C). Quando um objeto não carrega **nenhum** checksum
armazenado no servidor (copiado fora de banda, ou enviado com um padrão diferente), o servidor
**re-hasheia o corpo do objeto contra o BLAKE3 do manifesto** em vez de confiar no seu
comprimento de bytes — uma verificação de integridade solicitada nunca é silenciosamente
rebaixada para uma comparação de tamanho. Gerações publicadas pelo Slater sempre carregam o checksum, então permanecem no
caminho barato de metadados.

O que esta coluna verifica, em todos os backends, é que os arquivos **correspondem ao manifesto**.
Se o próprio manifesto pode ser confiável é uma questão separada, e é a
chave mestra que a responde: com uma chave configurada, o manifesto carrega um MAC com chave
que o servidor verifica antes de confiar em qualquer campo (incluindo esses hashes), então um
manifesto reescrito para descrever arquivos adulterados é recusado; sem chave, a comparação é sem chave em todo o processo, e
alguém que possa escrever no diretório de dados pode reescrever um arquivo e o manifesto
juntos. Veja
[O que integridade significa em cada configuração](https://github.com/hikari-systems/slater/blob/main/THREAT_MODEL.md#what-integrity-means-in-each-configuration).
A verificação em si pode ser desativada com `dataBackend.verifyIntegrity: false`, o que
a troca por uma abertura mais rápida.

### Filesystem (`fs`)

O padrão, enraizado em `dataBackend.fs.dir`. A escolha certa para a maioria das
implantações: uma geração num SSD local (ou um mount NFS/EBS) servida somente-leitura.
A integridade é um re-hash BLAKE3 completo de cada arquivo na abertura.

### Amazon S3 (`s3`)

Um bucket S3 ou compatível com S3 (AWS, MinIO, localstack). As credenciais vêm **primeiro**
da configuração (`dataBackend.s3.awsAccessKey` / `awsSecretKey`, além de
`awsSessionToken` para credenciais STS temporárias) e recaem na cadeia padrão da AWS
(`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` env, perfil compartilhado ou
função instance/IRSA) quando deixadas vazias.```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)

Um bucket GCS, acessado pela API JSON. A autorização é nativa do GCP: por padrão, ela resolve as Application Default Credentials — GKE Workload Identity, o servidor de metadados do GCE ou uma chave gcloud / GOOGLE_APPLICATION_CREDENTIALS. Defina dataBackend.gcs.credentialsPath (um arquivo de chave JSON de conta de serviço) ou credentialsJson inline para uma chave explícita. dataBackend.gcs.endpoint aponta para um emulador fake-gcs-server, e dataBackend.gcs.anonymous=true habilita acesso não autenticado apenas para esse emulador — nunca contra o GCS real.```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

Em todos os casos, o slater-build grava a geração finalizada em --data-dir primeiro (sua área de staging local) e adicionalmente a envia para o bucket; o ponteiro current remoto é gravado por último, então um nó de serviço nunca vê uma geração meio-publicada.

Quando usar um object store (S3 ou GCS)

Recorra a s3 ou gcs quando quiser gerações em armazenamento de objetos durável e central em vez de no disco de um nó — tipicamente: publicar uma vez e distribuir para muitas réplicas de servidor stateless e sem disco que leem o mesmo bucket; desacoplar o host de build dos hosts de serviço; ou contar com a durabilidade/versionamento/ciclo de vida do armazenamento em vez de gerenciar volumes. A contrapartida é a latência: um bloco frio é uma ida-e-volta de rede (~10–50 ms) em vez de uma leitura local (~0,1 ms). O Slater esconde a maior parte disso com o cache de blocos em memória, leitura antecipada concorrente e o cache de disco opcional abaixo. Se suas gerações já estão em armazenamento local rápido e você não precisa do modelo de bucket central, fs é mais simples e mais rápido.

Cache de blocos em disco local (segunda camada do object store)

O BlockCache em memória é deliberadamente pequeno (RSS limitado é a garantia principal), então em um conjunto de trabalho maior que a RAM os mesmos blocos seriam buscados novamente no object store a cada spill. Uma segunda camada de cache opcional em SSD local resolve isso: um bloco despejado da RAM é servido do disco local (~0,1 ms) em vez de um novo GET de objeto, sobrevivendo ao despejo da memória e reduzindo a contagem/custo de requisições ao object store — aproximando um nó apoiado em object store do desempenho de um filesystem local quando aquecido. É opt-in tanto para s3 quanto para gcs, habilitado definindo dataBackend.<s3|gcs>.diskCacheBytes > 0 e um diskCacheDir gravável.

  • Ele armazena em cache os bytes selados exatamente como buscados — já comprimidos e (para gerações com --encrypt) ainda selados com AEAD — abaixo da descriptografia/ descompressão. A camada de cache nunca detém a chave de criptografia e nunca re-criptografa, então o status em repouso é preservado de graça: uma geração criptografada chega ao disco ainda selada.
  • As gravações são write-behind: um miss retorna os bytes buscados à consulta imediatamente, e então uma thread em segundo plano faz a gravação em disco e o trim de LRU, então o caminho da consulta nunca bloqueia em I/O de disco. O despejo mantém o cache dentro de seu orçamento de bytes; um checksum por arquivo verificado em cada leitura auto-corrige um arquivo de cache corrompido para um miss (→ nova busca no object store).
  • diskCacheDir deve apontar para um volume real e gravável — nunca tmpfs (tmpfs é RAM e anularia a garantia de RSS limitado). O índice em memória que o rastreia custa um pouco de RAM (~dezenas de bytes por bloco em cache), o que conta contra seu teto de RSS — dimensione o diretório ≫ o cache de blocos em memória.
  • O outro custo de RAM da camada é a fila write-behind, que prepara blocos no caminho para o disco. Ela é limitada em blockCacheBytes / 8 (com piso em diskCacheBytes) — 8 MiB no padrão — e descarta em vez de crescer, então uma varredura fria não pode inflá-la; um bloco descartado simplesmente é buscado de novo no próximo miss. Ela não precisa de configuração: escala com blockCacheBytes, então a camada de disco não adiciona nenhum número novo ao orçamento de RSS além de seu índice.

Mounts

Uma réplica de leitura roda com um filesystem raiz somente leitura e um usuário não-root (appuser:1000) — tudo o que ela precisa é montado somente leitura. Um writer (delta.enabled) adicionalmente precisa de um volume durável e gravável para seu WAL.

Ambiente / configuração

A configuração é carregada pelo loader em camadas padrão da casa: o config.json embutido, depois /sandbox/config.json deep-merged sobre ele, depois overrides de ambiente KEY__sub (duplo underscore para aninhamento; as chaves correspondem à configuração camelCase).

Cada botão de configuração — sua chave camelCase, o override de ambiente KEY__sub, seu padrão e o que faz — está tabulado na Referência de configuração. Os botões mais ajustados são os orçamentos de cache (cache.*), os guards de consulta (query.*), os limites de conexão (server.*), o backend de armazenamento (dataBackend.*) e a camada gravável (delta.*).

Memória residente rastreia blockCacheBytes + vectorCacheBytes + resultCacheBytes dentro de overhead limitado por entrada e do alocador — cada pool pesa seu próprio conteúdo (strings e containers pela capacidade alocada) e despeja para ficar sob o orçamento, mas a contabilidade por entrada e o arredondamento de classe de tamanho do alocador ficam em cima do número que você define — mais um pequeno overhead fixo (e até degreeColumnBytes para a coluna de grau lazy, uma vez que o caminho rápido de soma de graus count(endpoint) é exercitado). É independente do tamanho do grafo — essa é a garantia principal, exercitada pelo teste de integração rss_stays_bounded_under_sustained_knn_load, que mantém o crescimento de RSS pico-vs-aquecido bem dentro dos orçamentos somados. Buffers por conexão vivem fora dos orçamentos de cache, então a garantia se mantém sob carga adversa apenas porque server.maxConnections limita quantos podem existir ao mesmo tempo.

Postura de rede

O Slater é um handle de réplica de leitura; o controle primário de segurança de conexão é a rede, não o binário. Vincule-o a uma interface privada, restrinja faixas de origem na camada de rede (security groups / NetworkPolicy) e — se ele enfrentar qualquer coisa além de clientes confiáveis — coloque na frente um proxy L4 com limite de conexões (HAProxy maxconn + uma stick-table por origem, ou nftables connlimit

  • hashlimit). Isso fica antes de o file descriptor ser entregue ao processo, então é o limite mais robusto.

Os limites no binário acima (maxConnections, maxPreAuthConnections, maxConnectionsPerIp, os limites diferenciais de bytes e loginTimeoutMs) são defesa em profundidade: vêm ligados por padrão e generosos para serem invisíveis a uma população legítima de clientes, mas fazem a garantia de RSS limitado se manter mesmo quando o proxy é esquecido. Veja docs/HARDENING.md para a postura defensiva completa, e THREAT_MODEL.md / SECURITY_WORKLIST.md para o detalhe canônico.

Guard de geração

O Slater consulta o ponteiro current de cada grafo a cada generationPollMs (poll, não inotify — o diretório de dados pode ser armazenamento remoto/de rede como NFS, onde eventos de mudança de filesystem não são confiáveis). Quando ele muda:

  • reloadStrategy=exit (padrão): o servidor registra fatal e sai com código não-zero para que o orquestrador o reinicie limpo contra a nova geração.
  • reloadStrategy=swap: o servidor abre e valida a nova geração (mesmo guard de hash de conteúdo do boot), troca-a atomicamente e deixa consultas em andamento terminarem na antiga. Uma nova imagem corrompida/incompleta é recusada e a geração antiga continua servindo.

ACL

acl.json mapeia usuários para hashes de senha argon2id e concessões por grafo de read / write. Gere um hash (nunca armazene texto claro) com:```sh slater hash-password 's3cret' # prints a $argon2id$… string for acl.json

root@kitploit:~
Um `acl.json` inicial é fornecido na raiz do repositório; sua estrutura é:```json
{
  "users": {
    "reporting": {
      "passwordArgon2id": "$argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>",
      "grants": {
        "people": ["read"],
        "products": ["read", "write"]
      }
    }
  }
}
  • users — uma entrada por login, identificada pelo nome de usuário.

  • passwordArgon2id — a string $argon2id$… de slater hash-password (nunca em texto claro; o arquivo em si é JSON simples e reside em armazenamento compartilhado).

  • grants — listas de capacidades por grafo. Duas permissões são significativas:

    • read — consultar o grafo. Um grafo ausente das permissões de um usuário é invisível para ele.
    • write — modificar o grafo por meio da camada gravável (delta.enabled): as instruções MERGE / SET / DELETE e CALL slater.consolidate().

Monte-o somente leitura no caminho indicado por aclPath (padrão /config/acl.json). O servidor o recarrega a cada troca a quente de geração, e o carimbo ACL em repouso é reverificado a cada recarga (veja requireAclStamp).

Verificação de saúde

O binário slater também serve como sua própria sonda de atividade: slater healthcheck [host] [port] realiza um handshake Bolt (não uma solicitação HTTP) contra o servidor e sai com 0 se negociar uma versão de protocolo, 1 caso contrário — usando como padrão localhost e a porta Bolt configurada. É isso que o HEALTHCHECK do contêiner executa, para que orquestradores vejam um servidor verdadeiramente pronto para Bolt, não apenas um socket aberto:```sh slater healthcheck localhost 7687 # exit 0 = healthy docker exec slater /app/slater healthcheck # inside the container

root@kitploit:~
## Consulta única

Para scripts, verificações de CI e consultas rápidas, `slater query` monta a
geração atual de um grafo, executa uma única consulta Cypher somente leitura
em processo, imprime o resultado como um objeto JSON e sai — sem servidor, sem
conexão Bolt. Ele respeita a mesma configuração do servidor (backend de
armazenamento, chave de criptografia, orçamentos de consulta):```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"]]}

Nodes and relationships expand to their labels/type and properties. Use -q when you want machine-parseable output (the result JSON is the only thing on stdout); omit it for an operator-facing run with logs. Without -q a metrics-only summary is logged after each run — e.g.```text INFO query executed cost=2389 resultCount=10 execMs=441 limitRowCount=10

root@kitploit:~
carregando a consulta `cost` (elementos cobrados), `resultCount`, `execMs` e
`limitRowCount` (apenas quando a consulta especifica um `LIMIT`) — nunca o texto da consulta
ou qualquer valor de resultado. O status de saída é `0` em caso de sucesso, `1` em caso de erro
de análise/abertura/execução (mensagem no stderr).

## Exportar um grafo (`slater dump`)

`slater dump` exporta um grafo de um servidor **em execução** como Cypher `MERGE` de chave de negócio
— o mesmo dialeto que o `slater-build` ingere — para que um grafo faça um ciclo completo
(dump → `slater-build` → nova geração) para migração ou backup de texto. Ao contrário
do `slater query`, ele conecta-se via **Bolt**, autentica-se e respeita os ACLs por grafo,
portanto não precisa de acesso a disco no servidor. A senha é lida de
`SLATER_DUMP_PASSWORD` ou do stdin (nunca de uma flag, mantendo-a fora do `ps`/histórico).```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

Cada chave de identidade de um rótulo é a propriedade carregada pelo seu índice de intervalo; substitua com --key Label=prop (repetível) ou um --pk <campo> global. O DDL CREATE INDEX é emitido primeiro para que a reconstrução recrie os índices. Um nó com vários rótulos mantém todos os rótulos — ele é emitido como MERGE (n:Ident:Outro {chave: v}), com o rótulo de identidade (aquele que fornece a chave de negócio) primeiro e o restante ordenado; o merge é baseado apenas no rótulo de identidade, então os rótulos finais são gravados no nó sem criar outro. Rótulos, tipos de relacionamento e chaves de propriedade contendo caracteres especiais são citados com crases na emissão, então nomes incomuns são preservados fielmente e não podem injetar Cypher na reconstrução. Vetores (e outros valores sem grafia literal em Cypher) não podem acompanhar um dump MERGE e são descartados com um aviso no stderr. O status de saída é 0 em caso de sucesso, 1 em caso de erro.

Exemplo prático

Um passo a passo completo e executável — construir um grafo, servi-lo, conectar-se com os drivers neo4j JavaScript e Python, e gravar nele — está nas páginas Quickstart e Writing data do manual, usando o grafo de exemplo incluído em docs/manual/examples/.

Desenvolvimento```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:~
### Backends de object-store são recursos opcionais do cargo

Um `cargo build` simples produz um binário **apenas com filesystem** — os
backends `s3` e `gcs` ficam atrás de recursos do cargo para que o build padrão
permaneça enxuto (sem AWS ou Google SDK, sem runtime assíncrono). Ative o que
precisar em **ambos** `slater` (serve) e `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

Cada crate expõe funcionalidades correspondentes s3 / gcs que encaminham para graph-format/{s3,gcs}. Solicitar um backend em tempo de execução (dataBackend.kind=s3|gcs, ou slater-build --publish-{s3,gcs}-*) sem a sua funcionalidade compilada falha rapidamente com um erro claro de "compilado sem a funcionalidade …". A imagem Docker publicada ativa ambas (Dockerfile CARGO_FEATURES), portanto imagens pré-construídas não precisam de sinalizadores extras — isso só importa ao compilar a partir do código-fonte. Os testes de integração também são limitados da mesma forma: --features s3 --test s3_minio, --features gcs --test gcs_emulator (um fake-gcs-server), e --features gcs --test gcs_real (GCS real via ADC); cada um é ignorado a menos que suas variáveis de ambiente SLATER_* estejam definidas.

Consulte docs/PLAN.md, docs/PROGRESS.md e docs/DECISIONS.md para o design, o registro de marcos e o registro de decisões.

Desempenho

Até seis engines, uma suíte de cliente único, grafos de um brinquedo de 62 mil nós até Wikidata 91,6M de nós / 1,5B de arestas. Cada engine é medido isoladamente (todos os outros contêineres parados — RSS e latência são sua própria pegada). As tabelas de latência abaixo foram remedidas no Slater 0.21.0 (a build gravável): os grafos pequenos/médios (MeSH, EU-AI-Act) recentemente, e o grafo de 91,6M como uma nova passagem mesma-máquina, âncora-compartilhada slater-vs-Neo4j (veja essa tabela). Os números de memória residente são transportados da passagem anterior (medidos via cgroup do contêiner; o caminho de leitura é byte-idêntico com a camada gravável ociosa). Os números dos outros engines são da execução cross-engine estabelecida (suas versões/desempenho não mudaram). Todos os números são medianas (ms) ou memória residente de pico (MiB). Menor é melhor em todos os lugares; negrito = melhor na linha. O slater foi executado em seu backend de sistema de arquivos local (fs); os backends S3 e GCS trocam a latência de leitura local por idas e voltas ao armazenamento de objetos (mitigadas pelos caches em memória e pela camada de cache em disco local opcional), portanto esses números caracterizam o engine, não uma implantação de armazenamento em rede.

Os três engines que paginam do disco — slater, Neo4j 5 e LadybugDB — carregam todos os cinco grafos. O trio em memória (Memgraph · FalkorDB · ArcadeDB) não consegue conter o grafo de 1,5B de arestas de forma alguma (ele precisa de ~64–128 GiB residentes), e o importador do ArcadeDB também não consegue terminá-lo.

Memória residente (MiB) — limitada conforme o grafo cresce ~1.500×

Cada número é memória de trabalho comprometida — o que o SO não pode recuperar. Todo engine exceto o slater mantém seu grafo em memória anônima comprometida (heap próprio, cache de páginas off-heap do Neo4j, ou um pool de buffers), portanto seu RSS de pico é sua pegada comprometida. Somente o slater atende a partir do cache de páginas do SO recuperável de seu armazenamento em disco, portanto seu número é o conjunto de trabalho anônimo; o cache de páginas do armazenamento (despejável sob pressão — o slater continua atendendo) é excluído e mostrado como total entre parênteses para o grafo de 91,6M. Negrito = mais baixo.

O slater é o mais baixo em todas as escalas e cresce ~50× enquanto o grafo cresce ~1.500× — sua pegada acompanha o conjunto de trabalho da consulta, não o grafo (ocioso ~16–71 MiB durante todo o processo). O trio em memória cresce ~linearmente e não consegue carregar o grafo de 1,5B; o Neo4j compromete um heap de ~2 GiB independentemente da consulta. († LadybugDB apenas nas formas limitadas — suas travessias de hub / comprimento variável / caminho mais curto em 1,5B de arestas precisam que seu pool de leitura seja elevado para ≥2 GiB, vs. o limite automático maxIntermediate do slater.) Os histogramas valor→contagem em tempo de build adicionam memória residente insignificante — alguns KB para uma coluna indexada de baixa cardinalidade, e zero para grafos de chave única como Wikidata (wikidata_id excede o limite de cardinalidade do histograma, portanto nenhum é armazenado) — portanto esses números são inalterados por essa funcionalidade.

Latência (ms mediana) — grafo cabe na RAM (MeSH, 341k / 469k)

O slater domina as formas de metadados / índice / varredura (count, rótulo, idx-eq, varredura — ~0,4 ms, 10–200× os engines de serviço), a busca pontual indexada (0,43 ms, agora superando o par em memória de 0,48 ms), o multi-salto sem âncora (2-saltos 1,40 ms via varredura por tipo de relacionamento, o mais rápido do campo), e — via um histograma valor→contagem em tempo de build na chave de agrupamento indexada — o group-by / count(DISTINCT) de rótulo inteiro (0,45 ms, à frente dos 5,3 ms colunares do LadybugDB). Os servidores em memória mantêm apenas o 1-salto bruto (Memgraph 1,21 ms vs. 1,28 ms do slater). (pole 62k/106k parece o mesmo: slater único mais rápido em count/varredura ~0,4 ms, ~1,3–2,6 ms em saltos.)

Latência (ms mediana) — vetores (kNN EU-AI-Act, 15k × 1024-dim)

O slater responde ao kNN com uma varredura exata de força bruta (esses conjuntos estão abaixo de seu limite ANN de 50k vetores) onde os outros usam um HNSW residente aproximado — portanto os resultados do slater são exatos (recall 1,0). Um kernel de distância SIMD + uma matriz de vetores residente e pré-normalizada levou Concept de ~23 → ~2,9 ms e Chunk de ~10 → ~2,4 ms, portanto o slater agora supera Neo4j e LadybugDB e está dentro de ~1,4× do Memgraph, ficando atrás apenas do FalkorDB — enquanto é exato.

Escada de gravação de vetores — inserir / atualizar / excluir sem reconstrução

As tabelas acima são comparações de leitura entre engines. O caminho de gravação de vetores (a escada de gravação estilo FreshDiskANN sobre a base Vamana estática) não tem contraparte entre engines — nenhum outro engine aqui faz ANN gravável nativo em disco — portanto os números abaixo são benchmarks de componente de engine único sobre um fixture sintético semelhante a embeddings (uma variedade de baixo posto, dim 768, normas desiguais), comprometidos sob crates/slater/benches/ e documentados por extenso — com a metodologia e todas as ressalvas — em docs/PERF-REPORT.md. O recall é sempre medido contra uma força bruta exata sobre o conjunto vivo, nunca um índice contra outro. A escala aqui é representativa e extrapolada apenas onde a métrica é linear em tamanho.

O único número que merece a caixa de desempenho dedicada é a taxa de transferência de reescrita de consolidação do caminho lento — quando uma consolidação carrega exclusões ou novos vetores em vez de uma permutação pura, é uma recompressão sequencial limitada por zstd de thread única e disco local, portanto o MiB/s absoluto é específico do ambiente (o relatório mostra a forma e explica a faixa ambiental).

Latência (ms mediana) — grafo ≫ RAM (Wikidata 91,6M / 1,5B)

Os engines em memória (Memgraph / FalkorDB / ArcadeDB) não conseguem carregar este grafo de forma alguma (~64–128 GiB residentes). Apenas o slater e o Neo4j 5 conseguem. Esta é uma nova passagem mesma-máquina, mesmo-dia contra um conjunto de âncoras fixo e compartilhado — cada consulta atinge os nós idênticos em ambos os engines, portanto o confronto direto é comparável (um pool comum de wikidata_id de âncoras de grau moderado; veja a nota abaixo sobre por que isso importa). O slater é mostrado em ambos os fanouts (query.maxFanout 1 = padrão de taxa de transferência, 8 = o dial de latência que sobrepõe leituras de blocos frios). Negrito = melhor na linha.

O quadro honesto: o slater domina as formas de metadados / índice — count(*) é atendido por metadados (0,41 ms vs. a varredura de disco de 3,6 s do Neo4j, ~8800×), e busca pontual / grau / 3-saltos rodam ~2–10× mais rápido — está empatado com o Neo4j em 1–2 saltos (fanout 8 avança em leituras frias), mas perde comprimento variável *1..2 distinto decisivamente (≈1 s vs. os 47 ms do Neo4j): a expansão distinta de comprimento variável do slater é materialmente mais lenta aqui, uma fraqueza real que merece sua própria investigação. Tudo isso com algumas centenas de MB de RSS versus o heap comprometido de ~2 GiB do Neo4j.

Sobre as âncoras. Esses números de travessia dependem fortemente de quais nós você começa — um nó a um link de um mega-hub do Wikidata ("humano", "país") tem uma vizinhança de 2-saltos com milhões de membros, portanto o custo de comprimento variável/salto oscila por ordens de magnitude com a escolha da âncora. A edição anterior desta tabela amostrava os próprios "primeiros N por varredura" de cada engine, o que não é nem estável nem comparável; esta passagem fixa um único conjunto de âncoras compartilhado e limitado por grau para ambos os engines. (shortestPath é omitido desta passagem — entre duas âncoras arbitrárias é dependente da existência do caminho e variância muito alta para mediana significativa.)

count(*) multi-salto — memória desacoplada do tamanho do resultado

RETURN count(*) multi-salto sem limite conta durante a expansão em vez de materializar as linhas correspondentes. Mesmas âncoras de hub no grafo de 91,6M, maxIntermediate=20M:

3-saltos count(*) @ 91,6Mfanout=1fanout=8
latência / conjunto de trabalho de pico554 ms / 0,66 GiB298 ms / 1,9 GiB

A contagem mantém O(1) linhas. A cobrança não muda, portanto uma contagem de mega-hub ainda dispara maxIntermediate em computação (leituras de adjacência), limitada como antes.

Paralelismo por consulta (maxFanout)

Elevar query.maxFanout sobrepõe as leituras de blocos frios e limitadas por IO de uma consulta entre núcleos — ajuda formas limitadas por disco com conjunto de trabalho frio grande e é estável em formas quentes. No grafo de 1,5B : shortestPath ≤6 918 → 608 ms (1,5×, maior busca 6.269 → 2.350 ms, 2,7×); 3-saltos count 547 → 298 ms. maxFanout=1 é o padrão (orientado a taxa de transferência); 8 é o dial de latência, com mais memória de trabalho transitória.

Onde o slater vence / fica atrás

Tabelas completas por engine (pole, MeSH, EU-AI-Act + o dial RAM↔latência blockCacheBytes, Wikidata 1M & 91,6M) estão em perf/cross-engine-hs/README.md; a nova passagem apenas-slater (ambos os fanouts, todos os conjuntos de dados) está em perf/PERF_CURRENT_STATUS.md.

Concorrência e brown-out (teste de carga)

Os benchmarks acima são de cliente único. O eixo complementar — comportamento sob muitos clientes concorrentes — tem seu próprio harness, perf/loadtest/: um driver Locust sobre Bolt mais um coordenador que aumenta a carga, lê CALL slater.diagnostics(), encontra o joelho de capacidade e nomeia o limitador (método completo em docs/LOAD-TESTING.md). Destaques de uma execução com cache de 256 MiB no grafo Wikidata-1M (uma máquina de 16 núcleos):

Ambos os problemas de memória que o teste de carga revelou agora estão fechados; todos rastreados no documento de teste de carga.

Licença

Licenciado sob a Apache License, Versão 2.0. Consulte LICENSE para o texto completo e NOTICE para atribuição. A menos que você declare explicitamente o contrário, qualquer contribuição intencionalmente submetida para inclusão neste trabalho, conforme definido na licença Apache 2.0, será licenciada como acima, sem quaisquer termos ou condições adicionais.

SPDX-License-Identifier: Apache-2.0

Baixar ferramenta
INSERT
SET
REMOVE
DELETE
RecursoO que significa para você
Memória limitada e previsívelA memória residente acompanha três orçamentos de cache que você define, dentro de overhead limitado por entrada e do alocador — ela não cresce com o tamanho do grafo; você ajusta a compensação desempenho/RAM em vez de provisionar para o grafo inteiro. Um alocador jemalloc com purga em segundo plano devolve memória liberada ao SO após rajadas pesadas de consultas, então o tamanho residente cai de volta ao seu piso ocioso em vez de permanecer fixado na marca d'água alta pós-rajada.
Multilocatário pronto para usoUm servidor hospeda muitos grafos com concessões de leitura por usuário — isolamento multi-banco que a maioria dos bancos de grafos reserva para um nível pago/empresarial.
Criptografia em repouso e em trânsitoSelagem por bloco XChaCha20-Poly1305 (a chave nunca é gravada em disco) além de TLS opcional (bolt+s://). Compatível com GDPR por construção. A criptografia também é o que compra integridade autenticada: o construtor sela o manifesto com um MAC com chave, e um servidor que detém a chave o verifica e recusa servir uma geração cujo manifesto foi forjado, alterado ou teve seu MAC removido. Uma imagem sem chave (texto simples) é protegida apenas pelo hash de conteúdo sem chave — completude e corrupção, não adulteração. Veja O que integridade significa em cada configuração.
Instalação mínimaUm binário pequeno e reduzido em uma base glibc distroless (sem shell/apt) — a imagem multi-arquitetura (amd64/arm64) puxa ~22 MB, ou ~12 MB para a tag slater:latest-lite somente servidor; TLS Rust puro, sem OpenSSL. Puxe e execute.
Feito para publicação periódicaConstrua um grafo offline, sirva-o imutável e troque atomicamente por uma nova versão com zero tempo de inatividade — ideal para cargas de trabalho de data warehouse / atualização agendada.
Robusto sob cargaO servidor e o construtor offline compilam com #![forbid(unsafe_code)] — o único unsafe do mecanismo vive no crate auditado do alocador jemalloc. O núcleo é imutável, então as leituras não usam locks e nunca esperam por um gravador; um único gravador serializa mutações apenas atrás do caminho de gravação. Sem pausas de GC, sem corridas de dados. Uma consulta ruim não pode derrubar o servidor.
Funciona com suas ferramentas neo4jFala Bolt 5.4 / 4.4 / 4.1 — use os drivers neo4j padrão (JS, Python, Go, Java…), cypher-shell ou navegadores de grafos sem alterações.
Superfície de consulta Cypher ricaUma ampla superfície de leitura: MATCH/WHERE/WITH/UNION, subconsultas CALL {…}, 70+ funções e agregações, valores temporais e geoespaciais e regex.
Gravações ao vivo e duráveisUma camada LSM opcional de gravador único sobre o núcleo imutável (delta.enabled): MERGE / SET / DELETE / CREATE / REMOVE por chave de negócio sobre nós e relacionamentos, UNWIND de gravação em lote (um fsync por lote) e CALL slater.consolidate() — com commit em grupo, durável via fsync e dobrado de volta em um núcleo novo pela consolidação. O caminho de leitura é byte-idêntico quando o delta está vazio.
ISO GQL, leitura e gravaçãoFala um subconjunto de ISO GQL (ISO/IEC 39075) sobre a mesma conexão Bolt — caminhos quantificados, restritores de caminho, seletores de caminho mais curto, expressões booleanas de rótulo/tipo, FOR, CAST, um prefixo de dialeto opcional GQL/CYPHER — e, com a camada gravável ativada, as declarações de modificação de dados do GQL (INSERT / SET / REMOVE / [DETACH] DELETE) descem para o mesmo caminho de gravação durável. Cypher e GQL, leituras e gravações, em um único mecanismo.
Vetores + grafo em um mecanismoBusca vetorial ANN nativa em disco (Vamana + PQ; cosseno / L2 / dot) para embeddings/RAG, além de algoritmos de grafo (PageRank, BFS, betweenness, WCC…) — memória limitada mesmo com milhões de vetores. Os embeddings são graváveis (uma escada de gravação estilo FreshDiskANN): insira / atualize / exclua um vetor, visível no KNN imediatamente, dobrado na base sem reconstrução.
Seguro em armazenamento de redeCada arquivo é hashado com BLAKE3 por conteúdo e verificado na abertura; imagens rasgadas ou copiadas pela metade são recusadas, não servidas. Projetado para volumes NFS/remotos (sem surpresas de mmap).
Backends de armazenamento plugáveisSirva o mesmo formato de geração de um sistema de arquivos local, um bucket S3 (compatível com S3) ou um bucket Google Cloud Storage — publique uma vez, distribua para réplicas sem estado — com uma camada de cache SSD local opcional na frente do armazenamento de objetos. Veja Backends de armazenamento.
CaminhoFinalidadeNotas
/dataAs gerações do grafo (<graph>/<uuid>/… + current).Somente leitura para réplicas; produzido pelo slater-build. Pode viver em armazenamento remoto/de rede (ex.: NFS), então leituras não são assumidas como latências rápidas de SSD local.
/sandboxOverlay de configuração por ambiente + segredos./sandbox/config.json é deep-merged sobre o config.json embutido; também contém acl.json, material PEM de TLS, o arquivo de chave em repouso.
/tmp, /runScratch (tmpfs).Uma réplica de leitura nunca grava em disco por padrão.
(writer) delta.walDirO write-ahead log + segmentos delta L0, quando delta.enabled.Gravável, e um volume durável e real — nunca tmpfs (é o piso de durabilidade). Um caminho relativo resolve sob o diretório de dados; dê a um writer seu próprio volume persistente aqui.
(opcional) cache de discoO cache de blocos em disco local, quando dataBackend.s3.diskCacheBytes / dataBackend.gcs.diskCacheBytes > 0.Gravável, e um volume real — não tmpfs. Usado pelos backends s3 e gcs; veja Storage backends.

Elas são independentes: uma permissão read não confere acesso de escrita. Ativar a camada gravável, portanto, não pode promover seus leitores existentes a escritores. Um escritor precisa de ambas — ["read", "write"] — porque resolver uma chave de negócio para escrevê-la é uma leitura. Strings de permissão não reconhecidas são ignoradas (elas não concedem nada).

engineclasselimite de memória
slatercom suporte em disco, paginadoquery.maxIntermediate limita o conjunto de trabalho automaticamente
Neo4j 5com suporte em disco, JVMheap de ~2 GiB + off-heap, comprometido independentemente da consulta
Memgraph · FalkorDBem memóriagrafo inteiro residente na RAM
ArcadeDBem memória, JVMgrafo inteiro residente; o mais pesado
LadybugDBembutido, colunarpool de buffers manual que deve exceder a consulta
grafo (nós / arestas)slaterNeo4j 5MemgraphFalkorDBArcadeDBLadybugDB
pole — 62k / 106k117461141401.556198
MeSH — 341k / 469k631.0833584551.631121
EU-AI-Act — 21k / 45k (+55 MiB vec)997292293121.948286
Wikidata — 91,6M / 1,5B584 (4.595 total)~2.900não-carregávelnão-carregávelnão-carregável~652 †
formaslaterNeo4j 5MemgraphFalkorDBArcadeDBLadybugDB
count(*) todos os nós0,4115,023,816,482,02,2
contagem de rótulo0,424,220,71,14,44,3
busca pontual indexada0,433,90,480,480,658,8
contagem idx-eq0,424,95,02,03812,5
1-salto (âncora indexada)1,285,81,214,13904,9
2-saltos (sem âncora)1,405,68,516,74446,4
group-by / count(DISTINCT)0,4547–5163–6431–394115,3
varredura completa CONTAINS0,435,424,11,716,34,1
formaslaterNeo4j 5MemgraphFalkorDBLadybugDB
kNN top-10 Concept2,98,61,91,22,8
kNN top-10 Chunk2,45,71,91,53,2
propriedademedidopor que importa
Latência KNN vs. gravações pendentesÍndice RW ~1,5–2 ms, estável até 50k pendentes; a sobreposição de força bruta pré-indexada 1,9 → 115 ms (linear no delta) — 61× em 50ka latência de consulta não degrada conforme as gravações se acumulam entre consolidações
Inserção de embedding~1,5–2 ms por vetor no índice vivouma gravação é visível ao KNN imediatamente; o orçamento de reconstrução de delta é ≈ 2 ms × o limite de delta
IO de exclusão em recall iso2,9× menos buscas de nós por consulta em 67 % excluídos, 5,2× em 80 % (recall ≥ 0,90)um grafo consolidado não paga imposto de leitura por vetores excluídos
Consolidação, permutação puraO(1) — o .vamana é hard-linked byte-idêntico, apenas a coluna de id é reescritadobrar gravações de vetores na base ignora a reconstrução O(N·R·L)
Recall na escadaconsolidado ≥ base para cosseno, L2 e produto escalara escada de gravação preserva o recall em cada degrau
formaslater (fan 1)slater (fan 8)Neo4j 5
count(*) todos os nós0,410,413606
busca pontual (indexada)0,720,496,3
grau (contagem de 1-salto)0,430,446,0
vizinhos de 1-salto9,84,510,1
2-saltos372334,5
3-saltos322574
comprimento variável *1..2 distinto985105647
dimensãoslatermelhor do campoveredito
memória residente, qualquer escala11–584 MiB (62k → 91,6M)em memória 1,5–2,7 GiB; não consegue carregar 1,5Bslater
count / metadados / varredura~0,4 msengines de serviço 5–80 msslater (10–200×)
busca pontual indexada0,43 ms (MeSH)Memgraph · FalkorDB 0,48 msslater (supera o par em memória)
multi-salto sem âncora (linhas)1,40 ms (MeSH 2-saltos)Neo4j 5,6 msslater (varredura por tipo de relacionamento)
agregação (group-by / DISTINCT)0,45 msLadybugDB 5 ms (colunar)slater (histograma em tempo de build)
kNN2,4–2,9 ms (exato)FalkorDB 1,2 ms (HNSW)supera Neo4j/Ladybug; ~1,4× atrás do Memgraph; exato
91,6M metadados / ponto / grau / 3-saltos0,4–32 msNeo4j 6–3.600 msslater (2–8800×)
91,6M 1–2-saltos4,5–23 ms (fan 8)Neo4j 10–35 ms~empatado
91,6M comprimento variável *1..2 distinto~1 sNeo4j 47 msNeo4j (um ponto fraco real do slater)
count(*) multi-salto em escala0,3–0,6 GiBengines em memória materializam o conjunto de linhasslater, limitado
resultadomedição
Mantém 1000 clientes concorrentes, zero falhastaxa de transferência atinge pico de ~2,5k rps; o joelho de latência se instala em torno de 750 clientes (p99 51 → 750 ms) — fila sob contenção de núcleo, não um limite rígido (execução única, WSL2)
Cache de blocos limitado e eficaztaxa de acerto de 100%, 0 despejos, 50 MB residentes para um conjunto de trabalho que cabe no cache
RSS mantido sob carga sustentadao alocador jemalloc mantém o RSS em ~0,6 GB em uma rampa wiki_cache_churn de 100→500 clientes — limitado por cache e estável, sem nenhum ajuste MALLOC_* (o antigo MALLOC_ARENA_MAX=2 + limite de corte está aposentado); sua purga em segundo plano também retorna o pico pós-explosão em vez de deixá-lo fixado
Memória agregada limitadaquery.maxIntermediateGlobal em todo o servidor + expansão cobrada por adjacência seguram a inundação de 2-saltos wiki_budget em 1000 clientes sem OOM (RSS ~0,6 GB; o guarda descarta ~60% das consultas de hub como erros de orçamento repetíveis)