Features
One engine, written in Rust, from an embedded test graph to a disk-resident store served over Bolt with TLS.
Cypher, the language your team already reads
A large part of openCypher and the Neo4j dialect, parsed and planned by GraphBuff itself. What is not supported yet returns an Unsupported error rather than a wrong answer.
Clauses
- MATCH
- OPTIONAL MATCH
- WHERE
- CREATE
- MERGE … ON CREATE / ON MATCH
- SET
- REMOVE
- DETACH DELETE
- UNWIND
- WITH
- ORDER BY · SKIP · LIMIT
- UNION [ALL]
- FOREACH
- LOAD CSV
- CALL { … } subqueries
Patterns
- variable length *1..4
- named paths
- shortestPath
- allShortestPaths
- type alternatives [:A|B]
- pattern predicates
Expressions and values
- list comprehensions
- reduce
- CASE
- EXISTS / COUNT / COLLECT { }
- =~ regex
- percentileCont
- date · datetime · duration
- 2D/3D points, WGS-84
- apoc.coll · apoc.text · apoc.map
Schema and planning
- range indexes
- relationship indexes
- UNIQUE · NOT NULL · NODE KEY
- EXPLAIN
- PROFILE
- cost-based planner
PROFILE MATCH (p:Person)-[:ACTED_IN]->(m:Movie {title: 'The Matrix'}) RETURN p.name ORDER BY p.name
Select [3 rows, 0.000 ms]
Sort p.name [3 rows, 0.008 ms]
Project p.name [3 rows, 0.002 ms]
Expand(All) (m)<-[:ACTED_IN]-(p:Person) (~0.5 rows) [3 rows, 0.002 ms]
NodeByLabelScan (m:Movie {…}) (~3.0 rows) [1 row, 0.007 ms]
Start [1 row, 0.001 ms]Three storage engines, chosen per database
Open a database in memory, make it durable with a write-ahead log, or page it from disk when it no longer fits in RAM. The Database API and every query stay the same.
let db = Database::open(StorageMode::InMemory)?;- Survives restart
- No
- Memory use
- The whole graph
- Use it for
- Tests, scratch analysis, caches
let db = Database::open(StorageMode::hybrid("data/movies"))?;- Survives restart
- Yes, by default every returned write is fsynced
- Memory use
- The whole graph
- Open time
- Load snapshot, replay log
let db = Database::open(StorageMode::Persistent(
PersistentOptions::new("data/web")
.cache_bytes(1 << 30),
))?;- Survives restart
- Yes, torn pages repaired from the redo log
- Memory use
- Page cache (default 64 MiB) plus metadata
- Open time
- Read metadata
Nodes with more than 50 relationships become dense: their relationships are grouped by type, so a typed expansion only walks the matching group.
Graph kernels on the GPU you already have
The gpu build feature adds a backend on wgpu: Vulkan, Direct3D 12 or Metal, whichever the machine has. Start the CLI or the server with --gpu and the heavy, whole-graph work moves to the card. Nothing in your queries changes.
On the GPU
- Multi-hop walk counts (
count(*)over paths) - Breadth-first search
- Connected components
- Triangle counts
- PageRank, on devices with 64-bit floats
Stays on the CPU
- Filters, aggregates, Top-N, expansions
- Kernels over fewer than 262,144 relationships
- Anything the device cannot hold
- Everything, after a device error
count(*)5.0×13 million relationships. Results are the CPU's results exactly, PageRank bit for bit.
Kernels pay off on the CPU too
The planner recognises aggregate and Top-N query shapes and runs them as kernels over a compressed sparse row export when its cost model says that is cheaper. EXPLAIN shows the choice; the row plan stays as an exact fallback.
sumORDER BY … LIMIT 10100,000 nodes, 1.3 million relationships, in memory, CPU kernels.
Graph algorithms as procedures
Call them from Cypher and keep working with the results in the same query. Each takes an optional map with relationshipTypes and orientation.
| Procedure | Yields | 100k nodes, 1.3M rels |
|---|---|---|
algo.pageRank | node, score | 104 ms |
algo.wcc | node, componentId | 52 ms |
algo.triangleCount | node, triangleCount | 184 ms |
algo.bfs | node, depth | not measured |
CALL algo.pageRank({relationshipTypes: 'FOLLOWS'}) YIELD node, score
RETURN node.name, score ORDER BY score DESC LIMIT 10Snapshot reads, serialised writes
Queries that write run one at a time. Read queries never wait for them: each reads a snapshot of the last commit, so it sees all or nothing of every write. All three storage engines take snapshots.
let mut tx = db.begin()?;
tx.execute("MATCH (a:Account {id: 1}) SET a.balance = a.balance - 10")?;
tx.execute("MATCH (a:Account {id: 2}) SET a.balance = a.balance + 10")?;
tx.commit()?; // or tx.rollback(); dropping an open transaction rolls it back- Per-query time limits and cancellation from another thread
- A cap on transaction size instead of an unbounded page cache
- Explicit transactions over HTTP and Bolt, rolled back after an idle timeout
HTTP/JSON and Bolt from one server
graphbuff-server serves many named databases. HTTP is one JSON request per query, from any language. With --bolt it also speaks Bolt 4.4 and 5.0 to 5.4, so the official Neo4j drivers connect unchanged, including neo4j:// routing URIs.
Neo4j drivers
Python 5.28, 6.4
Java 5.28, 6.0
| Request | Does |
|---|---|
POST /db/{name}/query | Run a query, with parameters and an optional timeout |
POST /db/{name}/tx | Begin a transaction that spans requests |
POST /db/{name}/backup | Online backup (admins) |
GET /health | Liveness, no token needed |
Users, roles and TLS
Users log in with HTTP Basic or Bolt basic. Passwords are stored as PBKDF2-HMAC-SHA-256 with 600,000 rounds. Roles apply to open connections the moment they are granted or revoked.
EXPLAIN, SHOW DATABASESgraphbuff-server --tls-cert cert.pem --tls-key key.pem --bolt 0.0.0.0:7687 --users users.txt
# drivers connect with bolt+s://, browsers and curl with https://Bulk import, backup and checks
graphbuff import reads CSV in the neo4j-admin database import format and writes store records directly, without Cypher or a redo log.
graphbuff backup --data data --database social --to backups/social-monday
graphbuff restore --from backups/social-monday --data data --database social --overwrite
graphbuff check --data data --database social # exit code 1 if inconsistent