vectordb-bench 1.0.4__py3-none-any.whl → 1.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vectordb_bench/__init__.py +1 -0
- vectordb_bench/backend/cases.py +45 -1
- vectordb_bench/backend/clients/__init__.py +47 -0
- vectordb_bench/backend/clients/api.py +2 -0
- vectordb_bench/backend/clients/aws_opensearch/aws_opensearch.py +104 -40
- vectordb_bench/backend/clients/aws_opensearch/cli.py +52 -15
- vectordb_bench/backend/clients/aws_opensearch/config.py +27 -7
- vectordb_bench/backend/clients/hologres/cli.py +50 -0
- vectordb_bench/backend/clients/hologres/config.py +121 -0
- vectordb_bench/backend/clients/hologres/hologres.py +365 -0
- vectordb_bench/backend/clients/lancedb/lancedb.py +1 -0
- vectordb_bench/backend/clients/milvus/cli.py +29 -9
- vectordb_bench/backend/clients/milvus/config.py +2 -0
- vectordb_bench/backend/clients/milvus/milvus.py +1 -1
- vectordb_bench/backend/clients/oceanbase/cli.py +1 -0
- vectordb_bench/backend/clients/oceanbase/config.py +3 -1
- vectordb_bench/backend/clients/oceanbase/oceanbase.py +20 -4
- vectordb_bench/backend/clients/oss_opensearch/cli.py +155 -0
- vectordb_bench/backend/clients/oss_opensearch/config.py +157 -0
- vectordb_bench/backend/clients/oss_opensearch/oss_opensearch.py +582 -0
- vectordb_bench/backend/clients/oss_opensearch/run.py +166 -0
- vectordb_bench/backend/clients/pgdiskann/cli.py +45 -0
- vectordb_bench/backend/clients/pgdiskann/config.py +16 -0
- vectordb_bench/backend/clients/pgdiskann/pgdiskann.py +94 -26
- vectordb_bench/backend/clients/s3_vectors/config.py +41 -0
- vectordb_bench/backend/clients/s3_vectors/s3_vectors.py +171 -0
- vectordb_bench/backend/clients/tidb/cli.py +0 -4
- vectordb_bench/backend/clients/tidb/config.py +22 -2
- vectordb_bench/backend/clients/zilliz_cloud/cli.py +14 -1
- vectordb_bench/backend/clients/zilliz_cloud/config.py +4 -1
- vectordb_bench/backend/dataset.py +70 -0
- vectordb_bench/backend/filter.py +17 -0
- vectordb_bench/backend/runner/mp_runner.py +4 -0
- vectordb_bench/backend/runner/rate_runner.py +23 -11
- vectordb_bench/backend/runner/read_write_runner.py +10 -9
- vectordb_bench/backend/runner/serial_runner.py +23 -7
- vectordb_bench/backend/task_runner.py +5 -4
- vectordb_bench/cli/cli.py +36 -0
- vectordb_bench/cli/vectordbbench.py +4 -0
- vectordb_bench/fig/custom_case_run_test.png +0 -0
- vectordb_bench/fig/custom_dataset.png +0 -0
- vectordb_bench/fig/homepage/bar-chart.png +0 -0
- vectordb_bench/fig/homepage/concurrent.png +0 -0
- vectordb_bench/fig/homepage/custom.png +0 -0
- vectordb_bench/fig/homepage/label_filter.png +0 -0
- vectordb_bench/fig/homepage/qp$.png +0 -0
- vectordb_bench/fig/homepage/run_test.png +0 -0
- vectordb_bench/fig/homepage/streaming.png +0 -0
- vectordb_bench/fig/homepage/table.png +0 -0
- vectordb_bench/fig/run_test_select_case.png +0 -0
- vectordb_bench/fig/run_test_select_db.png +0 -0
- vectordb_bench/fig/run_test_submit.png +0 -0
- vectordb_bench/frontend/components/check_results/filters.py +1 -4
- vectordb_bench/frontend/components/check_results/nav.py +2 -1
- vectordb_bench/frontend/components/concurrent/charts.py +5 -0
- vectordb_bench/frontend/components/int_filter/charts.py +60 -0
- vectordb_bench/frontend/components/streaming/data.py +7 -0
- vectordb_bench/frontend/components/welcome/welcomePrams.py +42 -4
- vectordb_bench/frontend/config/dbCaseConfigs.py +142 -16
- vectordb_bench/frontend/config/styles.py +4 -0
- vectordb_bench/frontend/pages/concurrent.py +1 -1
- vectordb_bench/frontend/pages/custom.py +1 -1
- vectordb_bench/frontend/pages/int_filter.py +56 -0
- vectordb_bench/frontend/pages/streaming.py +16 -3
- vectordb_bench/interface.py +5 -1
- vectordb_bench/metric.py +7 -0
- vectordb_bench/models.py +39 -4
- vectordb_bench/results/S3Vectors/result_20250722_standard_s3vectors.json +2509 -0
- vectordb_bench/results/getLeaderboardDataV2.py +23 -2
- vectordb_bench/results/leaderboard_v2.json +200 -0
- vectordb_bench/results/leaderboard_v2_streaming.json +128 -0
- {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/METADATA +40 -8
- {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/RECORD +77 -51
- {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/WHEEL +0 -0
- {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/entry_points.txt +0 -0
- {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/licenses/LICENSE +0 -0
- {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,20 @@
|
|
1
|
-
from
|
1
|
+
from typing import TypedDict
|
2
|
+
|
3
|
+
from pydantic import BaseModel, SecretStr, validator
|
2
4
|
|
3
5
|
from ..api import DBCaseConfig, DBConfig, MetricType
|
4
6
|
|
5
7
|
|
8
|
+
class TiDBConfigDict(TypedDict):
|
9
|
+
host: str
|
10
|
+
port: int
|
11
|
+
user: str
|
12
|
+
password: str
|
13
|
+
database: str
|
14
|
+
ssl_verify_cert: bool
|
15
|
+
ssl_verify_identity: bool
|
16
|
+
|
17
|
+
|
6
18
|
class TiDBConfig(DBConfig):
|
7
19
|
user_name: str = "root"
|
8
20
|
password: SecretStr
|
@@ -11,7 +23,7 @@ class TiDBConfig(DBConfig):
|
|
11
23
|
db_name: str = "test"
|
12
24
|
ssl: bool = False
|
13
25
|
|
14
|
-
def to_dict(self) ->
|
26
|
+
def to_dict(self) -> TiDBConfigDict:
|
15
27
|
pwd_str = self.password.get_secret_value()
|
16
28
|
return {
|
17
29
|
"host": self.host,
|
@@ -23,6 +35,14 @@ class TiDBConfig(DBConfig):
|
|
23
35
|
"ssl_verify_identity": self.ssl,
|
24
36
|
}
|
25
37
|
|
38
|
+
@validator("*")
|
39
|
+
def not_empty_field(cls, v: any, field: any):
|
40
|
+
if field.name in ["password", "db_label"]:
|
41
|
+
return v
|
42
|
+
if isinstance(v, str | SecretStr) and len(v) == 0:
|
43
|
+
raise ValueError("Empty string!")
|
44
|
+
return v
|
45
|
+
|
26
46
|
|
27
47
|
class TiDBIndexConfig(BaseModel, DBCaseConfig):
|
28
48
|
metric_type: MetricType | None = None
|
@@ -36,6 +36,17 @@ class ZillizTypedDict(CommonTypedDict):
|
|
36
36
|
str,
|
37
37
|
click.option("--level", type=str, help="Zilliz index level", required=False),
|
38
38
|
]
|
39
|
+
num_shards: Annotated[
|
40
|
+
int,
|
41
|
+
click.option(
|
42
|
+
"--num-shards",
|
43
|
+
type=int,
|
44
|
+
help="Number of shards",
|
45
|
+
required=False,
|
46
|
+
default=1,
|
47
|
+
show_default=True,
|
48
|
+
),
|
49
|
+
]
|
39
50
|
|
40
51
|
|
41
52
|
@cli.command()
|
@@ -50,9 +61,11 @@ def ZillizAutoIndex(**parameters: Unpack[ZillizTypedDict]):
|
|
50
61
|
uri=SecretStr(parameters["uri"]),
|
51
62
|
user=parameters["user_name"],
|
52
63
|
password=SecretStr(parameters["password"]),
|
64
|
+
num_shards=parameters["num_shards"],
|
53
65
|
),
|
54
66
|
db_case_config=AutoIndexConfig(
|
55
|
-
|
67
|
+
level=int(parameters["level"]) if parameters["level"] else 1,
|
68
|
+
num_shards=parameters["num_shards"],
|
56
69
|
),
|
57
70
|
**parameters,
|
58
71
|
)
|
@@ -8,24 +8,27 @@ class ZillizCloudConfig(DBConfig):
|
|
8
8
|
uri: SecretStr
|
9
9
|
user: str
|
10
10
|
password: SecretStr
|
11
|
+
num_shards: int = 1
|
11
12
|
|
12
13
|
def to_dict(self) -> dict:
|
13
14
|
return {
|
14
15
|
"uri": self.uri.get_secret_value(),
|
15
16
|
"user": self.user,
|
16
17
|
"password": self.password.get_secret_value(),
|
18
|
+
"num_shards": self.num_shards,
|
17
19
|
}
|
18
20
|
|
19
21
|
|
20
22
|
class AutoIndexConfig(MilvusIndexConfig, DBCaseConfig):
|
21
23
|
index: IndexType = IndexType.AUTOINDEX
|
22
24
|
level: int = 1
|
25
|
+
num_shards: int = 1
|
23
26
|
|
24
27
|
def index_param(self) -> dict:
|
25
28
|
return {
|
26
29
|
"metric_type": self.parse_metric(),
|
27
30
|
"index_type": self.index.value,
|
28
|
-
"params": {},
|
31
|
+
"params": {"shardsNum": self.num_shards},
|
29
32
|
}
|
30
33
|
|
31
34
|
def search_param(self) -> dict:
|
@@ -48,6 +48,7 @@ class BaseDataset(BaseModel):
|
|
48
48
|
scalar_labels_file_separated: bool = True
|
49
49
|
scalar_labels_file: str = "scalar_labels.parquet"
|
50
50
|
scalar_label_percentages: list[float] = []
|
51
|
+
scalar_int_rates: list[float] = []
|
51
52
|
train_id_field: str = "id"
|
52
53
|
train_vector_field: str = "emb"
|
53
54
|
test_file: str = "test.parquet"
|
@@ -164,6 +165,29 @@ class Cohere(BaseDataset):
|
|
164
165
|
}
|
165
166
|
with_scalar_labels: bool = True
|
166
167
|
scalar_label_percentages: list[float] = [0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5]
|
168
|
+
scalar_int_rates: list[float] = [
|
169
|
+
0.001,
|
170
|
+
0.002,
|
171
|
+
0.005,
|
172
|
+
0.01,
|
173
|
+
0.02,
|
174
|
+
0.05,
|
175
|
+
0.1,
|
176
|
+
0.2,
|
177
|
+
0.3,
|
178
|
+
0.4,
|
179
|
+
0.5,
|
180
|
+
0.6,
|
181
|
+
0.7,
|
182
|
+
0.8,
|
183
|
+
0.9,
|
184
|
+
0.95,
|
185
|
+
0.98,
|
186
|
+
0.99,
|
187
|
+
0.995,
|
188
|
+
0.998,
|
189
|
+
0.999,
|
190
|
+
]
|
167
191
|
|
168
192
|
|
169
193
|
class Bioasq(BaseDataset):
|
@@ -178,6 +202,29 @@ class Bioasq(BaseDataset):
|
|
178
202
|
}
|
179
203
|
with_scalar_labels: bool = True
|
180
204
|
scalar_label_percentages: list[float] = [0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5]
|
205
|
+
scalar_int_rates: list[float] = [
|
206
|
+
0.001,
|
207
|
+
0.002,
|
208
|
+
0.005,
|
209
|
+
0.01,
|
210
|
+
0.02,
|
211
|
+
0.05,
|
212
|
+
0.1,
|
213
|
+
0.2,
|
214
|
+
0.3,
|
215
|
+
0.4,
|
216
|
+
0.5,
|
217
|
+
0.6,
|
218
|
+
0.7,
|
219
|
+
0.8,
|
220
|
+
0.9,
|
221
|
+
0.95,
|
222
|
+
0.98,
|
223
|
+
0.99,
|
224
|
+
0.995,
|
225
|
+
0.998,
|
226
|
+
0.999,
|
227
|
+
]
|
181
228
|
|
182
229
|
|
183
230
|
class Glove(BaseDataset):
|
@@ -217,6 +264,29 @@ class OpenAI(BaseDataset):
|
|
217
264
|
}
|
218
265
|
with_scalar_labels: bool = True
|
219
266
|
scalar_label_percentages: list[float] = [0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5]
|
267
|
+
scalar_int_rates: list[float] = [
|
268
|
+
0.001,
|
269
|
+
0.002,
|
270
|
+
0.005,
|
271
|
+
0.01,
|
272
|
+
0.02,
|
273
|
+
0.05,
|
274
|
+
0.1,
|
275
|
+
0.2,
|
276
|
+
0.3,
|
277
|
+
0.4,
|
278
|
+
0.5,
|
279
|
+
0.6,
|
280
|
+
0.7,
|
281
|
+
0.8,
|
282
|
+
0.9,
|
283
|
+
0.95,
|
284
|
+
0.98,
|
285
|
+
0.99,
|
286
|
+
0.995,
|
287
|
+
0.998,
|
288
|
+
0.999,
|
289
|
+
]
|
220
290
|
|
221
291
|
|
222
292
|
class DatasetManager(BaseModel):
|
vectordb_bench/backend/filter.py
CHANGED
@@ -51,6 +51,23 @@ class IntFilter(Filter):
|
|
51
51
|
raise RuntimeError(msg)
|
52
52
|
|
53
53
|
|
54
|
+
class NewIntFilter(Filter):
|
55
|
+
type: FilterOp = FilterOp.NumGE
|
56
|
+
int_field: str = "id"
|
57
|
+
int_value: int
|
58
|
+
|
59
|
+
@property
|
60
|
+
def int_rate(self) -> str:
|
61
|
+
r = self.filter_rate * 100
|
62
|
+
if 1 <= r <= 99:
|
63
|
+
return f"int_{int(r)}p"
|
64
|
+
return f"int_{r:.1f}p"
|
65
|
+
|
66
|
+
@property
|
67
|
+
def groundtruth_file(self) -> str:
|
68
|
+
return f"neighbors_{self.int_rate}.parquet"
|
69
|
+
|
70
|
+
|
54
71
|
class LabelFilter(Filter):
|
55
72
|
"""
|
56
73
|
filter expr: label_field == label_value, like `color == "red"`
|
@@ -103,6 +103,7 @@ class MultiProcessingSearchRunner:
|
|
103
103
|
conc_num_list = []
|
104
104
|
conc_qps_list = []
|
105
105
|
conc_latency_p99_list = []
|
106
|
+
conc_latency_p95_list = []
|
106
107
|
conc_latency_avg_list = []
|
107
108
|
try:
|
108
109
|
for conc in self.concurrencies:
|
@@ -125,6 +126,7 @@ class MultiProcessingSearchRunner:
|
|
125
126
|
all_count = sum([r.result()[0] for r in future_iter])
|
126
127
|
latencies = sum([r.result()[2] for r in future_iter], start=[])
|
127
128
|
latency_p99 = np.percentile(latencies, 99)
|
129
|
+
latency_p95 = np.percentile(latencies, 95)
|
128
130
|
latency_avg = np.mean(latencies)
|
129
131
|
cost = time.perf_counter() - start
|
130
132
|
|
@@ -132,6 +134,7 @@ class MultiProcessingSearchRunner:
|
|
132
134
|
conc_num_list.append(conc)
|
133
135
|
conc_qps_list.append(qps)
|
134
136
|
conc_latency_p99_list.append(latency_p99)
|
137
|
+
conc_latency_p95_list.append(latency_p95)
|
135
138
|
conc_latency_avg_list.append(latency_avg)
|
136
139
|
log.info(f"End search in concurrency {conc}: dur={cost}s, total_count={all_count}, qps={qps}")
|
137
140
|
|
@@ -156,6 +159,7 @@ class MultiProcessingSearchRunner:
|
|
156
159
|
conc_num_list,
|
157
160
|
conc_qps_list,
|
158
161
|
conc_latency_p99_list,
|
162
|
+
conc_latency_p95_list,
|
159
163
|
conc_latency_avg_list,
|
160
164
|
)
|
161
165
|
|
@@ -3,9 +3,11 @@ import logging
|
|
3
3
|
import multiprocessing as mp
|
4
4
|
import time
|
5
5
|
from concurrent.futures import ThreadPoolExecutor
|
6
|
+
from copy import deepcopy
|
6
7
|
|
7
8
|
from vectordb_bench import config
|
8
9
|
from vectordb_bench.backend.clients import api
|
10
|
+
from vectordb_bench.backend.clients.pgvector.pgvector import PgVector
|
9
11
|
from vectordb_bench.backend.dataset import DataSetIterator
|
10
12
|
from vectordb_bench.backend.utils import time_it
|
11
13
|
|
@@ -33,17 +35,27 @@ class RatedMultiThreadingInsertRunner:
|
|
33
35
|
self.executing_futures = []
|
34
36
|
self.sig_idx = 0
|
35
37
|
|
36
|
-
def send_insert_task(self, db: api.VectorDB, emb: list[list[float]], metadata: list[str]
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
38
|
+
def send_insert_task(self, db: api.VectorDB, emb: list[list[float]], metadata: list[str]):
|
39
|
+
def _insert_embeddings(db: api.VectorDB, emb: list[list[float]], metadata: list[str], retry_idx: int = 0):
|
40
|
+
_, error = db.insert_embeddings(emb, metadata)
|
41
|
+
if error is not None:
|
42
|
+
log.warning(f"Insert Failed, try_idx={retry_idx}, Exception: {error}")
|
43
|
+
retry_idx += 1
|
44
|
+
if retry_idx <= config.MAX_INSERT_RETRY:
|
45
|
+
time.sleep(retry_idx)
|
46
|
+
_insert_embeddings(db, emb=emb, metadata=metadata, retry_idx=retry_idx)
|
47
|
+
else:
|
48
|
+
msg = f"Insert failed and retried more than {config.MAX_INSERT_RETRY} times"
|
49
|
+
raise RuntimeError(msg) from None
|
50
|
+
|
51
|
+
if isinstance(db, PgVector):
|
52
|
+
# pgvector is not thread-safe for concurrent insert,
|
53
|
+
# so we need to copy the db object, make sure each thread has its own connection
|
54
|
+
db_copy = deepcopy(db)
|
55
|
+
with db_copy.init():
|
56
|
+
_insert_embeddings(db_copy, emb, metadata, retry_idx=0)
|
57
|
+
else:
|
58
|
+
_insert_embeddings(db, emb, metadata, retry_idx=0)
|
47
59
|
|
48
60
|
@time_it
|
49
61
|
def run_with_rate(self, q: mp.Queue):
|
@@ -98,10 +98,10 @@ class ReadWriteRunner(MultiProcessingSearchRunner, RatedMultiThreadingInsertRunn
|
|
98
98
|
log.info("Search after write - Serial search start")
|
99
99
|
test_time = round(time.perf_counter(), 4)
|
100
100
|
res, ssearch_dur = self.serial_search_runner.run()
|
101
|
-
recall, ndcg, p99_latency = res
|
101
|
+
recall, ndcg, p99_latency, p95_latency = res
|
102
102
|
log.info(
|
103
103
|
f"Search after write - Serial search - recall={recall}, ndcg={ndcg}, "
|
104
|
-
f"p99={p99_latency}, dur={ssearch_dur:.4f}",
|
104
|
+
f"p99={p99_latency}, p95={p95_latency}, dur={ssearch_dur:.4f}",
|
105
105
|
)
|
106
106
|
log.info(
|
107
107
|
f"Search after wirte - Conc search start, dur for each conc={self.read_dur_after_write}",
|
@@ -109,7 +109,7 @@ class ReadWriteRunner(MultiProcessingSearchRunner, RatedMultiThreadingInsertRunn
|
|
109
109
|
max_qps, conc_failed_rate = self.run_by_dur(self.read_dur_after_write)
|
110
110
|
log.info(f"Search after wirte - Conc search finished, max_qps={max_qps}")
|
111
111
|
|
112
|
-
return [(perc, test_time, max_qps, recall, ndcg, p99_latency, conc_failed_rate)]
|
112
|
+
return [(perc, test_time, max_qps, recall, ndcg, p99_latency, p95_latency, conc_failed_rate)]
|
113
113
|
|
114
114
|
def run_read_write(self) -> Metric:
|
115
115
|
"""
|
@@ -157,7 +157,8 @@ class ReadWriteRunner(MultiProcessingSearchRunner, RatedMultiThreadingInsertRunn
|
|
157
157
|
m.st_recall_list = [d[3] for d in r]
|
158
158
|
m.st_ndcg_list = [d[4] for d in r]
|
159
159
|
m.st_serial_latency_p99_list = [d[5] for d in r]
|
160
|
-
m.
|
160
|
+
m.st_serial_latency_p95_list = [d[6] for d in r]
|
161
|
+
m.st_conc_failed_rate_list = [d[7] for d in r]
|
161
162
|
|
162
163
|
except Exception as e:
|
163
164
|
log.warning(f"Read and write error: {e}")
|
@@ -201,7 +202,7 @@ class ReadWriteRunner(MultiProcessingSearchRunner, RatedMultiThreadingInsertRunn
|
|
201
202
|
"""
|
202
203
|
result, start_batch = [], 0
|
203
204
|
total_batch = math.ceil(self.data_volume / self.insert_rate)
|
204
|
-
recall, ndcg, p99_latency = None, None, None
|
205
|
+
recall, ndcg, p99_latency, p95_latency = None, None, None, None
|
205
206
|
|
206
207
|
def wait_next_target(start: int, target_batch: int) -> bool:
|
207
208
|
"""Return False when receive True or None"""
|
@@ -224,15 +225,15 @@ class ReadWriteRunner(MultiProcessingSearchRunner, RatedMultiThreadingInsertRunn
|
|
224
225
|
|
225
226
|
log.info(f"Insert {perc}% done, total batch={total_batch}")
|
226
227
|
test_time = round(time.perf_counter(), 4)
|
227
|
-
max_qps, recall, ndcg, p99_latency, conc_failed_rate = 0, 0, 0, 0, 0
|
228
|
+
max_qps, recall, ndcg, p99_latency, p95_latency, conc_failed_rate = 0, 0, 0, 0, 0, 0
|
228
229
|
try:
|
229
230
|
log.info(f"[{target_batch}/{total_batch}] Serial search - {perc}% start")
|
230
231
|
res, ssearch_dur = self.serial_search_runner.run()
|
231
232
|
ssearch_dur = round(ssearch_dur, 4)
|
232
|
-
recall, ndcg, p99_latency = res
|
233
|
+
recall, ndcg, p99_latency, p95_latency = res
|
233
234
|
log.info(
|
234
235
|
f"[{target_batch}/{total_batch}] Serial search - {perc}% done, "
|
235
|
-
f"recall={recall}, ndcg={ndcg}, p99={p99_latency}, dur={ssearch_dur}"
|
236
|
+
f"recall={recall}, ndcg={ndcg}, p99={p99_latency}, p95={p95_latency}, dur={ssearch_dur}"
|
236
237
|
)
|
237
238
|
|
238
239
|
each_conc_search_dur = self.get_each_conc_search_dur(
|
@@ -250,7 +251,7 @@ class ReadWriteRunner(MultiProcessingSearchRunner, RatedMultiThreadingInsertRunn
|
|
250
251
|
log.warning(f"Skip concurrent tests, each_conc_search_dur={each_conc_search_dur} less than 10s.")
|
251
252
|
except Exception as e:
|
252
253
|
log.warning(f"Streaming Search Failed at stage={stage}. Exception: {e}")
|
253
|
-
result.append((perc, test_time, max_qps, recall, ndcg, p99_latency, conc_failed_rate))
|
254
|
+
result.append((perc, test_time, max_qps, recall, ndcg, p99_latency, p95_latency, conc_failed_rate))
|
254
255
|
start_batch = target_batch
|
255
256
|
|
256
257
|
# Drain the queue
|
@@ -241,7 +241,7 @@ class SerialSearchRunner:
|
|
241
241
|
|
242
242
|
return results
|
243
243
|
|
244
|
-
def search(self, args: tuple[list, list[list[int]]]) -> tuple[float, float, float]:
|
244
|
+
def search(self, args: tuple[list, list[list[int]]]) -> tuple[float, float, float, float]:
|
245
245
|
log.info(f"{mp.current_process().name:14} start search the entire test_data to get recall and latency")
|
246
246
|
with self.db.init():
|
247
247
|
self.db.prepare_filter(self.filters)
|
@@ -281,6 +281,7 @@ class SerialSearchRunner:
|
|
281
281
|
avg_ndcg = round(np.mean(ndcgs), 4)
|
282
282
|
cost = round(np.sum(latencies), 4)
|
283
283
|
p99 = round(np.percentile(latencies, 99), 4)
|
284
|
+
p95 = round(np.percentile(latencies, 95), 4)
|
284
285
|
log.info(
|
285
286
|
f"{mp.current_process().name:14} search entire test_data: "
|
286
287
|
f"cost={cost}s, "
|
@@ -288,20 +289,35 @@ class SerialSearchRunner:
|
|
288
289
|
f"avg_recall={avg_recall}, "
|
289
290
|
f"avg_ndcg={avg_ndcg}, "
|
290
291
|
f"avg_latency={avg_latency}, "
|
291
|
-
f"p99={p99}"
|
292
|
+
f"p99={p99}, "
|
293
|
+
f"p95={p95}"
|
292
294
|
)
|
293
|
-
return (avg_recall, avg_ndcg, p99)
|
295
|
+
return (avg_recall, avg_ndcg, p99, p95)
|
294
296
|
|
295
|
-
def _run_in_subprocess(self) -> tuple[float, float]:
|
297
|
+
def _run_in_subprocess(self) -> tuple[float, float, float, float]:
|
296
298
|
with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
|
297
299
|
future = executor.submit(self.search, (self.test_data, self.ground_truth))
|
298
300
|
return future.result()
|
299
301
|
|
300
302
|
@utils.time_it
|
301
|
-
def run(self) -> tuple[float, float, float]:
|
303
|
+
def run(self) -> tuple[float, float, float, float]:
|
304
|
+
log.info(f"{mp.current_process().name:14} start serial search")
|
305
|
+
if self.test_data is None:
|
306
|
+
msg = "empty test_data"
|
307
|
+
raise RuntimeError(msg)
|
308
|
+
|
309
|
+
return self._run_in_subprocess()
|
310
|
+
|
311
|
+
@utils.time_it
|
312
|
+
def run_with_cost(self) -> tuple[tuple[float, float, float, float], float]:
|
302
313
|
"""
|
314
|
+
Search all test data in serial.
|
303
315
|
Returns:
|
304
|
-
tuple[tuple[float, float, float], float]: (avg_recall, avg_ndcg, p99_latency), cost
|
305
|
-
|
316
|
+
tuple[tuple[float, float, float, float], float]: (avg_recall, avg_ndcg, p99_latency, p95_latency), cost
|
306
317
|
"""
|
318
|
+
log.info(f"{mp.current_process().name:14} start serial search")
|
319
|
+
if self.test_data is None:
|
320
|
+
msg = "empty test_data"
|
321
|
+
raise RuntimeError(msg)
|
322
|
+
|
307
323
|
return self._run_in_subprocess()
|
@@ -186,11 +186,12 @@ class CaseRunner(BaseModel):
|
|
186
186
|
m.conc_num_list,
|
187
187
|
m.conc_qps_list,
|
188
188
|
m.conc_latency_p99_list,
|
189
|
+
m.conc_latency_p95_list,
|
189
190
|
m.conc_latency_avg_list,
|
190
191
|
) = search_results
|
191
192
|
if TaskStage.SEARCH_SERIAL in self.config.stages:
|
192
193
|
search_results = self._serial_search()
|
193
|
-
m.recall, m.ndcg, m.serial_latency_p99 = search_results
|
194
|
+
m.recall, m.ndcg, m.serial_latency_p99, m.serial_latency_p95 = search_results
|
194
195
|
|
195
196
|
except Exception as e:
|
196
197
|
log.warning(f"Failed to run performance case, reason = {e}")
|
@@ -230,12 +231,12 @@ class CaseRunner(BaseModel):
|
|
230
231
|
finally:
|
231
232
|
runner = None
|
232
233
|
|
233
|
-
def _serial_search(self) -> tuple[float, float, float]:
|
234
|
+
def _serial_search(self) -> tuple[float, float, float, float]:
|
234
235
|
"""Performance serial tests, search the entire test data once,
|
235
|
-
calculate the recall, serial_latency_p99
|
236
|
+
calculate the recall, serial_latency_p99, serial_latency_p95
|
236
237
|
|
237
238
|
Returns:
|
238
|
-
tuple[float, float, float]: recall, ndcg, serial_latency_p99
|
239
|
+
tuple[float, float, float, float]: recall, ndcg, serial_latency_p99, serial_latency_p95
|
239
240
|
"""
|
240
241
|
try:
|
241
242
|
results, _ = self.serial_search_runner.run()
|
vectordb_bench/cli/cli.py
CHANGED
@@ -471,6 +471,33 @@ class HNSWFlavor4(HNSWBaseRequiredTypedDict):
|
|
471
471
|
]
|
472
472
|
|
473
473
|
|
474
|
+
class HNSWFlavor5(HNSWBaseRequiredTypedDict):
|
475
|
+
ef_search: Annotated[
|
476
|
+
int | None,
|
477
|
+
click.option("--ef-search", type=int, help="hnsw ef-search", required=True),
|
478
|
+
]
|
479
|
+
index_type: Annotated[
|
480
|
+
str | None,
|
481
|
+
click.option(
|
482
|
+
"--index-type",
|
483
|
+
type=click.Choice(["HGraph"], case_sensitive=True),
|
484
|
+
help="Type of index to use. Supported values: HGraph",
|
485
|
+
required=True,
|
486
|
+
),
|
487
|
+
]
|
488
|
+
use_reorder: Annotated[
|
489
|
+
bool,
|
490
|
+
click.option(
|
491
|
+
"--use-reorder/--no-use-reorder",
|
492
|
+
is_flag=True,
|
493
|
+
type=bool,
|
494
|
+
help="use reorder index",
|
495
|
+
default=True,
|
496
|
+
show_default=True,
|
497
|
+
),
|
498
|
+
]
|
499
|
+
|
500
|
+
|
474
501
|
class IVFFlatTypedDict(TypedDict):
|
475
502
|
lists: Annotated[int | None, click.option("--lists", type=int, help="ivfflat lists")]
|
476
503
|
probes: Annotated[int | None, click.option("--probes", type=int, help="ivfflat probes")]
|
@@ -501,6 +528,15 @@ class OceanBaseIVFTypedDict(TypedDict):
|
|
501
528
|
int | None,
|
502
529
|
click.option("--nlist", "nlist", type=int, help="Number of cluster centers", required=True),
|
503
530
|
]
|
531
|
+
nbits: Annotated[
|
532
|
+
int | None,
|
533
|
+
click.option(
|
534
|
+
"--nbits",
|
535
|
+
"nbits",
|
536
|
+
type=int,
|
537
|
+
help="Number of bits used to encode the index of a sub-vector's centroid in the compressed representation",
|
538
|
+
),
|
539
|
+
]
|
504
540
|
sample_per_nlist: Annotated[
|
505
541
|
int | None,
|
506
542
|
click.option(
|
@@ -1,11 +1,13 @@
|
|
1
1
|
from ..backend.clients.alloydb.cli import AlloyDBScaNN
|
2
2
|
from ..backend.clients.aws_opensearch.cli import AWSOpenSearch
|
3
3
|
from ..backend.clients.clickhouse.cli import Clickhouse
|
4
|
+
from ..backend.clients.hologres.cli import HologresHGraph
|
4
5
|
from ..backend.clients.lancedb.cli import LanceDB
|
5
6
|
from ..backend.clients.mariadb.cli import MariaDBHNSW
|
6
7
|
from ..backend.clients.memorydb.cli import MemoryDB
|
7
8
|
from ..backend.clients.milvus.cli import MilvusAutoIndex
|
8
9
|
from ..backend.clients.oceanbase.cli import OceanBaseHNSW, OceanBaseIVF
|
10
|
+
from ..backend.clients.oss_opensearch.cli import OSSOpenSearch
|
9
11
|
from ..backend.clients.pgdiskann.cli import PgDiskAnn
|
10
12
|
from ..backend.clients.pgvecto_rs.cli import PgVectoRSHNSW, PgVectoRSIVFFlat
|
11
13
|
from ..backend.clients.pgvector.cli import PgVectorHNSW
|
@@ -31,6 +33,7 @@ cli.add_command(Test)
|
|
31
33
|
cli.add_command(ZillizAutoIndex)
|
32
34
|
cli.add_command(MilvusAutoIndex)
|
33
35
|
cli.add_command(AWSOpenSearch)
|
36
|
+
cli.add_command(OSSOpenSearch)
|
34
37
|
cli.add_command(PgVectorScaleDiskAnn)
|
35
38
|
cli.add_command(PgDiskAnn)
|
36
39
|
cli.add_command(AlloyDBScaNN)
|
@@ -41,6 +44,7 @@ cli.add_command(TiDB)
|
|
41
44
|
cli.add_command(Clickhouse)
|
42
45
|
cli.add_command(Vespa)
|
43
46
|
cli.add_command(LanceDB)
|
47
|
+
cli.add_command(HologresHGraph)
|
44
48
|
cli.add_command(QdrantCloud)
|
45
49
|
cli.add_command(QdrantLocal)
|
46
50
|
cli.add_command(BatchCli)
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -89,7 +89,7 @@ def getShowDbsAndCases(st, result: list[CaseResult], filter_type: FilterOp) -> t
|
|
89
89
|
col=1,
|
90
90
|
)
|
91
91
|
|
92
|
-
if filter_type == FilterOp.StrEqual:
|
92
|
+
if filter_type == FilterOp.StrEqual or filter_type == FilterOp.NumGE:
|
93
93
|
container = st.container()
|
94
94
|
datasetWithSizeTypes = [dataset_with_size_type for dataset_with_size_type in DatasetWithSizeType]
|
95
95
|
showDatasetWithSizeTypes = filterView(
|
@@ -102,9 +102,6 @@ def getShowDbsAndCases(st, result: list[CaseResult], filter_type: FilterOp) -> t
|
|
102
102
|
datasets = [dataset_with_size_type.get_manager() for dataset_with_size_type in showDatasetWithSizeTypes]
|
103
103
|
showCaseNames = list(set([case.name for case in allCases if case.dataset in datasets]))
|
104
104
|
|
105
|
-
if filter_type == FilterOp.NumGE:
|
106
|
-
raise NotImplementedError
|
107
|
-
|
108
105
|
return showDBNames, showCaseNames
|
109
106
|
|
110
107
|
|
@@ -19,7 +19,7 @@ def NavToQuriesPerDollar(st):
|
|
19
19
|
def NavToResults(st, key="nav-to-results"):
|
20
20
|
navClick = st.button("< Back to Results", key=key)
|
21
21
|
if navClick:
|
22
|
-
switch_page("
|
22
|
+
switch_page("results")
|
23
23
|
|
24
24
|
|
25
25
|
def NavToPages(st):
|
@@ -29,6 +29,7 @@ def NavToPages(st):
|
|
29
29
|
{"name": "Quries Per Dollar", "link": "quries_per_dollar"},
|
30
30
|
{"name": "Concurrent", "link": "concurrent"},
|
31
31
|
{"name": "Label Filter", "link": "label_filter"},
|
32
|
+
{"name": "Int Filter", "link": "int_filter"},
|
32
33
|
{"name": "Streaming", "link": "streaming"},
|
33
34
|
{"name": "Tables", "link": "tables"},
|
34
35
|
{"name": "Custom Dataset", "link": "custom"},
|
@@ -20,6 +20,11 @@ def drawChartsByCase(allData, showCaseNames: list[str], st, latency_type: str):
|
|
20
20
|
if 0 <= i < len(caseData["conc_latency_p99_list"])
|
21
21
|
else 0
|
22
22
|
),
|
23
|
+
"latency_p95": (
|
24
|
+
caseData["conc_latency_p95_list"][i] * 1000
|
25
|
+
if "conc_latency_p95_list" in caseData and 0 <= i < len(caseData["conc_latency_p95_list"])
|
26
|
+
else 0
|
27
|
+
),
|
23
28
|
"latency_avg": (
|
24
29
|
caseData["conc_latency_avg_list"][i] * 1000
|
25
30
|
if 0 <= i < len(caseData["conc_latency_avg_list"])
|