Show HN: LatticeDB – Like SQLite but for graph databases

Hacker News Top Products

Summary

LatticeDB is an embedded property-graph database that integrates vector and full-text indexing into a single-file format, enabling graph traversal, similarity search, and BM25 search in one query layer for local applications.

We have been using graph DBs more and more at work. I found them painful to work with locally and decided to try and build something better.
Original Article
View Cached Full Text

Cached at: 08/25/26, 07:57 PM

jeffhajewski/latticedb

Source: https://github.com/jeffhajewski/latticedb

LatticeDB

Embedded property-graph database with native vector and full-text indexing.

LatticeDB 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.

LatticeDB 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.

  • 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.
-- Find chunks similar to a query, traverse to their document, then to the author
MATCH (chunk:Chunk)-[:PART_OF]->(doc:Document)-[:AUTHORED_BY]->(author:Person)
WHERE chunk.embedding <=> $query_vector < 0.3
  AND doc.content @@ "neural networks"
RETURN doc.title, chunk.text, author.name
ORDER BY chunk.embedding <=> $query_vector
LIMIT 10

Install

CLI

curl -fsSL https://raw.githubusercontent.com/jeffhajewski/latticedb/main/dist/install.sh | bash

Python

pip install latticedb

Published wheels are expected to bundle liblattice on supported platforms. Source installs can also bundle a staged native library during wheel builds with LATTICE_BUNDLE_LIB_DIR=/path/to/lib.

TypeScript / Node.js

npm install @hajewski/latticedb

Published package tarballs are expected to bundle liblattice on supported platforms. Source checkouts can stage the native library into the package with LATTICE_BUNDLE_LIB_DIR=/path/to/lib npm run bundle:native.

Go

See bindings/go/README.md for the current cgo workflow. The default consumer path uses installed pkg-config metadata; in-repo development can use -tags repolocal against zig-out/lib. There is also a runnable graph/vector/text retrieval example in examples/go.

Recent binding-surface cleanups moved embedding helpers into dedicated modules and subpackages. See docs/client_api_migration.md for the preferred imports and current compatibility aliases.

Start Here

Example

A complete example: create a small knowledge graph with documents and authors, store embeddings, index text, then query across all three search modes.

Python

from latticedb import Database
from latticedb.embedding import hash_embed

with Database("knowledge.db", create=True, enable_vectors=True, vector_dimensions=128) as db:

    # --- Build the graph ---
    with db.write() as txn:
        # Create authors
        alice = txn.create_node(labels=["Person"], properties={"name": "Alice", "field": "ML"})
        bob = txn.create_node(labels=["Person"], properties={"name": "Bob", "field": "Systems"})
        txn.create_edge(alice.id, bob.id, "COLLABORATES_WITH")

        # Create documents with chunks
        for title, text, author in [
            ("Attention Is All You Need", "The transformer architecture uses self-attention...", alice),
            ("Scaling Laws for LLMs", "We find that model performance scales predictably...", alice),
            ("Log-Structured Merge Trees", "LSM trees optimize write-heavy workloads...", bob),
        ]:
            doc = txn.create_node(labels=["Document"], properties={"title": title})
            chunk = txn.create_node(labels=["Chunk"], properties={"text": text})

            # Store embedding and index text
            txn.set_vector(chunk.id, "embedding", hash_embed(text, dimensions=128))
            txn.fts_index(chunk.id, text)

            txn.create_edge(chunk.id, doc.id, "PART_OF")
            txn.create_edge(doc.id, author.id, "AUTHORED_BY")

        txn.commit()

    # --- Query: vector search + text match + graph traversal ---
    results = db.query("""
        MATCH (chunk:Chunk)-[:PART_OF]->(doc:Document)-[:AUTHORED_BY]->(author:Person)
        WHERE chunk.embedding <=> $query < 0.5
        RETURN doc.title, chunk.text, author.name
        ORDER BY chunk.embedding <=> $query
        LIMIT 5
    """, parameters={"query": hash_embed("transformer attention mechanism", dimensions=128)})

    for row in results:
        print(f"{row['doc.title']} by {row['author.name']}")

    # --- Full-text search ---
    for r in db.fts_search("self-attention transformer"):
        print(f"Node {r.node_id}: score={r.score:.4f}")

    # --- Aggregations ---
    stats = db.query("""
        MATCH (doc:Document)-[:AUTHORED_BY]->(p:Person)
        RETURN p.name, count(doc) AS papers
        ORDER BY papers DESC
    """)
    for row in stats:
        print(f"{row['p.name']}: {row['papers']} papers")

TypeScript

import { Database } from "@hajewski/latticedb";
import { hashEmbed } from "@hajewski/latticedb/embedding";

const db = new Database("knowledge.db", {
  create: true,
  enableVectors: true,
  vectorDimensions: 128,
});
await db.open();

