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.
- vectordb_bench/__init__.py +14 -27
- vectordb_bench/backend/assembler.py +19 -6
- vectordb_bench/backend/cases.py +186 -23
- vectordb_bench/backend/clients/__init__.py +32 -0
- vectordb_bench/backend/clients/api.py +22 -1
- vectordb_bench/backend/clients/aws_opensearch/aws_opensearch.py +249 -43
- vectordb_bench/backend/clients/aws_opensearch/cli.py +51 -21
- vectordb_bench/backend/clients/aws_opensearch/config.py +58 -16
- vectordb_bench/backend/clients/chroma/chroma.py +6 -2
- vectordb_bench/backend/clients/elastic_cloud/config.py +19 -1
- vectordb_bench/backend/clients/elastic_cloud/elastic_cloud.py +133 -45
- vectordb_bench/backend/clients/lancedb/cli.py +62 -8
- vectordb_bench/backend/clients/lancedb/config.py +14 -1
- vectordb_bench/backend/clients/lancedb/lancedb.py +21 -9
- vectordb_bench/backend/clients/memorydb/memorydb.py +2 -2
- vectordb_bench/backend/clients/milvus/cli.py +30 -9
- vectordb_bench/backend/clients/milvus/config.py +3 -0
- vectordb_bench/backend/clients/milvus/milvus.py +81 -23
- vectordb_bench/backend/clients/oceanbase/cli.py +100 -0
- vectordb_bench/backend/clients/oceanbase/config.py +125 -0
- vectordb_bench/backend/clients/oceanbase/oceanbase.py +215 -0
- vectordb_bench/backend/clients/pinecone/pinecone.py +39 -25
- vectordb_bench/backend/clients/qdrant_cloud/config.py +59 -3
- vectordb_bench/backend/clients/qdrant_cloud/qdrant_cloud.py +100 -33
- vectordb_bench/backend/clients/qdrant_local/cli.py +60 -0
- vectordb_bench/backend/clients/qdrant_local/config.py +47 -0
- vectordb_bench/backend/clients/qdrant_local/qdrant_local.py +232 -0
- vectordb_bench/backend/clients/weaviate_cloud/cli.py +29 -3
- vectordb_bench/backend/clients/weaviate_cloud/config.py +2 -0
- vectordb_bench/backend/clients/weaviate_cloud/weaviate_cloud.py +5 -0
- vectordb_bench/backend/dataset.py +143 -27
- vectordb_bench/backend/filter.py +76 -0
- vectordb_bench/backend/runner/__init__.py +3 -3
- vectordb_bench/backend/runner/mp_runner.py +52 -39
- vectordb_bench/backend/runner/rate_runner.py +68 -52
- vectordb_bench/backend/runner/read_write_runner.py +125 -68
- vectordb_bench/backend/runner/serial_runner.py +56 -23
- vectordb_bench/backend/task_runner.py +48 -20
- vectordb_bench/cli/batch_cli.py +121 -0
- vectordb_bench/cli/cli.py +59 -1
- vectordb_bench/cli/vectordbbench.py +7 -0
- vectordb_bench/config-files/batch_sample_config.yml +17 -0
- vectordb_bench/frontend/components/check_results/data.py +16 -11
- vectordb_bench/frontend/components/check_results/filters.py +53 -25
- vectordb_bench/frontend/components/check_results/headerIcon.py +16 -13
- vectordb_bench/frontend/components/check_results/nav.py +20 -0
- vectordb_bench/frontend/components/custom/displayCustomCase.py +43 -8
- vectordb_bench/frontend/components/custom/displaypPrams.py +10 -5
- vectordb_bench/frontend/components/custom/getCustomConfig.py +10 -0
- vectordb_bench/frontend/components/label_filter/charts.py +60 -0
- vectordb_bench/frontend/components/run_test/caseSelector.py +48 -52
- vectordb_bench/frontend/components/run_test/dbSelector.py +9 -5
- vectordb_bench/frontend/components/run_test/inputWidget.py +48 -0
- vectordb_bench/frontend/components/run_test/submitTask.py +3 -1
- vectordb_bench/frontend/components/streaming/charts.py +253 -0
- vectordb_bench/frontend/components/streaming/data.py +62 -0
- vectordb_bench/frontend/components/tables/data.py +1 -1
- vectordb_bench/frontend/components/welcome/explainPrams.py +66 -0
- vectordb_bench/frontend/components/welcome/pagestyle.py +106 -0
- vectordb_bench/frontend/components/welcome/welcomePrams.py +147 -0
- vectordb_bench/frontend/config/dbCaseConfigs.py +420 -41
- vectordb_bench/frontend/config/styles.py +32 -2
- vectordb_bench/frontend/pages/concurrent.py +5 -1
- vectordb_bench/frontend/pages/custom.py +4 -0
- vectordb_bench/frontend/pages/label_filter.py +56 -0
- vectordb_bench/frontend/pages/quries_per_dollar.py +5 -1
- vectordb_bench/frontend/pages/results.py +60 -0
- vectordb_bench/frontend/pages/run_test.py +3 -3
- vectordb_bench/frontend/pages/streaming.py +135 -0
- vectordb_bench/frontend/pages/tables.py +4 -0
- vectordb_bench/frontend/vdb_benchmark.py +16 -41
- vectordb_bench/interface.py +6 -2
- vectordb_bench/metric.py +15 -1
- vectordb_bench/models.py +38 -11
- vectordb_bench/results/ElasticCloud/result_20250318_standard_elasticcloud.json +5890 -0
- vectordb_bench/results/Milvus/result_20250509_standard_milvus.json +6138 -0
- vectordb_bench/results/OpenSearch/result_20250224_standard_opensearch.json +7319 -0
- vectordb_bench/results/Pinecone/result_20250124_standard_pinecone.json +2365 -0
- vectordb_bench/results/QdrantCloud/result_20250602_standard_qdrantcloud.json +3556 -0
- vectordb_bench/results/ZillizCloud/result_20250613_standard_zillizcloud.json +6290 -0
- vectordb_bench/results/dbPrices.json +12 -4
- {vectordb_bench-0.0.29.dist-info → vectordb_bench-1.0.0.dist-info}/METADATA +131 -32
- {vectordb_bench-0.0.29.dist-info → vectordb_bench-1.0.0.dist-info}/RECORD +87 -65
- {vectordb_bench-0.0.29.dist-info → vectordb_bench-1.0.0.dist-info}/WHEEL +1 -1
- vectordb_bench/results/ZillizCloud/result_20230727_standard_zillizcloud.json +0 -791
- vectordb_bench/results/ZillizCloud/result_20230808_standard_zillizcloud.json +0 -679
- vectordb_bench/results/ZillizCloud/result_20240105_standard_202401_zillizcloud.json +0 -1352
- {vectordb_bench-0.0.29.dist-info → vectordb_bench-1.0.0.dist-info}/entry_points.txt +0 -0
- {vectordb_bench-0.0.29.dist-info → vectordb_bench-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
9
|
-
|
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.
|
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
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
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":
|
89
|
+
"number_of_replicas": self.case_config.number_of_replicas,
|
68
90
|
"translog.flush_threshold_size": self.case_config.flush_threshold_size,
|
69
|
-
|
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
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
insert_data.append(
|
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
|
-
|
123
|
-
|
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.
|
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
|
-
|
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[
|
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": {
|
155
|
-
|
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
|
-
|
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
|
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
|
-
|
232
|
-
|
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.
|
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
|
-
|
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
|
-
|
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=
|
19
|
-
user: Annotated[str, click.option("--user", type=str,
|
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
|
-
|
42
|
-
|
45
|
+
engine: Annotated[
|
46
|
+
str,
|
43
47
|
click.option(
|
44
|
-
"--
|
45
|
-
type=
|
46
|
-
help="
|
47
|
-
default=
|
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
|
-
|
52
|
-
|
55
|
+
metric_type: Annotated[
|
56
|
+
str,
|
53
57
|
click.option(
|
54
|
-
"--
|
55
|
-
type=
|
56
|
-
help="
|
57
|
-
default=
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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
|
)
|