vectordb-bench 0.0.29__py3-none-any.whl → 1.0.0__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 (90) hide show
  1. vectordb_bench/__init__.py +14 -27
  2. vectordb_bench/backend/assembler.py +19 -6
  3. vectordb_bench/backend/cases.py +186 -23
  4. vectordb_bench/backend/clients/__init__.py +32 -0
  5. vectordb_bench/backend/clients/api.py +22 -1
  6. vectordb_bench/backend/clients/aws_opensearch/aws_opensearch.py +249 -43
  7. vectordb_bench/backend/clients/aws_opensearch/cli.py +51 -21
  8. vectordb_bench/backend/clients/aws_opensearch/config.py +58 -16
  9. vectordb_bench/backend/clients/chroma/chroma.py +6 -2
  10. vectordb_bench/backend/clients/elastic_cloud/config.py +19 -1
  11. vectordb_bench/backend/clients/elastic_cloud/elastic_cloud.py +133 -45
  12. vectordb_bench/backend/clients/lancedb/cli.py +62 -8
  13. vectordb_bench/backend/clients/lancedb/config.py +14 -1
  14. vectordb_bench/backend/clients/lancedb/lancedb.py +21 -9
  15. vectordb_bench/backend/clients/memorydb/memorydb.py +2 -2
  16. vectordb_bench/backend/clients/milvus/cli.py +30 -9
  17. vectordb_bench/backend/clients/milvus/config.py +3 -0
  18. vectordb_bench/backend/clients/milvus/milvus.py +81 -23
  19. vectordb_bench/backend/clients/oceanbase/cli.py +100 -0
  20. vectordb_bench/backend/clients/oceanbase/config.py +125 -0
  21. vectordb_bench/backend/clients/oceanbase/oceanbase.py +215 -0
  22. vectordb_bench/backend/clients/pinecone/pinecone.py +39 -25
  23. vectordb_bench/backend/clients/qdrant_cloud/config.py +59 -3
  24. vectordb_bench/backend/clients/qdrant_cloud/qdrant_cloud.py +100 -33
  25. vectordb_bench/backend/clients/qdrant_local/cli.py +60 -0
  26. vectordb_bench/backend/clients/qdrant_local/config.py +47 -0
  27. vectordb_bench/backend/clients/qdrant_local/qdrant_local.py +232 -0
  28. vectordb_bench/backend/clients/weaviate_cloud/cli.py +29 -3
  29. vectordb_bench/backend/clients/weaviate_cloud/config.py +2 -0
  30. vectordb_bench/backend/clients/weaviate_cloud/weaviate_cloud.py +5 -0
  31. vectordb_bench/backend/dataset.py +143 -27
  32. vectordb_bench/backend/filter.py +76 -0
  33. vectordb_bench/backend/runner/__init__.py +3 -3
  34. vectordb_bench/backend/runner/mp_runner.py +52 -39
  35. vectordb_bench/backend/runner/rate_runner.py +68 -52
  36. vectordb_bench/backend/runner/read_write_runner.py +125 -68
  37. vectordb_bench/backend/runner/serial_runner.py +56 -23
  38. vectordb_bench/backend/task_runner.py +48 -20
  39. vectordb_bench/cli/batch_cli.py +121 -0
  40. vectordb_bench/cli/cli.py +59 -1
  41. vectordb_bench/cli/vectordbbench.py +7 -0
  42. vectordb_bench/config-files/batch_sample_config.yml +17 -0
  43. vectordb_bench/frontend/components/check_results/data.py +16 -11
  44. vectordb_bench/frontend/components/check_results/filters.py +53 -25
  45. vectordb_bench/frontend/components/check_results/headerIcon.py +16 -13
  46. vectordb_bench/frontend/components/check_results/nav.py +20 -0
  47. vectordb_bench/frontend/components/custom/displayCustomCase.py +43 -8
  48. vectordb_bench/frontend/components/custom/displaypPrams.py +10 -5
  49. vectordb_bench/frontend/components/custom/getCustomConfig.py +10 -0
  50. vectordb_bench/frontend/components/label_filter/charts.py +60 -0
  51. vectordb_bench/frontend/components/run_test/caseSelector.py +48 -52
  52. vectordb_bench/frontend/components/run_test/dbSelector.py +9 -5
  53. vectordb_bench/frontend/components/run_test/inputWidget.py +48 -0
  54. vectordb_bench/frontend/components/run_test/submitTask.py +3 -1
  55. vectordb_bench/frontend/components/streaming/charts.py +253 -0
  56. vectordb_bench/frontend/components/streaming/data.py +62 -0
  57. vectordb_bench/frontend/components/tables/data.py +1 -1
  58. vectordb_bench/frontend/components/welcome/explainPrams.py +66 -0
  59. vectordb_bench/frontend/components/welcome/pagestyle.py +106 -0
  60. vectordb_bench/frontend/components/welcome/welcomePrams.py +147 -0
  61. vectordb_bench/frontend/config/dbCaseConfigs.py +420 -41
  62. vectordb_bench/frontend/config/styles.py +32 -2
  63. vectordb_bench/frontend/pages/concurrent.py +5 -1
  64. vectordb_bench/frontend/pages/custom.py +4 -0
  65. vectordb_bench/frontend/pages/label_filter.py +56 -0
  66. vectordb_bench/frontend/pages/quries_per_dollar.py +5 -1
  67. vectordb_bench/frontend/pages/results.py +60 -0
  68. vectordb_bench/frontend/pages/run_test.py +3 -3
  69. vectordb_bench/frontend/pages/streaming.py +135 -0
  70. vectordb_bench/frontend/pages/tables.py +4 -0
  71. vectordb_bench/frontend/vdb_benchmark.py +16 -41
  72. vectordb_bench/interface.py +6 -2
  73. vectordb_bench/metric.py +15 -1
  74. vectordb_bench/models.py +38 -11
  75. vectordb_bench/results/ElasticCloud/result_20250318_standard_elasticcloud.json +5890 -0
  76. vectordb_bench/results/Milvus/result_20250509_standard_milvus.json +6138 -0
  77. vectordb_bench/results/OpenSearch/result_20250224_standard_opensearch.json +7319 -0
  78. vectordb_bench/results/Pinecone/result_20250124_standard_pinecone.json +2365 -0
  79. vectordb_bench/results/QdrantCloud/result_20250602_standard_qdrantcloud.json +3556 -0
  80. vectordb_bench/results/ZillizCloud/result_20250613_standard_zillizcloud.json +6290 -0
  81. vectordb_bench/results/dbPrices.json +12 -4
  82. {vectordb_bench-0.0.29.dist-info → vectordb_bench-1.0.0.dist-info}/METADATA +131 -32
  83. {vectordb_bench-0.0.29.dist-info → vectordb_bench-1.0.0.dist-info}/RECORD +87 -65
  84. {vectordb_bench-0.0.29.dist-info → vectordb_bench-1.0.0.dist-info}/WHEEL +1 -1
  85. vectordb_bench/results/ZillizCloud/result_20230727_standard_zillizcloud.json +0 -791
  86. vectordb_bench/results/ZillizCloud/result_20230808_standard_zillizcloud.json +0 -679
  87. vectordb_bench/results/ZillizCloud/result_20240105_standard_202401_zillizcloud.json +0 -1352
  88. {vectordb_bench-0.0.29.dist-info → vectordb_bench-1.0.0.dist-info}/entry_points.txt +0 -0
  89. {vectordb_bench-0.0.29.dist-info → vectordb_bench-1.0.0.dist-info}/licenses/LICENSE +0 -0
  90. {vectordb_bench-0.0.29.dist-info → vectordb_bench-1.0.0.dist-info}/top_level.txt +0 -0