// Build a graph
await db.write(async (txn) => {
  const alice = await txn.createNode({
    labels: ["Person"],
    properties: { name: "Alice", field: "ML" },
  });
  const doc = await txn.createNode({
    labels: ["Document"],
    properties: { title: "Attention Is All You Need" },
  });
  const chunk = await txn.createNode({
    labels: ["Chunk"],
    properties: { text: "The transformer architecture uses self-attention..." },
  });

  await txn.setVector(chunk.id, "embedding", hashEmbed("transformer self-attention", 128));
  await txn.ftsIndex(chunk.id, "The transformer architecture uses self-attention...");

  await txn.createEdge(chunk.id, doc.id, "PART_OF");
  await txn.createEdge(doc.id, alice.id, "AUTHORED_BY");
});

// Query across vector search + graph traversal
const results = await db.query(
  `MATCH (chunk:Chunk)-[:PART_OF]->(doc:Document)-[:AUTHORED_BY]->(author:Person)
   WHERE chunk.embedding <=> $query < 0.5
   RETURN doc.title, chunk.text, author.name
   ORDER BY chunk.embedding <=> $query
   LIMIT 5`,
  { query: hashEmbed("attention mechanism", 128) }
);

for (const row of results.rows) {
  console.log(`${row["doc.title"]} by ${row["author.name"]}`);
}

await db.close();

Go

db, err := latticedb.Open("knowledge.db", latticedb.OpenOptions{
    Create: true,
    EnableVectors: true,
    VectorDimensions: 128,
})
if err != nil {
    log.Fatal(err)
}
defer db.Close()

err = db.Update(func(tx *latticedb.Tx) error {
    node, err := tx.CreateNode(latticedb.CreateNodeOptions{
        Labels: []string{"Chunk"},
        Properties: map[string]latticedb.Value{"text": "The transformer architecture uses self-attention..."},
    })
    if err != nil {
        return err
    }
    if err := tx.SetVector(node.ID, "embedding", []float32{1, 0, 0, 0}); err != nil {
        return err
    }
    return tx.FTSIndex(node.ID, "The transformer architecture uses self-attention...")
})
if err != nil {
    log.Fatal(err)
}

Performance

Benchmarked on Apple M1, single-threaded, with auto-scaled buffer pool. Run zig build benchmark to reproduce. For the repeated-term FTS indexing workload that previously exposed quadratic append behavior, run zig build fts-benchmark.

Core Operations

OperationLatencyThroughputTargetStatus
Node lookup0.13 μs7.9M ops/sec< 1 μsPASS
Node creation0.65 μs1.5M ops/sec
Edge traversal9 μs111K ops/sec
Full-text search (100 docs)19 μs53K ops/sec
10-NN vector search (1M vectors)0.83 ms1.2K ops/sec< 10 ms @ 1MPASS

Vector Search (HNSW) at Scale

128-dimensional cosine vectors, M=16, ef_construction=200, ef_search=64, k=10. Run zig build vector-benchmark to reproduce.

ScaleMean LatencyP99 LatencyRecall@10Memory
1,00065 μs70 μs100%1 MB
10,000174 μs695 μs99%10 MB
100,000438 μs1.2 ms99%101 MB
1,000,000832 μs1.8 ms100%1,040 MB

Search 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.

ef_search Sensitivity (1M vectors)

ef_searchMean LatencyRecall@10
16506 μs57%
321.9 ms79%
64990 μs100%
1283.2 ms100%
25611.6 ms100%

Competitive Analysis

Point Lookups

SystemLatencyTypeSource
LatticeDB0.13 μsEmbeddedzig build benchmark
RocksDB (in-memory)0.14 μsEmbeddedRocksDB wiki
SQLite (in-memory)~0.2 μsEmbeddedTurso blog
SQLite (WAL, disk)3 μs (p90)Embeddedmarending.dev
Neo4j28 ms (p99)ServerMemgraph comparison

LatticeDB’s B+Tree achieves sub-microsecond cached lookups, matching RocksDB in-memory and outperforming SQLite on disk by 23x.

Vector Search

SystemLatency (10-NN)ScaleTypeSource
LatticeDB0.83 ms mean, 100% recall1MEmbeddedzig build vector-benchmark
FAISS HNSW (single-thread)0.5–3 ms1MLibraryFAISS wiki
Weaviate1.4 ms mean, 3.1 ms P991MServerWeaviate benchmarks
Qdrant~1–2 ms1MServerQdrant benchmarks
Milvus + SQ82.2 ms P991MServerVectorDBBench
pgvector HNSW~5 ms @ 99% recall1MExtensionJonathan Katz
LanceDB3–5 ms1MEmbeddedLanceDB blog
Chroma4–5 ms mean1MEmbeddedChroma docs
Pinecone P2~15 ms (incl. network)1MCloudPinecone blog
sqlite-vec (brute force)17 ms1MExtensionAlex Garcia

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).

Graph Traversal

