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.
Files changed (77) hide show
  1. vectordb_bench/__init__.py +1 -0
  2. vectordb_bench/backend/cases.py +45 -1
  3. vectordb_bench/backend/clients/__init__.py +47 -0
  4. vectordb_bench/backend/clients/api.py +2 -0
  5. vectordb_bench/backend/clients/aws_opensearch/aws_opensearch.py +104 -40
  6. vectordb_bench/backend/clients/aws_opensearch/cli.py +52 -15
  7. vectordb_bench/backend/clients/aws_opensearch/config.py +27 -7
  8. vectordb_bench/backend/clients/hologres/cli.py +50 -0
  9. vectordb_bench/backend/clients/hologres/config.py +121 -0
  10. vectordb_bench/backend/clients/hologres/hologres.py +365 -0
  11. vectordb_bench/backend/clients/lancedb/lancedb.py +1 -0
  12. vectordb_bench/backend/clients/milvus/cli.py +29 -9
  13. vectordb_bench/backend/clients/milvus/config.py +2 -0
  14. vectordb_bench/backend/clients/milvus/milvus.py +1 -1
  15. vectordb_bench/backend/clients/oceanbase/cli.py +1 -0
  16. vectordb_bench/backend/clients/oceanbase/config.py +3 -1
  17. vectordb_bench/backend/clients/oceanbase/oceanbase.py +20 -4
  18. vectordb_bench/backend/clients/oss_opensearch/cli.py +155 -0
  19. vectordb_bench/backend/clients/oss_opensearch/config.py +157 -0
  20. vectordb_bench/backend/clients/oss_opensearch/oss_opensearch.py +582 -0
  21. vectordb_bench/backend/clients/oss_opensearch/run.py +166 -0
  22. vectordb_bench/backend/clients/pgdiskann/cli.py +45 -0
  23. vectordb_bench/backend/clients/pgdiskann/config.py +16 -0
  24. vectordb_bench/backend/clients/pgdiskann/pgdiskann.py +94 -26
  25. vectordb_bench/backend/clients/s3_vectors/config.py +41 -0
  26. vectordb_bench/backend/clients/s3_vectors/s3_vectors.py +171 -0
  27. vectordb_bench/backend/clients/tidb/cli.py +0 -4
  28. vectordb_bench/backend/clients/tidb/config.py +22 -2
  29. vectordb_bench/backend/clients/zilliz_cloud/cli.py +14 -1
  30. vectordb_bench/backend/clients/zilliz_cloud/config.py +4 -1
  31. vectordb_bench/backend/dataset.py +70 -0
  32. vectordb_bench/backend/filter.py +17 -0
  33. vectordb_bench/backend/runner/mp_runner.py +4 -0
  34. vectordb_bench/backend/runner/rate_runner.py +23 -11
  35. vectordb_bench/backend/runner/read_write_runner.py +10 -9
  36. vectordb_bench/backend/runner/serial_runner.py +23 -7
  37. vectordb_bench/backend/task_runner.py +5 -4
  38. vectordb_bench/cli/cli.py +36 -0
  39. vectordb_bench/cli/vectordbbench.py +4 -0
  40. vectordb_bench/fig/custom_case_run_test.png +0 -0
  41. vectordb_bench/fig/custom_dataset.png +0 -0
  42. vectordb_bench/fig/homepage/bar-chart.png +0 -0
  43. vectordb_bench/fig/homepage/concurrent.png +0 -0
  44. vectordb_bench/fig/homepage/custom.png +0 -0
  45. vectordb_bench/fig/homepage/label_filter.png +0 -0
  46. vectordb_bench/fig/homepage/qp$.png +0 -0
  47. vectordb_bench/fig/homepage/run_test.png +0 -0
  48. vectordb_bench/fig/homepage/streaming.png +0 -0
  49. vectordb_bench/fig/homepage/table.png +0 -0
  50. vectordb_bench/fig/run_test_select_case.png +0 -0
  51. vectordb_bench/fig/run_test_select_db.png +0 -0
  52. vectordb_bench/fig/run_test_submit.png +0 -0
  53. vectordb_bench/frontend/components/check_results/filters.py +1 -4
  54. vectordb_bench/frontend/components/check_results/nav.py +2 -1
  55. vectordb_bench/frontend/components/concurrent/charts.py +5 -0
  56. vectordb_bench/frontend/components/int_filter/charts.py +60 -0
  57. vectordb_bench/frontend/components/streaming/data.py +7 -0
  58. vectordb_bench/frontend/components/welcome/welcomePrams.py +42 -4
  59. vectordb_bench/frontend/config/dbCaseConfigs.py +142 -16
  60. vectordb_bench/frontend/config/styles.py +4 -0
  61. vectordb_bench/frontend/pages/concurrent.py +1 -1
  62. vectordb_bench/frontend/pages/custom.py +1 -1
  63. vectordb_bench/frontend/pages/int_filter.py +56 -0
  64. vectordb_bench/frontend/pages/streaming.py +16 -3
  65. vectordb_bench/interface.py +5 -1
  66. vectordb_bench/metric.py +7 -0
  67. vectordb_bench/models.py +39 -4
  68. vectordb_bench/results/S3Vectors/result_20250722_standard_s3vectors.json +2509 -0
  69. vectordb_bench/results/getLeaderboardDataV2.py +23 -2
  70. vectordb_bench/results/leaderboard_v2.json +200 -0
  71. vectordb_bench/results/leaderboard_v2_streaming.json +128 -0
  72. {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/METADATA +40 -8
  73. {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/RECORD +77 -51
  74. {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/WHEEL +0 -0
  75. {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/entry_points.txt +0 -0
  76. {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/licenses/LICENSE +0 -0
  77. {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 pydantic import BaseModel, SecretStr
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) -> dict:
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
- params={parameters["level"]},
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):
@@ -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], retry_idx: int = 0):
37
- _, error = db.insert_embeddings(emb, metadata)
38
- if error is not None:
39
- log.warning(f"Insert Failed, try_idx={retry_idx}, Exception: {error}")
40
- retry_idx += 1
41
- if retry_idx <= config.MAX_INSERT_RETRY:
42
- time.sleep(retry_idx)
43
- self.send_insert_task(db, emb=emb, metadata=metadata, retry_idx=retry_idx)
44
- else:
45
- msg = f"Insert failed and retried more than {config.MAX_INSERT_RETRY} times"
46
- raise RuntimeError(msg) from None
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.st_conc_failed_rate_list = [d[6] for d in r]
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
@@ -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("< &nbsp;&nbsp;Back to Results", key=key)
21
21
  if navClick:
22
- switch_page("vdb benchmark")
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"])