@@ -5,8 +5,10 @@ from contextlib import contextmanager
5
5
 
6
6
  from opensearchpy import OpenSearch
7
7
 
8
- from ..api import IndexType, VectorDB
9
- from .config import AWSOpenSearchConfig, AWSOpenSearchIndexConfig, AWSOS_Engine
8
+ from vectordb_bench.backend.filter import Filter, FilterOp
9
+
10
+ from ..api import VectorDB
11
+ from .config import AWSOpenSearchIndexConfig, AWSOS_Engine
10
12
 
11
13
  log = logging.getLogger(__name__)
12
14
 
@@ -16,6 +18,12 @@ SECONDS_WAITING_FOR_REPLICAS_TO_BE_ENABLED_SEC = 30
16
18
 
17
19
 
18
20
  class AWSOpenSearch(VectorDB):
21
+ supported_filter_types: list[FilterOp] = [
22
+ FilterOp.NonFilter,
23
+ FilterOp.NumGE,
24
+ FilterOp.StrEqual,
25
+ ]
26
+
19
27
  def __init__(
20
28
  self,
21
29
  dim: int,
@@ -23,8 +31,10 @@ class AWSOpenSearch(VectorDB):
23
31
  db_case_config: AWSOpenSearchIndexConfig,
24
32
  index_name: str = "vdb_bench_index", # must be lowercase
25
33
  id_col_name: str = "_id",
34
+ label_col_name: str = "label",
26
35
  vector_col_name: str = "embedding",
27
36
  drop_old: bool = False,
37
+ with_scalar_labels: bool = False,
28
38
  **kwargs,
29
39
  ):
