A simple, learning-focused vector database implementation in Rust. Kantan (簡単) means "simple" in Japanese - this project focuses on understanding core vector database concepts without production complexities.
kvdb is an educational project that implements a working vector database from scratch. It demonstrates fundamental concepts like vector normalization, similarity search, and persistent storage - perfect for learning how vector databases work under the hood.
Current Status: v3.0 - REST API with Actix-web
- L2 Vector Normalization: Automatic normalization on insertion
- Cosine Similarity Search: Using dot product on normalized vectors
- Flat Array Storage: Memory-efficient contiguous storage with excellent cache locality
- Persistence: Save/load databases to disk with bincode binary format
- REST API: Stateless HTTP API with insert, search, get, delete endpoints
- Library-First Architecture: Core logic separated from interface for future extensibility
- Comprehensive Testing: Unit tests + API integration tests + end-to-end persistence tests
- Rust (2024 edition)
git clone https://github.com/Kurotsuba/kvdb.git
cd kvdb
cargo build --release
cargo testuse kvdb::VecDB;
let mut db = VecDB::new();
// Insert vectors (automatically L2-normalized)
db.insert("doc1".to_string(), vec![1.0, 0.0, 0.0]).unwrap();
db.insert("doc2".to_string(), vec![0.0, 1.0, 0.0]).unwrap();
db.insert("doc3".to_string(), vec![0.7, 0.7, 0.0]).unwrap();
// Search for k most similar vectors
let results = db.search(vec![1.0, 1.0, 0.0], 2).unwrap();
for (id, _vector, score) in results {
println!("{}: similarity = {:.4}", id, score);
}
// Retrieve by ID
let vec = db.get("doc1").unwrap();
// Delete
db.delete("doc2").unwrap();
// Count
println!("{} vectors", db.count());
// Persist to disk
db.save("my_database.db").unwrap();
// Load from disk
let db = VecDB::load("my_database.db").unwrap();kvdb includes a command-line interface for interactive use.
./target/release/kvdb
kvdb> insert vec1 1.0 0.0 0.0
kvdb> insert vec2 0.0 1.0 0.0
kvdb> search 0.7 0.7 0.0 --k_top 2
kvdb> save my_data.db
kvdb> load my_data.db
kvdb> count
kvdb> get vec1
kvdb> delete vec1
kvdb> list
kvdb> help
kvdb> exit# Usage: kvdb <db_path> <command> [args...]
# Automatically loads the database (or creates new), executes, and saves back.
./target/release/kvdb data.db insert vec1 1.0 2.0 3.0
./target/release/kvdb data.db search 1.0 2.0 3.0 --k_top 5
./target/release/kvdb data.db count
./target/release/kvdb data.db listStart the server:
./target/release/kvdb serve
# Server listens on 0.0.0.0:7878All endpoints are POST with JSON bodies. Each request includes a "db" field specifying the database file path. The server is stateless — it loads the database from disk on each request and saves after mutations.
curl -X POST http://localhost:7878/insert \
-H "Content-Type: application/json" \
-d '{"db":"mydata.db", "vectors":[
{"id":"vec1", "values":[1.0, 0.0, 0.0]},
{"id":"vec2", "values":[0.0, 1.0, 0.0]}
]}'Response: {"inserted": 2, "results": [{"id":"vec1", "status":"ok", "message":"..."}, ...]}
curl -X POST http://localhost:7878/search \
-H "Content-Type: application/json" \
-d '{"db":"mydata.db", "queries":[{"value":[1.0, 0.0, 0.0], "top_k":3}]}'Response: {"results": [{"matches": [{"id":"vec1", "score":1.0, "values":[...]}], "message":"..."}]}
curl -X POST http://localhost:7878/get \
-H "Content-Type: application/json" \
-d '{"db":"mydata.db", "ids":["vec1", "vec2"]}'Response: {"results": [{"id":"vec1", "values":[1.0, 0.0, 0.0]}, {"id":"missing", "values":null}]}
curl -X POST http://localhost:7878/delete \
-H "Content-Type: application/json" \
-d '{"db":"mydata.db", "ids":["vec1"]}'Response: {"deleted": 1, "results": [{"id":"vec1", "status":"Success", "message":"..."}]}
src/
├── lib.rs # Public API (VecDB)
├── vector.rs # Vector math (L2 norm, dot product)
├── db.rs # Core database logic + persistence
├── server.rs # REST API handlers + route config
├── cli.rs # CLI parsing, REPL, command execution
└── main.rs # Entry point
- All vectors stored contiguously:
[v1_d1, v1_d2, ..., v2_d1, v2_d2, ...] - Parallel ID array:
["vec1", "vec2", ...] - Excellent memory locality for cache efficiency
- SIMD-friendly layout for future optimizations
- Bincode binary serialization via serde
- Buffered I/O for efficient read/write
| Operation | Time | Space |
|---|---|---|
| Insert | O(d) | O(d) |
| Search | O(n*d) | O(k) |
| Get | O(n) | O(d) |
| Delete | O(n*d) | O(1) |
| Save | O(n*d) | O(1) |
| Load | O(n*d) | O(n*d) |
Where n = number of vectors, d = dimension, k = top_k results.
Insertion: ~12,000 inserts/sec
Search: ~22 searches/sec (brute-force)
Save: ~0.3s (294 MB)
Load: ~0.2s
The examples/ directory contains standalone scripts for demonstration:
gen_demo.rs- Generate 100K random 768-d vectors and save to demo.dbdemo_operations.rs- Run search, insert, delete operations against demo.dbembed_wikipedia.rs- Embed Wikipedia descriptions with BERT (BAAI/bge-base-en-v1.5, 768-dim) using candledemo_semantic_search.rs- Semantic search CLI over wikipedia.dbfetch_wikipedia.py- Fetch 100K random Wikipedia pages (title + description)
# Generate demo database
cargo run --release --example gen_demo
# Run operations against it
cargo run --release --example demo_operations
# Semantic search (requires wikipedia.db from embed_wikipedia)
cargo run --release --example demo_semantic_search -- "famous physicist"- Core CRUD operations
- L2 normalization + cosine similarity
- REPL and CLI modes
- Persistence (bincode serialization)
- REST API with Actix-web
- JSON request/response
- API integration tests
v4.0 - Optimizations
- HNSW indexing
- Product Quantization
- SIMD-accelerated dot product
- Parallel search with Rayon
- Memory-mapped file support
MIT License - Copyright (c) 2026 Kurotsuba
See LICENSE for details.
Kantan (簡単) - Simple, but complete enough to learn from.