23_bolt_server

command
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: MIT Imports: 19 Imported by: 0

README

Example 23 — Bolt v5 server round-trip

What it demonstrates

GoGraph speaking the Bolt v5 wire protocol end to end: it starts the embedded bolt/server over an in-memory graph, connects the official neo4j-go-driver/v5 as a real client, drives a battery of Cypher queries over driver sessions, and shuts the server down cleanly with no goroutine left behind. Because it puts the wire path under load, it also serves as a Bolt-throughput benchmark, reporting query throughput, a p50/p95/p99 latency distribution, and live heap.

Domain / scenario

A directed social network. Each :Person node carries an id (a 24-char hex string) and a name. Every person is given a random out-degree in [knows-min, knows-max] to distinct other people through :KNOWS edges, and every :KNOWS edge carries a mandatory since date. The dates are stored as ISO-8601 (YYYY-MM-DD) strings drawn from the seeded RNG and anchored to a fixed reference date, so they are reproducible for a given -seed and the engine reads them back as non-null, chronologically sortable values.

The graph is seeded in process through the property-graph API before the server starts. A pool of Bolt client sessions then fires the fixed query MATCH (n:Person) RETURN count(n) AS c repeatedly over the wire, verifying each response and timing it.

The listener binds to 127.0.0.1:0, so the kernel assigns a free port (a test run never collides on a fixed port); the client discovers it from ln.Addr(). The server is fully compatible with neo4j-go-driver/v5 and cypher-shell.

How to run

go run ./examples/23_bolt_server                                          # small deterministic default
go run ./examples/23_bolt_server -nodes 200000 -queries 50000 -sessions 16 -seed 7  # observable-scale run

Scale and flags

Flag Meaning Default Representative large value
-nodes number of :Person nodes to seed 2000 200000
-knows-min minimum :KNOWS out-degree per person 5 20
-knows-max maximum :KNOWS out-degree per person 8 50
-queries number of read queries to fire over the wire 2000 50000
-sessions number of concurrent driver sessions 4 16
-seed RNG seed (fixes the deterministic data shape) 42 any int64

The default is small enough to seed, serve, and query in well under a second, so the regression test stays comfortably within the short-layer 60 s package budget. Scale up the dataset, the query count, and the session count to make the Bolt path's throughput and latency tail observable.

Expected output

The deterministic facts — the seeded node count, the fixed query's result over that data, the number of queries that succeeded, and the seed-stable edge total — are reproducible for a fixed -seed. The # -prefixed telemetry lines (throughput, latency percentiles, heap) vary per run and per machine and are never pinned.

config.nodes=2000
config.knows=[5,8]
config.queries=2000
config.sessions=4
config.seed=42
nodes.person=2000
edges.knows=13012
q.count_person=2000
queries.ok=2000
# load.elapsed=856ms
# load.throughput=2336 queries/s
# load.latency_p50=1.671ms
# load.latency_p95=2.304ms
# load.latency_p99=2.77ms
# mem.heap_alloc=1001.59 KiB
# server shut down cleanly

edges.knows is the realised sum of the random per-person out-degrees: it is fixed for -seed 42 but changes with a different seed. The five # load.* lines and # mem.heap_alloc are illustrative — every run prints different timings, throughput, and heap.

Evidence it collects

For the Bolt/Cypher subject, the example reports:

  • Query throughput (# load.throughput) — successful queries per second across all sessions.
  • Latency distribution (# load.latency_p50/p95/p99) — the per-query round-trip latency tail over the whole load.
  • Live heap (# mem.heap_alloc) — the resident Go heap after a forced GC.

Scale -nodes, -queries, and -sessions up and watch how throughput and the latency tail respond to more data and more concurrent sessions — that is where the wire path's behaviour becomes interesting.

Key APIs

  • bolt/server.NewServer / Server.Serve / Server.Shutdown — start the Bolt v5 TCP server on a listener and tear it down gracefully, draining every per-connection goroutine.
  • bolt/server.Options — bound concurrent connections (MaxConnections) and set the per-connection idle deadline (ConnTimeout).
  • cypher.NewEngine — build the query engine over the in-memory graph the server serves.
  • graph/lpg.New / Graph.AddEdgeLabeled / Graph.SetEdgeProperty — seed the labelled property graph with :Person nodes and dated :KNOWS edges.
  • github.com/neo4j/neo4j-go-driver/v5/neo4j — the official Bolt client: NewDriverWithContext, VerifyConnectivity, NewSession, Session.Run, Result.Single, and clean Close on teardown.

Further reading

Documentation

Overview

Example 23_bolt_server drives the GoGraph Bolt v5 server end to end: it starts the embedded bolt/server over an in-memory labelled property graph, connects the official neo4j-go-driver/v5 as a real client, runs a battery of Cypher queries over driver sessions, and shuts everything down cleanly with no goroutine left behind.

Unlike a hello-world round-trip, this example seeds the served graph from a seeded, scale-parametrised generator and then puts the wire path under load, so it doubles as a Bolt-throughput benchmark. It reports the evidence that matters for the Bolt/Cypher subject — query throughput, a p50/p95/p99 latency distribution, and live Go heap — as volatile telemetry, while the deterministic results (a label-scan count equal to the known node count and the number of queries that succeeded) are printed as bare facts a regression test can pin.

Model

(:Person {id, name})                        // id is a 24-char hex string
(:Person)-[:KNOWS {since}]->(:Person)        // knowsMin..knowsMax per person

The graph is a directed social network: every person is given a random out-degree in [knowsMin, knowsMax] to distinct other people (no self-loops, no duplicate targets). Every KNOWS edge carries a mandatory since date, stored as an ISO-8601 (YYYY-MM-DD) string drawn from the seeded RNG and anchored to a fixed reference date, so it is reproducible for a given -seed and the cypher.Engine reads it back as a non-null, chronologically sortable value (lpg.TimeValue is not used: the Cypher reader maps it to null, whereas the tagged date strings round-trip).

Scale and load

Run with no flags, the example seeds a small deterministic graph (2000 people) and fires 2000 read queries from a pool of driver sessions, fast enough to stay well under the 60 s short-test budget. Every dimension is a flag, so the same binary scales the dataset and the query load up to where the Bolt path's behaviour is actually observable:

go run ./examples/23_bolt_server -nodes 200000 -queries 50000 -sessions 16 -seed 7

The deterministic facts are reproducible for a fixed -seed; only the telemetry (lines prefixed with "# ") — throughput, latency percentiles, and heap — varies between runs and machines.

Teardown

The listener binds to 127.0.0.1:0 so the kernel assigns a free port and a test run never collides. Serve runs under a cancellable context; on completion the client driver is closed, the server is gracefully shut down, and the serve goroutine is drained. Serve only returns after every per-connection goroutine has finished, so the drain guarantees no goroutine leaks — the same teardown discipline as bolt/server/example_test.go.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL