What to measure
Three numbers carry most decisions: recall@k against an exact baseline, p95 query latency at your real top-k, and write throughput at your real batch size. Anything else is downstream of these.
elips bench /tmp/bench_db --count 100000 --dim 128For reference, the shipped benchmark on an Apple M4 (dim 128, nb 100000, nq 1000, k 10, L2, ELIPS 1.1.0):
| Configuration | Recall@10 | QPS |
|---|---|---|
IndexFlat (CPU) | 1.000 | 489 |
bf_knn (Metal, batched) | 1.000 | 914 |
IndexIVFFlat nlist=316, nprobe=128 | 0.801 | 1616 |
IndexIVFFlat nlist=316, nprobe=8 | 0.152 | 14123 |
nprobe is the recall/throughput dial: more probes recover recall, fewer trade it for speed.
Tuning HNSW
| Parameter | Default | Trade-off |
|---|---|---|
M | 32 | Higher → better recall, more memory. |
ef_construction | 200 | Higher → better graph, slower writes. |
ef_search | 64 | Higher → better recall, slower reads. |
Use IndexFlat at the same query set as a ground-truth baseline when sweeping ef_search.
Plan for filters
Equality and set-membership predicates accelerate through MetadataIndex. When the candidate set is small enough the planner switches to exact_candidates — often faster than running ANN over the full population. Inspect with collection.explain.
Durability cost
paranoid calls fsync per write; throughput is bounded by your storage's sync rate. standard drops the fsync but keeps the flush. relaxed buffers and amortises at checkpoint — pick it when crash recovery to the most recent checkpoint is acceptable.
GPU batching
With ELIPS_GPU_ENABLED, the QueryBatcher coalesces concurrent queries into a single GPU launch. The payoff is large: on the Apple M4 reference above, bf_knn on Metal runs at 914 QPS when queries are batched but only 24 QPS issued one at a time. Increase the in-flight query window in your serving layer to give the batcher room to work; very low concurrency leaves the GPU under-fed.