30
40
  self.dim = dim
@@ -32,10 +42,12 @@ class AWSOpenSearch(VectorDB):
32
42
  self.case_config = db_case_config
33
43
  self.index_name = index_name
34
44
  self.id_col_name = id_col_name
35
- self.category_col_names = [f"scalar-{categoryCount}" for categoryCount in [2, 5, 10, 100, 1000]]
45
+ self.label_col_name = label_col_name
36
46
  self.vector_col_name = vector_col_name
47
+ self.with_scalar_labels = with_scalar_labels
37
48
 
38
49
  log.info(f"AWS_OpenSearch client config: {self.db_config}")
50
+ log.info(f"AWS_OpenSearch db case config : {self.case_config}")
39
51
  client = OpenSearch(**self.db_config)
40
52
  if drop_old:
41
53
  log.info(f"AWS_OpenSearch client drop old index: {self.index_name}")
@@ -43,16 +55,26 @@ class AWSOpenSearch(VectorDB):
43
55
  if is_existed:
44
56
  client.indices.delete(index=self.index_name)
45
57
  self._create_index(client)
58
+ else:
59
+ is_existed = client.indices.exists(index=self.index_name)
60
+ if not is_existed:
61
+ self._create_index(client)
62
+ log.info(f"AWS_OpenSearch client create index: {self.index_name}")
63
+
64
+ self._update_ef_search_before_search(client)
65
+ self._load_graphs_to_memory(client)
46
66
 
47
- @classmethod
48
- def config_cls(cls) -> AWSOpenSearchConfig:
49
- return AWSOpenSearchConfig
67
+ def _create_index(self, client: OpenSearch) -> None:
68
+ ef_search_value = (
69
+ self.case_config.ef_search if self.case_config.ef_search is not None else self.case_config.efSearch
70
+ )
71
+ log.info(f"Creating index with ef_search: {ef_search_value}")
72
+ log.info(f"Creating index with number_of_replicas: {self.case_config.number_of_replicas}")
50
73
 
51
- @classmethod
52
- def case_config_cls(cls, index_type: IndexType | None = None) -> AWSOpenSearchIndexConfig:
53
- return AWSOpenSearchIndexConfig
74
+ log.info(f"Creating index with engine: {self.case_config.engine}")
75
+ log.info(f"Creating index with metric type: {self.case_config.metric_type_name}")
76
+ log.info(f"All case_config parameters: {self.case_config.__dict__}")
54
77
 
