kglite-bolt-server — Neo4j Bolt v5 protocol server¶
The bolt-server is a pure-Rust binary that exposes a loaded kglite
.kgl graph over the Bolt v5 wire protocol.
Any Neo4j-aware client — the official Python/JS/Java/Go drivers,
Cypher Shell, Neo4j Browser, BloodHound, LangChain’s Neo4jGraph —
can connect and run Cypher queries with zero consumer-side
changes.
Architecture¶
The Bolt server is a thin async transport over the same sync Cypher pipeline the Python API uses:
neo4j driver ──Bolt v5 over TCP──▶ boltr crate (server crate
handles handshake, message
framing, PackStream, session
state machine)
│
▼
KgliteBackend (impl BoltBackend)
│
│ ┌──────────────────┐
├──▶ kglite::api::cypher
│ parse → validate →
│ optimize → execute
│
│ (same code path as
│ Python cypher())
│
▼
Arc<Mutex<Arc<DirGraph>>>
+ per-tx working copies
Three things live in bolt-server but not in the Python API:
Arc<Mutex<Arc<DirGraph>>>for the shared graph — so commits can swap the inner Arc.Per-session transaction state in a
HashMap<TransactionHandle, Arc<Mutex<TxState>>>mirroring the snapshot/working CoW shape the PythonTransactionclass uses.async fnglue to satisfy boltr’sBoltBackendtrait.
The Cypher pipeline itself is the same. Both cypher() and bolt-
server’s execute() call into kglite::api::cypher::parse_cypher,
validate_schema, rewrite_text_score, optimize_with_disabled,
is_mutation_query, CypherExecutor::with_params(...).execute(),
and execute_mutable(). Differential testing against the
27-query corpus confirms row-for-row equivalence
(tests/test_bolt_server_differential.py).
CLI reference¶
kglite-bolt-server --graph PATH [OPTIONS]
Flag |
Default |
Meaning |
|---|---|---|
|
(required) |
Path to a |
|
|
Interface to bind |
|
|
TCP port (Neo4j default) |
|
off |
Reject all mutations: auto-commit + explicit |
|
|
|
|
— |
Username for |
|
— |
Password for |
|
(disabled) |
Per-session idle timeout — boltr reaps idle sessions, calling |
|
|
Max concurrent Bolt sessions |
|
|
Reject Bolt messages exceeding this size — protects against memory exhaustion from pathologically large queries |
Example:
kglite-bolt-server \
--graph my-graph.kgl \
--bind 0.0.0.0 --port 7687 \
--auth basic --auth-user neo4j --auth-pass secret \
--idle-timeout 300 \
--readonly
Connection URLs¶
bolt://host:port— direct connection. Use this.neo4j://host:port— routed connection (cluster-aware). Rejected: returnsNeo.ClientError.Request.Invalidwith the messagerouting not supported by kglite-bolt-server — connect with bolt:// (direct) rather than neo4j:// (routed).
Auth modes¶
Mode |
Behavior |
|---|---|
|
boltr’s LOGON handler accepts any credentials. Drivers sending |
|
|
Tracing / observability¶
The server uses tracing for structured logs. Filter via RUST_LOG:
# Default: info-level for our crate, warn-level for boltr.
RUST_LOG=kglite_bolt_server=info,boltr=warn kglite-bolt-server ...
# Verbose: per-session create/configure/close + per-tx begin/commit/rollback.
RUST_LOG=kglite_bolt_server=debug kglite-bolt-server ...
# Quiet: errors only.
RUST_LOG=kglite_bolt_server=warn kglite-bolt-server ...
Each log line carries structured fields (session_id, tx, etc.) for filtering downstream.
Known limitations¶
No auto-commit mutations. Mutations (
CREATE/SET/DELETE/MERGE) must be wrapped in an explicitBEGIN/COMMIT. Auto-commit reads work fine. (Drivers always wrap writes in BEGIN/COMMIT in practice; supporting auto-commit mutations adds surface for no real win.)Single-graph only. No multi-database support.
USE db_nameand per-session database switching viaconfigure_sessionare accepted but ignored.No causal consistency / bookmarks. Each session sees a consistent snapshot during a transaction; the
bookmarkfield is not returned on COMMIT.
Formerly listed here, now supported: OCC version checking on
commit (Phase E.4 — conflicting concurrent commits are rejected
with a retryable error instead of last-writer-wins), TLS via
--tls-cert / --tls-key (Phase F), neo4j:// routing URIs
via a single-server routing table (--advertise-addr), and
Neo4j-canonical CALL db.labels() YIELD label /
db.relationshipTypes() YIELD relationshipType column names.
Driver compatibility matrix¶
Driver |
Status |
Notes |
|---|---|---|
|
✓ Verified — 226+ tests in |
6.1.0 actively tested in CI |
Cypher Shell |
Untested |
Should work; uses Java driver internally |
Neo4j Browser |
Untested |
Configure browser to point at |
LangChain |
Untested |
Uses Python driver; should work for basic Cypher |
BloodHound |
Untested |
Should work for Cypher-level queries; BloodHound-specific Neo4j features (APOC procs etc.) NOT supported |
Java / JS / Go drivers |
Untested |
All use the same Bolt v5 protocol; should work but not exercised |
The Python driver path is the only one with automated regression coverage. Other drivers are likely to work but exercise them manually before relying on them.
Common error symptoms¶
Symptom |
Cause |
Fix |
|---|---|---|
|
Server not running, or wrong port |
Check |
|
|
Match driver’s |
|
Driver used |
Switch to |
|
|
Either start server without |
|
Whitespace-only or empty string sent |
Check the query string isn’t being silently truncated upstream |
|
Cypher with a |
Split into separate |
|
Parser rejected the query |
Standard Cypher syntax fix |
|
Parameter was |
Send |
|
Cypher executor returned an error not yet typed by the string heuristic |
Check server tracing logs for the underlying error; the message in the FAILURE response is unchanged from the original kglite error string |
Connection drops with no error |
Tokio task panicked (RA-2 + RA-3 should prevent this; if you see it, file a bug) |
Run with |
Performance shape¶
Numbers from tests/benchmarks/test_bench_bolt.py on M4 Apple
Silicon (release build, debug-build kglite extension), one bolt-
server, one driver, fresh 10k-Person + 30k-KNOWS graph:
Benchmark |
Min latency |
Notes |
|---|---|---|
|
~77 µs |
BEGIN + commit() with zero mutations. Arc clone + handle mint + cleanup. |
|
~278 µs |
Full handshake + LOGON + RUN + GOODBYE for one query. Amortizes to negligible across many queries per session. |
|
~583 µs |
One small RUN+PULL on an already-open driver session. Compare to direct-Python |
|
~8.8 ms |
BEGIN + 100 CREATEs + COMMIT (≈88 µs per CREATE inside a tx). |
|
~63 ms |
10k Person.name rows. Boltr’s PULL pagination + driver-side materialization. |
|
~167 ms |
10k full Node structs (Phase A.1 + C.4 path). The 2.6× ratio over |
Practical advice:
For request/response patterns (a driver sending one query and reading the result): expect ~300 µs to ~5 ms latency depending on query complexity.
For bulk read: a 10k-row pull takes 60-170 ms. If the same data fits in memory you may want to do it in-process via the Python API (no wire tax at all).
For bulk write: BEGIN + many CREATEs + COMMIT is the right pattern; ≈88 µs per CREATE means 100k inserts is roughly 9 seconds.
For many small sessions: amortize by holding the driver connection open (the neo4j driver pools sessions). A fresh
verify_connectivity+ RUN + close costs ~280 µs; reuse is much cheaper.
See also¶
docs/python/transactions.md— how the PythonTransactionclass works, including the OCC + CoW pattern that bolt-server mirrors.docs/concepts/concurrency.md— the single-owner contract,freeze()snapshots, and the underlyingArc<DirGraph>+ GIL-release model.bolt_implementation.md— Phase plan and status, including the boltr v0.2 dependency rationale.tests/test_bolt_server_*.py— the 226+ tests exercising the server (smoke / correctness / transactions / concurrency / robustness / differential).