Skip to content

Commit 8471644

Browse files
committed
Merge branch 'main' into feat/add-vectorchord-benchmark
2 parents 2bb2200 + 243eb2e commit 8471644

38 files changed

Lines changed: 1763 additions & 305 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ venv/
1313
results/
1414
logs/
1515

16+
# Worktrees
17+
.worktrees/
18+
1619
# AI rules
1720
CLAUDE.md
1821
AGENTS.md

README.md

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ python >= 3.11
2727
pip install vectordb-bench
2828
```
2929

30-
**Install all database clients**
31-
32-
``` shell
33-
pip install 'vectordb-bench[all]'
34-
```
3530
**Install the specific database client**
3631

3732
```shell
@@ -42,7 +37,6 @@ All the database client supported
4237
| Optional database client | install command |
4338
|--------------------------|---------------------------------------------|
4439
| pymilvus, zilliz_cloud (*default*) | `pip install vectordb-bench` |
45-
| all (*clients requirements might be conflict with each other*) | `pip install vectordb-bench[all]` |
4640
| qdrant | `pip install vectordb-bench[qdrant]` |
4741
| pinecone | `pip install vectordb-bench[pinecone]` |
4842
| weaviate | `pip install vectordb-bench[weaviate]` |
@@ -62,6 +56,7 @@ All the database client supported
6256
| hologres | `pip install vectordb-bench[hologres]` |
6357
| tencent_es | `pip install vectordb-bench[tencent_es]` |
6458
| alisql | `pip install 'vectordb-bench[alisql]'` |
59+
| polardb | `pip install vectordb-bench[polardb]` |
6560
| doris | `pip install vectordb-bench[doris]` |
6661
| zvec | `pip install vectordb-bench[zvec]` |
6762
| endee | `pip install vectordb-bench[endee]` |
@@ -225,7 +220,6 @@ Options:
225220
226221
--ondisk Ondisk mode with binary quantization(32x compression)
227222
--oversample-factor Controls the degree of oversampling applied to minority classes in imbalanced datasets to improve model performance by balancing class distributions.(default 1.0)
228-
229223
230224
# Quantization Type
231225
--quantization-type TEXT which type of quantization to use valid values [fp32, fp16, bq]
@@ -294,13 +288,13 @@ Options:
294288
# Connection
295289
--cloud-id TEXT Elastic Cloud ID [required]
296290
--password TEXT Elastic Cloud password [required]
297-
291+
298292
# HNSW Index Parameters
299293
--m INTEGER HNSW M parameter [default: 16]
300294
--ef-construction INTEGER HNSW efConstruction parameter [default: 100]
301295
--num-candidates INTEGER Number of candidates for search [default: 100]
302296
--element-type [float|byte] Element type for vectors (float: 4 bytes, byte: 1 byte) [default: float]
303-
297+
304298
# Index Configuration
305299
--number-of-shards INTEGER Number of shards [default: 1]
306300
--number-of-replicas INTEGER Number of replicas [default: 0]
@@ -311,7 +305,7 @@ Options:
311305
--use-routing BOOLEAN Whether to use routing [default: False]
312306
--use-rescore BOOLEAN Whether to use rescore [default: False]
313307
--oversample-ratio FLOAT Oversample ratio for rescore [default: 2.0]
314-
308+
315309
# Common Options
316310
--case-type [CapacityDim128|CapacityDim960|Performance768D100M|...]
317311
Case type
@@ -527,6 +521,47 @@ To list the options for Lindorm, execute `vectordbbench lindormhnsw --help`, The
527521
--ef-search INTEGER hnsw ef-search [required]
528522
```
529523

524+
### Run PolarDB from command line
525+
526+
PolarDB supports index types: faiss_hnsw_flat, faiss_hnsw_pq, and faiss_hnsw_sq.
527+
528+
**Example: Run faiss_hnsw_flat benchmark**
529+
530+
```shell
531+
vectordbbench polardbhnswflat \
532+
--case-type Performance768D1M \
533+
--username <db_user> \
534+
--password '<db_password>' \
535+
--host <db_host> \
536+
--port 3306 \
537+
--m 16 \
538+
--ef-construction 256 \
539+
--ef-search 256 \
540+
--insert-workers 64 \
541+
--num-concurrency '10,20,40,60,80' \
542+
--concurrency-duration 60 \
543+
--task-label <task_label> \
544+
--db-label <db_label> \
545+
--skip-search-serial \
546+
--post-load-index
547+
```
548+
549+
To list the options for PolarDB, execute `vectordbbench polardbhnswflat --help`. The following are some PolarDB-specific command-line options.
550+
551+
```text
552+
--username TEXT Username [required]
553+
--password TEXT Password
554+
--host TEXT Db host [default: 127.0.0.1]
555+
--port INTEGER Db Port [default: 3306]
556+
--database TEXT Database name [default: vectordbbench]
557+
--m INTEGER M parameter (max_degree) in HNSW
558+
--ef-construction INTEGER ef_construction parameter in HNSW
559+
--ef-search INTEGER polar_vector_index_hnsw_ef_search session variable
560+
--insert-workers INTEGER Number of concurrent threads for data insertion
561+
--post-load-index / --inline-index
562+
Create index after load or inline at table creation
563+
```
564+
530565
#### Using a configuration file.
531566

532567
The vectordbbench command can optionally read some or all the options from a yaml formatted configuration file.

install/requirements_py3.11.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ pymilvus
2626
clickhouse_connect
2727
pyvespa
2828
mysql-connector-python
29+
PyMySQL
2930
packaging
30-
hdrhistogram>=0.10.1
31+
hdrhistogram>=0.10.1

pyproject.toml

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ dependencies = [
3939
"environs",
4040
"pydantic<v2",
4141
"scikit-learn",
42-
"pymilvus", # with pandas, numpy, ujson
43-
"ujson",
42+
"pymilvus", # with pandas, numpy
4443
"hdrhistogram>=0.10.1",
44+
"ujson",
4545
]
4646
dynamic = ["version"]
4747

@@ -51,37 +51,7 @@ test = [
5151
"ruff",
5252
"pytest",
5353
]
54-
restful = [ "flask" ]
55-
56-
all = [
57-
"grpcio==1.53.0", # for qdrant-client and pymilvus
58-
"grpcio-tools==1.53.0", # for qdrant-client and pymilvus
59-
"qdrant-client",
60-
"pinecone",
61-
"weaviate-client",
62-
"elasticsearch",
63-
"sqlalchemy",
64-
"redis",
65-
"chromadb",
66-
"pgvector",
67-
"psycopg",
68-
"psycopg-binary",
69-
"pgvecto_rs[psycopg3]>=0.2.2",
70-
"opensearch-dsl",
71-
"opensearch-py",
72-
"memorydb",
73-
"alibabacloud_ha3engine_vector",
74-
"mariadb",
75-
"PyMySQL",
76-
"clickhouse-connect",
77-
"pyvespa",
78-
"lancedb",
79-
"mysql-connector-python",
80-
"turbopuffer[fast]",
81-
'zvec',
82-
"endee==0.1.10", # compatible with pydantic<2
83-
]
84-
54+
restful = [ "flask" ]
8555
qdrant = [ "qdrant-client" ]
8656
pinecone = [ "pinecone" ]
8757
weaviate = [ "weaviate-client" ]
@@ -106,6 +76,7 @@ vespa = [ "pyvespa" ]
10676
lancedb = [ "lancedb" ]
10777
oceanbase = [ "mysql-connector-python" ]
10878
alisql = [ "mysql-connector-python" ]
79+
polardb = [ "PyMySQL" ]
10980
doris = [ "doris-vector-search" ]
11081
turbopuffer = [ "turbopuffer" ]
11182
zvec = [ "zvec" ]
@@ -156,7 +127,7 @@ lint.ignore = [
156127
"INP001", # TODO
157128
"TID252", # TODO
158129
"N801", "N802", "N815",
159-
"S101", "S108", "S603", "S311",
130+
"S101", "S108", "S603", "S311", "S608",
160131
"PLR2004",
161132
"RUF017",
162133
"C416",

tests/pytest.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[pytest]
22

3-
filterwarnings =
3+
filterwarnings =
44
ignore::UserWarning
5+
ignore::DeprecationWarning

tests/test_bench_runner.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import time
22
import logging
3+
4+
import ujson
35
from vectordb_bench.interface import BenchMarkRunner
46
from vectordb_bench.models import (
57
DB, IndexType, CaseType, TaskConfig, CaseConfig,
@@ -55,6 +57,5 @@ def test_performance_case_no_error(self):
5557
d = t.json(exclude={'db_config': {'password', 'api_key'}})
5658
log.info(f"{d}")
5759

58-
import ujson
5960
loads = ujson.loads(d)
6061
log.info(f"{loads}")

tests/test_concurrent_runner.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
"""Tests for ConcurrentInsertRunner against a running Milvus instance.
2+
3+
Includes:
4+
- Correctness tests (threading & async backends)
5+
- Parameterized benchmark: serial vs concurrent across (batch_size, workers) matrix
6+
7+
NUM_PER_BATCH is set via os.environ before each run. Since runners execute
8+
task() in a spawn subprocess that re-imports config, the env var takes effect.
9+
10+
Requires:
11+
- Milvus running at localhost:19530
12+
- Network access to download OpenAI 50K dataset
13+
14+
Usage:
15+
pytest tests/test_concurrent_runner.py -v -s # correctness tests only
16+
python tests/test_concurrent_runner.py # full benchmark matrix
17+
"""
18+
19+
# ruff: noqa: T201
20+
21+
from __future__ import annotations
22+
23+
import logging
24+
import os
25+
import time
26+
27+
from vectordb_bench.backend.clients import DB
28+
from vectordb_bench.backend.clients.milvus.config import FLATConfig
29+
from vectordb_bench.backend.dataset import Dataset, DatasetSource
30+
from vectordb_bench.backend.runner.concurrent_runner import ConcurrentInsertRunner, ExecutorBackend
31+
from vectordb_bench.backend.runner.serial_runner import SerialInsertRunner
32+
33+
log = logging.getLogger("vectordb_bench")
34+
log.setLevel(logging.INFO)
35+
36+
DATASET_SIZE = 50_000
37+
38+
39+
# ── Shared helpers ──────────────────────────────────────────────────────
40+
41+
42+
def get_milvus_db(collection_name: str):
43+
return DB.Milvus.init_cls(
44+
dim=1536,
45+
db_config={"uri": "http://localhost:19530", "user": "", "password": ""},
46+
db_case_config=FLATConfig(metric_type="COSINE"),
47+
collection_name=collection_name,
48+
drop_old=True,
49+
)
50+
51+
52+
def prepare_dataset():
53+
dataset = Dataset.OPENAI.manager(DATASET_SIZE)
54+
dataset.prepare(DatasetSource.AliyunOSS)
55+
return dataset
56+
57+
58+
def set_batch_size(batch_size: int) -> None:
59+
os.environ["NUM_PER_BATCH"] = str(batch_size)
60+
61+
62+
def timed_run(runner: SerialInsertRunner | ConcurrentInsertRunner) -> tuple[int, float]:
63+
start = time.perf_counter()
64+
count = runner.run()
65+
return count, time.perf_counter() - start
66+
67+
68+
# ── Correctness tests (pytest) ──────────────────────────────────────────
69+
70+
71+
def test_concurrent_insert_threading():
72+
"""Test concurrent insert with threading backend."""
73+
db = get_milvus_db("test_conc_threading")
74+
runner = ConcurrentInsertRunner(
75+
db=db,
76+
dataset=prepare_dataset(),
77+
normalize=False,
78+
max_workers=4,
79+
backend=ExecutorBackend.THREADING,
80+
)
81+
count = runner.run()
82+
assert count == DATASET_SIZE, f"Expected {DATASET_SIZE}, got {count}"
83+
84+
85+
def test_concurrent_insert_async():
86+
"""Test concurrent insert with async backend."""
87+
db = get_milvus_db("test_conc_async")
88+
runner = ConcurrentInsertRunner(
89+
db=db,
90+
dataset=prepare_dataset(),
91+
normalize=False,
92+
max_workers=4,
93+
backend=ExecutorBackend.ASYNC,
94+
)
95+
count = runner.run()
96+
assert count == DATASET_SIZE, f"Expected {DATASET_SIZE}, got {count}"
97+
98+
99+
# ── Parameterized benchmark ────────────────────────────────────────────
100+
101+
102+
def run_serial(batch_size: int) -> tuple[int, float]:
103+
set_batch_size(batch_size)
104+
runner = SerialInsertRunner(
105+
db=get_milvus_db(f"bench_serial_b{batch_size}"),
106+
dataset=prepare_dataset(),
107+
normalize=False,
108+
)
109+
return timed_run(runner)
110+
111+
112+
def run_concurrent(batch_size: int, workers: int) -> tuple[int, float]:
113+
set_batch_size(batch_size)
114+
runner = ConcurrentInsertRunner(
115+
db=get_milvus_db(f"bench_conc_b{batch_size}_w{workers}"),
116+
dataset=prepare_dataset(),
117+
normalize=False,
118+
max_workers=workers,
119+
backend=ExecutorBackend.THREADING,
120+
)
121+
return timed_run(runner)
122+
123+
124+
def bench_matrix():
125+
batch_sizes = [100, 500, 1000, 5000]
126+
worker_counts = [1, 2, 4, 8]
127+
128+
conc_headers = [f"conc({w}w)" for w in worker_counts]
129+
speedup_headers = [f"speedup({w}w)" for w in worker_counts]
130+
print(f"\n{'Batch':>6} {'#Bat':>5} {'serial':>8}", end="")
131+
for h in conc_headers:
132+
print(f" {h:>10}", end="")
133+
for h in speedup_headers:
134+
print(f" {h:>12}", end="")
135+
print()
136+
print("-" * (22 + 10 * len(worker_counts) + 12 * len(worker_counts)))
137+
138+
for bs in batch_sizes:
139+
n_batches = DATASET_SIZE // bs
140+
_, dur_s = run_serial(bs)
141+
142+
conc_durs = []
143+
for w in worker_counts:
144+
_, dur_c = run_concurrent(bs, w)
145+
conc_durs.append(dur_c)
146+
147+
print(f"{bs:>6} {n_batches:>5} {dur_s:>7.2f}s", end="")
148+
for dur_c in conc_durs:
149+
print(f" {dur_c:>9.2f}s", end="")
150+
for dur_c in conc_durs:
151+
print(f" {dur_s / dur_c:>11.2f}x", end="")
152+
print()
153+
154+
# restore default
155+
set_batch_size(100)
156+
157+
158+
if __name__ == "__main__":
159+
bench_matrix()

0 commit comments

Comments
 (0)