Build a production-quality vector database that can run approximate nearest-neighbor (ANN) search over a 100GB dataset while consuming ≤8GB of RAM. The system must be competitive with DiskANN (Microsoft Research, NeurIPS 2019) on recall, QPS, and I/O efficiency.
This is an exploratory systems project. Architecture decisions are intentionally left open at the start of each stage and resolved through benchmarking, not upfront design.
| Layer | Technology | Why |
|---|---|---|
| Core engine | C++17 | Performance, SIMD, direct memory control |
| Build system | CMake 3.20+ | Industry standard, FetchContent for deps |
| Unit tests | Google Test | Industry standard |
| Microbenchmarks | Google Benchmark | Criterion-equivalent for C++ |
| Logging | spdlog | Header-only, zero-overhead when disabled |
| Config/serialization | nlohmann/json | Header-only, ergonomic |
| Python bindings | pybind11 | Expose C++ index to Python eval harness |
| Evaluation | Python 3.10+ | numpy, h5py, matplotlib, faiss-cpu |
| Async I/O (optional) | liburing (io_uring) | Linux kernel 5.1+, zero-copy page I/O |
C++ standard: C++17. Use std::filesystem, std::optional, std::string_view, structured bindings, if constexpr.
No external ANN libraries in the core engine - the whole point is to implement them. FAISS and hnswlib are used only as evaluation baselines.
| Stage | Name | Deliverable | Est. Effort |
|---|---|---|---|
| 1 | Storage Engine | Buffer pool + disk manager, all tests pass | 1–2 weeks |
| 2 | Core Algorithms | Distance functions (SIMD), k-means, flat index | 1 week |
| 3 | HNSW (in-memory) | Full HNSW build + search, recall ≥ 0.95 on SIFT-1M | 2 weeks |
| 4 | Out-of-Core HNSW | HNSW over buffer pool, RSS ≤ 512 MB on SIFT-1M | 2 weeks |
| 5 | Product Quantization | PQ codec, HNSW+PQ integration, 32× compression | 1.5 weeks |
| 6 | IVF and IVF-PQ | IVF index, IVF-PQ hybrid | 1.5 weeks |
| 7 | Query Engine | Unified API, planner, re-ranking | 1 week |
| 8 | Evaluation Harness | Python bindings, full ANN Benchmarks comparison | 1 week |
Total estimated time: 11–12 weeks for one engineer.
Goal: Build the I/O foundation everything else sits on. This is the most critical stage - a bug here will corrupt data in every subsequent stage.
Deliverables:
include/oocvdb/storage/page.h- page layout constants and typesinclude/oocvdb/storage/disk_manager.h+src/storage/disk_manager.cppinclude/oocvdb/storage/buffer_pool.h+src/storage/buffer_pool.cpptests/test_storage.cpp- full test coverage
Acceptance Criteria:
- Write 10,000 pages and read them back; checksums match.
- Buffer pool holds exactly
capacitypages under random access. - Eviction under Clock policy measurably outperforms random eviction on sequential scan benchmark.
- No memory leaks (Valgrind / AddressSanitizer clean).
See: docs/stages/STAGE_1_STORAGE.md for full spec.
Goal: Implement the mathematical primitives that every index depends on. Validate against brute-force search for correctness.
Deliverables:
include/oocvdb/utils/distance.h- L2, inner product, cosine (scalar + AVX2)include/oocvdb/utils/kmeans.h- Lloyd's algorithm k-meansinclude/oocvdb/index/flat.h- brute-force exact ANN (the correctness oracle)tests/test_distance.cpp,tests/test_kmeans.cpp,tests/test_flat.cppbenchmarks/bench_distance.cpp
Acceptance Criteria:
- L2 distance matches
numpy.linalg.normto float32 precision. - k-means converges on SIFT-1M in < 60 s with 256 clusters.
- Flat index achieves Recall@10 = 1.0 on SIFT-1M (by definition).
- AVX2 L2 is ≥ 4× faster than scalar on 128-dim vectors.
See: docs/stages/STAGE_2_ALGORITHMS.md
Goal: Build a fully correct, in-memory HNSW index. This is the core algorithm. Do not introduce disk I/O yet - keep this stage focused on correctness and recall.
Deliverables:
include/oocvdb/index/hnsw/graph.h- adjacency listinclude/oocvdb/index/hnsw/build.h- insertioninclude/oocvdb/index/hnsw/search.h- querytests/test_hnsw.cppbenchmarks/bench_hnsw.cpp
Acceptance Criteria:
- Recall@10 ≥ 0.95 on SIFT-1M with
M=16,ef_construction=200,ef=100. - Build time ≤ 300 s for SIFT-1M on a single thread.
- Recall curve (Recall vs.
ef) matches hnswlib to within 2%.
See: docs/stages/STAGE_3_HNSW.md
Goal: Serialize the HNSW graph to the buffer pool and page it in/out during search. This is the project's defining technical challenge.
Deliverables:
- Extended
include/oocvdb/index/hnsw/graph.h- on-disk adjacency list include/oocvdb/index/hnsw/serializer.h- write/load graph to/from disk- Prefetch logic integrated into beam search
tests/test_ooc_hnsw.cpp- Memory usage benchmark comparing in-memory vs. out-of-core at same recall
Acceptance Criteria:
- Recall@10 ≥ 0.93 on SIFT-1M with buffer pool capped at 256 MB (20% of index).
- No regression in recall vs. Stage 3 when buffer pool is large enough to hold full index.
- I/O ops per query measured and logged; benchmark shows prefetch reduces stalls vs. no-prefetch.
See: docs/stages/STAGE_4_OOC.md
Goal: Implement PQ compression. A 128-dim float32 vector (512 bytes) should compress to 16 bytes with < 5% recall loss.
Deliverables:
include/oocvdb/index/pq/train.h- codebook traininginclude/oocvdb/index/pq/codec.h- encode/decode, ADC distance tables- HNSW+PQ integration: store PQ codes in graph nodes, re-rank top candidates with exact distance
tests/test_pq.cpp
Acceptance Criteria:
- 32× compression (512 → 16 bytes) on 128-dim float32.
- Recall@10 ≥ 0.90 on SIFT-1M (5% budget for compression loss).
- ADC distance computation ≥ 8× faster than exact float32 L2.
See: docs/stages/STAGE_5_PQ.md
Goal: Add coarse-level partitioning via IVF and combine with PQ for the memory-efficient 100GB-scale index.
Deliverables:
include/oocvdb/index/ivf/index.h- inverted file indexinclude/oocvdb/index/ivf_pq.h- IVF-PQ hybridtests/test_ivf.cpp,tests/test_ivf_pq.cpp
Acceptance Criteria:
- IVF: Recall@10 ≥ 0.90 on SIFT-1M with
nlist=1024,nprobe=32. - IVF-PQ: Recall@10 ≥ 0.85 on SIFT-1M with memory ≤ 64 MB.
- IVF-PQ search QPS ≥ 10× flat-index QPS at same recall.
See: docs/stages/STAGE_6_IVF.md
Goal: Unify all index types behind a single API with a query planner that automatically selects parameters.
Deliverables:
include/oocvdb/query/engine.h- unified search APIinclude/oocvdb/query/planner.h- parameter selection- C-compatible API (
extern "C") for FFI tests/test_engine.cpp
Acceptance Criteria:
- Single
Search(query, k)call works for all index types. - Planner selects correct index type given RAM budget constraint.
- Re-ranking reduces recall gap between PQ and exact to < 2%.
See: docs/stages/STAGE_7_QUERY.md
Goal: Benchmark against FAISS, hnswlib, and DiskANN. Produce publication-quality QPS vs. Recall curves.
Deliverables:
- pybind11 bindings exposing
Index.build(),Index.search(),Index.save(),Index.load() eval/recall.py,eval/throughput.py,eval/compare.py,eval/plot.py- Results JSON in
eval/results/
Acceptance Criteria:
- Recall@10 and QPS numbers reproducible with
python eval/compare.py. - Out-of-core HNSW within 2× QPS of DiskANN at Recall@10 ≥ 0.90.
- All plots auto-generated from results JSON.
See: docs/stages/STAGE_8_EVAL.md
Use std::expected<T, Error> (C++23) or a simple Result<T> type wrapping std::variant. No exceptions in hot paths. Errors from disk I/O must be propagated, not swallowed.
Use spdlog. Log at DEBUG level: every page fault, every eviction. Log at INFO level: build progress, search latency. Production builds compile out DEBUG logs.
All tunable parameters (buffer pool size, M, ef, nlist, nprobe) come from a JSON config file or programmatic Config struct - never hardcoded.
- Unit tests: Google Test, one file per module.
- Property tests: for correctness invariants (e.g., every inserted node is findable).
- Regression tests: recall must not drop between commits (checked in CI).
Every major allocation goes through a MemoryTracker that asserts we stay within the configured RAM budget. This is how we prove the out-of-core claim.