System2-hop (100K nodes)TypeSource
LatticeDB39 μsEmbeddedzig build sqlite-benchmark
SQLite (recursive CTE)548 μsEmbeddedzig build sqlite-benchmark
Kuzu19 msEmbeddedThe Data Quarry
Neo4j10 ms (1M nodes)ServerNeo4j blog

LatticeDB vs SQLite — Social network graph with power-law degree distribution, adjacency cache pre-warmed:

Small Scale (10K nodes, 50K edges)

WorkloadLatticeDBSQLiteSpeedup
1-hop traversal560 ns13.0 μs23x
2-hop traversal3.0 μs37.5 μs13x
3-hop traversal19.1 μs178.5 μs9x
Variable path (1..5)82.4 μs4.3 ms52x

Medium Scale (100K nodes, 500K edges)

WorkloadLatticeDBSQLiteSpeedup
1-hop traversal8.0 μs290.0 μs36x
2-hop traversal38.7 μs548.3 μs14x
3-hop traversal197.3 μs1.2 ms6x
Variable path (1..5)134.4 μs10.1 ms75x

Depth-Limited Traversal (10K nodes, 50K edges)

DepthLatticeDBSQLiteSpeedup
10311 μs121 ms390x
15380 μs271 ms713x
25318 μs587 ms1,848x
50500 μs1.4 s2,819x

LatticeDB uses BFS with adjacency cache and bitset visited tracking. SQLite uses a recursive CTE with UNION deduplication. 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 to reproduce.

Full-Text Search (BM25)

SystemSearch LatencyTypeSource
LatticeDB19 μsEmbeddedzig build benchmark
SQLite FTS5< 6 msEmbeddedSQLite Cloud
Elasticsearch1–10 msServerVarious
Tantivy10–100 μsLibraryVarious

LatticeDB’s inverted index with BM25 scoring is ~300x faster than SQLite FTS5 and competitive with Tantivy (a dedicated Rust search library).

Features

Graph

  • Nodes and edges with labels and arbitrary properties
  • Durable explicit equality indexes for scoped node and edge properties
  • Multi-hop traversal, variable-length paths (*1..3)
  • ACID transactions with commit/rollback and crash recovery
  • MERGE, WITH, UNWIND, aggregations (count, sum, avg, min, max, collect)

Vector Search

  • HNSW approximate nearest neighbor with configurable M, ef
  • Built-in hash embeddings or HTTP client for Ollama/OpenAI
  • Bulk vector node insertion for fast ingestion

Full-Text Search

  • BM25-ranked inverted index with tokenization and stemming
  • Fuzzy search with configurable Levenshtein distance

Cypher Query Language

  • MATCH, WHERE, RETURN, CREATE, DELETE, SET, REMOVE
  • ORDER BY, LIMIT, SKIP, DETACH DELETE
  • Vector distance operator: <=>
  • Full-text search operator: @@
  • Parameters: $name

Operations

  • Single-file storage with write-ahead log for crash recovery
  • Durable named streams with explicit consumer offsets, manual trim, and graph changefeeds
  • Online freelist reuse plus lattice compact for safe physical tail reclamation
  • Zero configuration — open a file and start working
  • Embedded single-writer model for local applications
  • Clean C API; Python, TypeScript, and Go bindings wrap it

Use Cases

  • 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

When to Use Something Else

LatticeDB is fast, but speed is not the only thing that matters. Here are cases where a different tool is the better choice.

You need multiple applications writing to the same database at the same time. LatticeDB 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.

Your data is fundamentally tabular. If 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.

You need to scale beyond a single machine. LatticeDB 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.

You need the full Cypher language. LatticeDB supports most of Cypher but not all of it. Features like OPTIONAL MATCH and CALL procedures are not yet implemented. If your queries depend on these, Neo4j is the complete implementation.

You need mature tooling and ecosystem. Neo4j 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.

Building from Source

Written in Zig. No dependencies.

git clone https://github.com/jeffhajewski/latticedb.git
cd latticedb
zig build                  # build everything
zig build test             # run tests
zig build -Doptimize=ReleaseFast   # optimized build

Documentation

License

MIT

Similar Articles

Show HN: HelixDB – A graph database built on object storage

Hacker News Top

HelixDB is a graph-vector database built in Rust for knowledge graphs and AI memory, offering a unified platform that supports graph, vector, KV, document, and relational data models, with tools for easy local and cloud deployment.

Fluree DB (GitHub Repo)

TLDR AI

Fluree DB is an open-source, temporal graph database with git-like branching, integrated vector/text/geo search, fine-grained access control, and support for SPARQL, JSON-LD, and Open Cypher. It is optimized for AI agent memory and achieves high performance on billion-scale graphs.

Slater – Low-memory graphdb designed for read-heavy graphs

Hacker News Top

Slater is a low-memory graph database for read-heavy workloads that serves large graphs from disk using a fixed cache budget, enabling query of hundreds of millions of nodes and billions of edges from just a few hundred MB of RAM, with standard Bolt protocol compatibility and live writes.