vectordb-bench 0.0.8__tar.gz → 0.0.9__tar.gz

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 (118) hide show
  1. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/PKG-INFO +4 -3
  2. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/pyproject.toml +3 -2
  3. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/api.py +1 -0
  4. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/milvus/milvus.py +2 -3
  5. vectordb_bench-0.0.9/vectordb_bench/backend/clients/pgvector/config.py +215 -0
  6. vectordb_bench-0.0.9/vectordb_bench/backend/clients/pgvector/pgvector.py +347 -0
  7. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/runner/serial_runner.py +0 -2
  8. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/run_test/caseSelector.py +6 -3
  9. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/const/dbCaseConfigs.py +118 -2
  10. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/models.py +6 -3
  11. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench.egg-info/PKG-INFO +4 -3
  12. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench.egg-info/requires.txt +3 -2
  13. vectordb_bench-0.0.8/vectordb_bench/backend/clients/pgvector/config.py +0 -100
  14. vectordb_bench-0.0.8/vectordb_bench/backend/clients/pgvector/pgvector.py +0 -187
  15. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/.devcontainer/Dockerfile +0 -0
  16. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/.devcontainer/devcontainer.json +0 -0
  17. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/.env.example +0 -0
  18. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/.github/workflows/publish_package_on_release.yml +0 -0
  19. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/.github/workflows/pull_request.yml +0 -0
  20. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/.gitignore +0 -0
  21. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/.ruff.toml +0 -0
  22. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/Dockerfile +0 -0
  23. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/LICENSE +0 -0
  24. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/Makefile +0 -0
  25. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/OWNERS +0 -0
  26. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/README.md +0 -0
  27. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/install/requirements_py3.11.txt +0 -0
  28. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/install.py +0 -0
  29. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/setup.cfg +0 -0
  30. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/tests/conftest.py +0 -0
  31. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/tests/pytest.ini +0 -0
  32. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/tests/test_bench_runner.py +0 -0
  33. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/tests/test_chroma.py +0 -0
  34. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/tests/test_data_source.py +0 -0
  35. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/tests/test_dataset.py +0 -0
  36. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/tests/test_elasticsearch_cloud.py +0 -0
  37. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/tests/test_models.py +0 -0
  38. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/tests/test_redis.py +0 -0
  39. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/tests/test_utils.py +0 -0
  40. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/tests/ut_cases.py +0 -0
  41. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/__init__.py +0 -0
  42. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/__main__.py +0 -0
  43. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/__init__.py +0 -0
  44. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/assembler.py +0 -0
  45. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/cases.py +0 -0
  46. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/__init__.py +0 -0
  47. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/chroma/chroma.py +0 -0
  48. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/chroma/config.py +0 -0
  49. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/elastic_cloud/config.py +0 -0
  50. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/elastic_cloud/elastic_cloud.py +0 -0
  51. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/milvus/config.py +0 -0
  52. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/pgvecto_rs/config.py +0 -0
  53. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/pgvecto_rs/pgvecto_rs.py +0 -0
  54. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/pinecone/config.py +0 -0
  55. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/pinecone/pinecone.py +0 -0
  56. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/qdrant_cloud/config.py +0 -0
  57. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/qdrant_cloud/qdrant_cloud.py +0 -0
  58. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/redis/config.py +0 -0
  59. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/redis/redis.py +0 -0
  60. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/weaviate_cloud/config.py +0 -0
  61. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/weaviate_cloud/weaviate_cloud.py +0 -0
  62. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/zilliz_cloud/config.py +0 -0
  63. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/clients/zilliz_cloud/zilliz_cloud.py +0 -0
  64. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/data_source.py +0 -0
  65. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/dataset.py +0 -0
  66. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/result_collector.py +0 -0
  67. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/runner/__init__.py +0 -0
  68. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/runner/mp_runner.py +0 -0
  69. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/task_runner.py +1 -1
  70. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/backend/utils.py +0 -0
  71. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/base.py +0 -0
  72. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/check_results/charts.py +0 -0
  73. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/check_results/data.py +0 -0
  74. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/check_results/expanderStyle.py +0 -0
  75. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/check_results/filters.py +0 -0
  76. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/check_results/footer.py +0 -0
  77. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/check_results/headerIcon.py +0 -0
  78. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/check_results/nav.py +0 -0
  79. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/check_results/priceTable.py +0 -0
  80. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/check_results/stPageConfig.py +0 -0
  81. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/get_results/saveAsImage.py +0 -0
  82. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/run_test/autoRefresh.py +0 -0
  83. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/run_test/dbConfigSetting.py +0 -0
  84. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/run_test/dbSelector.py +0 -0
  85. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/run_test/generateTasks.py +0 -0
  86. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/run_test/hideSidebar.py +0 -0
  87. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/components/run_test/submitTask.py +0 -0
  88. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/const/dbPrices.py +0 -0
  89. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/const/styles.py +0 -0
  90. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/pages/quries_per_dollar.py +0 -0
  91. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/pages/run_test.py +0 -0
  92. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/utils.py +0 -0
  93. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/frontend/vdb_benchmark.py +0 -0
  94. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/interface.py +0 -0
  95. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/log_util.py +0 -0
  96. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/metric.py +0 -0
  97. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/ElasticCloud/result_20230727_standard_elasticcloud.json +0 -0
  98. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/ElasticCloud/result_20230808_standard_elasticcloud.json +0 -0
  99. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/Milvus/result_20230727_standard_milvus.json +0 -0
  100. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/Milvus/result_20230808_standard_milvus.json +0 -0
  101. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/PgVector/result_20230727_standard_pgvector.json +0 -0
  102. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/PgVector/result_20230808_standard_pgvector.json +0 -0
  103. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/Pinecone/result_20230727_standard_pinecone.json +0 -0
  104. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/Pinecone/result_20230808_standard_pinecone.json +0 -0
  105. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/QdrantCloud/result_20230727_standard_qdrantcloud.json +0 -0
  106. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/QdrantCloud/result_20230808_standard_qdrantcloud.json +0 -0
  107. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/WeaviateCloud/result_20230727_standard_weaviatecloud.json +0 -0
  108. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/WeaviateCloud/result_20230808_standard_weaviatecloud.json +0 -0
  109. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/ZillizCloud/result_20230727_standard_zillizcloud.json +0 -0
  110. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/ZillizCloud/result_20230808_standard_zillizcloud.json +0 -0
  111. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/ZillizCloud/result_20240105_standard_202401_zillizcloud.json +0 -0
  112. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/dbPrices.json +0 -0
  113. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/getLeaderboardData.py +0 -0
  114. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench/results/leaderboard.json +0 -0
  115. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench.egg-info/SOURCES.txt +0 -0
  116. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench.egg-info/dependency_links.txt +0 -0
  117. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench.egg-info/entry_points.txt +0 -0
  118. {vectordb_bench-0.0.8 → vectordb_bench-0.0.9}/vectordb_bench.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vectordb-bench
