Enterprise AI database · written in Rust
One database for enterprise AI
NebulaDB stores, indexes and retrieves your enterprise data with SQL, JSON documents, vectors, hybrid search and streaming RAG, and exposes all of it to AI agents over MCP. It is one Rust binary, built from the ground up for the workloads that relational and search databases were never designed to carry.
- REST + SSE
- gRPC
- Postgres wire
- MCP
- Azure AI Search API
-- vector retrieval and structured filters, one statement SELECT id, title, region, severity FROM incidents WHERE semantic_match(content, 'TLS resets at checkout') AND region IN ('eu', 'uk') AND severity <= 2 LIMIT 3;
| id | title | region | severity |
|---|---|---|---|
| pm-2310 | ECONNRESET in payment gateway | eu | 1 |
| pm-2287 | p95 regression on checkout-api | uk | 2 |
| pm-2251 | Handshake timeouts after cert rotation | eu | 2 |
Counted from the public repository: crates/**/*.rs and #[test] / #[tokio::test] attributes.
The problem
Traditional databases were not built for AI workloads
Enterprises bolt vector search onto relational databases, run a separate search cluster, and glue an embedder, a chunker and an LLM gateway on top. Each layer has its own deploy, its own auth and its own way to fail. NebulaDB takes those layers into the database.
-
01
RAG is five services glued together
Vector store, embedding service, chunking script, LLM gateway and a streaming layer: five processes, five deploys, five places for auth to leak.
One binary does the pipeline. Ingest chunks and embeds server-side;
nebula-chunk · nebula-embed · nebula-llm · nebula-serverPOST /api/v1/ai/ragstreams retrieved context and generated tokens over one SSE response, with citations. -
02
Vectors are an extension, not a native type
Bolted-on vector types share a planner, buffer pool and vacuum designed for rows. Similarity search competes with the workload the engine was tuned for.
HNSW is the core index. Cosine, L2 and dot metrics, int8 quantisation with dequantisation fused into the distance loop, and SQL that plans
nebula-vector · nebula-sqlsemantic_match()as a first-class retrieval step. -
03
Exact tokens get lost in embeddings
Dense retrieval handles paraphrase but misses error codes, SKUs and function names. Teams run a second search engine to get them back.
Hybrid search in the same index. Okapi BM25 and HNSW are fused with per-bucket weights, with optional query expansion and a pluggable reranker.
nebula-bm25 · nebula-rerank · NEBULA_HYBRID_WEIGHTS -
04
In-memory vector indexes vanish on restart
A pod restart drops the index and someone re-ingests from source, paying for every embedding again.
Durable by design. Every mutation is CRC32-framed and fsynced to the WAL before it is applied. Boot loads the newest snapshot and replays the tail; a torn frame is truncated, never trusted. Vectors are stored in the log, so recovery needs no embedder.
nebula-wal · nebula-index/durability · nebula-backup -
05
Analysts and agents cannot reach the vector store
BI tools speak Postgres. Agents speak MCP. A bespoke REST dialect turns into adapters that nobody maintains.
Every client speaks its own protocol. The same SQL engine serves REST, gRPC and the Postgres wire protocol, so
nebula-pgwire · nebula-grpc · nebula-mcp · nebula-searchpsqlworks. A native MCP server gives agents 24 tools with auth passed through. -
06
HA and multi-region are bolted on later
Replicas fall behind silently, compaction deletes segments a follower still needs, and a write to the wrong node diverges forever.
Replication is part of the design. Raft within a region, home-region XDCR across regions, follower ack cursors that clamp WAL compaction, a write guard that returns 409 on followers, and a region-aware client that retries on the right home.
nebula-raft · nebula-cdc · nebula-client · operator/
Why Rust from the ground up
The language is part of the database's guarantees
A database is judged on its worst millisecond and its worst crash. Rust lets NebulaDB keep the speed of C++ while ruling out whole classes of bugs at compile time.
-
01
No garbage-collector pauses
Memory is freed deterministically by ownership. Tail latency for search and RAG is not hostage to a collector, which matters when an agent waits on every call.
-
02
Memory safety without a runtime
Buffer overflows, use-after-free and data races are compile errors, not CVEs. The WAL and HNSW arena are written in safe Rust with checked UTF-8 chunk boundaries.
-
03
Concurrency the compiler checks
Tokio serves REST, gRPC, pgwire and SSE from one async runtime. The HNSW graph uses per-node, per-layer locks, so readers search while writers insert.
-
04
Traits instead of glue
Embedder,LlmClient,ChunkerandRerankerare traits. Swap Ollama for OpenAI, Anthropic or Gemini by configuration; the deterministic mocks keep CI offline.
The workspace, crate by crate
Each concern is a separate crate with its own tests, so the boundaries in the diagram are the boundaries in the code.
- AI and retrieval
- Durability and HA
- Protocols and clients
- Core
nebula-coreIDs, error type and shared primitivesnebula-indexBuckets, chunks, parent/child docs, durable TextIndexnebula-vectorHNSW graph, distance metrics, int8 quantisationnebula-bm25Okapi BM25 inverted index for lexical recallnebula-rerankReranker and query-expansion stagesnebula-embedEmbedder trait, OpenAI-compatible and Ollama clientsnebula-cacheIn-process LRU embedding cachenebula-redis-cacheShared Redis tier, failure-transparentnebula-chunkFixed, sentence, heading-aware and code-aware chunkersnebula-llmStreaming LLM clients: Ollama, OpenAI, Anthropic, Gemininebula-sqlParser, typed plan tree, executor, result cache, EXPLAINnebula-walCRC-framed segments, subscribe API, ack-clamped compactionnebula-raftRaft log, state machine and snapshots (openraft)nebula-cdcTyped change events from the WAL to webhooks and filesnebula-backupSnapshot + WAL bundles to disk or S3, manifests, restorenebula-resourceMemory, CPU and disk pressure → operating modenebula-serverAxum REST, SSE, auth, rate limit, audit, metricsnebula-grpcTonic Document, Search and AI servicesnebula-pgwirePostgres wire protocol over the same SQL enginenebula-mcpModel Context Protocol server for agentsnebula-searchAzure AI Search-compatible APInebula-clientRegion-aware SDK, retries to the home regionnebulactlBackup and restore CLI, run by the operator
See it work
The algorithms, running in your browser
These demos run the same algorithms NebulaDB uses, on toy data, so you can change a parameter and watch the effect. For the real thing over a live corpus, open the showcase.
Find neighbours without reading every vector
Click the graph to place a query. The search enters at the top layer, takes long hops through the sparse upper layers, then widens to a beam of ef candidates on layer 0. Green rings are the 5 results.
Lexical and semantic, fused
Set the weight to 1.00 (vector only) on the error-code query: a look-alike ECONNRESET postmortem from another service ranks first. Move towards BM25 and the exact ERR_CONN_RESET document wins. NebulaDB min-max normalises both scores and takes a weighted sum, per bucket.
Kill it mid-write. Lose nothing acknowledged.
Write a few documents, take a snapshot, write more, then crash during an fsync. On restart NebulaDB loads the snapshot, replays only the newer frames, and truncates the half-written tail because its CRC32 does not match.
Frame format: len u32 · crc32 u32 · bincode(WalRecord). Snapshots are written to .tmp and atomically renamed.
curl -s -X POST http://localhost:8080/api/v1/ai/search \
-H 'content-type: application/json' \
-d '{"query": "dns failover", "top_k": 3, "hybrid": true}'
psql -h localhost -p 5433 -U any -d any -c \
"SELECT id, text FROM docs
WHERE semantic_match(content, 'dns failover')
LIMIT 3"
grpcurl -plaintext \
-import-path crates/nebula-grpc/proto -proto nebula.proto \
-d '{"query": "dns failover", "top_k": 3}' \
localhost:50051 nebula.v1.SearchService/SemanticSearch
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "semantic_search",
"arguments": { "query": "dns failover", "top_k": 3 } } }
curl -N -X POST http://localhost:8080/api/v1/ai/rag \
-H 'content-type: application/json' \
-d '{"query": "explain dns failover", "top_k": 3, "stream": true}'
event: context ← one per retrieved chunk, before any token
event: answer_delta ← LLM tokens as they are generated
event: done
Capabilities
Everything an AI application needs from its data layer
Store, index, understand, retrieve, answer and automate, on one highly available database.
-
SQL + JSON
SQL over documents
SQL and documentssemantic_match()andvector_distance()beside filters,IN,array_contains,GROUP BY, inner joins andEXPLAIN. JSON metadata on every document. -
Vectors
HNSW and hybrid search
Native HNSW with three metrics and int8 quantisation, BM25 fusion, query expansion, reranking and per-bucket weights.
Vector and hybrid search -
RAG
Batteries-included RAG
Smart chunking, embedding with a two-tier cache, grounded answers with
RAG pipeline[n]citations, history and caller-supplied context, streamed over SSE. -
Agents
MCP for AI agents
24 tools across SQL, search, RAG, documents, per-user long-term memory, cluster health, replication, backups and audit. Every tool declares whether it writes.
Agents and MCP -
HA
Replicated, multi-region
Raft within a region, home-region XDCR across regions, follower promotion, zero-downtime swap rebalance and a Kubernetes operator.
High availability -
Operations
Durable and observable
WAL, snapshots, backup to S3 and restore, CDC, Prometheus metrics with Grafana dashboards, live log streaming, slow-query log, audit log and graceful degradation.
Durability and operations
Architecture
One engine behind every protocol
Every surface calls the same index, so a bug fix or a feature lands everywhere at once and REST, gRPC, pgwire and MCP can never disagree.
Compared with a typical stack
What changes when the database understands AI
| Concern | Relational DB + extensions + glue | NebulaDB |
|---|---|---|
| Vector search | Extension type on a row engine; tuning shared with OLTP | Native HNSW, int8 quantisation, planned by SQL |
| Keyword recall | Separate search cluster to operate and sync | BM25 in the same index, fused per bucket |
| RAG pipeline | Chunker, embedder, LLM gateway and streamer as separate services | Server-side chunk, embed, retrieve and stream over one SSE call |
| Embedding cost | Cache built per application, if at all | LRU + Redis tiers with hit/miss metrics |
| AI agents | Custom tool wrappers per framework | MCP server with 24 typed tools, auth passed through |
| Restart | In-memory vector indexes rebuilt from source | Snapshot + WAL replay, no re-embedding |
| Multi-region | Replicate each component separately | Home-region XDCR for documents and vectors together |
| Runtime | Mixed: C, JVM, Python, Node | One Rust binary, no GC |
Quick start
Running in one command
The start script picks free ports, builds the stack, pulls local models through Ollama, seeds a corpus and prints the URLs. No external AI API is required.
git clone https://github.com/bwalia/nebuladb cd nebuladb ./scripts/start.sh # tinyllama + nomic-embed-text ./scripts/start.sh --model llama3 # a larger chat model
docker pull bwalia/nebula-server:latest docker pull bwalia/nebula-showcase:latest helm install nebula deploy/helm/nebuladb
Ingest
POST /api/v1/bucket/docs/documentchunks and embeds server-side.Search
POST /api/v1/ai/search, vector or hybrid.Answer
POST /api/v1/ai/ragstreams context then tokens.Query
psql -p 5433withsemantic_match().
Honest limits
What NebulaDB does not do yet
Knowing where a system stops is part of choosing it. These are documented in the repository and return clear errors rather than surprising results.
- SQL subsetNo
ORinWHERE, outer joins, subqueries, CTEs orHAVING. Unsupported syntax returns400 sql_unsupported(SQLSTATE 42601 on pgwire). - Simple query protocol only
psqlworks; drivers that require the extended protocol (prepare / bind) do not yet. - One writer per bucketMulti-region is active-active per cluster, with each bucket homed in one region. No multi-master writes to the same bucket.
- No cross-document transactionsEach upsert is its own durable commit.
- Coarse access controlAPI keys and JWT grant API access; there are no row- or field-level ACLs inside search results.
- Index in RAMThe working index lives in memory with snapshots on disk; there is no cold-storage tiering for rarely used vectors.
Showcase
See it on a live corpus
The showcase runs NebulaDB over real data: Companies House due diligence, a shop catalogue, Talent Match, Incident Copilot, Contract Review and Support Desk, plus cluster, rebalance and backup views.