vectordb-bench 0.0.20__py3-none-any.whl → 0.0.22__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/backend/assembler.py +2 -2
- vectordb_bench/backend/clients/__init__.py +28 -2
- vectordb_bench/backend/clients/aliyun_opensearch/aliyun_opensearch.py +1 -7
- vectordb_bench/backend/clients/alloydb/alloydb.py +1 -4
- vectordb_bench/backend/clients/api.py +8 -15
- vectordb_bench/backend/clients/aws_opensearch/aws_opensearch.py +54 -8
- vectordb_bench/backend/clients/aws_opensearch/cli.py +85 -1
- vectordb_bench/backend/clients/aws_opensearch/config.py +10 -0
- vectordb_bench/backend/clients/chroma/chroma.py +1 -4
- vectordb_bench/backend/clients/elastic_cloud/elastic_cloud.py +1 -4
- vectordb_bench/backend/clients/memorydb/cli.py +2 -2
- vectordb_bench/backend/clients/memorydb/memorydb.py +2 -5
- vectordb_bench/backend/clients/milvus/milvus.py +1 -20
- vectordb_bench/backend/clients/mongodb/config.py +53 -0
- vectordb_bench/backend/clients/mongodb/mongodb.py +200 -0
- vectordb_bench/backend/clients/pgdiskann/pgdiskann.py +1 -4
- vectordb_bench/backend/clients/pgvecto_rs/pgvecto_rs.py +3 -11
- vectordb_bench/backend/clients/pgvector/pgvector.py +2 -7
- vectordb_bench/backend/clients/pgvectorscale/pgvectorscale.py +2 -7
- vectordb_bench/backend/clients/pinecone/pinecone.py +1 -4
- vectordb_bench/backend/clients/qdrant_cloud/qdrant_cloud.py +3 -6
- vectordb_bench/backend/clients/redis/redis.py +1 -4
- vectordb_bench/backend/clients/test/cli.py +1 -1
- vectordb_bench/backend/clients/test/test.py +1 -4
- vectordb_bench/backend/clients/weaviate_cloud/weaviate_cloud.py +1 -4
- vectordb_bench/backend/data_source.py +4 -12
- vectordb_bench/backend/runner/mp_runner.py +16 -34
- vectordb_bench/backend/runner/rate_runner.py +4 -4
- vectordb_bench/backend/runner/read_write_runner.py +11 -15
- vectordb_bench/backend/runner/serial_runner.py +20 -28
- vectordb_bench/backend/task_runner.py +6 -26
- vectordb_bench/frontend/components/custom/displaypPrams.py +12 -1
- vectordb_bench/frontend/components/run_test/submitTask.py +20 -3
- vectordb_bench/frontend/config/dbCaseConfigs.py +32 -0
- vectordb_bench/interface.py +10 -19
- vectordb_bench/log_util.py +15 -2
- vectordb_bench/models.py +4 -0
- {vectordb_bench-0.0.20.dist-info → vectordb_bench-0.0.22.dist-info}/METADATA +55 -2
- {vectordb_bench-0.0.20.dist-info → vectordb_bench-0.0.22.dist-info}/RECORD +43 -41
- {vectordb_bench-0.0.20.dist-info → vectordb_bench-0.0.22.dist-info}/LICENSE +0 -0
- {vectordb_bench-0.0.20.dist-info → vectordb_bench-0.0.22.dist-info}/WHEEL +0 -0
- {vectordb_bench-0.0.20.dist-info → vectordb_bench-0.0.22.dist-info}/entry_points.txt +0 -0
- {vectordb_bench-0.0.20.dist-info → vectordb_bench-0.0.22.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,200 @@
|
|
1
|
+
import logging
|
2
|
+
import time
|
3
|
+
from contextlib import contextmanager
|
4
|
+
|
5
|
+
from pymongo import MongoClient
|
6
|
+
from pymongo.operations import SearchIndexModel
|
7
|
+
|
8
|
+
from ..api import VectorDB
|
9
|
+
from .config import MongoDBIndexConfig
|
10
|
+
|
11
|
+
log = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
class MongoDBError(Exception):
|
15
|
+
"""Custom exception class for MongoDB client errors."""
|
16
|
+
|
17
|
+
|
18
|
+
class MongoDB(VectorDB):
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
dim: int,
|
22
|
+
db_config: dict,
|
23
|
+
db_case_config: MongoDBIndexConfig,
|
24
|
+
collection_name: str = "vdb_bench_collection",
|
25
|
+
id_field: str = "id",
|
26
|
+
vector_field: str = "vector",
|
27
|
+
drop_old: bool = False,
|
28
|
+
**kwargs,
|
29
|
+
):
|
30
|
+
self.dim = dim
|
31
|
+
self.db_config = db_config
|
32
|
+
self.case_config = db_case_config
|
33
|
+
self.collection_name = collection_name
|
34
|
+
self.id_field = id_field
|
35
|
+
self.vector_field = vector_field
|
36
|
+
self.drop_old = drop_old
|
37
|
+
|
38
|
+
# Update index dimensions
|
39
|
+
index_params = self.case_config.index_param()
|
40
|
+
log.info(f"index params: {index_params}")
|
41
|
+
index_params["fields"][0]["numDimensions"] = dim
|
42
|
+
self.index_params = index_params
|
43
|
+
|
44
|
+
# Initialize - they'll also be set in init()
|
45
|
+
uri = self.db_config["connection_string"]
|
46
|
+
self.client = MongoClient(uri)
|
47
|
+
self.db = self.client[self.db_config["database"]]
|
48
|
+
self.collection = self.db[self.collection_name]
|
49
|
+
if self.drop_old and self.collection_name in self.db.list_collection_names():
|
50
|
+
log.info(f"MongoDB client dropping old collection: {self.collection_name}")
|
51
|
+
self.db.drop_collection(self.collection_name)
|
52
|
+
self.client = None
|
53
|
+
self.db = None
|
54
|
+
self.collection = None
|
55
|
+
|
56
|
+
@contextmanager
|
57
|
+
def init(self):
|
58
|
+
"""Initialize MongoDB client and cleanup when done"""
|
59
|
+
try:
|
60
|
+
uri = self.db_config["connection_string"]
|
61
|
+
self.client = MongoClient(uri)
|
62
|
+
self.db = self.client[self.db_config["database"]]
|
63
|
+
self.collection = self.db[self.collection_name]
|
64
|
+
|
65
|
+
yield
|
66
|
+
finally:
|
67
|
+
if self.client is not None:
|
68
|
+
self.client.close()
|
69
|
+
self.client = None
|
70
|
+
self.db = None
|
71
|
+
self.collection = None
|
72
|
+
|
73
|
+
def _create_index(self) -> None:
|
74
|
+
"""Create vector search index"""
|
75
|
+
index_name = "vector_index"
|
76
|
+
index_params = self.index_params
|
77
|
+
log.info(f"index params {index_params}")
|
78
|
+
# drop index if already exists
|
79
|
+
if self.collection.list_indexes():
|
80
|
+
all_indexes = self.collection.list_search_indexes()
|
81
|
+
if any(idx.get("name") == index_name for idx in all_indexes):
|
82
|
+
log.info(f"Drop index: {index_name}")
|
83
|
+
try:
|
84
|
+
self.collection.drop_search_index(index_name)
|
85
|
+
while True:
|
86
|
+
indices = list(self.collection.list_search_indexes())
|
87
|
+
indices = [idx for idx in indices if idx["name"] == index_name]
|
88
|
+
log.debug(f"index status {indices}")
|
89
|
+
if len(indices) == 0:
|
90
|
+
break
|
91
|
+
log.info(f"index deleting {indices}")
|
92
|
+
except Exception:
|
93
|
+
log.exception(f"Error dropping index {index_name}")
|
94
|
+
try:
|
95
|
+
# Create vector search index
|
96
|
+
search_index = SearchIndexModel(definition=index_params, name=index_name, type="vectorSearch")
|
97
|
+
|
98
|
+
self.collection.create_search_index(search_index)
|
99
|
+
log.info(f"Created vector search index: {index_name}")
|
100
|
+
self._wait_for_index_ready(index_name)
|
101
|
+
|
102
|
+
# Create regular index on id field for faster lookups
|
103
|
+
self.collection.create_index(self.id_field)
|
104
|
+
log.info(f"Created index on {self.id_field} field")
|
105
|
+
|
106
|
+
except Exception:
|
107
|
+
log.exception(f"Error creating index {index_name}")
|
108
|
+
raise
|
109
|
+
|
110
|
+
def _wait_for_index_ready(self, index_name: str, check_interval: int = 5) -> None:
|
111
|
+
"""Wait for index to be ready"""
|
112
|
+
while True:
|
113
|
+
indices = list(self.collection.list_search_indexes())
|
114
|
+
log.debug(f"index status {indices}")
|
115
|
+
if indices and any(idx.get("name") == index_name and idx.get("queryable") for idx in indices):
|
116
|
+
break
|
117
|
+
for idx in indices:
|
118
|
+
if idx.get("name") == index_name and idx.get("status") == "FAILED":
|
119
|
+
error_msg = f"Index {index_name} failed to build"
|
120
|
+
raise MongoDBError(error_msg)
|
121
|
+
|
122
|
+
time.sleep(check_interval)
|
123
|
+
log.info(f"Index {index_name} is ready")
|
124
|
+
|
125
|
+
def need_normalize_cosine(self) -> bool:
|
126
|
+
return False
|
127
|
+
|
128
|
+
def insert_embeddings(
|
129
|
+
self,
|
130
|
+
embeddings: list[list[float]],
|
131
|
+
metadata: list[int],
|
132
|
+
**kwargs,
|
133
|
+
) -> (int, Exception | None):
|
134
|
+
"""Insert embeddings into MongoDB"""
|
135
|
+
|
136
|
+
# Prepare documents in bulk
|
137
|
+
documents = [
|
138
|
+
{
|
139
|
+
self.id_field: id_,
|
140
|
+
self.vector_field: embedding,
|
141
|
+
}
|
142
|
+
for id_, embedding in zip(metadata, embeddings, strict=False)
|
143
|
+
]
|
144
|
+
|
145
|
+
# Use ordered=False for better insert performance
|
146
|
+
try:
|
147
|
+
self.collection.insert_many(documents, ordered=False)
|
148
|
+
except Exception as e:
|
149
|
+
return 0, e
|
150
|
+
return len(documents), None
|
151
|
+
|
152
|
+
def search_embedding(
|
153
|
+
self,
|
154
|
+
query: list[float],
|
155
|
+
k: int = 100,
|
156
|
+
filters: dict | None = None,
|
157
|
+
**kwargs,
|
158
|
+
) -> list[int]:
|
159
|
+
"""Search for similar vectors"""
|
160
|
+
search_params = self.case_config.search_param()
|
161
|
+
|
162
|
+
vector_search = {"queryVector": query, "index": "vector_index", "path": self.vector_field, "limit": k}
|
163
|
+
|
164
|
+
# Add exact search parameter if specified
|
165
|
+
if search_params["exact"]:
|
166
|
+
vector_search["exact"] = True
|
167
|
+
else:
|
168
|
+
# Set numCandidates based on k value and data size
|
169
|
+
# For 50K dataset, use higher multiplier for better recall
|
170
|
+
num_candidates = min(10000, k * search_params["num_candidates_ratio"])
|
171
|
+
vector_search["numCandidates"] = num_candidates
|
172
|
+
|
173
|
+
# Add filter if specified
|
174
|
+
if filters:
|
175
|
+
log.info(f"Applying filter: {filters}")
|
176
|
+
vector_search["filter"] = {
|
177
|
+
"id": {"gte": filters["id"]},
|
178
|
+
}
|
179
|
+
pipeline = [
|
180
|
+
{"$vectorSearch": vector_search},
|
181
|
+
{
|
182
|
+
"$project": {
|
183
|
+
"_id": 0,
|
184
|
+
self.id_field: 1,
|
185
|
+
"score": {"$meta": "vectorSearchScore"}, # Include similarity score
|
186
|
+
}
|
187
|
+
},
|
188
|
+
]
|
189
|
+
|
190
|
+
results = list(self.collection.aggregate(pipeline))
|
191
|
+
return [doc[self.id_field] for doc in results]
|
192
|
+
|
193
|
+
def optimize(self, data_size: int | None = None) -> None:
|
194
|
+
"""MongoDB vector search indexes are self-optimizing"""
|
195
|
+
log.info("optimize for search")
|
196
|
+
self._create_index()
|
197
|
+
self._wait_for_index_ready("vector_index")
|
198
|
+
|
199
|
+
def ready_to_load(self) -> None:
|
200
|
+
"""MongoDB is always ready to load"""
|
@@ -153,10 +153,7 @@ class PgVectoRS(VectorDB):
|
|
153
153
|
)
|
154
154
|
self.conn.commit()
|
155
155
|
|
156
|
-
def
|
157
|
-
pass
|
158
|
-
|
159
|
-
def optimize(self):
|
156
|
+
def optimize(self, data_size: int | None = None):
|
160
157
|
self._post_insert()
|
161
158
|
|
162
159
|
def _post_insert(self):
|
@@ -200,10 +197,7 @@ class PgVectoRS(VectorDB):
|
|
200
197
|
self.cursor.execute(index_create_sql)
|
201
198
|
self.conn.commit()
|
202
199
|
except Exception as e:
|
203
|
-
log.warning(
|
204
|
-
f"Failed to create pgvecto.rs index {self._index_name} \
|
205
|
-
at table {self.table_name} error: {e}",
|
206
|
-
)
|
200
|
+
log.warning(f"Failed to create pgvecto.rs index {self._index_name} at table {self.table_name} error: {e}")
|
207
201
|
raise e from None
|
208
202
|
|
209
203
|
def _create_table(self, dim: int):
|
@@ -258,9 +252,7 @@ class PgVectoRS(VectorDB):
|
|
258
252
|
|
259
253
|
return len(metadata), None
|
260
254
|
except Exception as e:
|
261
|
-
log.warning(
|
262
|
-
f"Failed to insert data into pgvecto.rs table ({self.table_name}), error: {e}",
|
263
|
-
)
|
255
|
+
log.warning(f"Failed to insert data into pgvecto.rs table ({self.table_name}), error: {e}")
|
264
256
|
return 0, e
|
265
257
|
|
266
258
|
def search_embedding(
|
@@ -228,10 +228,7 @@ class PgVector(VectorDB):
|
|
228
228
|
)
|
229
229
|
self.conn.commit()
|
230
230
|
|
231
|
-
def
|
232
|
-
pass
|
233
|
-
|
234
|
-
def optimize(self):
|
231
|
+
def optimize(self, data_size: int | None = None):
|
235
232
|
self._post_insert()
|
236
233
|
|
237
234
|
def _post_insert(self):
|
@@ -415,9 +412,7 @@ class PgVector(VectorDB):
|
|
415
412
|
|
416
413
|
return len(metadata), None
|
417
414
|
except Exception as e:
|
418
|
-
log.warning(
|
419
|
-
f"Failed to insert data into pgvector table ({self.table_name}), error: {e}",
|
420
|
-
)
|
415
|
+
log.warning(f"Failed to insert data into pgvector table ({self.table_name}), error: {e}")
|
421
416
|
return 0, e
|
422
417
|
|
423
418
|
def search_embedding(
|
@@ -143,10 +143,7 @@ class PgVectorScale(VectorDB):
|
|
143
143
|
)
|
144
144
|
self.conn.commit()
|
145
145
|
|
146
|
-
def
|
147
|
-
pass
|
148
|
-
|
149
|
-
def optimize(self):
|
146
|
+
def optimize(self, data_size: int | None = None):
|
150
147
|
self._post_insert()
|
151
148
|
|
152
149
|
def _post_insert(self):
|
@@ -255,9 +252,7 @@ class PgVectorScale(VectorDB):
|
|
255
252
|
|
256
253
|
return len(metadata), None
|
257
254
|
except Exception as e:
|
258
|
-
log.warning(
|
259
|
-
f"Failed to insert data into pgvector table ({self.table_name}), error: {e}",
|
260
|
-
)
|
255
|
+
log.warning(f"Failed to insert data into pgvector table ({self.table_name}), error: {e}")
|
261
256
|
return 0, e
|
262
257
|
|
263
258
|
def search_embedding(
|
@@ -62,10 +62,7 @@ class QdrantCloud(VectorDB):
|
|
62
62
|
self.qdrant_client = None
|
63
63
|
del self.qdrant_client
|
64
64
|
|
65
|
-
def
|
66
|
-
pass
|
67
|
-
|
68
|
-
def optimize(self):
|
65
|
+
def optimize(self, data_size: int | None = None):
|
69
66
|
assert self.qdrant_client, "Please call self.init() before"
|
70
67
|
# wait for vectors to be fully indexed
|
71
68
|
try:
|
@@ -76,8 +73,8 @@ class QdrantCloud(VectorDB):
|
|
76
73
|
continue
|
77
74
|
if info.status == CollectionStatus.GREEN:
|
78
75
|
msg = (
|
79
|
-
f"Stored vectors: {info.vectors_count}, Indexed vectors: {info.indexed_vectors_count}, "
|
80
|
-
f"Collection status: {info.indexed_vectors_count}"
|
76
|
+
f"Stored vectors: {info.vectors_count}, Indexed vectors: {info.indexed_vectors_count}, "
|
77
|
+
f"Collection status: {info.indexed_vectors_count}"
|
81
78
|
)
|
82
79
|
log.info(msg)
|
83
80
|
return
|
@@ -95,10 +95,7 @@ class Redis(VectorDB):
|
|
95
95
|
def ready_to_search(self) -> bool:
|
96
96
|
"""Check if the database is ready to search."""
|
97
97
|
|
98
|
-
def
|
99
|
-
pass
|
100
|
-
|
101
|
-
def optimize(self) -> None:
|
98
|
+
def optimize(self, data_size: int | None = None):
|
102
99
|
pass
|
103
100
|
|
104
101
|
def insert_embeddings(
|
@@ -17,7 +17,7 @@ class TestTypedDict(CommonTypedDict): ...
|
|
17
17
|
@click_parameter_decorators_from_typed_dict(TestTypedDict)
|
18
18
|
def Test(**parameters: Unpack[TestTypedDict]):
|
19
19
|
run(
|
20
|
-
db=DB.
|
20
|
+
db=DB.Test,
|
21
21
|
db_config=TestConfig(db_label=parameters["db_label"]),
|
22
22
|
db_case_config=TestIndexConfig(),
|
23
23
|
**parameters,
|
@@ -67,10 +67,7 @@ class WeaviateCloud(VectorDB):
|
|
67
67
|
self.client = None
|
68
68
|
del self.client
|
69
69
|
|
70
|
-
def
|
71
|
-
"""Should call insert first, do nothing"""
|
72
|
-
|
73
|
-
def optimize(self):
|
70
|
+
def optimize(self, data_size: int | None = None):
|
74
71
|
assert self.client.schema.exists(self.collection_name)
|
75
72
|
self.client.schema.update_config(
|
76
73
|
self.collection_name,
|
@@ -63,9 +63,7 @@ class AliyunOSSReader(DatasetReader):
|
|
63
63
|
# check size equal
|
64
64
|
remote_size, local_size = info.content_length, local.stat().st_size
|
65
65
|
if remote_size != local_size:
|
66
|
-
log.info(
|
67
|
-
f"local file: {local} size[{local_size}] not match with remote size[{remote_size}]",
|
68
|
-
)
|
66
|
+
log.info(f"local file: {local} size[{local_size}] not match with remote size[{remote_size}]")
|
69
67
|
return False
|
70
68
|
|
71
69
|
return True
|
@@ -89,9 +87,7 @@ class AliyunOSSReader(DatasetReader):
|
|
89
87
|
local_file = local_ds_root.joinpath(file)
|
90
88
|
|
91
89
|
if (not local_file.exists()) or (not self.validate_file(remote_file, local_file)):
|
92
|
-
log.info(
|
93
|
-
f"local file: {local_file} not match with remote: {remote_file}; add to downloading list",
|
94
|
-
)
|
90
|
+
log.info(f"local file: {local_file} not match with remote: {remote_file}; add to downloading list")
|
95
91
|
downloads.append((remote_file, local_file))
|
96
92
|
|
97
93
|
if len(downloads) == 0:
|
@@ -135,9 +131,7 @@ class AwsS3Reader(DatasetReader):
|
|
135
131
|
local_file = local_ds_root.joinpath(file)
|
136
132
|
|
137
133
|
if (not local_file.exists()) or (not self.validate_file(remote_file, local_file)):
|
138
|
-
log.info(
|
139
|
-
f"local file: {local_file} not match with remote: {remote_file}; add to downloading list",
|
140
|
-
)
|
134
|
+
log.info(f"local file: {local_file} not match with remote: {remote_file}; add to downloading list")
|
141
135
|
downloads.append(remote_file)
|
142
136
|
|
143
137
|
if len(downloads) == 0:
|
@@ -157,9 +151,7 @@ class AwsS3Reader(DatasetReader):
|
|
157
151
|
# check size equal
|
158
152
|
remote_size, local_size = info.get("size"), local.stat().st_size
|
159
153
|
if remote_size != local_size:
|
160
|
-
log.info(
|
161
|
-
f"local file: {local} size[{local_size}] not match with remote size[{remote_size}]",
|
162
|
-
)
|
154
|
+
log.info(f"local file: {local} size[{local_size}] not match with remote size[{remote_size}]")
|
163
155
|
return False
|
164
156
|
|
165
157
|
return True
|
@@ -79,14 +79,14 @@ class MultiProcessingSearchRunner:
|
|
79
79
|
|
80
80
|
if count % 500 == 0:
|
81
81
|
log.debug(
|
82
|
-
f"({mp.current_process().name:16}) "
|
83
|
-
f"search_count: {count}, latest_latency={time.perf_counter()-s}"
|
82
|
+
f"({mp.current_process().name:16}) "
|
83
|
+
f"search_count: {count}, latest_latency={time.perf_counter()-s}"
|
84
84
|
)
|
85
85
|
|
86
86
|
total_dur = round(time.perf_counter() - start_time, 4)
|
87
87
|
log.info(
|
88
88
|
f"{mp.current_process().name:16} search {self.duration}s: "
|
89
|
-
f"actual_dur={total_dur}s, count={count}, qps in this process: {round(count / total_dur, 4):3}"
|
89
|
+
f"actual_dur={total_dur}s, count={count}, qps in this process: {round(count / total_dur, 4):3}"
|
90
90
|
)
|
91
91
|
|
92
92
|
return (count, total_dur, latencies)
|
@@ -94,9 +94,7 @@ class MultiProcessingSearchRunner:
|
|
94
94
|
@staticmethod
|
95
95
|
def get_mp_context():
|
96
96
|
mp_start_method = "spawn"
|
97
|
-
log.debug(
|
98
|
-
f"MultiProcessingSearchRunner get multiprocessing start method: {mp_start_method}",
|
99
|
-
)
|
97
|
+
log.debug(f"MultiProcessingSearchRunner get multiprocessing start method: {mp_start_method}")
|
100
98
|
return mp.get_context(mp_start_method)
|
101
99
|
|
102
100
|
def _run_all_concurrencies_mem_efficient(self):
|
@@ -113,9 +111,7 @@ class MultiProcessingSearchRunner:
|
|
113
111
|
mp_context=self.get_mp_context(),
|
114
112
|
max_workers=conc,
|
115
113
|
) as executor:
|
116
|
-
log.info(
|
117
|
-
f"Start search {self.duration}s in concurrency {conc}, filters: {self.filters}",
|
118
|
-
)
|
114
|
+
log.info(f"Start search {self.duration}s in concurrency {conc}, filters: {self.filters}")
|
119
115
|
future_iter = [executor.submit(self.search, self.test_data, q, cond) for i in range(conc)]
|
120
116
|
# Sync all processes
|
121
117
|
while q.qsize() < conc:
|
@@ -124,9 +120,7 @@ class MultiProcessingSearchRunner:
|
|
124
120
|
|
125
121
|
with cond:
|
126
122
|
cond.notify_all()
|
127
|
-
log.info(
|
128
|
-
f"Syncing all process and start concurrency search, concurrency={conc}",
|
129
|
-
)
|
123
|
+
log.info(f"Syncing all process and start concurrency search, concurrency={conc}")
|
130
124
|
|
131
125
|
start = time.perf_counter()
|
132
126
|
all_count = sum([r.result()[0] for r in future_iter])
|
@@ -140,18 +134,14 @@ class MultiProcessingSearchRunner:
|
|
140
134
|
conc_qps_list.append(qps)
|
141
135
|
conc_latency_p99_list.append(latency_p99)
|
142
136
|
conc_latency_avg_list.append(latency_avg)
|
143
|
-
log.info(
|
144
|
-
f"End search in concurrency {conc}: dur={cost}s, total_count={all_count}, qps={qps}",
|
145
|
-
)
|
137
|
+
log.info(f"End search in concurrency {conc}: dur={cost}s, total_count={all_count}, qps={qps}")
|
146
138
|
|
147
139
|
if qps > max_qps:
|
148
140
|
max_qps = qps
|
149
|
-
log.info(
|
150
|
-
f"Update largest qps with concurrency {conc}: current max_qps={max_qps}",
|
151
|
-
)
|
141
|
+
log.info(f"Update largest qps with concurrency {conc}: current max_qps={max_qps}")
|
152
142
|
except Exception as e:
|
153
143
|
log.warning(
|
154
|
-
f"Fail to search
|
144
|
+
f"Fail to search, concurrencies: {self.concurrencies}, max_qps before failure={max_qps}, reason={e}"
|
155
145
|
)
|
156
146
|
traceback.print_exc()
|
157
147
|
|
@@ -193,9 +183,7 @@ class MultiProcessingSearchRunner:
|
|
193
183
|
mp_context=self.get_mp_context(),
|
194
184
|
max_workers=conc,
|
195
185
|
) as executor:
|
196
|
-
log.info(
|
197
|
-
f"Start search_by_dur {duration}s in concurrency {conc}, filters: {self.filters}",
|
198
|
-
)
|
186
|
+
log.info(f"Start search_by_dur {duration}s in concurrency {conc}, filters: {self.filters}")
|
199
187
|
future_iter = [
|
200
188
|
executor.submit(self.search_by_dur, duration, self.test_data, q, cond) for i in range(conc)
|
201
189
|
]
|
@@ -206,24 +194,18 @@ class MultiProcessingSearchRunner:
|
|
206
194
|
|
207
195
|
with cond:
|
208
196
|
cond.notify_all()
|
209
|
-
log.info(
|
210
|
-
f"Syncing all process and start concurrency search, concurrency={conc}",
|
211
|
-
)
|
197
|
+
log.info(f"Syncing all process and start concurrency search, concurrency={conc}")
|
212
198
|
|
213
199
|
start = time.perf_counter()
|
214
200
|
all_count = sum([r.result() for r in future_iter])
|
215
201
|
cost = time.perf_counter() - start
|
216
202
|
|
217
203
|
qps = round(all_count / cost, 4)
|
218
|
-
log.info(
|
219
|
-
f"End search in concurrency {conc}: dur={cost}s, total_count={all_count}, qps={qps}",
|
220
|
-
)
|
204
|
+
log.info(f"End search in concurrency {conc}: dur={cost}s, total_count={all_count}, qps={qps}")
|
221
205
|
|
222
206
|
if qps > max_qps:
|
223
207
|
max_qps = qps
|
224
|
-
log.info(
|
225
|
-
f"Update largest qps with concurrency {conc}: current max_qps={max_qps}",
|
226
|
-
)
|
208
|
+
log.info(f"Update largest qps with concurrency {conc}: current max_qps={max_qps}")
|
227
209
|
except Exception as e:
|
228
210
|
log.warning(
|
229
211
|
f"Fail to search all concurrencies: {self.concurrencies}, max_qps before failure={max_qps}, reason={e}",
|
@@ -275,14 +257,14 @@ class MultiProcessingSearchRunner:
|
|
275
257
|
|
276
258
|
if count % 500 == 0:
|
277
259
|
log.debug(
|
278
|
-
f"({mp.current_process().name:16}) search_count: {count}, "
|
279
|
-
f"latest_latency={time.perf_counter()-s}"
|
260
|
+
f"({mp.current_process().name:16}) search_count: {count}, "
|
261
|
+
f"latest_latency={time.perf_counter()-s}"
|
280
262
|
)
|
281
263
|
|
282
264
|
total_dur = round(time.perf_counter() - start_time, 4)
|
283
265
|
log.debug(
|
284
266
|
f"{mp.current_process().name:16} search {self.duration}s: "
|
285
|
-
f"actual_dur={total_dur}s, count={count}, qps in this process: {round(count / total_dur, 4):3}"
|
267
|
+
f"actual_dur={total_dur}s, count={count}, qps in this process: {round(count / total_dur, 4):3}"
|
286
268
|
)
|
287
269
|
|
288
270
|
return count
|
@@ -73,14 +73,14 @@ class RatedMultiThreadingInsertRunner:
|
|
73
73
|
|
74
74
|
if len(not_done) > 0:
|
75
75
|
log.warning(
|
76
|
-
f"Failed to finish all tasks in 1s, [{len(not_done)}/{len(executing_futures)}] "
|
77
|
-
f"tasks are not done, waited={wait_interval:.2f}, trying to wait in the next round"
|
76
|
+
f"Failed to finish all tasks in 1s, [{len(not_done)}/{len(executing_futures)}] "
|
77
|
+
f"tasks are not done, waited={wait_interval:.2f}, trying to wait in the next round"
|
78
78
|
)
|
79
79
|
executing_futures = list(not_done)
|
80
80
|
else:
|
81
81
|
log.debug(
|
82
|
-
f"Finished {len(executing_futures)} insert-{config.NUM_PER_BATCH} "
|
83
|
-
f"task in 1s, wait_interval={wait_interval:.2f}"
|
82
|
+
f"Finished {len(executing_futures)} insert-{config.NUM_PER_BATCH} "
|
83
|
+
f"task in 1s, wait_interval={wait_interval:.2f}"
|
84
84
|
)
|
85
85
|
executing_futures = []
|
86
86
|
except Exception as e:
|
@@ -45,8 +45,8 @@ class ReadWriteRunner(MultiProcessingSearchRunner, RatedMultiThreadingInsertRunn
|
|
45
45
|
self.read_dur_after_write = read_dur_after_write
|
46
46
|
|
47
47
|
log.info(
|
48
|
-
f"Init runner, concurencys={concurrencies}, search_stage={search_stage}, "
|
49
|
-
f"stage_search_dur={read_dur_after_write}"
|
48
|
+
f"Init runner, concurencys={concurrencies}, search_stage={search_stage}, "
|
49
|
+
f"stage_search_dur={read_dur_after_write}"
|
50
50
|
)
|
51
51
|
|
52
52
|
test_emb = np.stack(dataset.test_data["emb"])
|
@@ -80,7 +80,7 @@ class ReadWriteRunner(MultiProcessingSearchRunner, RatedMultiThreadingInsertRunn
|
|
80
80
|
"""Optimize needs to run in differenct process for pymilvus schema recursion problem"""
|
81
81
|
with self.db.init():
|
82
82
|
log.info("Search after write - Optimize start")
|
83
|
-
self.db.optimize()
|
83
|
+
self.db.optimize(data_size=self.data_volume)
|
84
84
|
log.info("Search after write - Optimize finished")
|
85
85
|
|
86
86
|
def run_search(self):
|
@@ -88,12 +88,10 @@ class ReadWriteRunner(MultiProcessingSearchRunner, RatedMultiThreadingInsertRunn
|
|
88
88
|
res, ssearch_dur = self.serial_search_runner.run()
|
89
89
|
recall, ndcg, p99_latency = res
|
90
90
|
log.info(
|
91
|
-
f"Search after write - Serial search - recall={recall}, ndcg={ndcg}, p99={p99_latency}, "
|
91
|
+
f"Search after write - Serial search - recall={recall}, ndcg={ndcg}, p99={p99_latency}, "
|
92
92
|
f"dur={ssearch_dur:.4f}",
|
93
93
|
)
|
94
|
-
log.info(
|
95
|
-
f"Search after wirte - Conc search start, dur for each conc={self.read_dur_after_write}",
|
96
|
-
)
|
94
|
+
log.info(f"Search after wirte - Conc search start, dur for each conc={self.read_dur_after_write}")
|
97
95
|
max_qps = self.run_by_dur(self.read_dur_after_write)
|
98
96
|
log.info(f"Search after wirte - Conc search finished, max_qps={max_qps}")
|
99
97
|
|
@@ -157,9 +155,7 @@ class ReadWriteRunner(MultiProcessingSearchRunner, RatedMultiThreadingInsertRunn
|
|
157
155
|
|
158
156
|
got = wait_next_target(start_batch, target_batch)
|
159
157
|
if got is False:
|
160
|
-
log.warning(
|
161
|
-
f"Abnormal exit, target_batch={target_batch}, start_batch={start_batch}",
|
162
|
-
)
|
158
|
+
log.warning(f"Abnormal exit, target_batch={target_batch}, start_batch={start_batch}")
|
163
159
|
return None
|
164
160
|
|
165
161
|
log.info(f"Insert {perc}% done, total batch={total_batch}")
|
@@ -167,8 +163,8 @@ class ReadWriteRunner(MultiProcessingSearchRunner, RatedMultiThreadingInsertRunn
|
|
167
163
|
res, ssearch_dur = self.serial_search_runner.run()
|
168
164
|
recall, ndcg, p99_latency = res
|
169
165
|
log.info(
|
170
|
-
f"[{target_batch}/{total_batch}] Serial search - {perc}% done, recall={recall}, "
|
171
|
-
f"ndcg={ndcg}, p99={p99_latency}, dur={ssearch_dur:.4f}"
|
166
|
+
f"[{target_batch}/{total_batch}] Serial search - {perc}% done, recall={recall}, "
|
167
|
+
f"ndcg={ndcg}, p99={p99_latency}, dur={ssearch_dur:.4f}"
|
172
168
|
)
|
173
169
|
|
174
170
|
# Search duration for non-last search stage is carefully calculated.
|
@@ -183,8 +179,8 @@ class ReadWriteRunner(MultiProcessingSearchRunner, RatedMultiThreadingInsertRunn
|
|
183
179
|
each_conc_search_dur = csearch_dur / len(self.concurrencies)
|
184
180
|
if each_conc_search_dur < 30:
|
185
181
|
warning_msg = (
|
186
|
-
f"Results might be inaccurate, duration[{csearch_dur:.4f}] left for conc-search is too short, "
|
187
|
-
f"total available dur={total_dur_between_stages}, serial_search_cost={ssearch_dur}."
|
182
|
+
f"Results might be inaccurate, duration[{csearch_dur:.4f}] left for conc-search is too short, "
|
183
|
+
f"total available dur={total_dur_between_stages}, serial_search_cost={ssearch_dur}."
|
188
184
|
)
|
189
185
|
log.warning(warning_msg)
|
190
186
|
|
@@ -193,7 +189,7 @@ class ReadWriteRunner(MultiProcessingSearchRunner, RatedMultiThreadingInsertRunn
|
|
193
189
|
each_conc_search_dur = 60
|
194
190
|
|
195
191
|
log.info(
|
196
|
-
f"[{target_batch}/{total_batch}] Concurrent search - {perc}% start, dur={each_conc_search_dur:.4f}"
|
192
|
+
f"[{target_batch}/{total_batch}] Concurrent search - {perc}% start, dur={each_conc_search_dur:.4f}"
|
197
193
|
)
|
198
194
|
max_qps = self.run_by_dur(each_conc_search_dur)
|
199
195
|
result.append((perc, max_qps, recall, ndcg, p99_latency))
|