{"slug": "latticedb-like-sqlite-but-for-graph-databases", "title": "LatticeDB – Like SQLite but for graph databases", "summary": "LatticeDB, an embedded single-file property-graph database developed by Jeff Hajewski, combines graph traversal, HNSW vector similarity search, and BM25 full-text search in one query layer, with performance benchmarks of 0.13 μs node lookups and 0.83 ms vector search at 1M vectors with 100% recall. The database is designed for local, relationship-heavy workloads such as Graph RAG and agent memory, and offers CLI, Python, TypeScript/Node.js, and Go bindings.", "body_md": "**Embedded property-graph database with native vector and full-text indexing.**\n\nLatticeDB is a single-file local database for connected, semantic, and textual data. It lets you traverse relationships, run vector similarity search, and do BM25 full-text search over the same dataset in one engine and one query layer. It is designed for relationship-heavy workloads on a single machine, with zero-config operation and an embedded single-writer model.\n\nLatticeDB is an embedded, single-file graph database that lets local applications query the same data by relationship, semantics, and text, then consume durable graph and application events from the same file. Workloads like Graph RAG, agent memory, and local knowledge tools are examples built on those primitives, not the definition of the engine.\n\n**One file.** Your entire database is a single portable file. No server, no configuration.**One query layer.** Graph traversal, HNSW vector similarity, and BM25 full-text — in the same query language.**One event log.** Durable named streams and a built-in graph changefeed share the same transaction/WAL path as graph writes.**Local-first.** Designed for one owning process on one machine, with WAL-backed durability.**Fast.** 0.13 μs node lookups. 0.83 ms vector search at 1M vectors with 100% recall.\n\n```\n-- Find chunks similar to a query, traverse to their document, then to the author\nMATCH (chunk:Chunk)-[:PART_OF]->(doc:Document)-[:AUTHORED_BY]->(author:Person)\nWHERE chunk.embedding <=> $query_vector < 0.3\n  AND doc.content @@ \"neural networks\"\nRETURN doc.title, chunk.text, author.name\nORDER BY chunk.embedding <=> $query_vector\nLIMIT 10\n```\n\n**CLI**\n\n```\ncurl -fsSL https://raw.githubusercontent.com/jeffhajewski/latticedb/main/dist/install.sh | bash\n```\n\n**Python**\n\n```\npip install latticedb\n```\n\nPublished wheels are expected to bundle `liblattice`\n\non supported platforms. Source installs can also bundle a staged native library during wheel builds with `LATTICE_BUNDLE_LIB_DIR=/path/to/lib`\n\n.\n\n**TypeScript / Node.js**\n\n```\nnpm install @hajewski/latticedb\n```\n\nPublished package tarballs are expected to bundle `liblattice`\n\non supported platforms. Source checkouts can stage the native library into the package with `LATTICE_BUNDLE_LIB_DIR=/path/to/lib npm run bundle:native`\n\n.\n\n**Go**\n\nSee [bindings/go/README.md](/jeffhajewski/latticedb/blob/main/bindings/go/README.md) for the current cgo workflow. The default consumer path uses installed `pkg-config`\n\nmetadata; in-repo development can use `-tags repolocal`\n\nagainst `zig-out/lib`\n\n.\nThere is also a runnable graph/vector/text retrieval example in [examples/go](/jeffhajewski/latticedb/blob/main/examples/go).\n\nRecent binding-surface cleanups moved embedding helpers into dedicated modules and subpackages. See [docs/client_api_migration.md](/jeffhajewski/latticedb/blob/main/docs/client_api_migration.md) for the preferred imports and current compatibility aliases.\n\n[Getting Started](/jeffhajewski/latticedb/blob/main/docs/getting_started.md)maps the shortest path for CLI, Python, TypeScript, and Go.[CLI Quickstart](/jeffhajewski/latticedb/blob/main/examples/cli/README.md)is the smallest copy-paste example in the repo.[Examples Overview](/jeffhajewski/latticedb/blob/main/examples/README.md)covers the larger graph/vector/text retrieval demos.\n\nA complete example: create a small knowledge graph with documents and authors, store embeddings, index text, then query across all three search modes.\n\n``` python\nfrom latticedb import Database\nfrom latticedb.embedding import hash_embed\n\nwith Database(\"knowledge.db\", create=True, enable_vectors=True, vector_dimensions=128) as db:\n\n    # --- Build the graph ---\n    with db.write() as txn:\n        # Create authors\n        alice = txn.create_node(labels=[\"Person\"], properties={\"name\": \"Alice\", \"field\": \"ML\"})\n        bob = txn.create_node(labels=[\"Person\"], properties={\"name\": \"Bob\", \"field\": \"Systems\"})\n        txn.create_edge(alice.id, bob.id, \"COLLABORATES_WITH\")\n\n        # Create documents with chunks\n        for title, text, author in [\n            (\"Attention Is All You Need\", \"The transformer architecture uses self-attention...\", alice),\n            (\"Scaling Laws for LLMs\", \"We find that model performance scales predictably...\", alice),\n            (\"Log-Structured Merge Trees\", \"LSM trees optimize write-heavy workloads...\", bob),\n        ]:\n            doc = txn.create_node(labels=[\"Document\"], properties={\"title\": title})\n            chunk = txn.create_node(labels=[\"Chunk\"], properties={\"text\": text})\n\n            # Store embedding and index text\n            txn.set_vector(chunk.id, \"embedding\", hash_embed(text, dimensions=128))\n            txn.fts_index(chunk.id, text)\n\n            txn.create_edge(chunk.id, doc.id, \"PART_OF\")\n            txn.create_edge(doc.id, author.id, \"AUTHORED_BY\")\n\n        txn.commit()\n\n    # --- Query: vector search + text match + graph traversal ---\n    results = db.query(\"\"\"\n        MATCH (chunk:Chunk)-[:PART_OF]->(doc:Document)-[:AUTHORED_BY]->(author:Person)\n        WHERE chunk.embedding <=> $query < 0.5\n        RETURN doc.title, chunk.text, author.name\n        ORDER BY chunk.embedding <=> $query\n        LIMIT 5\n    \"\"\", parameters={\"query\": hash_embed(\"transformer attention mechanism\", dimensions=128)})\n\n    for row in results:\n        print(f\"{row['doc.title']} by {row['author.name']}\")\n\n    # --- Full-text search ---\n    for r in db.fts_search(\"self-attention transformer\"):\n        print(f\"Node {r.node_id}: score={r.score:.4f}\")\n\n    # --- Aggregations ---\n    stats = db.query(\"\"\"\n        MATCH (doc:Document)-[:AUTHORED_BY]->(p:Person)\n        RETURN p.name, count(doc) AS papers\n        ORDER BY papers DESC\n    \"\"\")\n    for row in stats:\n        print(f\"{row['p.name']}: {row['papers']} papers\")\njs\nimport { Database } from \"@hajewski/latticedb\";\nimport { hashEmbed } from \"@hajewski/latticedb/embedding\";\n\nconst db = new Database(\"knowledge.db\", {\n  create: true,\n  enableVectors: true,\n  vectorDimensions: 128,\n});\nawait db.open();\n\n// Build a graph\nawait db.write(async (txn) => {\n  const alice = await txn.createNode({\n    labels: [\"Person\"],\n    properties: { name: \"Alice\", field: \"ML\" },\n  });\n  const doc = await txn.createNode({\n    labels: [\"Document\"],\n    properties: { title: \"Attention Is All You Need\" },\n  });\n  const chunk = await txn.createNode({\n    labels: [\"Chunk\"],\n    properties: { text: \"The transformer architecture uses self-attention...\" },\n  });\n\n  await txn.setVector(chunk.id, \"embedding\", hashEmbed(\"transformer self-attention\", 128));\n  await txn.ftsIndex(chunk.id, \"The transformer architecture uses self-attention...\");\n\n  await txn.createEdge(chunk.id, doc.id, \"PART_OF\");\n  await txn.createEdge(doc.id, alice.id, \"AUTHORED_BY\");\n});\n\n// Query across vector search + graph traversal\nconst results = await db.query(\n  `MATCH (chunk:Chunk)-[:PART_OF]->(doc:Document)-[:AUTHORED_BY]->(author:Person)\n   WHERE chunk.embedding <=> $query < 0.5\n   RETURN doc.title, chunk.text, author.name\n   ORDER BY chunk.embedding <=> $query\n   LIMIT 5`,\n  { query: hashEmbed(\"attention mechanism\", 128) }\n);\n\nfor (const row of results.rows) {\n  console.log(`${row[\"doc.title\"]} by ${row[\"author.name\"]}`);\n}\n\nawait db.close();\ndb, err := latticedb.Open(\"knowledge.db\", latticedb.OpenOptions{\n    Create: true,\n    EnableVectors: true,\n    VectorDimensions: 128,\n})\nif err != nil {\n    log.Fatal(err)\n}\ndefer db.Close()\n\nerr = db.Update(func(tx *latticedb.Tx) error {\n    node, err := tx.CreateNode(latticedb.CreateNodeOptions{\n        Labels: []string{\"Chunk\"},\n        Properties: map[string]latticedb.Value{\"text\": \"The transformer architecture uses self-attention...\"},\n    })\n    if err != nil {\n        return err\n    }\n    if err := tx.SetVector(node.ID, \"embedding\", []float32{1, 0, 0, 0}); err != nil {\n        return err\n    }\n    return tx.FTSIndex(node.ID, \"The transformer architecture uses self-attention...\")\n})\nif err != nil {\n    log.Fatal(err)\n}\n```\n\nBenchmarked on Apple M1, single-threaded, with auto-scaled buffer pool. Run `zig build benchmark`\n\nto reproduce.\nFor the repeated-term FTS indexing workload that previously exposed quadratic append behavior, run `zig build fts-benchmark`\n\n.\n\n| Operation | Latency | Throughput | Target | Status |\n|---|---|---|---|---|\n| Node lookup | 0.13 μs | 7.9M ops/sec | < 1 μs | PASS |\n| Node creation | 0.65 μs | 1.5M ops/sec | — | — |\n| Edge traversal | 9 μs | 111K ops/sec | — | — |\n| Full-text search (100 docs) | 19 μs | 53K ops/sec | — | — |\n| 10-NN vector search (1M vectors) | 0.83 ms | 1.2K ops/sec | < 10 ms @ 1M | PASS |\n\n128-dimensional cosine vectors, M=16, ef_construction=200, ef_search=64, k=10. Run `zig build vector-benchmark`\n\nto reproduce.\n\n| Scale | Mean Latency | P99 Latency | Recall@10 | Memory |\n|---|---|---|---|---|\n| 1,000 | 65 μs | 70 μs | 100% | 1 MB |\n| 10,000 | 174 μs | 695 μs | 99% | 10 MB |\n| 100,000 | 438 μs | 1.2 ms | 99% | 101 MB |\n| 1,000,000 | 832 μs | 1.8 ms | 100% | 1,040 MB |\n\nSearch latency scales sub-linearly (O(log N)) with 99–100% recall@10. Uses heuristic neighbor selection (HNSW paper Algorithm 4) for diverse graph connectivity, connection page packing for ~4.5x memory reduction, and pre-normalized dot product for fast cosine distance.\n\n**ef_search Sensitivity (1M vectors)**\n\n| ef_search | Mean Latency | Recall@10 |\n|---|---|---|\n| 16 | 506 μs | 57% |\n| 32 | 1.9 ms | 79% |\n| 64 | 990 μs | 100% |\n| 128 | 3.2 ms | 100% |\n| 256 | 11.6 ms | 100% |\n\n| System | Latency | Type | Source |\n|---|---|---|---|\nLatticeDB |\n0.13 μs |\nEmbedded | `zig build benchmark` |\n| RocksDB (in-memory) | 0.14 μs | Embedded |\n|\n\n[Turso blog](https://turso.tech/blog/microsecond-level-sql-query-latency-with-libsql-local-replicas-5e4ae19b628b)[marending.dev](https://marending.dev/notes/sqlite-benchmarks/)[Memgraph comparison](https://memgraph.com/blog/memgraph-vs-neo4j-performance-benchmark-comparison)LatticeDB's B+Tree achieves sub-microsecond cached lookups, matching RocksDB in-memory and outperforming SQLite on disk by 23x.\n\n| System | Latency (10-NN) | Scale | Type | Source |\n|---|---|---|---|---|\nLatticeDB |\n0.83 ms mean, 100% recall |\n1M | Embedded | `zig build vector-benchmark` |\n| FAISS HNSW (single-thread) | 0.5–3 ms | 1M | Library |\n|\n\n[Weaviate benchmarks](https://docs.weaviate.io/weaviate/benchmarks/ann)[Qdrant benchmarks](https://qdrant.tech/benchmarks/)[VectorDBBench](https://zilliz.com/vdbbench-leaderboard)[Jonathan Katz](https://jkatz05.com/post/postgres/pgvector-performance-150x-speedup/)[LanceDB blog](https://medium.com/etoai/benchmarking-lancedb-92b01032874a)[Chroma docs](https://docs.trychroma.com/production/administration/performance)[Pinecone blog](https://www.pinecone.io/blog/dedicated-read-nodes/)[Alex Garcia](https://alexgarcia.xyz/blog/2024/sqlite-vec-stable-release/index.html)LatticeDB at 1M achieves 0.83 ms mean with 100% recall@10 — faster than FAISS single-threaded HNSW and competitive with Weaviate and Qdrant server-based systems (which add network overhead in practice).\n\n| System | 2-hop (100K nodes) | Type | Source |\n|---|---|---|---|\nLatticeDB |\n39 μs |\nEmbedded | `zig build sqlite-benchmark` |\n| SQLite (recursive CTE) | 548 μs | Embedded | `zig build sqlite-benchmark` |\n| Kuzu | 19 ms | Embedded |\n|\n\n[Neo4j blog](https://neo4j.com/news/how-much-faster-is-a-graph-database-really/)**LatticeDB vs SQLite** — Social network graph with power-law degree distribution, adjacency cache pre-warmed:\n\n**Small Scale (10K nodes, 50K edges)**\n\n| Workload | LatticeDB | SQLite | Speedup |\n|---|---|---|---|\n| 1-hop traversal | 560 ns | 13.0 μs | 23x |\n| 2-hop traversal | 3.0 μs | 37.5 μs | 13x |\n| 3-hop traversal | 19.1 μs | 178.5 μs | 9x |\n| Variable path (1..5) | 82.4 μs | 4.3 ms | 52x |\n\n**Medium Scale (100K nodes, 500K edges)**\n\n| Workload | LatticeDB | SQLite | Speedup |\n|---|---|---|---|\n| 1-hop traversal | 8.0 μs | 290.0 μs | 36x |\n| 2-hop traversal | 38.7 μs | 548.3 μs | 14x |\n| 3-hop traversal | 197.3 μs | 1.2 ms | 6x |\n| Variable path (1..5) | 134.4 μs | 10.1 ms | 75x |\n\n**Depth-Limited Traversal (10K nodes, 50K edges)**\n\n| Depth | LatticeDB | SQLite | Speedup |\n|---|---|---|---|\n| 10 | 311 μs | 121 ms | 390x |\n| 15 | 380 μs | 271 ms | 713x |\n| 25 | 318 μs | 587 ms | 1,848x |\n| 50 | 500 μs | 1.4 s | 2,819x |\n\nLatticeDB uses BFS with adjacency cache and bitset visited tracking. SQLite uses a recursive CTE with `UNION`\n\ndeduplication. Both compute identical reachable node sets (~8K nodes). The gap widens at deeper depths as SQLite's CTE overhead grows with each recursion level. Run `zig build graph-benchmark -- --quick`\n\nto reproduce.\n\n| System | Search Latency | Type | Source |\n|---|---|---|---|\nLatticeDB |\n19 μs |\nEmbedded | `zig build benchmark` |\n| SQLite FTS5 | < 6 ms | Embedded |\n|\n\nLatticeDB's inverted index with BM25 scoring is ~300x faster than SQLite FTS5 and competitive with Tantivy (a dedicated Rust search library).\n\n**Graph**\n\n- Nodes and edges with labels and arbitrary properties\n- Durable explicit equality indexes for scoped node and edge properties\n- Multi-hop traversal, variable-length paths (\n`*1..3`\n\n) - ACID transactions with commit/rollback and crash recovery\n- MERGE, WITH, UNWIND, aggregations (\n`count`\n\n,`sum`\n\n,`avg`\n\n,`min`\n\n,`max`\n\n,`collect`\n\n)\n\n**Vector Search**\n\n- HNSW approximate nearest neighbor with configurable M, ef\n- Built-in hash embeddings or HTTP client for Ollama/OpenAI\n- Bulk vector node insertion for fast ingestion\n\n**Full-Text Search**\n\n- BM25-ranked inverted index with tokenization and stemming\n- Fuzzy search with configurable Levenshtein distance\n\n**Cypher Query Language**\n\n- MATCH, WHERE, RETURN, CREATE, DELETE, SET, REMOVE\n- ORDER BY, LIMIT, SKIP, DETACH DELETE\n- Vector distance operator:\n`<=>`\n\n- Full-text search operator:\n`@@`\n\n- Parameters:\n`$name`\n\n**Operations**\n\n- Single-file storage with write-ahead log for crash recovery\n- Durable named streams with explicit consumer offsets, manual trim, and graph changefeeds\n- Online freelist reuse plus\n`lattice compact`\n\nfor safe physical tail reclamation - Zero configuration — open a file and start working\n- Embedded single-writer model for local applications\n- Clean C API; Python, TypeScript, and Go bindings wrap it\n\n**Connected local data**— Notes, documents, catalogs, citation graphs, and entity graphs** Graph plus retrieval**— Relationship traversal, semantic search, and lexical search over the same dataset** Local knowledge tools**— Embedded apps that need graph structure without running a separate server** Agent memory and RAG pipelines**— One example class of workload built on the graph/vector/text substrate** Local development**— Lightweight alternative to Neo4j or Weaviate for prototyping on one machine\n\nLatticeDB is fast, but speed is not the only thing that matters. Here are cases where a different tool is the better choice.\n\n**You need multiple applications writing to the same database at the same time.**\nLatticeDB is embedded with a single-writer model. One process opens the file and owns it. If you need many clients connecting over a network, use Neo4j, PostgreSQL, or another client-server database.\n\n**Your data is fundamentally tabular.**\nIf your data fits naturally into rows and columns — sales records, user accounts, time series — a relational database like SQLite or PostgreSQL will be simpler and just as fast. Graph databases shine when relationships between records are the point, not an afterthought.\n\n**You need to scale beyond a single machine.**\nLatticeDB stores everything in one file on one machine. If you need sharding, replication, or distributed queries across billions of nodes, look at Neo4j cluster, Dgraph, or a managed service like Neptune.\n\n**You need the full Cypher language.**\nLatticeDB supports most of Cypher but not all of it. Features like `OPTIONAL MATCH`\n\nand `CALL`\n\nprocedures are not yet implemented. If your queries depend on these, Neo4j is the complete implementation.\n\n**You need mature tooling and ecosystem.**\nNeo4j has visualization tools, admin dashboards, monitoring, drivers in every language, and years of community resources. PostgreSQL has decades of tooling. LatticeDB is new and lean — which is a strength for embedding, but a weakness if you need a rich operational ecosystem around your database.\n\nWritten in Zig. No dependencies.\n\n```\ngit clone https://github.com/jeffhajewski/latticedb.git\ncd latticedb\nzig build                  # build everything\nzig build test             # run tests\nzig build -Doptimize=ReleaseFast   # optimized build\n```\n\n[Getting Started](/jeffhajewski/latticedb/blob/main/docs/getting_started.md)[Durable Streams and Graph Changefeeds](/jeffhajewski/latticedb/blob/main/docs/14_durable_streams.md)[Property Indexes](/jeffhajewski/latticedb/blob/main/docs/property_index_design.md)[Examples Overview](/jeffhajewski/latticedb/blob/main/examples/README.md)[CLI Quickstart](/jeffhajewski/latticedb/blob/main/examples/cli/README.md)[Architecture Overview](/jeffhajewski/latticedb/blob/main/docs/00_introduction.md)[0.10.0 Release Notes](/jeffhajewski/latticedb/blob/main/docs/release_notes_0.10.0.md)[0.9.6 Release Notes](/jeffhajewski/latticedb/blob/main/docs/release_notes_0.9.6.md)[0.9.5 Release Notes](/jeffhajewski/latticedb/blob/main/docs/release_notes_0.9.5.md)[0.9.0 Release Notes](/jeffhajewski/latticedb/blob/main/docs/release_notes_0.9.0.md)[0.8.7 Release Notes](/jeffhajewski/latticedb/blob/main/docs/release_notes_0.8.7.md)[0.8.6 Release Notes](/jeffhajewski/latticedb/blob/main/docs/release_notes_0.8.6.md)[0.8.5 Release Notes](/jeffhajewski/latticedb/blob/main/docs/release_notes_0.8.5.md)[0.8.4 Release Notes](/jeffhajewski/latticedb/blob/main/docs/release_notes_0.8.4.md)[0.8.2 Release Notes](/jeffhajewski/latticedb/blob/main/docs/release_notes_0.8.2.md)[0.8.0 Release Notes](/jeffhajewski/latticedb/blob/main/docs/release_notes_0.8.0.md)[Client API Migration Notes](/jeffhajewski/latticedb/blob/main/docs/client_api_migration.md)[Python API Reference](/jeffhajewski/latticedb/blob/main/bindings/python/README.md)[TypeScript API Reference](/jeffhajewski/latticedb/blob/main/bindings/typescript/README.md)[Go API Reference](/jeffhajewski/latticedb/blob/main/bindings/go/README.md)[C API Header](/jeffhajewski/latticedb/blob/main/include/lattice.h)", "url": "https://wpnews.pro/news/latticedb-like-sqlite-but-for-graph-databases", "canonical_source": "https://github.com/jeffhajewski/latticedb", "published_at": "2026-08-25 16:52:05+00:00", "updated_at": "2026-08-25 18:12:55.526933+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["LatticeDB", "Jeff Hajewski", "HNSW", "BM25", "Python", "TypeScript", "Node.js", "Go"], "alternates": {"html": "https://wpnews.pro/news/latticedb-like-sqlite-but-for-graph-databases", "markdown": "https://wpnews.pro/news/latticedb-like-sqlite-but-for-graph-databases.md", "text": "https://wpnews.pro/news/latticedb-like-sqlite-but-for-graph-databases.txt", "jsonld": "https://wpnews.pro/news/latticedb-like-sqlite-but-for-graph-databases.jsonld"}}