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.

RAMVolatile. Gone when the process exits.
let db = Database::open(StorageMode::InMemory)?;
Survives restart
No
Memory use
The whole graph
Use it for
Tests, scratch analysis, caches

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
Breadth-first search510 ms21 ms24×
Three-hop walk counts180 ms19 ms9.5×
Cypher two-hop count(*)154 ms31 ms5.0×
PageRank, 20 iterations1,293 ms557 ms2.3×

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.

14 s → 8 msCypher two-hop sum
63 → 2.4 msfiltered aggregate
68 → 3.3 msORDER BY … LIMIT 10

100,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.

ProcedureYields100k nodes, 1.3M rels
algo.pageRanknode, score104 ms
algo.wccnode, componentId52 ms
algo.triangleCountnode, triangleCount184 ms
algo.bfsnode, depthnot measured
CALL algo.pageRank({relationshipTypes: 'FOLLOWS'}) YIELD node, score
RETURN node.name, score ORDER BY score DESC LIMIT 10

Snapshot 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
RequestDoes
POST /db/{name}/queryRun a query, with parameters and an optional timeout
POST /db/{name}/txBegin a transaction that spans requests
POST /db/{name}/backupOnline backup (admins)
GET /healthLiveness, 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.

readerRead queries, EXPLAIN, SHOW DATABASES
editorAlso writes data, indexes and constraints
adminAlso databases and user management
graphbuff-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.

LOAD CSV + CREATE~140 s
graphbuff import~9 s

100,000 nodes and 1,000,000 relationships with properties.

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