vectordb-bench 1.0.4__py3-none-any.whl → 1.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vectordb_bench/__init__.py +1 -0
- vectordb_bench/backend/cases.py +45 -1
- vectordb_bench/backend/clients/__init__.py +47 -0
- vectordb_bench/backend/clients/api.py +2 -0
- vectordb_bench/backend/clients/aws_opensearch/aws_opensearch.py +104 -40
- vectordb_bench/backend/clients/aws_opensearch/cli.py +52 -15
- vectordb_bench/backend/clients/aws_opensearch/config.py +27 -7
- vectordb_bench/backend/clients/hologres/cli.py +50 -0
- vectordb_bench/backend/clients/hologres/config.py +121 -0
- vectordb_bench/backend/clients/hologres/hologres.py +365 -0
- vectordb_bench/backend/clients/lancedb/lancedb.py +1 -0
- vectordb_bench/backend/clients/milvus/cli.py +29 -9
- vectordb_bench/backend/clients/milvus/config.py +2 -0
- vectordb_bench/backend/clients/milvus/milvus.py +1 -1
- vectordb_bench/backend/clients/oceanbase/cli.py +1 -0
- vectordb_bench/backend/clients/oceanbase/config.py +3 -1
- vectordb_bench/backend/clients/oceanbase/oceanbase.py +20 -4
- vectordb_bench/backend/clients/oss_opensearch/cli.py +155 -0
- vectordb_bench/backend/clients/oss_opensearch/config.py +157 -0
- vectordb_bench/backend/clients/oss_opensearch/oss_opensearch.py +582 -0
- vectordb_bench/backend/clients/oss_opensearch/run.py +166 -0
- vectordb_bench/backend/clients/pgdiskann/cli.py +45 -0
- vectordb_bench/backend/clients/pgdiskann/config.py +16 -0
- vectordb_bench/backend/clients/pgdiskann/pgdiskann.py +94 -26
- vectordb_bench/backend/clients/s3_vectors/config.py +41 -0
- vectordb_bench/backend/clients/s3_vectors/s3_vectors.py +171 -0
- vectordb_bench/backend/clients/tidb/cli.py +0 -4
- vectordb_bench/backend/clients/tidb/config.py +22 -2
- vectordb_bench/backend/clients/zilliz_cloud/cli.py +14 -1
- vectordb_bench/backend/clients/zilliz_cloud/config.py +4 -1
- vectordb_bench/backend/dataset.py +70 -0
- vectordb_bench/backend/filter.py +17 -0
- vectordb_bench/backend/runner/mp_runner.py +4 -0
- vectordb_bench/backend/runner/rate_runner.py +23 -11
- vectordb_bench/backend/runner/read_write_runner.py +10 -9
- vectordb_bench/backend/runner/serial_runner.py +23 -7
- vectordb_bench/backend/task_runner.py +5 -4
- vectordb_bench/cli/cli.py +36 -0
- vectordb_bench/cli/vectordbbench.py +4 -0
- vectordb_bench/fig/custom_case_run_test.png +0 -0
- vectordb_bench/fig/custom_dataset.png +0 -0
- vectordb_bench/fig/homepage/bar-chart.png +0 -0
- vectordb_bench/fig/homepage/concurrent.png +0 -0
- vectordb_bench/fig/homepage/custom.png +0 -0
- vectordb_bench/fig/homepage/label_filter.png +0 -0
- vectordb_bench/fig/homepage/qp$.png +0 -0
- vectordb_bench/fig/homepage/run_test.png +0 -0
- vectordb_bench/fig/homepage/streaming.png +0 -0
- vectordb_bench/fig/homepage/table.png +0 -0
- vectordb_bench/fig/run_test_select_case.png +0 -0
- vectordb_bench/fig/run_test_select_db.png +0 -0
- vectordb_bench/fig/run_test_submit.png +0 -0
- vectordb_bench/frontend/components/check_results/filters.py +1 -4
- vectordb_bench/frontend/components/check_results/nav.py +2 -1
- vectordb_bench/frontend/components/concurrent/charts.py +5 -0
- vectordb_bench/frontend/components/int_filter/charts.py +60 -0
- vectordb_bench/frontend/components/streaming/data.py +7 -0
- vectordb_bench/frontend/components/welcome/welcomePrams.py +42 -4
- vectordb_bench/frontend/config/dbCaseConfigs.py +142 -16
- vectordb_bench/frontend/config/styles.py +4 -0
- vectordb_bench/frontend/pages/concurrent.py +1 -1
- vectordb_bench/frontend/pages/custom.py +1 -1
- vectordb_bench/frontend/pages/int_filter.py +56 -0
- vectordb_bench/frontend/pages/streaming.py +16 -3
- vectordb_bench/interface.py +5 -1
- vectordb_bench/metric.py +7 -0
- vectordb_bench/models.py +39 -4
- vectordb_bench/results/S3Vectors/result_20250722_standard_s3vectors.json +2509 -0
- vectordb_bench/results/getLeaderboardDataV2.py +23 -2
- vectordb_bench/results/leaderboard_v2.json +200 -0
- vectordb_bench/results/leaderboard_v2_streaming.json +128 -0
- {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/METADATA +40 -8
- {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/RECORD +77 -51
- {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/WHEEL +0 -0
- {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/entry_points.txt +0 -0
- {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/licenses/LICENSE +0 -0
- {vectordb_bench-1.0.4.dist-info → vectordb_bench-1.0.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
from pydantic import BaseModel, SecretStr
|
2
|
+
|
3
|
+
from ..api import DBCaseConfig, DBConfig, IndexType, MetricType
|
4
|
+
|
5
|
+
|
6
|
+
class HologresConfig(DBConfig):
|
7
|
+
user_name: SecretStr = SecretStr("hologres")
|
8
|
+
password: SecretStr
|
9
|
+
host: str = "localhost"
|
10
|
+
port: int = 5432
|
11
|
+
db_name: str
|
12
|
+
|
13
|
+
def to_dict(self) -> dict:
|
14
|
+
user_str = self.user_name.get_secret_value()
|
15
|
+
pwd_str = self.password.get_secret_value()
|
16
|
+
return {
|
17
|
+
"host": self.host,
|
18
|
+
"port": self.port,
|
19
|
+
"dbname": self.db_name,
|
20
|
+
"user": user_str,
|
21
|
+
"password": pwd_str,
|
22
|
+
}
|
23
|
+
|
24
|
+
|
25
|
+
class HologresIndexConfig(BaseModel, DBCaseConfig):
|
26
|
+
index: IndexType = IndexType.Hologres_HGraph
|
27
|
+
metric_type: MetricType | None = None
|
28
|
+
|
29
|
+
create_index_before_load: bool = False
|
30
|
+
create_index_after_load: bool = True
|
31
|
+
|
32
|
+
min_flush_proxima_row_count: int = 1000
|
33
|
+
min_compaction_proxima_row_count: int = 1000
|
34
|
+
max_total_size_to_merge_mb: int = 4096
|
35
|
+
full_compact_max_file_size_mb: int = 4096
|
36
|
+
|
37
|
+
base_quantization_type: str = "sq8_uniform"
|
38
|
+
precise_quantization_type: str = "fp32"
|
39
|
+
use_reorder: bool = True
|
40
|
+
build_thread_count: int = 16
|
41
|
+
max_degree: int = 64
|
42
|
+
ef_construction: int = 400
|
43
|
+
|
44
|
+
ef_search: int = 51
|
45
|
+
|
46
|
+
def index_param(self) -> dict:
|
47
|
+
return {
|
48
|
+
"algorithm": self.algorithm(),
|
49
|
+
"distance_method": self.distance_method(),
|
50
|
+
"builder_params": self.builder_params(),
|
51
|
+
"full_compact_max_file_size_mb": self.full_compact_max_file_size_mb,
|
52
|
+
}
|
53
|
+
|
54
|
+
def search_param(self) -> dict:
|
55
|
+
return {
|
56
|
+
"distance_function": self.distance_function(),
|
57
|
+
"order_direction": self.order_direction(),
|
58
|
+
"searcher_params": self.search_params(),
|
59
|
+
}
|
60
|
+
|
61
|
+
def algorithm(self) -> str:
|
62
|
+
return self.index.value
|
63
|
+
|
64
|
+
def is_proxima(self) -> bool:
|
65
|
+
return self.index == IndexType.Hologres_Graph
|
66
|
+
|
67
|
+
def distance_method(self) -> str:
|
68
|
+
if self.metric_type == MetricType.L2:
|
69
|
+
if self.index == IndexType.Hologres_Graph:
|
70
|
+
return "SquaredEuclidean"
|
71
|
+
return "Euclidean"
|
72
|
+
if self.metric_type == MetricType.IP:
|
73
|
+
return "InnerProduct"
|
74
|
+
if self.metric_type == MetricType.COSINE:
|
75
|
+
if self.index == IndexType.Hologres_Graph:
|
76
|
+
return "InnerProduct"
|
77
|
+
return "Cosine"
|
78
|
+
return "Euclidean"
|
79
|
+
|
80
|
+
def distance_function(self) -> str:
|
81
|
+
if self.metric_type == MetricType.L2:
|
82
|
+
if self.index == IndexType.Hologres_Graph:
|
83
|
+
return "approx_squared_euclidean_distance"
|
84
|
+
return "approx_euclidean_distance"
|
85
|
+
if self.metric_type == MetricType.IP:
|
86
|
+
return "approx_inner_product_distance"
|
87
|
+
if self.metric_type == MetricType.COSINE:
|
88
|
+
if self.index == IndexType.Hologres_Graph:
|
89
|
+
return "approx_inner_product_distance"
|
90
|
+
return "approx_cosine_distance"
|
91
|
+
return "approx_euclidean_distance"
|
92
|
+
|
93
|
+
def order_direction(self) -> str:
|
94
|
+
if self.metric_type == MetricType.L2:
|
95
|
+
return "ASC"
|
96
|
+
if self.metric_type in {MetricType.IP, MetricType.COSINE}:
|
97
|
+
return "DESC"
|
98
|
+
return "ASC"
|
99
|
+
|
100
|
+
def builder_params(self) -> dict:
|
101
|
+
if self.use_reorder:
|
102
|
+
self.base_quantization_type = "sq8_uniform"
|
103
|
+
else:
|
104
|
+
self.base_quantization_type = "fp32"
|
105
|
+
|
106
|
+
return {
|
107
|
+
"min_flush_proxima_row_count": self.min_flush_proxima_row_count,
|
108
|
+
"min_compaction_proxima_row_count": self.min_compaction_proxima_row_count,
|
109
|
+
"max_total_size_to_merge_mb": self.max_total_size_to_merge_mb,
|
110
|
+
"build_thread_count": self.build_thread_count,
|
111
|
+
"base_quantization_type": self.base_quantization_type,
|
112
|
+
"max_degree": self.max_degree,
|
113
|
+
"ef_construction": self.ef_construction,
|
114
|
+
"precise_quantization_type": self.precise_quantization_type,
|
115
|
+
"use_reorder": self.use_reorder,
|
116
|
+
}
|
117
|
+
|
118
|
+
def searcher_params(self) -> dict:
|
119
|
+
return {
|
120
|
+
"ef_search": self.ef_search,
|
121
|
+
}
|
@@ -0,0 +1,365 @@
|
|
1
|
+
"""Wrapper around the Hologres vector database over VectorDB"""
|
2
|
+
|
3
|
+
import json
|
4
|
+
import logging
|
5
|
+
from collections.abc import Generator
|
6
|
+
from contextlib import contextmanager
|
7
|
+
from io import StringIO
|
8
|
+
from typing import Any
|
9
|
+
|
10
|
+
import psycopg
|
11
|
+
from psycopg import Connection, Cursor, sql
|
12
|
+
|
13
|
+
from ..api import VectorDB
|
14
|
+
from .config import HologresConfig, HologresIndexConfig
|
15
|
+
|
16
|
+
log = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
class Hologres(VectorDB):
|
20
|
+
"""Use psycopg instructions"""
|
21
|
+
|
22
|
+
conn: psycopg.Connection[Any] | None = None
|
23
|
+
cursor: psycopg.Cursor[Any] | None = None
|
24
|
+
|
25
|
+
_tg_name: str = "vdb_bench_tg_1"
|
26
|
+
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
dim: int,
|
30
|
+
db_config: HologresConfig,
|
31
|
+
db_case_config: HologresIndexConfig,
|
32
|
+
collection_name: str = "vector_collection",
|
33
|
+
drop_old: bool = False,
|
34
|
+
**kwargs,
|
35
|
+
):
|
36
|
+
self.name = "Alibaba Cloud Hologres"
|
37
|
+
self.db_config = db_config
|
38
|
+
self.case_config = db_case_config
|
39
|
+
self.table_name = collection_name
|
40
|
+
self.dim = dim
|
41
|
+
|
42
|
+
self._primary_field = "id"
|
43
|
+
self._vector_field = "embedding"
|
44
|
+
|
45
|
+
# construct basic units
|
46
|
+
self.conn, self.cursor = self._create_connection(**self.db_config)
|
47
|
+
|
48
|
+
# create vector extension
|
49
|
+
if self.case_config.is_proxima():
|
50
|
+
self.cursor.execute("CREATE EXTENSION proxima;")
|
51
|
+
self.conn.commit()
|
52
|
+
|
53
|
+
log.info(f"{self.name} config values: {self.db_config}\n{self.case_config}")
|
54
|
+
if not any(
|
55
|
+
(
|
56
|
+
self.case_config.create_index_before_load,
|
57
|
+
self.case_config.create_index_after_load,
|
58
|
+
),
|
59
|
+
):
|
60
|
+
msg = (
|
61
|
+
f"{self.name} config must create an index using create_index_before_load or create_index_after_load"
|
62
|
+
f"{self.name} config values: {self.db_config}\n{self.case_config}"
|
63
|
+
)
|
64
|
+
log.error(msg)
|
65
|
+
raise RuntimeError(msg)
|
66
|
+
|
67
|
+
if drop_old:
|
68
|
+
self._drop_table()
|
69
|
+
self._create_table(dim)
|
70
|
+
if self.case_config.create_index_before_load:
|
71
|
+
self._create_index()
|
72
|
+
|
73
|
+
self.cursor.close()
|
74
|
+
self.conn.close()
|
75
|
+
self.cursor = None
|
76
|
+
self.conn = None
|
77
|
+
|
78
|
+
@staticmethod
|
79
|
+
def _create_connection(**kwargs) -> tuple[Connection, Cursor]:
|
80
|
+
conn = psycopg.connect(**kwargs)
|
81
|
+
conn.autocommit = True
|
82
|
+
cursor = conn.cursor()
|
83
|
+
|
84
|
+
assert conn is not None, "Connection is not initialized"
|
85
|
+
assert cursor is not None, "Cursor is not initialized"
|
86
|
+
|
87
|
+
return conn, cursor
|
88
|
+
|
89
|
+
@contextmanager
|
90
|
+
def init(self) -> Generator[None, None, None]:
|
91
|
+
"""
|
92
|
+
Examples:
|
93
|
+
>>> with self.init():
|
94
|
+
>>> self.insert_embeddings()
|
95
|
+
>>> self.search_embedding()
|
96
|
+
"""
|
97
|
+
|
98
|
+
self.conn, self.cursor = self._create_connection(**self.db_config)
|
99
|
+
|
100
|
+
self._set_search_guc()
|
101
|
+
|
102
|
+
try:
|
103
|
+
yield
|
104
|
+
finally:
|
105
|
+
self.cursor.close()
|
106
|
+
self.conn.close()
|
107
|
+
self.cursor = None
|
108
|
+
self.conn = None
|
109
|
+
|
110
|
+
def _set_search_guc(self):
|
111
|
+
assert self.conn is not None, "Connection is not initialized"
|
112
|
+
assert self.cursor is not None, "Cursor is not initialized"
|
113
|
+
|
114
|
+
sql_guc = sql.SQL(f"SET hg_vector_ef_search = {self.case_config.ef_search};")
|
115
|
+
log.info(f"{self.name} client set search guc: {sql_guc.as_string()}")
|
116
|
+
self.cursor.execute(sql_guc)
|
117
|
+
self.conn.commit()
|
118
|
+
|
119
|
+
def _drop_table(self):
|
120
|
+
assert self.conn is not None, "Connection is not initialized"
|
121
|
+
assert self.cursor is not None, "Cursor is not initialized"
|
122
|
+
|
123
|
+
log.info(f"{self.name} client drop table : {self.table_name}")
|
124
|
+
self.cursor.execute(
|
125
|
+
sql.SQL("DROP TABLE IF EXISTS {table_name};").format(
|
126
|
+
table_name=sql.Identifier(self.table_name),
|
127
|
+
),
|
128
|
+
)
|
129
|
+
self.conn.commit()
|
130
|
+
|
131
|
+
try:
|
132
|
+
log.info(f"{self.name} client drop table group : {self._tg_name}")
|
133
|
+
self.cursor.execute(sql.SQL(f"CALL HG_DROP_TABLE_GROUP('{self._tg_name}');"))
|
134
|
+
except Exception as e:
|
135
|
+
log.info(f"{self.name} client drop table group : {self._tg_name} failed, error: {e}, ignore.")
|
136
|
+
finally:
|
137
|
+
self.conn.commit()
|
138
|
+
|
139
|
+
def optimize(self, data_size: int | None = None):
|
140
|
+
if self.case_config.create_index_after_load:
|
141
|
+
self._create_index()
|
142
|
+
self._full_compact()
|
143
|
+
self._analyze()
|
144
|
+
|
145
|
+
def _vacuum(self):
|
146
|
+
log.info(f"{self.name} client vacuum table : {self.table_name}")
|
147
|
+
try:
|
148
|
+
# VACUUM cannot run inside a transaction block
|
149
|
+
# it's better to new a connection
|
150
|
+
self.conn.autocommit = True
|
151
|
+
with self.conn.cursor() as cursor:
|
152
|
+
cursor.execute(
|
153
|
+
sql.SQL(
|
154
|
+
"""
|
155
|
+
VACUUM {table_name};
|
156
|
+
"""
|
157
|
+
).format(
|
158
|
+
table_name=sql.Identifier(self.table_name),
|
159
|
+
)
|
160
|
+
)
|
161
|
+
log.info(f"{self.name} client vacuum table : {self.table_name} done")
|
162
|
+
except Exception as e:
|
163
|
+
log.warning(f"Failed to vacuum table: {self.table_name} error: {e}")
|
164
|
+
raise e from None
|
165
|
+
finally:
|
166
|
+
self.conn.autocommit = True
|
167
|
+
|
168
|
+
def _analyze(self):
|
169
|
+
log.info(f"{self.name} client analyze table : {self.table_name}")
|
170
|
+
self.cursor.execute(sql.SQL(f"ANALYZE {self.table_name};"))
|
171
|
+
log.info(f"{self.name} client analyze table : {self.table_name} done")
|
172
|
+
|
173
|
+
def _full_compact(self):
|
174
|
+
log.info(f"{self.name} client full compact table : {self.table_name}")
|
175
|
+
self.cursor.execute(
|
176
|
+
sql.SQL(
|
177
|
+
"""
|
178
|
+
SELECT hologres.hg_full_compact_table(
|
179
|
+
'{table_name}',
|
180
|
+
'max_file_size_mb={full_compact_max_file_size_mb}'
|
181
|
+
);
|
182
|
+
"""
|
183
|
+
).format(
|
184
|
+
table_name=sql.SQL(self.table_name),
|
185
|
+
full_compact_max_file_size_mb=sql.SQL(str(self.case_config.full_compact_max_file_size_mb)),
|
186
|
+
)
|
187
|
+
)
|
188
|
+
log.info(f"{self.name} client full compact table : {self.table_name} done")
|
189
|
+
|
190
|
+
def _create_index(self):
|
191
|
+
assert self.conn is not None, "Connection is not initialized"
|
192
|
+
assert self.cursor is not None, "Cursor is not initialized"
|
193
|
+
|
194
|
+
sql_index = sql.SQL(
|
195
|
+
"""
|
196
|
+
CALL set_table_property ('{table_name}', 'vectors', '{{
|
197
|
+
"embedding": {{
|
198
|
+
"algorithm": "{algorithm}",
|
199
|
+
"distance_method": "{distance_method}",
|
200
|
+
"builder_params": {builder_params}
|
201
|
+
}}
|
202
|
+
}}');
|
203
|
+
"""
|
204
|
+
).format(
|
205
|
+
table_name=sql.Identifier(self.table_name),
|
206
|
+
algorithm=sql.SQL(self.case_config.algorithm()),
|
207
|
+
distance_method=sql.SQL(self.case_config.distance_method()),
|
208
|
+
builder_params=sql.SQL(json.dumps(self.case_config.builder_params())),
|
209
|
+
)
|
210
|
+
|
211
|
+
log.info(f"{self.name} client create index on table : {self.table_name}, with sql: {sql_index.as_string()}")
|
212
|
+
try:
|
213
|
+
self.cursor.execute(sql_index)
|
214
|
+
self.conn.commit()
|
215
|
+
except Exception as e:
|
216
|
+
log.warning(f"Failed to create index on table: {self.table_name} error: {e}")
|
217
|
+
raise e from None
|
218
|
+
|
219
|
+
def _set_replica_count(self, replica_count: int = 2):
|
220
|
+
assert self.conn is not None, "Connection is not initialized"
|
221
|
+
assert self.cursor is not None, "Cursor is not initialized"
|
222
|
+
|
223
|
+
try:
|
224
|
+
# non-warehouse mode by default
|
225
|
+
sql_tg_replica = sql.SQL(
|
226
|
+
f"CALL hg_set_table_group_property('{self._tg_name}', 'replica_count', '{replica_count}');"
|
227
|
+
)
|
228
|
+
|
229
|
+
# check warehouse mode
|
230
|
+
sql_check = sql.SQL("select count(*) from hologres.hg_warehouses;")
|
231
|
+
log.info(f"check warehouse mode with sql: {sql_check}")
|
232
|
+
self.cursor.execute(sql_check)
|
233
|
+
result_check = self.cursor.fetchone()[0]
|
234
|
+
if result_check > 0:
|
235
|
+
# get warehouse name
|
236
|
+
sql_get_warehouse_name = sql.SQL("select current_warehouse();")
|
237
|
+
log.info(f"get warehouse name with sql: {sql_get_warehouse_name}")
|
238
|
+
self.cursor.execute(sql_get_warehouse_name)
|
239
|
+
sql_tg_replica = sql.SQL(
|
240
|
+
"""
|
241
|
+
CALL hg_table_group_set_warehouse_replica_count (
|
242
|
+
'{dbname}.{tg_name}',
|
243
|
+
{replica_count},
|
244
|
+
'{warehouse_name}'
|
245
|
+
);
|
246
|
+
"""
|
247
|
+
).format(
|
248
|
+
tg_name=sql.SQL(self._tg_name),
|
249
|
+
warehouse_name=sql.SQL(self.cursor.fetchone()[0]),
|
250
|
+
dbname=sql.SQL(self.db_config["dbname"]),
|
251
|
+
replica_count=replica_count,
|
252
|
+
)
|
253
|
+
log.info(f"{self.name} client set table group replica: {self._tg_name}, with sql: {sql_tg_replica}")
|
254
|
+
self.cursor.execute(sql_tg_replica)
|
255
|
+
except Exception as e:
|
256
|
+
log.warning(f"Failed to set replica count, error: {e}, ignore")
|
257
|
+
finally:
|
258
|
+
self.conn.commit()
|
259
|
+
|
260
|
+
def _create_table(self, dim: int):
|
261
|
+
assert self.conn is not None, "Connection is not initialized"
|
262
|
+
assert self.cursor is not None, "Cursor is not initialized"
|
263
|
+
|
264
|
+
sql_tg = sql.SQL(f"CALL HG_CREATE_TABLE_GROUP ('{self._tg_name}', 1);")
|
265
|
+
log.info(f"{self.name} client create table group : {self._tg_name}, with sql: {sql_tg}")
|
266
|
+
try:
|
267
|
+
self.cursor.execute(sql_tg)
|
268
|
+
except Exception as e:
|
269
|
+
log.warning(f"Failed to create table group : {self._tg_name} error: {e}, ignore")
|
270
|
+
finally:
|
271
|
+
self.conn.commit()
|
272
|
+
|
273
|
+
self._set_replica_count(replica_count=2)
|
274
|
+
|
275
|
+
sql_table = sql.SQL(
|
276
|
+
"""
|
277
|
+
CREATE TABLE IF NOT EXISTS {table_name} (
|
278
|
+
id BIGINT PRIMARY KEY,
|
279
|
+
embedding FLOAT4[] CHECK (array_ndims(embedding) = 1 AND array_length(embedding, 1) = {dim})
|
280
|
+
)
|
281
|
+
WITH (table_group = {tg_name});
|
282
|
+
"""
|
283
|
+
).format(
|
284
|
+
table_name=sql.Identifier(self.table_name),
|
285
|
+
dim=dim,
|
286
|
+
tg_name=sql.SQL(self._tg_name),
|
287
|
+
)
|
288
|
+
log.info(f"{self.name} client create table : {self.table_name}, with sql: {sql_table.as_string()}")
|
289
|
+
try:
|
290
|
+
self.cursor.execute(sql_table)
|
291
|
+
self.conn.commit()
|
292
|
+
except Exception as e:
|
293
|
+
log.warning(f"Failed to create table : {self.table_name} error: {e}")
|
294
|
+
raise e from None
|
295
|
+
|
296
|
+
def insert_embeddings(
|
297
|
+
self,
|
298
|
+
embeddings: list[list[float]],
|
299
|
+
metadata: list[int],
|
300
|
+
**kwargs: Any,
|
301
|
+
) -> tuple[int, Exception | None]:
|
302
|
+
assert self.conn is not None, "Connection is not initialized"
|
303
|
+
assert self.cursor is not None, "Cursor is not initialized"
|
304
|
+
|
305
|
+
try:
|
306
|
+
buffer = StringIO()
|
307
|
+
for i in range(len(metadata)):
|
308
|
+
buffer.write("%d\t%s\n" % (metadata[i], "{" + ",".join("%f" % x for x in embeddings[i]) + "}"))
|
309
|
+
buffer.seek(0)
|
310
|
+
|
311
|
+
with self.cursor.copy(
|
312
|
+
sql.SQL("COPY {table_name} FROM STDIN").format(table_name=sql.Identifier(self.table_name))
|
313
|
+
) as copy:
|
314
|
+
copy.write(buffer.getvalue())
|
315
|
+
self.conn.commit()
|
316
|
+
|
317
|
+
return len(metadata), None
|
318
|
+
except Exception as e:
|
319
|
+
log.warning(f"Failed to insert data into table ({self.table_name}), error: {e}")
|
320
|
+
return 0, e
|
321
|
+
|
322
|
+
def _compose_query_and_params(self, vec: list[float], topk: int, ge_id: int | None = None):
|
323
|
+
params = []
|
324
|
+
|
325
|
+
where_clause = sql.SQL("")
|
326
|
+
if ge_id is not None:
|
327
|
+
where_clause = sql.SQL(" WHERE id >= %s ")
|
328
|
+
params.append(ge_id)
|
329
|
+
|
330
|
+
vec_float4 = [psycopg._wrappers.Float4(i) for i in vec]
|
331
|
+
params.append(vec_float4)
|
332
|
+
params.append(topk)
|
333
|
+
|
334
|
+
query = sql.SQL(
|
335
|
+
"""
|
336
|
+
SELECT id
|
337
|
+
FROM {table_name}
|
338
|
+
{where_clause}
|
339
|
+
ORDER BY {distance_function}(embedding, %b)
|
340
|
+
{order_direction}
|
341
|
+
LIMIT %s;
|
342
|
+
"""
|
343
|
+
).format(
|
344
|
+
table_name=sql.Identifier(self.table_name),
|
345
|
+
distance_function=sql.SQL(self.case_config.distance_function()),
|
346
|
+
where_clause=where_clause,
|
347
|
+
order_direction=sql.SQL(self.case_config.order_direction()),
|
348
|
+
)
|
349
|
+
|
350
|
+
return query, params
|
351
|
+
|
352
|
+
def search_embedding(
|
353
|
+
self,
|
354
|
+
query: list[float],
|
355
|
+
k: int = 100,
|
356
|
+
filters: dict | None = None,
|
357
|
+
timeout: int | None = None,
|
358
|
+
) -> list[int]:
|
359
|
+
assert self.conn is not None, "Connection is not initialized"
|
360
|
+
assert self.cursor is not None, "Cursor is not initialized"
|
361
|
+
|
362
|
+
ge = filters.get("id") if filters else None
|
363
|
+
q, params = self._compose_query_and_params(query, k, ge)
|
364
|
+
result = self.cursor.execute(q, params, prepare=True, binary=True)
|
365
|
+
return [int(i[0]) for i in result.fetchall()]
|
@@ -40,6 +40,17 @@ class MilvusTypedDict(TypedDict):
|
|
40
40
|
show_default=True,
|
41
41
|
),
|
42
42
|
]
|
43
|
+
replica_number: Annotated[
|
44
|
+
int,
|
45
|
+
click.option(
|
46
|
+
"--replica-number",
|
47
|
+
type=int,
|
48
|
+
help="Number of replicas",
|
49
|
+
required=False,
|
50
|
+
default=1,
|
51
|
+
show_default=True,
|
52
|
+
),
|
53
|
+
]
|
43
54
|
|
44
55
|
|
45
56
|
class MilvusAutoIndexTypedDict(CommonTypedDict, MilvusTypedDict): ...
|
@@ -58,6 +69,7 @@ def MilvusAutoIndex(**parameters: Unpack[MilvusAutoIndexTypedDict]):
|
|
58
69
|
user=parameters["user_name"],
|
59
70
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
60
71
|
num_shards=int(parameters["num_shards"]),
|
72
|
+
replica_number=int(parameters["replica_number"]),
|
61
73
|
),
|
62
74
|
db_case_config=AutoIndexConfig(),
|
63
75
|
**parameters,
|
@@ -77,6 +89,7 @@ def MilvusFlat(**parameters: Unpack[MilvusAutoIndexTypedDict]):
|
|
77
89
|
user=parameters["user_name"],
|
78
90
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
79
91
|
num_shards=int(parameters["num_shards"]),
|
92
|
+
replica_number=int(parameters["replica_number"]),
|
80
93
|
),
|
81
94
|
db_case_config=FLATConfig(),
|
82
95
|
**parameters,
|
@@ -99,6 +112,7 @@ def MilvusHNSW(**parameters: Unpack[MilvusHNSWTypedDict]):
|
|
99
112
|
user=parameters["user_name"],
|
100
113
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
101
114
|
num_shards=int(parameters["num_shards"]),
|
115
|
+
replica_number=int(parameters["replica_number"]),
|
102
116
|
),
|
103
117
|
db_case_config=HNSWConfig(
|
104
118
|
M=parameters["m"],
|
@@ -139,19 +153,14 @@ class MilvusRefineTypedDict(TypedDict):
|
|
139
153
|
]
|
140
154
|
|
141
155
|
|
142
|
-
class MilvusHNSWPQTypedDict(
|
143
|
-
CommonTypedDict,
|
144
|
-
MilvusTypedDict,
|
145
|
-
MilvusHNSWTypedDict,
|
146
|
-
MilvusRefineTypedDict
|
147
|
-
):
|
156
|
+
class MilvusHNSWPQTypedDict(CommonTypedDict, MilvusTypedDict, MilvusHNSWTypedDict, MilvusRefineTypedDict):
|
148
157
|
nbits: Annotated[
|
149
158
|
int,
|
150
159
|
click.option(
|
151
160
|
"--nbits",
|
152
161
|
type=int,
|
153
162
|
required=True,
|
154
|
-
)
|
163
|
+
),
|
155
164
|
]
|
156
165
|
|
157
166
|
|
@@ -168,6 +177,7 @@ def MilvusHNSWPQ(**parameters: Unpack[MilvusHNSWPQTypedDict]):
|
|
168
177
|
user=parameters["user_name"],
|
169
178
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
170
179
|
num_shards=int(parameters["num_shards"]),
|
180
|
+
replica_number=int(parameters["replica_number"]),
|
171
181
|
),
|
172
182
|
db_case_config=HNSWPQConfig(
|
173
183
|
M=parameters["m"],
|
@@ -194,7 +204,7 @@ class MilvusHNSWPRQTypedDict(
|
|
194
204
|
type=int,
|
195
205
|
help="The number of residual subquantizers.",
|
196
206
|
required=True,
|
197
|
-
)
|
207
|
+
),
|
198
208
|
]
|
199
209
|
|
200
210
|
|
@@ -211,6 +221,7 @@ def MilvusHNSWPRQ(**parameters: Unpack[MilvusHNSWPRQTypedDict]):
|
|
211
221
|
user=parameters["user_name"],
|
212
222
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
213
223
|
num_shards=int(parameters["num_shards"]),
|
224
|
+
replica_number=int(parameters["replica_number"]),
|
214
225
|
),
|
215
226
|
db_case_config=HNSWPRQConfig(
|
216
227
|
M=parameters["m"],
|
@@ -220,7 +231,7 @@ def MilvusHNSWPRQ(**parameters: Unpack[MilvusHNSWPRQTypedDict]):
|
|
220
231
|
refine=parameters["refine"],
|
221
232
|
refine_type=parameters["refine_type"],
|
222
233
|
refine_k=parameters["refine_k"],
|
223
|
-
nrq=parameters["nrq"]
|
234
|
+
nrq=parameters["nrq"],
|
224
235
|
),
|
225
236
|
**parameters,
|
226
237
|
)
|
@@ -251,6 +262,7 @@ def MilvusHNSWSQ(**parameters: Unpack[MilvusHNSWSQTypedDict]):
|
|
251
262
|
user=parameters["user_name"],
|
252
263
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
253
264
|
num_shards=int(parameters["num_shards"]),
|
265
|
+
replica_number=int(parameters["replica_number"]),
|
254
266
|
),
|
255
267
|
db_case_config=HNSWSQConfig(
|
256
268
|
M=parameters["m"],
|
@@ -281,6 +293,7 @@ def MilvusIVFFlat(**parameters: Unpack[MilvusIVFFlatTypedDict]):
|
|
281
293
|
user=parameters["user_name"],
|
282
294
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
283
295
|
num_shards=int(parameters["num_shards"]),
|
296
|
+
replica_number=int(parameters["replica_number"]),
|
284
297
|
),
|
285
298
|
db_case_config=IVFFlatConfig(
|
286
299
|
nlist=parameters["nlist"],
|
@@ -303,6 +316,7 @@ def MilvusIVFSQ8(**parameters: Unpack[MilvusIVFFlatTypedDict]):
|
|
303
316
|
user=parameters["user_name"],
|
304
317
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
305
318
|
num_shards=int(parameters["num_shards"]),
|
319
|
+
replica_number=int(parameters["replica_number"]),
|
306
320
|
),
|
307
321
|
db_case_config=IVFSQ8Config(
|
308
322
|
nlist=parameters["nlist"],
|
@@ -364,6 +378,7 @@ def MilvusIVFRabitQ(**parameters: Unpack[MilvusIVFRABITQTypedDict]):
|
|
364
378
|
user=parameters["user_name"],
|
365
379
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
366
380
|
num_shards=int(parameters["num_shards"]),
|
381
|
+
replica_number=int(parameters["replica_number"]),
|
367
382
|
),
|
368
383
|
db_case_config=IVFRABITQConfig(
|
369
384
|
nlist=parameters["nlist"],
|
@@ -394,6 +409,7 @@ def MilvusDISKANN(**parameters: Unpack[MilvusDISKANNTypedDict]):
|
|
394
409
|
user=parameters["user_name"],
|
395
410
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
396
411
|
num_shards=int(parameters["num_shards"]),
|
412
|
+
replica_number=int(parameters["replica_number"]),
|
397
413
|
),
|
398
414
|
db_case_config=DISKANNConfig(
|
399
415
|
search_list=parameters["search_list"],
|
@@ -423,6 +439,7 @@ def MilvusGPUIVFFlat(**parameters: Unpack[MilvusGPUIVFTypedDict]):
|
|
423
439
|
user=parameters["user_name"],
|
424
440
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
425
441
|
num_shards=int(parameters["num_shards"]),
|
442
|
+
replica_number=int(parameters["replica_number"]),
|
426
443
|
),
|
427
444
|
db_case_config=GPUIVFFlatConfig(
|
428
445
|
nlist=parameters["nlist"],
|
@@ -458,6 +475,7 @@ def MilvusGPUBruteForce(**parameters: Unpack[MilvusGPUBruteForceTypedDict]):
|
|
458
475
|
user=parameters["user_name"],
|
459
476
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
460
477
|
num_shards=int(parameters["num_shards"]),
|
478
|
+
replica_number=int(parameters["replica_number"]),
|
461
479
|
),
|
462
480
|
db_case_config=GPUBruteForceConfig(
|
463
481
|
metric_type=parameters["metric_type"],
|
@@ -490,6 +508,7 @@ def MilvusGPUIVFPQ(**parameters: Unpack[MilvusGPUIVFPQTypedDict]):
|
|
490
508
|
user=parameters["user_name"],
|
491
509
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
492
510
|
num_shards=int(parameters["num_shards"]),
|
511
|
+
replica_number=int(parameters["replica_number"]),
|
493
512
|
),
|
494
513
|
db_case_config=GPUIVFPQConfig(
|
495
514
|
nlist=parameters["nlist"],
|
@@ -530,6 +549,7 @@ def MilvusGPUCAGRA(**parameters: Unpack[MilvusGPUCAGRATypedDict]):
|
|
530
549
|
user=parameters["user_name"],
|
531
550
|
password=SecretStr(parameters["password"]) if parameters["password"] else None,
|
532
551
|
num_shards=int(parameters["num_shards"]),
|
552
|
+
replica_number=int(parameters["replica_number"]),
|
533
553
|
),
|
534
554
|
db_case_config=GPUCAGRAConfig(
|
535
555
|
intermediate_graph_degree=parameters["intermediate_graph_degree"],
|
@@ -8,6 +8,7 @@ class MilvusConfig(DBConfig):
|
|
8
8
|
user: str | None = None
|
9
9
|
password: SecretStr | None = None
|
10
10
|
num_shards: int = 1
|
11
|
+
replica_number: int = 1
|
11
12
|
|
12
13
|
def to_dict(self) -> dict:
|
13
14
|
return {
|
@@ -15,6 +16,7 @@ class MilvusConfig(DBConfig):
|
|
15
16
|
"user": self.user if self.user else None,
|
16
17
|
"password": self.password.get_secret_value() if self.password else None,
|
17
18
|
"num_shards": self.num_shards,
|
19
|
+
"replica_number": self.replica_number,
|
18
20
|
}
|
19
21
|
|
20
22
|
@validator("*")
|
@@ -93,6 +93,7 @@ def OceanBaseIVF(**parameters: Unpack[OceanBaseIVFTypedDict]):
|
|
93
93
|
m=input_m,
|
94
94
|
nlist=parameters["nlist"],
|
95
95
|
sample_per_nlist=parameters["sample_per_nlist"],
|
96
|
+
nbits=parameters["nbits"],
|
96
97
|
index=input_index_type,
|
97
98
|
ivf_nprobes=parameters["ivf_nprobes"],
|
98
99
|
),
|