You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'd like to propose a substring / "grep" search benchmark case — text LIKE '%pattern%' over a text column — plus adding the Pizza&Chili corpus as a new data source to back it.
Today VDBBench has no lexical coverage at all: every case ultimately drives search_embedding, and there is no substring, wildcard, or full-text case type. #687 asks for match_phrase + kNN hybrid, which is adjacent but a different problem — phrase matching runs on a tokenized inverted index, while arbitrary substring matching cannot use one (the pattern may start or end mid-token). Both are worth having; this issue is about the substring half.
Motivation
Substring filtering over text is a first-class production workload, not a niche one:
Log / observability search — find every line containing a request ID, an error fragment, a stack frame.
Code search — identifier and snippet lookup.
RAG metadata filtering — narrow a corpus by a substring on a text attribute before or during vector retrieval.
Multiple systems in VDBBench's comparison set already ship dedicated index structures for exactly this, so the case is meaningfully cross-DB rather than vendor-specific:
System
Mechanism
Milvus
NGRAM index; FM-index (suffix-array based) landing now
Elasticsearch / OpenSearch
wildcard field type
PostgreSQL / pgvector
pg_trgm GIN index
ClickHouse
ngrambf_v1 / tokenbf_v1 skip indexes
There is currently no shared, reproducible benchmark for any of it. The practical consequence is that every vendor publishes numbers on a self-chosen corpus with self-chosen patterns, and none of them are falsifiable or comparable.
Why this cannot be a single number
This is the part that shapes the whole design. Substring-index performance is not one value — it moves by orders of magnitude along axes that a naive benchmark holds fixed:
Pattern length. Suffix-array / FM-index backward search costs scale with pattern length. n-gram indexes go the other way: patterns shorter than n degenerate to a full scan. The crossover between the two families lives entirely in this axis, so a benchmark with one fixed pattern length cannot show it.
Selectivity (occurrence count). Counting matches is cheap; locating them costs time proportional to the number of hits. High-frequency patterns are the worst case for a compressed index and the best case for a bitmap. A single hand-picked pattern hides this cliff completely.
Build time and index size. This is the adoption gate — an index that is 3× the raw text is a different product than one that is 0.3×. It belongs in the report, not a footnote.
Corpus repetitiveness. Determines how much a compressed index can exploit; separates run-length-compressed indexes (r-index) from plain FM-index by a large factor.
So the deliverable is a matrix — selectivity × pattern length — not a headline number.
Proposed data source: Pizza&Chili corpus
pizzachili.dcc.uchile.cl has been the standard corpus for compressed full-text indexes since ~2005 (Ferragina @ Pisa + Navarro @ Chile — hence the name). Using it means results are directly comparable to twenty years of published numbers, and reviewers in this area recognize the setup immediately.
Concretely I'd propose these, matching the axes above. δ is substring complexity, the standard repetitiveness measure — lower = more repetitive:
File
Size
Alphabet
δ
Why include it
english (200MB prefix)
200 MB of 2.1 GB
239
—
Natural-language baseline
sources (200MB prefix)
200 MB of 201 MiB
230
—
Code search; C/Java from Linux 2.6.11.6 + GCC 4.0.0
einstein.en.txt
446 MiB
139
42,884
Extreme repetition — all revisions of one Wikipedia article. 7z's to 320 KB. Separates run-length-compressed indexes from plain ones
HDFS_1
100 MiB
73
1,173,611
Hadoop DFS logs
BGL
100 MiB
78
1,301,165
Blue Gene/L supercomputer logs
Thunderbird
100 MiB
92
1,048,593
Thunderbird supercomputer logs
Android
100 MiB
176
881,571
Android system logs
EDGAR
100 MiB
71
1,116,985
SEC EDGAR financial system logs
Windows
100 MiB
91
61,711
Windows system logs — notably more regular than the other five, useful as a contrast
The six log files are the highest-value group: real system logs are the actual target workload, and they're already normalized to 100 MiB each, which conveniently sits at about one segment/shard worth of text for most engines — per-segment index build and size can be measured without any truncation.
Pizza&Chili also ships three execution traces (Horspool 546 MiB, NQueens 289 MiB, Quicksort 364 MiB) with δ in the tens of thousands. They produce very flattering numbers precisely because they're so repetitive, so I'd either leave them out or label them clearly as a corner case rather than a representative workload.
Query set design
This is where most of the real work is, and where a shared benchmark adds the most value over everyone rolling their own:
Selectivity buckets: ~1e-6, 1e-5, 1e-4, 1e-3, 1e-2, plus a high-frequency bucket.
N patterns per cell, sampled deterministically (fixed seed) from the corpus itself so every pattern is guaranteed to have the intended hit count.
Ship the generator script plus the resulting pattern file — the patterns are kilobytes, so no large artifact needs hosting for this part.
Report each cell separately. Averaging across selectivity is what makes published substring-search numbers uninformative.
Ground truth is exact — and that changes the design
Unlike ANN, substring-match ground truth is exact and cheap to compute: it's just the set of matching row IDs from a linear scan. Two useful consequences:
Correctness becomes a pass/fail assertion, not a tuning dial. Recall must be exactly 1.0. Anything else is a bug in the index, not a speed/accuracy trade-off. That's a stronger guarantee than any ANN case can offer and it should be checked, since a wrong-but-fast substring index is easy to ship accidentally.
Index build time and index size (absolute, and as a ratio to raw text)
p50 / p95 / p99 latency per (selectivity, length) cell
QPS under concurrency
Resident memory
Correctness assertion (exact match against GT)
Two query shapes
Scalar-only grep — SELECT id WHERE text LIKE '%pat%' LIMIT n, no vector involved. This isolates the index and is the number that's comparable to the full-text-index literature.
Filtered ANN — vector search carrying a substring predicate. This is the shape that actually shows up in RAG, and it stresses a different path (pre-filter vs. post-filter, bitmap handoff to the vector index) that shape 1 never touches.
Shape 2 fits the existing runner almost as-is. Shape 1 does not, which brings us to:
What's missing in VDBBench today
Mapping the gap concretely, since this touches more than one extension point:
FilterOp has no substring op — backend/filter.py:6-9 has only NumGE, StrEqual, NonFilter. Needs a StrContains plus a Filter subclass, and each client extends supported_filter_types / prepare_filter. The rejection path for non-supporting clients already exists (FilterNotSupportedError in assembler.py), so unsupported DBs degrade cleanly.
There is no non-vector query path — VectorDB.search_embedding is the sole query entry point, and SerialSearchRunner unconditionally passes an embedding. Shape 1 needs a new abstract method (something like scalar_query(expr, limit)) and a runner path that doesn't require a query vector.
Metrics are selected by CaseLabel, not by the case class — task_runner.py:241-258 dispatches on it. A new metric shape means a new CaseLabel, a new _run_*_case branch, and new Metric fields.
Datasets assume a vector column — BaseDataset is built around emb + neighbors_id. A text dataset needs an id + text variant, registered in both the Dataset enum and DatasetWithSizeType/DatasetWithSizeMap.
Hosting: converting the selected files to parquet and mirroring them under the existing assets.zilliz.com/benchmark/ root would need no new download machinery — AwsS3Reader / AliyunOSSReader already handle it.
Worth noting there's a documented recipe for adding a client (README "Adding New Clients") but none for adding a dataset or a case. If this lands, that gap is probably worth closing at the same time.
Open questions for maintainers
Is a non-vector (scalar-only) case type something the project wants to host at all, or should this be scoped to filtered-ANN only to avoid introducing a second query path?
Licensing — Pizza&Chili is derived from Gutenberg, Linux/GCC sources, Wikipedia, and public log datasets. I'd want to confirm redistribution terms before mirroring to assets.zilliz.com; alternatively the case could download from the upstream mirror directly. Preference?
Full matrix, or a trimmed default? The full selectivity × length grid across 9 corpora is a long run — a --quick subset (say the six log files at three selectivity levels) is probably what most people should run by default.
Happy to contribute the corpus conversion, the pattern generator, and the Milvus-side client implementation if there's interest in the direction.
Summary
I'd like to propose a substring / "grep" search benchmark case —
text LIKE '%pattern%'over a text column — plus adding the Pizza&Chili corpus as a new data source to back it.Today VDBBench has no lexical coverage at all: every case ultimately drives
search_embedding, and there is no substring, wildcard, or full-text case type. #687 asks formatch_phrase+ kNN hybrid, which is adjacent but a different problem — phrase matching runs on a tokenized inverted index, while arbitrary substring matching cannot use one (the pattern may start or end mid-token). Both are worth having; this issue is about the substring half.Motivation
Substring filtering over text is a first-class production workload, not a niche one:
Multiple systems in VDBBench's comparison set already ship dedicated index structures for exactly this, so the case is meaningfully cross-DB rather than vendor-specific:
wildcardfield typepg_trgmGIN indexngrambf_v1/tokenbf_v1skip indexesThere is currently no shared, reproducible benchmark for any of it. The practical consequence is that every vendor publishes numbers on a self-chosen corpus with self-chosen patterns, and none of them are falsifiable or comparable.
Why this cannot be a single number
This is the part that shapes the whole design. Substring-index performance is not one value — it moves by orders of magnitude along axes that a naive benchmark holds fixed:
ndegenerate to a full scan. The crossover between the two families lives entirely in this axis, so a benchmark with one fixed pattern length cannot show it.So the deliverable is a matrix — selectivity × pattern length — not a headline number.
Proposed data source: Pizza&Chili corpus
pizzachili.dcc.uchile.cl has been the standard corpus for compressed full-text indexes since ~2005 (Ferragina @ Pisa + Navarro @ Chile — hence the name). Using it means results are directly comparable to twenty years of published numbers, and reviewers in this area recognize the setup immediately.
Concretely I'd propose these, matching the axes above.
δis substring complexity, the standard repetitiveness measure — lower = more repetitive:english(200MB prefix)sources(200MB prefix)einstein.en.txtHDFS_1BGLThunderbirdAndroidEDGARWindowsThe six log files are the highest-value group: real system logs are the actual target workload, and they're already normalized to 100 MiB each, which conveniently sits at about one segment/shard worth of text for most engines — per-segment index build and size can be measured without any truncation.
Pizza&Chili also ships three execution traces (
Horspool546 MiB,NQueens289 MiB,Quicksort364 MiB) with δ in the tens of thousands. They produce very flattering numbers precisely because they're so repetitive, so I'd either leave them out or label them clearly as a corner case rather than a representative workload.Query set design
This is where most of the real work is, and where a shared benchmark adds the most value over everyone rolling their own:
Report each cell separately. Averaging across selectivity is what makes published substring-search numbers uninformative.
Ground truth is exact — and that changes the design
Unlike ANN, substring-match ground truth is exact and cheap to compute: it's just the set of matching row IDs from a linear scan. Two useful consequences:
Metrics to report
Two query shapes
SELECT id WHERE text LIKE '%pat%' LIMIT n, no vector involved. This isolates the index and is the number that's comparable to the full-text-index literature.Shape 2 fits the existing runner almost as-is. Shape 1 does not, which brings us to:
What's missing in VDBBench today
Mapping the gap concretely, since this touches more than one extension point:
FilterOphas no substring op —backend/filter.py:6-9has onlyNumGE,StrEqual,NonFilter. Needs aStrContainsplus aFiltersubclass, and each client extendssupported_filter_types/prepare_filter. The rejection path for non-supporting clients already exists (FilterNotSupportedErrorinassembler.py), so unsupported DBs degrade cleanly.VectorDB.search_embeddingis the sole query entry point, andSerialSearchRunnerunconditionally passes an embedding. Shape 1 needs a new abstract method (something likescalar_query(expr, limit)) and a runner path that doesn't require a query vector.CaseLabel, not by the case class —task_runner.py:241-258dispatches on it. A new metric shape means a newCaseLabel, a new_run_*_casebranch, and newMetricfields.BaseDatasetis built aroundemb+neighbors_id. A text dataset needs anid+textvariant, registered in both theDatasetenum andDatasetWithSizeType/DatasetWithSizeMap.assets.zilliz.com/benchmark/root would need no new download machinery —AwsS3Reader/AliyunOSSReaderalready handle it.Open questions for maintainers
assets.zilliz.com; alternatively the case could download from the upstream mirror directly. Preference?--quicksubset (say the six log files at three selectivity levels) is probably what most people should run by default.Happy to contribute the corpus conversion, the pattern generator, and the Milvus-side client implementation if there's interest in the direction.