55
- def _create_index(self, client: OpenSearch):
56
78
  cluster_settings_body = {
57
79
  "persistent": {
58
80
  "knn.algo_param.index_thread_qty": self.case_config.index_thread_qty,
@@ -64,20 +86,18 @@ class AWSOpenSearch(VectorDB):
64
86
  "index": {
65
87
  "knn": True,
66
88
  "number_of_shards": self.case_config.number_of_shards,
67
- "number_of_replicas": 0,
89
+ "number_of_replicas": self.case_config.number_of_replicas,
68
90
  "translog.flush_threshold_size": self.case_config.flush_threshold_size,
69
- # Setting trans log threshold to 5GB
70
- **(
71
- {"knn.algo_param.ef_search": self.case_config.ef_search}
72
- if self.case_config.engine == AWSOS_Engine.nmslib
73
- else {}
74
- ),
91
+ "knn.advanced.approximate_threshold": "-1",
75
92
  },
76
93
  "refresh_interval": self.case_config.refresh_interval,
77
94
  }
95
+ settings["index"]["knn.algo_param.ef_search"] = ef_search_value
78
96
  mappings = {
97
+ "_source": {"excludes": [self.vector_col_name], "recovery_source_excludes": [self.vector_col_name]},
79
98
  "properties": {
80
- **{categoryCol: {"type": "keyword"} for categoryCol in self.category_col_names},
99
+ self.id_col_name: {"type": "integer", "store": True},
100
+ self.label_col_name: {"type": "keyword"},
81
101
  self.vector_col_name: {
82
102
  "type": "knn_vector",
83
103
  "dimension": self.dim,
@@ -86,6 +106,8 @@ class AWSOpenSearch(VectorDB):
86
106
  },
87
107
  }
88
108
  try:
109
+ log.info(f"Creating index with settings: {settings}")
110
+ log.info(f"Creating index with mappings: {mappings}")
89
111
  client.indices.create(
90
112
  index=self.index_name,
91
113
  body={"settings": settings, "mappings": mappings},
@@ -107,53 +129,193 @@ class AWSOpenSearch(VectorDB):
107
129
  self,
108
130
  embeddings: Iterable[list[float]],
109
131
  metadata: list[int],
132
+ labels_data: list[str] | None = None,
110
133
  **kwargs,
111
134
  ) -> tuple[int, Exception]:
112
135
  """Insert the embeddings to the opensearch."""
113
136
  assert self.client is not None, "should self.init() first"
114
137
 
138
+ num_clients = self.case_config.number_of_indexing_clients or 1
139
+ log.info(f"Number of indexing clients from case_config: {num_clients}")
140
+
141
+ if num_clients <= 1:
142
+ log.info("Using single client for data insertion")
143
+ return self._insert_with_single_client(embeddings, metadata, labels_data)
144
+ log.info(f"Using {num_clients} parallel clients for data insertion")
145
+ return self._insert_with_multiple_clients(embeddings, metadata, num_clients, labels_data)
146
+
147
+ def _insert_with_single_client(
148
+ self,
149
+ embeddings: Iterable[list[float]],
150
+ metadata: list[int],
151
+ labels_data: list[str] | None = None,
152
+ ) -> tuple[int, Exception]:
115
153
  insert_data = []
116
154
  for i in range(len(embeddings)):
117
- insert_data.append(
118
- {"index": {"_index": self.index_name, self.id_col_name: metadata[i]}},
119
- )
120
- insert_data.append({self.vector_col_name: embeddings[i]})
155
+ index_data = {"index": {"_index": self.index_name, self.id_col_name: metadata[i]}}
156
+ if self.with_scalar_labels and self.case_config.use_routing:
157
+ index_data["routing"] = labels_data[i]
158
+ insert_data.append(index_data)
159
+
160
+ other_data = {self.vector_col_name: embeddings[i]}
161
+ if self.with_scalar_labels:
162
+ other_data[self.label_col_name] = labels_data[i]
163
+ insert_data.append(other_data)
164
+
121
165
  try:
122
- resp = self.client.bulk(insert_data)
123
- log.info(f"AWS_OpenSearch adding documents: {len(resp['items'])}")
124
- resp = self.client.indices.stats(self.index_name)
125
- log.info(
126
- f"Total document count in index: {resp['_all']['primaries']['indexing']['index_total']}",
127
- )
128
- return (len(embeddings), None)
166
+ self.client.bulk(insert_data)
167
+ return len(embeddings), None
129
168
  except Exception as e:
130
169
  log.warning(f"Failed to insert data: {self.index_name} error: {e!s}")
131
170
  time.sleep(10)
132
- return self.insert_embeddings(embeddings, metadata)
171
+ return self._insert_with_single_client(embeddings, metadata)
172
+
173
+ def _insert_with_multiple_clients(
174
+ self,
175
+ embeddings: Iterable[list[float]],
176
+ metadata: list[int],
177
+ num_clients: int,
178
+ labels_data: list[str] | None = None,
179
+ ) -> tuple[int, Exception]:
180
+ import concurrent.futures
181
+ from concurrent.futures import ThreadPoolExecutor
182
+
183
+ embeddings_list = list(embeddings)
184
+ chunk_size = max(1, len(embeddings_list) // num_clients)
185
+ chunks = []
186
+
187
+ for i in range(0, len(embeddings_list), chunk_size):
188
+ end = min(i + chunk_size, len(embeddings_list))
189
+ chunks.append((embeddings_list[i:end], metadata[i:end], labels_data[i:end]))
190
+
191
+ clients = []
192
+ for _ in range(min(num_clients, len(chunks))):
193
+ client = OpenSearch(**self.db_config)
194
+ clients.append(client)
195
+
196
+ log.info(f"AWS_OpenSearch using {len(clients)} parallel clients for data insertion")
197
+
198
+ def insert_chunk(client_idx: int, chunk_idx: int):
199
+ chunk_embeddings, chunk_metadata, chunk_labels_data = chunks[chunk_idx]
200
+ client = clients[client_idx]
201
+
202
+ insert_data = []
203
+ for i in range(len(chunk_embeddings)):
204
+ index_data = {"index": {"_index": self.index_name, self.id_col_name: chunk_metadata[i]}}
205
+ if self.with_scalar_labels and self.case_config.use_routing:
206
+ index_data["routing"] = chunk_labels_data[i]
207
+ insert_data.append(index_data)
208
+
209
+ other_data = {self.vector_col_name: chunk_embeddings[i]}
210
+ if self.with_scalar_labels:
211
+ other_data[self.label_col_name] = chunk_labels_data[i]
212
+ insert_data.append(other_data)
213
+
214
+ try:
215
+ resp = client.bulk(insert_data)
216
+ log.info(f"Client {client_idx} added {len(resp['items'])} documents")
217
+ return len(chunk_embeddings), None
218
+ except Exception as e:
219
+ log.warning(f"Client {client_idx} failed to insert data: {e!s}")
220
+ return 0, e
221
+
222
+ results = []
223
+ with ThreadPoolExecutor(max_workers=len(clients)) as executor:
224
+ futures = []
225
+
226
+ for chunk_idx in range(len(chunks)):
227
+ client_idx = chunk_idx % len(clients)
228
+ futures.append(executor.submit(insert_chunk, client_idx, chunk_idx))
229
+
230
+ for future in concurrent.futures.as_completed(futures):
231
+ count, error = future.result()
232
+ results.append((count, error))
233
+
234
+ from contextlib import suppress
235
+
236
+ for client in clients:
237
+ with suppress(Exception):
238
+ client.close()
239
+
240
+ total_count = sum(count for count, _ in results)
241
+ errors = [error for _, error in results if error is not None]
242
+
243
+ if errors:
244
+ log.warning("Some clients failed to insert data, retrying with single client")
245
+ time.sleep(10)
246
+ return self._insert_with_single_client(embeddings, metadata)
247
+
248
+ resp = self.client.indices.stats(self.index_name)
249
+ log.info(
250
+ f"""Total document count in index after parallel insertion:
251
+ {resp['_all']['primaries']['indexing']['index_total']}""",
252
+ )
253
+
254
+ return (total_count, None)
255
+
256
+ def _update_ef_search_before_search(self, client: OpenSearch):
257
+ ef_search_value = (
258
+ self.case_config.ef_search if self.case_config.ef_search is not None else self.case_config.efSearch
259
+ )
260
+
261
+ try:
262
+ index_settings = client.indices.get_settings(index=self.index_name)
263
+ current_ef_search = (
264
+ index_settings.get(self.index_name, {})
265
+ .get("settings", {})
266
+ .get("index", {})
267
+ .get("knn.algo_param", {})
268
+ .get("ef_search")
269
+ )
270
+
271
+ if current_ef_search != str(ef_search_value):
272
+ log.info(f"Updating ef_search before search from {current_ef_search} to {ef_search_value}")
273
+ settings_body = {"index": {"knn.algo_param.ef_search": ef_search_value}}
274
+ client.indices.put_settings(index=self.index_name, body=settings_body)
275
+ log.info(f"Successfully updated ef_search to {ef_search_value} before search")
276
+
277
+ log.info(f"Current engine: {self.case_config.engine}")
278
+ log.info(f"Current metric_type: {self.case_config.metric_type_name}")
279
+
280
+ except Exception as e:
281
+ log.warning(f"Failed to update ef_search parameter before search: {e}")
133
282
 
134
283
  def search_embedding(
135
284
  self,
136
285
  query: list[float],
137
286
  k: int = 100,
138
- filters: dict | None = None,
287
+ **kwargs,
139
288
  ) -> list[int]:
140
289
  """Get k most similar embeddings to query vector.
141
290
 
142
291
  Args:
143
292
  query(list[float]): query embedding to look up documents similar to.
144
293
  k(int): Number of most similar embeddings to return. Defaults to 100.
145
- filters(dict, optional): filtering expression to filter the data while searching.
146
294
 
147
295
  Returns:
148
- list[tuple[int, float]]: list of k most similar embeddings in (id, score) tuple to the query embedding.
296
+ list[int]: list of k most similar ids to the query embedding.
149
297
  """
150
298
  assert self.client is not None, "should self.init() first"
151
299
 
152
300
  body = {
153
301
  "size": k,
154
- "query": {"knn": {self.vector_col_name: {"vector": query, "k": k}}},
155
- **({"filter": {"range": {self.id_col_name: {"gt": filters["id"]}}}} if filters else {}),
302
+ "query": {
303
+ "knn": {
304
+ self.vector_col_name: {
305
+ "vector": query,
306
+ "k": k,
307
+ "method_parameters": self.case_config.search_param(),
308
+ **({"filter": self.filter} if self.filter else {}),
309
+ **(
310
+ {"rescore": {"oversample_factor": self.case_config.oversample_factor}}
311
+ if self.case_config.use_quant
312
+ else {}
313
+ ),
314
+ }
315
+ }
316
+ },
156
317
  }
318
+
157
319
  try:
158
320
  resp = self.client.search(
159
321
  index=self.index_name,
@@ -162,17 +324,38 @@ class AWSOpenSearch(VectorDB):
162
324
  _source=False,
163
325
  docvalue_fields=[self.id_col_name],
164
326
  stored_fields="_none_",
327
+ preference="_only_local" if self.case_config.number_of_shards == 1 else None,
328
+ routing=self.routing_key,
165
329
  )
166
330
  log.debug(f"Search took: {resp['took']}")
167
331
  log.debug(f"Search shards: {resp['_shards']}")
168
332
  log.debug(f"Search hits total: {resp['hits']['total']}")
169
- return [int(h["fields"][self.id_col_name][0]) for h in resp["hits"]["hits"]]
333
+ try:
334
+ return [int(h["fields"][self.id_col_name][0]) for h in resp["hits"]["hits"]]
335
+ except Exception:
336
+ # empty results
337
+ return []
170
338
  except Exception as e:
171
339
  log.warning(f"Failed to search: {self.index_name} error: {e!s}")
172
340
  raise e from None
173
341
 
342
+ def prepare_filter(self, filters: Filter):
343
+ self.routing_key = None
344
+ if filters.type == FilterOp.NonFilter:
345
+ self.filter = None
346
+ elif filters.type == FilterOp.NumGE:
347
+ self.filter = {"range": {self.id_col_name: {"gt": filters.int_value}}}
348
+ elif filters.type == FilterOp.StrEqual:
349
+ self.filter = {"term": {self.label_col_name: filters.label_value}}
350
+ if self.case_config.use_routing:
351
+ self.routing_key = filters.label_value
352
+ else:
353
+ msg = f"Not support Filter for OpenSearch - {filters}"
354
+ raise ValueError(msg)
355
+
174
356
  def optimize(self, data_size: int | None = None):
175
357
  """optimize will be called between insertion and search in performance cases."""
358
+ self._update_ef_search()
176
359
  # Call refresh first to ensure that all segments are created
177
360
  self._refresh_index()
178
361
  if self.case_config.force_merge_enabled:
@@ -182,7 +365,22 @@ class AWSOpenSearch(VectorDB):
182
365
  # Call refresh again to ensure that the index is ready after force merge.
183
366
  self._refresh_index()
184
367
  # ensure that all graphs are loaded in memory and ready for search
185
- self._load_graphs_to_memory()
368
+ self._load_graphs_to_memory(self.client)
369
+
370
+ def _update_ef_search(self):
371
+ ef_search_value = (
372
+ self.case_config.ef_search if self.case_config.ef_search is not None else self.case_config.efSearch
373
+ )
374
+ log.info(f"Updating ef_search parameter to: {ef_search_value}")
375
+
376
+ settings_body = {"index": {"knn.algo_param.ef_search": ef_search_value}}
377
+ try:
378
+ self.client.indices.put_settings(index=self.index_name, body=settings_body)
379
+ log.info(f"Successfully updated ef_search to {ef_search_value}")
380
+ log.info(f"Current engine: {self.case_config.engine}")
381
+ log.info(f"Current metric_type: {self.case_config.metric_type}")
382
+ except Exception as e:
383
+ log.warning(f"Failed to update ef_search parameter: {e}")
186
384
 
187
385
  def _update_replicas(self):
188
386
  index_settings = self.client.indices.get_settings(index=self.index_name)
@@ -200,7 +398,7 @@ class AWSOpenSearch(VectorDB):
200
398
  while True:
201
399
  res = self.client.cat.indices(index=self.index_name, h="health", format="json")
202
400
  health = res[0]["health"]
203
- if health != "green":
401
+ if health == "green":
204
402
  break
205
403
  log.info(f"The index {self.index_name} has health : {health} and is not green. Retrying")
206
404
  time.sleep(SECONDS_WAITING_FOR_REPLICAS_TO_BE_ENABLED_SEC)
@@ -228,18 +426,26 @@ class AWSOpenSearch(VectorDB):
228
426
  "persistent": {"knn.algo_param.index_thread_qty": self.case_config.index_thread_qty_during_force_merge}
229
427
  }
230
428
  self.client.cluster.put_settings(cluster_settings_body)
231
- log.debug(f"Starting force merge for index {self.index_name}")
232
- force_merge_endpoint = f"/{self.index_name}/_forcemerge?max_num_segments=1&wait_for_completion=false"
429
+
430
+ log.info("Updating the graph threshold to ensure that during merge we can do graph creation.")
431
+ output = self.client.indices.put_settings(
432
+ index=self.index_name, body={"index.knn.advanced.approximate_threshold": "0"}
433
+ )
434
+ log.info(f"response of updating setting is: {output}")
435
+
436
+ log.info(f"Starting force merge for index {self.index_name}")
437
+ segments = self.case_config.number_of_segments
438
+ force_merge_endpoint = f"/{self.index_name}/_forcemerge?max_num_segments={segments}&wait_for_completion=false"
233
439
  force_merge_task_id = self.client.transport.perform_request("POST", force_merge_endpoint)["task"]
234
440
  while True:
235
441
  time.sleep(WAITING_FOR_FORCE_MERGE_SEC)
236
442
  task_status = self.client.tasks.get(task_id=force_merge_task_id)
237
443
  if task_status["completed"]:
238
444
  break
239
- log.debug(f"Completed force merge for index {self.index_name}")
445
+ log.info(f"Completed force merge for index {self.index_name}")
240
446
 
241
- def _load_graphs_to_memory(self):
447
+ def _load_graphs_to_memory(self, client: OpenSearch):
242
448
  if self.case_config.engine != AWSOS_Engine.lucene:
243
449
  log.info("Calling warmup API to load graphs into memory")
244
450
  warmup_endpoint = f"/_plugins/_knn/warmup/{self.index_name}"
245
- self.client.transport.perform_request("GET", warmup_endpoint)
451
+ client.transport.perform_request("GET", warmup_endpoint)
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  from typing import Annotated, TypedDict, Unpack
2
3
 
3
4
  import click
@@ -5,18 +6,21 @@ from pydantic import SecretStr
5
6
 
6
7
  from ....cli.cli import (
7
8
  CommonTypedDict,
8
- HNSWFlavor2,
9
+ HNSWFlavor1,
9
10
  cli,
10
11
  click_parameter_decorators_from_typed_dict,
11
12
  run,
12
13
  )
13
14
  from .. import DB
15
+ from .config import AWSOS_Engine, AWSOSQuantization
16
+
17
+ log = logging.getLogger(__name__)
14
18
 
15
19
 
16
20
  class AWSOpenSearchTypedDict(TypedDict):
17
21
  host: Annotated[str, click.option("--host", type=str, help="Db host", required=True)]
18
- port: Annotated[int, click.option("--port", type=int, default=443, help="Db Port")]
19
- user: Annotated[str, click.option("--user", type=str, default="admin", help="Db User")]
22
+ port: Annotated[int, click.option("--port", type=int, default=80, help="Db Port")]
23
+ user: Annotated[str, click.option("--user", type=str, help="Db User")]
20
24
  password: Annotated[str, click.option("--password", type=str, help="Db password")]
21
25
  number_of_shards: Annotated[
22
26
  int,
@@ -38,23 +42,23 @@ class AWSOpenSearchTypedDict(TypedDict):
38
42
  ),
39
43
  ]
40
44
 
41
- index_thread_qty_during_force_merge: Annotated[
42
- int,
45
+ engine: Annotated[
46
+ str,
43
47
  click.option(
44
- "--index-thread-qty-during-force-merge",
45
- type=int,
46
- help="Thread count during force merge operations",
47
- default=4,
48
+ "--engine",
49
+ type=click.Choice(["nmslib", "faiss", "lucene"], case_sensitive=False),
50
+ help="HNSW algorithm implementation to use",
51
+ default="faiss",
48
52
  ),
49
53
  ]
50
54
 
51
- number_of_indexing_clients: Annotated[
52
- int,
55
+ metric_type: Annotated[
56
+ str,
53
57
  click.option(
54
- "--number-of-indexing-clients",
55
- type=int,
56
- help="Number of concurrent indexing clients",
57
- default=1,
58
+ "--metric-type",
59
+ type=click.Choice(["l2", "cosine", "ip"], case_sensitive=False),
60
+ help="Distance metric type for vector similarity",
61
+ default="l2",
58
62
  ),
59
63
  ]
60
64
 
@@ -64,26 +68,26 @@ class AWSOpenSearchTypedDict(TypedDict):
64
68
  ]
65
69
 
66
70
  refresh_interval: Annotated[
67
- int,
71
+ str,
68
72
  click.option(
69
73
  "--refresh-interval", type=str, help="How often to make new data available for search", default="60s"
70
74
  ),
71
75
  ]
72
76
 
73
77
  force_merge_enabled: Annotated[
74
- int,
78
+ bool,
75
79
  click.option("--force-merge-enabled", type=bool, help="Whether to perform force merge operation", default=True),
76
80
  ]
77
81
 
78
82
  flush_threshold_size: Annotated[
79
- int,
83
+ str,
80
84
  click.option(
81
85
  "--flush-threshold-size", type=str, help="Size threshold for flushing the transaction log", default="5120mb"
82
86
  ),
83
87
  ]
84
88
 
85
89
  cb_threshold: Annotated[
86
- int,
90
+ str,
87
91
  click.option(
88
92
  "--cb-threshold",
89
93
  type=str,
@@ -92,8 +96,30 @@ class AWSOpenSearchTypedDict(TypedDict):
92
96
  ),
93
97
  ]
94
98
 
99
+ quantization_type: Annotated[
100
+ str | None,
101
+ click.option(
102
+ "--quantization-type",
103
+ type=click.Choice(["fp32", "fp16"]),
104
+ help="quantization type for vectors (in index)",
105
+ default="fp32",
106
+ required=False,
107
+ ),
108
+ ]
109
+
110
+ engine: Annotated[
111
+ str | None,
112
+ click.option(
113
+ "--engine",
114
+ type=click.Choice(["faiss", "lucene"]),
115
+ help="quantization type for vectors (in index)",
116
+ default="faiss",
117
+ required=False,
118
+ ),
119
+ ]
120
+
95
121
 
96
- class AWSOpenSearchHNSWTypedDict(CommonTypedDict, AWSOpenSearchTypedDict, HNSWFlavor2): ...
122
+ class AWSOpenSearchHNSWTypedDict(CommonTypedDict, AWSOpenSearchTypedDict, HNSWFlavor1): ...
97
123
 
98
124
 
99
125
  @cli.command()
@@ -117,9 +143,13 @@ def AWSOpenSearch(**parameters: Unpack[AWSOpenSearchHNSWTypedDict]):
117
143
  refresh_interval=parameters["refresh_interval"],
118
144
  force_merge_enabled=parameters["force_merge_enabled"],
119
145
  flush_threshold_size=parameters["flush_threshold_size"],
120
- number_of_indexing_clients=parameters["number_of_indexing_clients"],
121
146
  index_thread_qty_during_force_merge=parameters["index_thread_qty_during_force_merge"],
122
147
  cb_threshold=parameters["cb_threshold"],
148
+ efConstruction=parameters["ef_construction"],
149
+ efSearch=parameters["ef_runtime"],
150
+ M=parameters["m"],
151
+ engine=AWSOS_Engine(parameters["engine"]),
152
+ quantization_type=AWSOSQuantization(parameters["quantization_type"]),
123
153
  ),
124
154
  **parameters,
125
155
  )