feat(benchmarker): add bm25-benchmark command with tombstone testing#103
Closed
jfrancoa wants to merge 1 commit into
Closed
feat(benchmarker): add bm25-benchmark command with tombstone testing#103jfrancoa wants to merge 1 commit into
jfrancoa wants to merge 1 commit into
Conversation
Add a `bm25-benchmark` CLI command that imports a BEIR-format text corpus into a vectorless collection and benchmarks BM25 keyword-search latency/QPS through the existing worker-pool harness. Reuses the shared measurement, gRPC transport, and JSON output; recall/NDCG are skipped since BM25 top-k is exact. - BEIR corpus/query loader (beir_dataset.go) implementing the Dataset interface to reuse the import pipeline. - gRPC BM25 query builder (bm25_query.go), cloned from nearVectorQueryGrpc, with AND/OR operator and an optional category filter; Hybrid seam documented. - Vectorless schema with a searchable text/title inverted index, filterable docId/category, and configurable k1/b. - Tombstone generation: delete or delete+reinsert a fraction of documents to measure BM25 latency under inverted-index tombstone load, with phased baseline-vs-tombstoned measurement and an optional concurrent-churn mode. - Multi-tenant support (per-tenant import + tenant-aware, race-free churn). Batch/writeChunk in ann_benchmark.go are extended to carry text properties (behavior-preserving for the existing ann-benchmark path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Orca Security Scan Summary
| Status | Check | Issues by priority | |
|---|---|---|---|
| Infrastructure as Code | View in Orca | ||
| SAST | View in Orca | ||
| Secrets | View in Orca | ||
| Vulnerabilities | View in Orca |
🛡️ The following SAST misconfigurations have been detected
| NAME | FILE | ||
|---|---|---|---|
| Avoid Using `math/rand` for Cryptographic Operations in Go | ...md/bm25_benchmark.go | View in code | |
| Directory Permissions Are Set to Overly Permissive Values | ...md/bm25_benchmark.go | View in code | |
| Overly Permissive File Permissions Set in Application | ...md/bm25_benchmark.go | View in code | |
| Overly Permissive File Permissions Set in Application | ...md/bm25_benchmark.go | View in code |
Contributor
Author
|
Superseded by #104, which uses the branch on weaviate/weaviate-benchmarking directly instead of a fork. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
We can benchmark ANN query performance today, but we have no equivalent harness for BM25 keyword search — and in particular no way to reproduce and quantify the latency degradation that inverted-index tombstones cause. When documents are updated or deleted, their old docIDs remain in the posting lists as tombstones and must be skipped at query time until compaction reclaims them. Under update-heavy workloads this can meaningfully hurt BM25 latency/QPS, and we needed a repeatable way to measure it against real corpora.
This PR adds a
bm25-benchmarkcommand that imports a BEIR-format text corpus into a vectorless collection and measures BM25 latency/QPS through the existing worker-pool harness, with an optional tombstone-generation mode to measure performance under inverted-index churn.Approach
The guiding decision was to reuse the existing import and measurement machinery rather than fork it:
BeirDatasetimplements the existingDatasetinterface, so the same 8-workerloadTrainDatapipeline imports the corpus unchanged. Vector-specific interface methods are no-ops; qrels are ignored because this is a performance test, not a relevance eval. Recall/NDCG are skipped since BM25 top-k is exact.benchmark(...)worker pool and the ANN-compatible JSON result shape, so runs plug straight into existing reporting.Configcopies, no shared mutation).The one invasive change is in
ann_benchmark.go:Batch/writeChunkwere generalized to carry optional text properties so the BM25 path could share them. This was done as a behavior-preserving refactor of the existing ANN write path (the loop now iterates overmax(len(vectors), len(text))and only touches vectors when present).Hybrid (BM25 + vector) was intentionally left out but the seam is documented in
bm25_query.go.Key areas for review
benchmarker/cmd/ann_benchmark.gowriteChunk— the riskiest change, the only edit to the shared ANN import path. Verify the pure-ANN branches (named vector, multivector, filter-only) are unchanged; note the inner multivector loop variable was renamedi→jto avoid shadowing the outer object index.benchmarker/cmd/bm25_query.gobm25QueryGrpc—Metadata.Uuidmust staytrue;processQueueGrpcdecodes each result UUID back into an int anduuid.Parse("")would panic. There's a test guarding this.benchmarker/cmd/bm25_benchmark.gogenerateTombstones/tombstoneOnce— concurrency safety: these run alongside the query phase in--tombstoneConcurrentmode.cfgis taken by value so tenant assignment stays local and the shared config is never mutated.benchmarker/cmd/bm25_benchmark.gobatchDeleteDocIDRange— the delete loop (server caps deletes at ~10k/call); verify termination onSuccessful == 0.benchmarker/cmd/beir_dataset.goStreamTrainData—startOffset/maxRecords+ absoluteOffsetare what make reinsert produce stable UUIDs/docIds, which makes "update" a true update rather than a duplicate insert.Risks and mitigations
ann-benchmarkimport path: thewriteChunkrewrite is behavior-preserving; validated by inspection and the existing suite (the ANN unit tests stay green). Worth a careful read of that function.--tombstoneConcurrent): mitigated by passing per-tenantConfigcopies (by value) into the churn goroutine — no shared mutable state is touched during the query window; the goroutine is stopped and joined viastop/donechannels before results are written. Validation also requires--queryDuration > 0and--tombstoneMode updatefor this mode.[0,k)reuses identical UUIDs and genuinely overwrites.Testing
Unit tests (no Weaviate required), passing (
go test ./cmd/..., incl.-race):bm25_query_test.go— the gRPC query builder: OR operator withminimumOrTokensMatch+ multi-property scoring, AND operator, category filter + tenant wiring, defaults, and theMetadata.Uuid == trueinvariant.beir_dataset_test.go— the loader end-to-end on a fixture: streaming into batches, absolute batch offsets, filter-bucket assignment (idx % filterCount), the range re-stream used by tombstone "update" reinsert, and query parsing.Manually validated end-to-end against a live Weaviate on NFCorpus (single-tenant and 3-tenant): import → baseline → tombstone iterations → results JSON, with latency/QPS degrading as tombstone density grows, and update-mode preserving corpus size.
🤖 Generated with Claude Code