vectord: BatchAdd — single-lock variadic batch (Option A)
Replaces the per-item Add loop in the HTTP handler with one call to Index.BatchAdd, which acquires the write-lock once and pushes the whole batch through coder/hnsw's variadic Graph.Add. Pre-validation stays in the handler so per-item error messages keep their item-index precision. Microbench (internal/vectord/batch_bench_test.go) at d=768 cosine: N=16 SingleAdd 283µs/op → BatchAdd 170µs/op 1.66× N=128 SingleAdd 7.9ms/op → BatchAdd 7.5ms/op 1.05× N=1024 SingleAdd 87.5ms/op → BatchAdd 83.4ms/op 1.05× Win is biggest at staffing-driver batch sizes (N=16) where per-call lock + validation overhead is a meaningful fraction. At larger N the inner HNSW neighborhood search per insert dominates, which is the load-bearing finding for Option B (sharded indexes): the throughput ceiling lives inside the library, not at the lock, so sharding to N parallel Graphs is the only path to true concurrent-Add throughput. g1, g1p, g2 smokes all PASS post-change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+11
-14
@@ -274,21 +274,18 @@ func (h *handlers) handleAdd(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Pre-validation above is exhaustive (id, dim, finite, zero-norm),
|
||||
// so BatchAdd takes the write-lock once and pushes the whole batch
|
||||
// into coder/hnsw via one variadic Graph.Add. Saves N-1 lock
|
||||
// acquisitions per HTTP batch.
|
||||
batch := make([]vectord.BatchItem, len(req.Items))
|
||||
for j, it := range req.Items {
|
||||
if err := idx.Add(it.ID, it.Vector, it.Metadata); err != nil {
|
||||
// Vector-validation errors (NaN/Inf, zero-norm under
|
||||
// cosine) only surface here; pre-validation is intentional
|
||||
// minimal scope (id + dim only).
|
||||
if errors.Is(err, vectord.ErrDimensionMismatch) ||
|
||||
strings.Contains(err.Error(), "non-finite") ||
|
||||
strings.Contains(err.Error(), "zero-norm") {
|
||||
http.Error(w, "items["+strconv.Itoa(j)+"]: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
slog.Error("add", "name", name, "id", it.ID, "err", err)
|
||||
http.Error(w, "internal", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
batch[j] = vectord.BatchItem{ID: it.ID, Vector: it.Vector, Metadata: it.Metadata}
|
||||
}
|
||||
if err := idx.BatchAdd(batch); err != nil {
|
||||
slog.Error("batch add", "name", name, "err", err)
|
||||
http.Error(w, "internal", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// One save per batch (post-loop), not per item. Per scrum
|
||||
// O-W4-style discipline: HTTP-batch boundary is the natural unit.
|
||||
|
||||
Reference in New Issue
Block a user