3
- Version: 0.0.8
3
+ Version: 0.0.9
4
4
  Summary: VectorDBBench is not just an offering of benchmark results for mainstream vector databases and cloud services, it's your go-to tool for the ultimate performance and cost-effectiveness comparison. Designed with ease-of-use in mind, VectorDBBench is devised to help users, even non-professionals, reproduce results or test new systems, making the hunt for the optimal choice amongst a plethora of cloud services and open-source vector databases a breeze.
5
5
  Author-email: XuanYang-cn <xuan.yang@zilliz.com>
6
6
  Project-URL: repository, https://github.com/zilliztech/VectorDBBench
@@ -12,7 +12,7 @@ Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: pytz
14
14
  Requires-Dist: streamlit-autorefresh
15
- Requires-Dist: streamlit>=1.23.0
15
+ Requires-Dist: streamlit!=1.34.0
16
16
  Requires-Dist: streamlit_extras
17
17
  Requires-Dist: tqdm
18
18
  Requires-Dist: s3fs
@@ -39,6 +39,7 @@ Requires-Dist: sqlalchemy; extra == "all"
39
39
  Requires-Dist: redis; extra == "all"
40
40
  Requires-Dist: chromadb; extra == "all"
41
41
  Requires-Dist: psycopg2; extra == "all"
42
+ Requires-Dist: psycopg; extra == "all"
42
43
  Provides-Extra: qdrant
43
44
  Requires-Dist: qdrant-client; extra == "qdrant"
44
45
  Provides-Extra: pinecone
@@ -49,7 +50,7 @@ Provides-Extra: elastic
49
50
  Requires-Dist: elasticsearch; extra == "elastic"
