A clustered graph database built on LadybugDB — like Elasticsearch is to Lucene, Loveliness is to LadybugDB.
A "loveliness" is the collective noun for a group of ladybugs.
LadybugDB is a Kuzu fork — a fast, embedded, columnar graph engine with Cypher support. Loveliness wraps it in a distributed layer: hash-based sharding, Raft consensus, Bloom filter routing, edge-cut replication, and Neo4j Bolt protocol compatibility. Single Go binary, no external dependencies.
graph TB
HTTP[HTTP :8080] --> Router
Bolt[Bolt :7687] --> Router
Router[Query Router<br/>Bloom filter → route/scatter]
Router --> S0[Shard 0]
Router --> S1[Shard 1]
Router --> SN[Shard N]
Router -- remote --> TCP[TCP+MsgPack<br/>to peer nodes]
S0 --- WAL[WAL + Backup + Ingest Queue]
| Query Type | P50 | QPS |
|---|---|---|
| Point lookup | 425us | 10,758 |
| 1-hop traversal | 673us | 1,106 |
| Single write | 365us | 2,526 |
| Aggregation | 57ms | 16 |
| 2-hop traversal | 162ms | 5 |
| Bulk load | 70–190K nodes/sec | — |
Tested on 4× performance-4x machines (8 vCPU, 16GB RAM each), sjc region. 200 iterations, 16 concurrent workers. Benchmark binary runs inside the cluster on localhost for true in-network latencies.
| Query Type | P50 | P95 | QPS |
|---|---|---|---|
| Point lookup | 2.2ms | 3.2ms | 427 |
| Point lookup (16 workers) | 5.2ms | 12.2ms | 2,642 |
| Range filter | 3.2ms | 3.7ms | 290 |
| Count all nodes | 8.4ms | 11.0ms | 112 |
| Count filtered | 284ms | 310ms | 3 |
| Aggregation (avg) | 295ms | 321ms | 3 |
| Group-by aggregation | 769ms | 814ms | 1 |
| 1-hop traversal | 2.9ms | 380ms | 17 |
| 2-hop traversal | 241ms | 268ms | 4 |
| Variable-length path (1..3) | 2.3ms | 3.3ms | 414 |
| Friend-of-friend count | 476ms | 494ms | 2 |
| Mutual friends | 2.4ms | 3.0ms | 394 |
| Shortest path (1..6) | 29ms | 40ms | 33 |
| Single write | 1.8ms | 2.2ms | 542 |
| Merge upsert | 2.4ms | 3.0ms | 413 |
| Read-after-write | 3.3ms | 4.0ms | 305 |
| Bulk node load | 197K/sec | — | — |
| Bulk edge load | 373K/sec | — | — |
| Dataset | Nodes | Shards | Point lookup P50 | Concurrent QPS | Group-by P50 | Write P50 |
|---|---|---|---|---|---|---|
| 10M / 10M | 3 | 3 | 1.4ms | 1,792 | — | — |
| 20M / 20M | 3 | 3 | 863us | 1,991 | 838ms | — |
| 23M / 23M | 3 | 6 | 1.2ms | 6,831 | 246ms | 1.8ms |
| 50M / 50M | 4 | 12 | 2.2ms | 2,642 | 769ms | 1.8ms |
Point lookups stay sub-3ms through 50M nodes. Writes hold steady at 1.8ms regardless of dataset size. Bulk loading in-cluster hits 197K nodes/sec and 373K edges/sec.
# Full comparison: Loveliness (1-node, 3-node) vs Neo4j CE
./bench/run.sh
# Quick check: single-node Loveliness only
./bench/run.sh --quick
# Custom dataset size
./bench/run.sh --nodes=500000 --edges=500000
# 50M-scale on Fly.io (4 nodes, 12 shards)
./bench/run-50m.shResults land in bench/results/<timestamp>/ with JSON data, SVG charts, and a markdown comparison report. CI runs the full comparison on each release and opens a PR with updated results.
Full benchmarks and comparisons with Neo4j, Memgraph, TigerGraph, Neptune, and JanusGraph: docs/benchmarks.md
Speaks Bolt v4.x on :7687. Use any Neo4j driver — just change the URL. Both bolt:// (direct) and neo4j:// (routing with automatic failover) are supported.
from neo4j import GraphDatabase
# Direct connection
driver = GraphDatabase.driver("bolt://localhost:7687")
# Routing + automatic failover (recommended for clusters)
driver = GraphDatabase.driver("neo4j://localhost:7687")
with driver.session() as session:
result = session.run("MATCH (p:Person {name: $name}) RETURN p.name, p.age", name="Alice")72/72 exhaustive tests pass with the official Python driver. Details: docs/bolt.md
Fastest way — one command, real 3-node Raft cluster on your laptop:
loveliness up 3That's it. Three nodes, auto-configured ports, auto-bootstrap. Connect at bolt://localhost:7687 or http://localhost:8080.
From source:
git clone https://github.com/dreamware-nz/loveliness.git && cd loveliness
make build # requires LadybugDB: curl -fsSL https://install.ladybugdb.com | sh
make run # single node → :8080 (HTTP), :7687 (Bolt)
make docker # 3-node cluster via Docker Compose
make test # 260 tests across 12 packagesDeploy to Fly.io — zero-config cloud cluster in under 5 minutes:
cd deploy/fly
fly launch
fly scale count 3DNS auto-discovery handles peer finding. No manual peer configuration needed. See Fly.io deployment docs for details.
CLI:
loveliness help # show all commands
loveliness up 3 # 3-node local cluster
loveliness query "MATCH (p:Person {name: 'Alice'}) RETURN p" # query a running server
loveliness version # show versionSet LOVELINESS_URL to query a remote server (default: http://localhost:8080).
HTTP API:
# Schema (broadcast to all shards)
curl -s localhost:8080/cypher -d "CREATE NODE TABLE Person(name STRING, age INT64, PRIMARY KEY(name))"
# Write (routed to owning shard)
curl -s localhost:8080/cypher -d "CREATE (p:Person {name: 'Alice', age: 30})"
# Read (Bloom filter → single shard)
curl -s localhost:8080/cypher -d "MATCH (p:Person {name: 'Alice'}) RETURN p"
# Bulk load
curl -s localhost:8080/bulk/nodes -H "X-Table: Person" --data-binary @persons.csv
# Async ingest (returns 202 with job ID)
curl -s -X POST localhost:8080/ingest/nodes -H "X-Table: Person" --data-binary @persons.csvFull API reference: docs/api.md
# Single image
docker run -p 8080:8080 -p 7687:7687 dreamwarenz/loveliness
# 3-node cluster
docker compose upLoveliness ships a Model Context Protocol server so any MCP-aware LLM client (Claude Code, Claude Desktop, Cursor, Zed) can query and write through typed, schema-aware tools.
# one-shot: build the binary, register it with Claude, install the skill
make install-mcp
# or, manually:
claude mcp add loveliness -e LOVELINESS_URL=http://localhost:8080 -- loveliness-mcpmake install-mcp runs scripts/install-mcp.sh, which builds
loveliness-mcp, runs claude mcp add (or prints the manual JSON if
claude isn't on PATH), and links the bundled skill at
skills/loveliness/SKILL.md into ~/.claude/skills/loveliness/ so
agents get prose guidance on how to use the tools.
Full install snippets for Claude Desktop / Zed, tool reference, readonly and auth patterns: docs/mcp.md.
kubectl apply -f deploy/k8s/namespace.yml
kubectl apply -f deploy/k8s/service.yml
kubectl apply -f deploy/k8s/statefulset.ymlStatefulSet with headless service, persistent volumes, health probes. Details: docs/kubernetes.md
Loveliness uses Raft consensus with tight timeouts (1s heartbeat, 1s election). When a node fails:
- Leader failure: a new leader is elected within ~1-2 seconds. Writes fail during the election window, then resume on the new leader.
- Follower failure: reads and writes continue on remaining nodes. Shards that had replicas on the failed node become degraded but remain queryable from their primary.
- Connected node failure: your client gets a connection error. Reconnect to any other node in the cluster to continue.
Every node can serve both reads and writes. Reads execute locally via scatter-gather (the node forwards sub-queries to peer nodes holding remote shards). Writes to a non-leader node return a NOT_LEADER error that includes the current leader's address, so your client knows where to retry.
Put a load balancer (HAProxy, Nginx, cloud LB) in front of all nodes and use the health endpoint for routing:
GET /health → {"status": "ok", "role": "leader", "shards": {...}}
| Setup | How |
|---|---|
| Load balancer (recommended) | Point your client at the LB. Health check: GET /health returns 200 when the node is healthy. Works for both HTTP :8080 and Bolt :7687. |
| Client-side retry | Connect to any node. On connection failure, round-robin through the other nodes. All nodes accept all queries. |
| Kubernetes | The headless service (deploy/k8s/service.yml) already provides DNS-based discovery. The LoadBalancer service handles external traffic. |
Example HAProxy health check:
backend loveliness
option httpchk GET /health
http-check expect status 200
server node1 10.0.0.1:8080 check
server node2 10.0.0.2:8080 check
server node3 10.0.0.3:8080 check
For Bolt connections, you have two options:
| Scheme | Behavior |
|---|---|
neo4j://lb:7687 |
Driver sends a ROUTE message, discovers all cluster nodes, and handles failover automatically. Recommended for HA. |
bolt://lb:7687 |
Direct connection to a single node via the load balancer. The LB handles failover. |
The neo4j:// scheme works because Loveliness responds to ROUTE with all alive cluster members — the leader as the WRITE server, all nodes as READ and ROUTE servers. The driver automatically reconnects to another node if the current one goes down. TTL is 30 seconds, so topology changes propagate quickly.
from neo4j import GraphDatabase
# Automatic failover via driver routing (recommended)
driver = GraphDatabase.driver("neo4j://any-node:7687")
# Or via load balancer
driver = GraphDatabase.driver("neo4j://lb:7687")One env var enables token auth across HTTP and Bolt:
export LOVELINESS_AUTH_TOKEN=my-secret- HTTP: all endpoints except
/healthrequireAuthorization: Bearer <token> - Bolt: pass the token as the password in your driver auth (
auth=("neo4j", "my-secret")) - Disabled by default: empty token = open access (dev mode)
Details: docs/configuration.md
All transports support TLS. Three env vars enable it:
export LOVELINESS_TLS_CERT=/path/to/server.crt
export LOVELINESS_TLS_KEY=/path/to/server.key
export LOVELINESS_TLS_CA=/path/to/ca.crt # enables mTLS for inter-node traffic
export LOVELINESS_TLS_MODE=required| Boundary | What's encrypted | TLS type |
|---|---|---|
| Client → Node | HTTP :8080, Bolt :7687 |
Server TLS |
| Node → Node | TCP transport :9001, Raft |
mTLS (cluster CA) |
Without these vars, all listeners run plaintext (dev default). Details: docs/configuration.md
| Doc | Contents |
|---|---|
| Architecture | System design, query/write lifecycle, optimization phases, edge-cut replication, CGo safety |
| Benchmarks | Performance numbers, comparisons, transport benchmarks |
| Bolt Protocol | Neo4j driver compatibility, examples, test results |
| API Reference | HTTP endpoints, bulk loading, ingest queue, DR, consistency |
| Configuration | Environment variables, TLS, DNS discovery, shard count guidance |
| Fly.io Deploy | One-command cloud deployment with DNS auto-discovery |
| Kubernetes | StatefulSet deployment, scaling, backup to S3 |
| Project Structure | Package layout and file descriptions |
| Arrow Type Mapping | Cypher → Arrow IPC type mapping reference |
| Disaster Recovery | Backup, restore, manifest integrity, S3 |
| Metrics | Prometheus metrics catalogue and label budget |
| Contributing | Development setup, PR guidelines |