50
51
  Provides-Extra: pgvector
51
52
  Requires-Dist: pgvector; extra == "pgvector"
52
- Requires-Dist: psycopg2; extra == "pgvector"
53
+ Requires-Dist: psycopg; extra == "pgvector"
53
54
  Provides-Extra: pgvecto-rs
54
55
  Requires-Dist: psycopg2; extra == "pgvecto-rs"
55
56
  Provides-Extra: redis
@@ -26,7 +26,7 @@ classifiers = [
26
26
  dependencies = [
27
27
  "pytz",
28
28
  "streamlit-autorefresh",
29
- "streamlit>=1.23.0",
29
+ "streamlit!=1.34.0",
30
30
  "streamlit_extras",
31
31
  "tqdm",
32
32
  "s3fs",
@@ -59,13 +59,14 @@ all = [
59
59
  "redis",
60
60
  "chromadb",
61
61
  "psycopg2",
62
+ "psycopg",
62
63
  ]
63
64
 
64
65
  qdrant = [ "qdrant-client" ]
65
66
  pinecone = [ "pinecone-client" ]
66
67
  weaviate = [ "weaviate-client" ]
67
68
  elastic = [ "elasticsearch" ]
68
- pgvector = [ "pgvector", "psycopg2" ]
69
+ pgvector = [ "pgvector", "psycopg" ]
69
70
  pgvecto_rs = [ "psycopg2" ]
70
71
  redis = [ "redis" ]
71
72
  chromadb = [ "chromadb" ]
@@ -20,6 +20,7 @@ class IndexType(str, Enum):
20
20
  Flat = "FLAT"
21
21
  AUTOINDEX = "AUTOINDEX"
22
22
  ES_HNSW = "hnsw"
23
+ ES_IVFFlat = "ivfflat"
23
24
  GPU_IVF_FLAT = "GPU_IVF_FLAT"
24
25
  GPU_IVF_PQ = "GPU_IVF_PQ"
25
26
  GPU_CAGRA = "GPU_CAGRA"
@@ -89,6 +89,7 @@ class Milvus(VectorDB):
89
89
  connections.disconnect("default")
90
90
 
91
91
  def _optimize(self):
92
+ self._post_insert()
92
93
  log.info(f"{self.name} optimizing before search")
93
94
  try:
94
95
  self.col.load()
@@ -116,7 +117,7 @@ class Milvus(VectorDB):
116
117
  time.sleep(5)
117
118
 
118
119
  wait_index()
119
-
120
+
120
121
  # Skip compaction if use GPU indexType
121
122
  if self.case_config.index in [IndexType.GPU_CAGRA, IndexType.GPU_IVF_FLAT, IndexType.GPU_IVF_PQ]:
122
123
  log.debug("skip compaction for gpu index type.")
@@ -179,8 +180,6 @@ class Milvus(VectorDB):
179
180
  ]
180
181
  res = self.col.insert(insert_data)
181
182
  insert_count += len(res.primary_keys)
182
- if kwargs.get("last_batch"):
183
- self._post_insert()
184
183
  except MilvusException as e:
185
184
  log.info(f"Failed to insert data: {e}")
186
185
  return (insert_count, e)
@@ -0,0 +1,215 @@
1
+ from abc import abstractmethod
2
+ from typing import Any, Mapping, Optional, Sequence, TypedDict
3
+ from pydantic import BaseModel, SecretStr
4
+ from typing_extensions import LiteralString
5
+ from ..api import DBCaseConfig, DBConfig, IndexType, MetricType
6
+
7
+ POSTGRE_URL_PLACEHOLDER = "postgresql://%s:%s@%s/%s"
8
+
9
+
10
+ class PgVectorConfigDict(TypedDict):
11
+ """These keys will be directly used as kwargs in psycopg connection string,
12
+ so the names must match exactly psycopg API"""
13
+
14
+ user: str
15
+ password: str
16
+ host: str
17
+ port: int
18
+ dbname: str
19
+
20
+
21
+ class PgVectorConfig(DBConfig):
22
+ user_name: SecretStr = SecretStr("postgres")
23
+ password: SecretStr
24
+ host: str = "localhost"
25
+ port: int = 5432
26
+ db_name: str
27
+
28
+ def to_dict(self) -> PgVectorConfigDict:
29
+ user_str = self.user_name.get_secret_value()
30
+ pwd_str = self.password.get_secret_value()
31
+ return {
32
+ "host": self.host,
33
+ "port": self.port,
34
+ "dbname": self.db_name,
35
+ "user": user_str,
36
+ "password": pwd_str,
37
+ }
38
+
39
+
40
+ class PgVectorIndexParam(TypedDict):
41
+ metric: str
42
+ index_type: str
43
+ index_creation_with_options: Sequence[dict[str, Any]]
44
+ maintenance_work_mem: Optional[str]
45
+ max_parallel_workers: Optional[int]
46
+
47
+
48
+ class PgVectorSearchParam(TypedDict):
49
+ metric_fun_op: LiteralString
50
+
51
+
52
+ class PgVectorSessionCommands(TypedDict):
53
+ session_options: Sequence[dict[str, Any]]
54
+
55
+
56
+ class PgVectorIndexConfig(BaseModel, DBCaseConfig):
57
+ metric_type: MetricType | None = None
58
+ create_index_before_load: bool = False
59
+ create_index_after_load: bool = True
60
+
61
+ def parse_metric(self) -> str:
62
+ if self.metric_type == MetricType.L2:
63
+ return "vector_l2_ops"
64
+ elif self.metric_type == MetricType.IP:
65
+ return "vector_ip_ops"
66
+ return "vector_cosine_ops"
67
+
68
+ def parse_metric_fun_op(self) -> LiteralString:
69
+ if self.metric_type == MetricType.L2:
70
+ return "<->"
71
+ elif self.metric_type == MetricType.IP:
72
+ return "<#>"
73
+ return "<=>"
74
+
75
+ def parse_metric_fun_str(self) -> str:
76
+ if self.metric_type == MetricType.L2:
77
+ return "l2_distance"
78
+ elif self.metric_type == MetricType.IP:
79
+ return "max_inner_product"
80
+ return "cosine_distance"
81
+
82
+ @abstractmethod
83
+ def index_param(self) -> PgVectorIndexParam:
84
+ ...
85
+
86
+ @abstractmethod
87
+ def search_param(self) -> PgVectorSearchParam:
88
+ ...
89
+
90
+ @abstractmethod
91
+ def session_param(self) -> PgVectorSessionCommands:
92
+ ...
93
+
94
+ @staticmethod
95
+ def _optionally_build_with_options(with_options: Mapping[str, Any]) -> Sequence[dict[str, Any]]:
96
+ """Walk through mappings, creating a List of {key1 = value} pairs. That will be used to build a where clause"""
97
+ options = []
98
+ for option_name, value in with_options.items():
99
+ if value is not None:
100
+ options.append(
101
+ {
102
+ "option_name": option_name,
103
+ "val": str(value),
104
+ }
105
+ )
106
+ return options
107
+
108
+ @staticmethod
109
+ def _optionally_build_set_options(
110
+ set_mapping: Mapping[str, Any]
111
+ ) -> Sequence[dict[str, Any]]:
112
+ """Walk through options, creating 'SET 'key1 = "value1";' commands"""
113
+ session_options = []
114
+ for setting_name, value in set_mapping.items():
115
+ if value:
116
+ session_options.append(
117
+ {"parameter": {
118
+ "setting_name": setting_name,
119
+ "val": str(value),
120
+ },
121
+ }
122
+ )
123
+ return session_options
124
+
125
+
126
+ class PgVectorIVFFlatConfig(PgVectorIndexConfig):
127
+ """
128
+ An IVFFlat index divides vectors into lists, and then searches a subset of those lists that are
129
+ closest to the query vector. It has faster build times and uses less memory than HNSW,
130
+ but has lower query performance (in terms of speed-recall tradeoff).
131
+
132
+ Three keys to achieving good recall are:
133
+
134
+ Create the index after the table has some data
135
+ Choose an appropriate number of lists - a good place to start is rows / 1000 for up to 1M rows and sqrt(rows) for
136
+ over 1M rows.
137
+ When querying, specify an appropriate number of probes (higher is better for recall, lower is better for speed) -
138
+ a good place to start is sqrt(lists)
139
+ """
140
+
141
+ lists: int | None
142
+ probes: int | None
143
+ index: IndexType = IndexType.ES_IVFFlat
144
+ maintenance_work_mem: Optional[str] = None
145
+ max_parallel_workers: Optional[int] = None
146
+
147
+ def index_param(self) -> PgVectorIndexParam:
148
+ index_parameters = {"lists": self.lists}
149
+ return {
150
+ "metric": self.parse_metric(),
151
+ "index_type": self.index.value,
152
+ "index_creation_with_options": self._optionally_build_with_options(
153
+ index_parameters
154
+ ),
155
+ "maintenance_work_mem": self.maintenance_work_mem,
156
+ "max_parallel_workers": self.max_parallel_workers,
157
+ }
158
+
159
+ def search_param(self) -> PgVectorSearchParam:
160
+ return {
161
+ "metric_fun_op": self.parse_metric_fun_op(),
162
+ }
163
+
164
+ def session_param(self) -> PgVectorSessionCommands:
165
+ session_parameters = {"ivfflat.probes": self.probes}
166
+ return {
167
+ "session_options": self._optionally_build_set_options(session_parameters)
168
+ }
169
+
170
+
171
+ class PgVectorHNSWConfig(PgVectorIndexConfig):
172
+ """
173
+ An HNSW index creates a multilayer graph. It has better query performance than IVFFlat (in terms of
174
+ speed-recall tradeoff), but has slower build times and uses more memory. Also, an index can be
175
+ created without any data in the table since there isn't a training step like IVFFlat.
176
+ """
177
+
178
+ m: int | None # DETAIL: Valid values are between "2" and "100".
179
+ ef_construction: (
180
+ int | None
181
+ ) # ef_construction must be greater than or equal to 2 * m
182
+ ef_search: int | None
183
+ index: IndexType = IndexType.ES_HNSW
184
+ maintenance_work_mem: Optional[str] = None
185
+ max_parallel_workers: Optional[int] = None
186
+
187
+ def index_param(self) -> PgVectorIndexParam:
188
+ index_parameters = {"m": self.m, "ef_construction": self.ef_construction}
189
+ return {
190
+ "metric": self.parse_metric(),
191
+ "index_type": self.index.value,
192
+ "index_creation_with_options": self._optionally_build_with_options(
193
+ index_parameters
194
+ ),
195
+ "maintenance_work_mem": self.maintenance_work_mem,
196
+ "max_parallel_workers": self.max_parallel_workers,
197
+ }
198
+
199
+ def search_param(self) -> PgVectorSearchParam:
200
+ return {
201
+ "metric_fun_op": self.parse_metric_fun_op(),
202
+ }
203
+
204
+ def session_param(self) -> PgVectorSessionCommands:
205
+ session_parameters = {"hnsw.ef_search": self.ef_search}
206
+ return {
207
+ "session_options": self._optionally_build_set_options(session_parameters)
208
+ }
209
+
210
+
211
+ _pgvector_case_config = {
212
+ IndexType.HNSW: PgVectorHNSWConfig,
213
+ IndexType.ES_HNSW: PgVectorHNSWConfig,
214
+ IndexType.IVFFlat: PgVectorIVFFlatConfig,
215
+ }
@@ -0,0 +1,347 @@
1
+ """Wrapper around the Pgvector vector database over VectorDB"""
2
+
3
+ import logging
4
+ import pprint
5
+ from contextlib import contextmanager
6
+ from typing import Any, Generator, Optional, Tuple, Sequence
7
+
8
+ import numpy as np
9
+ import psycopg
10
+ from pgvector.psycopg import register_vector
11
+ from psycopg import Connection, Cursor, sql
12
+
13
+ from ..api import VectorDB
14
+ from .config import PgVectorConfigDict, PgVectorIndexConfig
15
+
16
+ log = logging.getLogger(__name__)
17
+
18
+
19
+ class PgVector(VectorDB):
20
+ """Use psycopg instructions"""
21
+
22
+ conn: psycopg.Connection[Any] | None = None
23
+ cursor: psycopg.Cursor[Any] | None = None
24
+
25
+ # TODO add filters support
26
+ _unfiltered_search: sql.Composed
27
+
28
+ def __init__(
29
+ self,
30
+ dim: int,
31
+ db_config: PgVectorConfigDict,
32
+ db_case_config: PgVectorIndexConfig,
33
+ collection_name: str = "pg_vector_collection",
34
+ drop_old: bool = False,
35
+ **kwargs,
36
+ ):
37
+ self.name = "PgVector"
38
+ self.db_config = db_config
39
+ self.case_config = db_case_config
40
+ self.table_name = collection_name
41
+ self.dim = dim
42
+
43
+ self._index_name = "pgvector_index"
44
+ self._primary_field = "id"
45
+ self._vector_field = "embedding"
46
+
47
+ # construct basic units
48
+ self.conn, self.cursor = self._create_connection(**self.db_config)
49
+
50
+ # create vector extension
51
+ self.cursor.execute("CREATE EXTENSION IF NOT EXISTS vector")
52
+ self.conn.commit()
53
+
54
+ log.info(f"{self.name} config values: {self.db_config}\n{self.case_config}")
55
+ if not any(
56
+ (
57
+ self.case_config.create_index_before_load,
58
+ self.case_config.create_index_after_load,
59
+ )
60
+ ):
61
+ err = f"{self.name} config must create an index using create_index_before_load and/or create_index_after_load"
62
+ log.error(err)
63
+ raise RuntimeError(
64
+ f"{err}\n{pprint.pformat(self.db_config)}\n{pprint.pformat(self.case_config)}"
65
+ )
66
+
67
+ if drop_old:
68
+ # self.pg_table.drop(pg_engine, checkfirst=True)
69
+ self._drop_index()
70
+ self._drop_table()
71
+ self._create_table(dim)
72
+ if self.case_config.create_index_before_load:
73
+ self._create_index()
74
+
75
+ self.cursor.close()
76
+ self.conn.close()
77
+ self.cursor = None
78
+ self.conn = None
79
+
80
+ @staticmethod
81
+ def _create_connection(**kwargs) -> Tuple[Connection, Cursor]:
82
+ conn = psycopg.connect(**kwargs)
83
+ register_vector(conn)
84
+ conn.autocommit = False
85
+ cursor = conn.cursor()
86
+
87
+ assert conn is not None, "Connection is not initialized"
88
+ assert cursor is not None, "Cursor is not initialized"
89
+
90
+ return conn, cursor
91
+
92
+ @contextmanager
93
+ def init(self) -> Generator[None, None, None]:
94
+ """
95
+ Examples:
96
+ >>> with self.init():
97
+ >>> self.insert_embeddings()
98
+ >>> self.search_embedding()
99
+ """
100
+
101
+ self.conn, self.cursor = self._create_connection(**self.db_config)
102
+
103
+ # index configuration may have commands defined that we should set during each client session
104
+ session_options: Sequence[dict[str, Any]] = self.case_config.session_param()["session_options"]
105
+
106
+ if len(session_options) > 0:
107
+ for setting in session_options:
108
+ command = sql.SQL("SET {setting_name} " + "= {val};").format(
109
+ setting_name=sql.Identifier(setting['parameter']['setting_name']),
110
+ val=sql.Identifier(str(setting['parameter']['val'])),
111
+ )
112
+ log.debug(command.as_string(self.cursor))
113
+ self.cursor.execute(command)
114
+ self.conn.commit()
115
+
116
+ self._unfiltered_search = sql.Composed(
117
+ [
118
+ sql.SQL("SELECT id FROM public.{} ORDER BY embedding ").format(
119
+ sql.Identifier(self.table_name)
120
+ ),
121
+ sql.SQL(self.case_config.search_param()["metric_fun_op"]),
122
+ sql.SQL(" %s::vector LIMIT %s::int"),
123
+ ]
124
+ )
125
+
126
+ try:
127
+ yield
128
+ finally:
129
+ self.cursor.close()
130
+ self.conn.close()
131
+ self.cursor = None
132
+ self.conn = None
133
+
134
+ def _drop_table(self):
135
+ assert self.conn is not None, "Connection is not initialized"
136
+ assert self.cursor is not None, "Cursor is not initialized"
137
+ log.info(f"{self.name} client drop table : {self.table_name}")
138
+
139
+ self.cursor.execute(
140
+ sql.SQL("DROP TABLE IF EXISTS public.{table_name}").format(
141
+ table_name=sql.Identifier(self.table_name)
142
+ )
143
+ )
144
+ self.conn.commit()
145
+
146
+ def ready_to_load(self):
147
+ pass
148
+
149
+ def optimize(self):
150
+ self._post_insert()
151
+
152
+ def _post_insert(self):
153
+ log.info(f"{self.name} post insert before optimize")
154
+ if self.case_config.create_index_after_load:
155
+ self._drop_index()
156
+ self._create_index()
157
+
158
+ def _drop_index(self):
159
+ assert self.conn is not None, "Connection is not initialized"
160
+ assert self.cursor is not None, "Cursor is not initialized"
161
+ log.info(f"{self.name} client drop index : {self._index_name}")
162
+
163
+ drop_index_sql = sql.SQL("DROP INDEX IF EXISTS {index_name}").format(
164
+ index_name=sql.Identifier(self._index_name)
165
+ )
166
+ log.debug(drop_index_sql.as_string(self.cursor))
167
+ self.cursor.execute(drop_index_sql)
168
+ self.conn.commit()
169
+
170
+ def _set_parallel_index_build_param(self):
171
+ assert self.conn is not None, "Connection is not initialized"
172
+ assert self.cursor is not None, "Cursor is not initialized"
173
+
174
+ index_param = self.case_config.index_param()
175
+
176
+ if index_param["maintenance_work_mem"] is not None:
177
+ self.cursor.execute(
178
+ sql.SQL("SET maintenance_work_mem TO {};").format(
179
+ index_param["maintenance_work_mem"]
180
+ )
181
+ )
182
+ self.cursor.execute(
183
+ sql.SQL("ALTER USER {} SET maintenance_work_mem TO {};").format(
184
+ sql.Identifier(self.db_config["user"]),
185
+ index_param["maintenance_work_mem"],
186
+ )
187
+ )
188
+ self.conn.commit()
189
+
190
+ if index_param["max_parallel_workers"] is not None:
191
+ self.cursor.execute(
192
+ sql.SQL("SET max_parallel_maintenance_workers TO '{}';").format(
193
+ index_param["max_parallel_workers"]
194
+ )
195
+ )
196
+ self.cursor.execute(
197
+ sql.SQL(
198
+ "ALTER USER {} SET max_parallel_maintenance_workers TO '{}';"
199
+ ).format(
200
+ sql.Identifier(self.db_config["user"]),
201
+ index_param["max_parallel_workers"],
202
+ )
203
+ )
204
+ self.cursor.execute(
205
+ sql.SQL("SET max_parallel_workers TO '{}';").format(
206
+ index_param["max_parallel_workers"]
207
+ )
208
+ )
209
+ self.cursor.execute(
210
+ sql.SQL(
211
+ "ALTER USER {} SET max_parallel_workers TO '{}';"
212
+ ).format(
213
+ sql.Identifier(self.db_config["user"]),
214
+ index_param["max_parallel_workers"],
215
+ )
216
+ )
217
+ self.cursor.execute(
218
+ sql.SQL(
219
+ "ALTER TABLE {} SET (parallel_workers = {});"
220
+ ).format(
221
+ sql.Identifier(self.table_name),
222
+ index_param["max_parallel_workers"],
223
+ )
224
+ )
225
+ self.conn.commit()
226
+
227
+ results = self.cursor.execute(
228
+ sql.SQL("SHOW max_parallel_maintenance_workers;")
229
+ ).fetchall()
230
+ results.extend(
231
+ self.cursor.execute(sql.SQL("SHOW max_parallel_workers;")).fetchall()
232
+ )
233
+ results.extend(
234
+ self.cursor.execute(sql.SQL("SHOW maintenance_work_mem;")).fetchall()
235
+ )
236
+ log.info(f"{self.name} parallel index creation parameters: {results}")
237
+
238
+ def _create_index(self):
239
+ assert self.conn is not None, "Connection is not initialized"
240
+ assert self.cursor is not None, "Cursor is not initialized"
241
+ log.info(f"{self.name} client create index : {self._index_name}")
242
+
243
+ index_param = self.case_config.index_param()
244
+ self._set_parallel_index_build_param()
245
+ options = []
246
+ for option in index_param["index_creation_with_options"]:
247
+ if option['val'] is not None:
248
+ options.append(
249
+ sql.SQL("{option_name} = {val}").format(
250
+ option_name=sql.Identifier(option['option_name']),
251
+ val=sql.Identifier(str(option['val'])),
252
+ )
253
+ )
254
+ if any(options):
255
+ with_clause = sql.SQL("WITH ({});").format(sql.SQL(", ").join(options))
256
+ else:
257
+ with_clause = sql.Composed(())
258
+
259
+ index_create_sql = sql.SQL(
260
+ "CREATE INDEX IF NOT EXISTS {index_name} ON public.{table_name} USING {index_type} (embedding {embedding_metric})"
261
+ ).format(
262
+ index_name=sql.Identifier(self._index_name),
263
+ table_name=sql.Identifier(self.table_name),
264
+ index_type=sql.Identifier(index_param["index_type"]),
265
+ embedding_metric=sql.Identifier(index_param["metric"]),
266
+ )
267
+ index_create_sql_with_with_clause = (
268
+ index_create_sql + with_clause
269
+ ).join(" ")
270
+ log.debug(index_create_sql_with_with_clause.as_string(self.cursor))
271
+ self.cursor.execute(index_create_sql_with_with_clause)
272
+ self.conn.commit()
273
+
274
+ def _create_table(self, dim: int):
275
+ assert self.conn is not None, "Connection is not initialized"
276
+ assert self.cursor is not None, "Cursor is not initialized"
277
+
278
+ try:
279
+ log.info(f"{self.name} client create table : {self.table_name}")
280
+
281
+ # create table
282
+ self.cursor.execute(
283
+ sql.SQL(
284
+ "CREATE TABLE IF NOT EXISTS public.{table_name} (id BIGINT PRIMARY KEY, embedding vector({dim}));"
285
+ ).format(table_name=sql.Identifier(self.table_name), dim=dim)
286
+ )
287
+ self.cursor.execute(
288
+ sql.SQL(
289
+ "ALTER TABLE public.{table_name} ALTER COLUMN embedding SET STORAGE PLAIN;"
290
+ ).format(table_name=sql.Identifier(self.table_name))
291
+ )
292
+ self.conn.commit()
293
+ except Exception as e:
294
+ log.warning(
295
+ f"Failed to create pgvector table: {self.table_name} error: {e}"
296
+ )
297
+ raise e from None
298
+
299
+ def insert_embeddings(
300
+ self,
301
+ embeddings: list[list[float]],
302
+ metadata: list[int],
303
+ **kwargs: Any,
304
+ ) -> Tuple[int, Optional[Exception]]:
305
+ assert self.conn is not None, "Connection is not initialized"
306
+ assert self.cursor is not None, "Cursor is not initialized"
307
+
308
+ try:
309
+ metadata_arr = np.array(metadata)
310
+ embeddings_arr = np.array(embeddings)
311
+
312
+ with self.cursor.copy(
313
+ sql.SQL("COPY public.{table_name} FROM STDIN (FORMAT BINARY)").format(
314
+ table_name=sql.Identifier(self.table_name)
315
+ )
316
+ ) as copy:
317
+ copy.set_types(["bigint", "vector"])
318
+ for i, row in enumerate(metadata_arr):
319
+ copy.write_row((row, embeddings_arr[i]))
320
+ self.conn.commit()
321
+
322
+ if kwargs.get("last_batch"):
323
+ self._post_insert()
324
+
325
+ return len(metadata), None
326
+ except Exception as e:
327
+ log.warning(
328
+ f"Failed to insert data into pgvector table ({self.table_name}), error: {e}"
329
+ )
330
+ return 0, e
331
+
332
+ def search_embedding(
333
+ self,
334
+ query: list[float],
335
+ k: int = 100,
336
+ filters: dict | None = None,
337
+ timeout: int | None = None,
338
+ ) -> list[int]:
339
+ assert self.conn is not None, "Connection is not initialized"
340
+ assert self.cursor is not None, "Cursor is not initialized"
341
+
342
+ # TODO add filters support
343
+ result = self.cursor.execute(
344
+ self._unfiltered_search, (query, k), prepare=True, binary=True
345
+ )
346
+
347
+ return [int(i[0]) for i in result.fetchall()]
@@ -46,11 +46,9 @@ class SerialInsertRunner:
46
46
  del(emb_np)
47
47
  log.debug(f"batch dataset size: {len(all_embeddings)}, {len(all_metadata)}")
48
48
 
49
- last_batch = self.dataset.data.size - count == len(all_metadata)
50
49
  insert_count, error = self.db.insert_embeddings(
51
50
  embeddings=all_embeddings,
52
51
  metadata=all_metadata,
53
- last_batch=last_batch,
54
52
  )
55
53
  if error is not None:
56
54
  raise error