Skip to content

Commit 7de49c6

Browse files
niebayesyinbo3
andauthored
docs: vector index (#261)
* add docs for vector index Co-authored-by: YinBo <[email protected]>
1 parent 7ac3720 commit 7de49c6

File tree

6 files changed

+260
-16
lines changed

6 files changed

+260
-16
lines changed

dir.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,9 @@
334334
- title_en: Quick Start
335335
title_cn: 快速开始
336336
path: vector-search/quick-start
337+
- title_en: Vector Index
338+
title_cn: 向量索引
339+
path: vector-search/vector-index
337340

338341
- title_en: Key Value Data Model
339342
title_cn: Key-Value 存储

en_US/vector-search/vector-index.md

Whitespace-only changes.
15.8 KB
Loading

zh_CN/vector-search/overview.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
1-
# 向量检索概述
1+
# 向量检索技术指南
22

3-
向量检索是一种在非结构化数据中进行快速近似搜索的技术。常见的非结构化数据,例如文档、图像、音频、视频等数据,作为向量嵌入(Vector Embedding)存储在数据库中。用户使用向量检索技术,在数据库中查找与给定的目标向量最相似的数条向量,以及这些向量所对应的元信息,最终交给上层应用执行业务操作。
3+
## 概述
4+
向量检索是一种针对非结构化数据的高效近似搜索技术。该技术将文档、图像、音频、视频等非结构化数据转换为向量嵌入(Vector Embedding)存储在数据库中,通过计算向量间的相似度来实现数据检索。
45

56
![向量检索框架](../assets/vector-search-arch.png)
67

78
以电商搜索引擎业务为例,业务方使用嵌入模型(Embedding Model)将商品的名字、标签、描述、图片等统一转换为向量嵌入,存储到 Datalayers 等原生提供向量存储、向量检索的数据库中。用户在软件中搜索商品时,搜索词或图片被同一个嵌入模型转换为向量嵌入。软件后端将其作为目标向量,在数据库中进行检索。最终查找出与搜索词或图片最近似的一系列商品。
89

910
与传统的全文搜索不同,向量检索并不使用关键词进行精确或模糊匹配,而是用向量距离近似度量语义相似度,从而实现高效的近似检索。
1011

11-
## 为什么选择 Datalayers 向量检索
12-
13-
- 原生向量类型:原生提供向量类型,支持高压缩率向量存储、高性能向量检索。
14-
- 混合检索:支持向量检索、以及向量检索与标量检索的混合检索。
15-
- 简单易用:原生 SQL 支持,兼容 MySQL 方言。
16-
- 多模态:提供时序、关系、流计算、日志等引擎,快速集成向量检索至任何业务场景。
17-
- 存算分离:计算和存储节点解耦,均可无限水平扩展,支持海量向量数据的存储和搜索。
18-
- 分布式:采用分布式架构,提供完备的容灾、备份、恢复策略。
19-
- 数据安全:支持高性能、强隐私的数据加密。
12+
## Datalayers 向量检索优势
13+
14+
| 特性 | 说明 |
15+
| ------------- | ------------------------------------------------------------------- |
16+
| 原生向量类型 | 原生提供向量类型,支持高压缩率向量存储、高性能向量检索 |
17+
| 丰富的索引 | 丰富的索引类型,以满足各种应用场景 |
18+
| 混合检索 | 支持向量检索、以及向量检索与标量检索的混合检索 |
19+
| 简单易用 | 原生 SQL 支持,兼容 MySQL 方言 |
20+
| 多模态 | 提供时序、关系、流计算、日志等引擎,快速集成向量检索至任何业务场景 |
21+
| 存算分离 | 计算和存储节点解耦,均可无限水平扩展,支持海量向量数据的存储和搜索 |
22+
| 分布式 | 采用分布式架构,提供完备的容灾、备份、恢复策略 |
2023

2124
## 核心概念
2225

zh_CN/vector-search/quick-start.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
# 快速上手
1+
# 快速开始
22

3-
本教程介绍如何使用 Datalayers 数据库执行向量存储和向量检索
3+
本文档介绍如何在 Datalayers 数据库中进行向量数据的存储和检索操作
44

5-
我们创建一个含有向量列的表。
5+
## 创建向量表
66

7+
首先创建一个包含向量列的数据表:
78
```sql
89
CREATE TABLE t(
910
ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
@@ -18,17 +19,19 @@ ENGINE=TimeSeries
1819

1920
其中,`embed VECTOR(3)` 表示创建一个名为 `embed` 的向量列,该列中每个向量的维度为 3。
2021

21-
向该表写入一些数据:
22+
## 插入向量数据
2223

24+
向表中插入示例数据:
2325
```sql
2426
INSERT INTO t (id, tag, embed) VALUES
2527
(1, 'cat', [1.0, 1.1, 1.2]),
2628
(2, 'rat', [4.3, 12.1, 5.5]),
2729
(3, 'mouse', [6.4, 9.1, 7.8]);
2830
```
2931

30-
查询该表,应该得到如下数据:
32+
## 查询数据
3133

34+
查看表中所有数据:
3235
```sql
3336
> SELECT * FROM t ORDER BY id;
3437
+---------------------------+----+-------+------------------+
@@ -40,6 +43,7 @@ INSERT INTO t (id, tag, embed) VALUES
4043
+---------------------------+----+-------+------------------+
4144
```
4245

46+
## 向量相似度检索
4347
使用 ORDER BY + LIMIT 语句,构造出向量检索所对应的 SQL。例如搜索与目标向量最近的一个向量所对应的 tag:
4448

4549
```sql
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
# 向量索引技术指南
2+
3+
## 概述
4+
向量索引是加速大规模向量数据集检索的关键技术。Datalayers 支持多种向量索引类型,通过三层式架构实现高效的近似最近邻搜索。
5+
使用向量索引会带来额外的构建索引开销、检索开销,同时可能会降低召回率(recall),因此需要根据具体场景选择合适的索引、以及配置合适的参数。
6+
7+
## 向量索引模型
8+
9+
Datalayers 使用三层式结构对主流的向量索引进行统一建模。
10+
11+
![向量索引模型](../assets/vector-index-model.png)
12+
13+
### IVF Model
14+
15+
IVF(Inverted File)模型指质心 -> 向量组的倒排索引。在构建向量索引时,我们首先利用 K-Means 聚簇算法将向量数据集分成若干个向量组,每个向量组存在一个质心(Centroid)。IVF 模型维护了每个质心与其所在向量组的映射关系。
16+
17+
### Cell Index
18+
19+
我们将每一个向量组称为一个 Cell,该名称延用知名向量检索库 Faiss 中的命名。Datalayers 支持给向量组内的向量构建索引,称为 Cell Index,以加速向量组内的近似最近邻搜索。例如,当我们使用 HNSW 向量索引时,我们会为每个向量组构建一个图索引,利用图数据结构来加速检索。
20+
21+
### Vector Store
22+
23+
Vector Store 是向量的存储抽象。为了节省存储空间,我们支持对向量进行量化。量化指将原始向量投影到另一个更紧凑的向量空间,以达到数据压缩的目的。主流的量化算法包括乘积量化(PQ)、标量量化(SQ)等,它们均是有损、不可逆的量化算法,因此在搜索时会降低召回率。
24+
25+
## 基于索引的向量检索
26+
27+
给定查询向量 Q 以及 top-K 中的 K,基于索引的三层式结构,Datalayers 的向量检索分成如下步骤:
28+
29+
1. 模糊搜索:我们首先访问 IVF Model,计算 Q 与所有质心的距离,并取最近的 P 个质心所对应的向量组。
30+
2. 精确搜索:对于每个向量组,我们使用 Cell Index 加速向量组内的近似最近邻搜索,每个质心得到 top-N 个与 Q 距离最近的向量。
31+
3. 精炼:考虑到向量索引会降低召回率,对于搜索得到的 `P * N` 个向量,计算它们与 Q 的距离,得到最终的 top-K 个距离最近的向量。其中 `N / K` 称为 `refine_factor`,表示为了补偿召回率,我们在精确搜索时每个向量组额外检索了多少个向量。这个步骤称为精炼(Refine)。
32+
33+
## 索引类型
34+
35+
| | IVF Model | Cell Index | Vector Store | 是否已支持 |
36+
| :----- | :----------: | :---------: | :----------: | :-----: |
37+
| FLAT | Cell 个数固定为 1 | FLAT | FLAT ||
38+
| IVF_FLAT | 支持配置 Cell 个数 | FLAT | FLAT ||
39+
| IVF_PQ | 支持配置 Cell 个数 | FLAT | PQ ||
40+
| IVF_SQ | 支持配置 Cell 个数 | FLAT | SQ ||
41+
| IVF_RQ | 支持配置 Cell 个数 | FLAT | RQ ||
42+
| HNSW | Cell 个数固定为 1 | HNSW | FLAT ||
43+
| IVF_HNSW | 支持配置 Cell 个数 | HNSW | FLAT ||
44+
| IVF_HNSW_PQ | 支持配置 Cell 个数 | HNSW | PQ ||
45+
| IVF_HNSW_SQ | 支持配置 Cell 个数 | HNSW | SQ ||
46+
| IVF_HNSW_RQ | 支持配置 Cell 个数 | HNSW | RQ ||
47+
48+
注:
49+
50+
- Cell Index 为 FLAT,表示向量组内的搜索退回到平搜(Flat Search),即搜索所有向量。
51+
- Cell Index 为 HNSW,表示使用 HNSW(Hierarchical Navigable Small Worlds)索引加速向量组内的搜索。
52+
- Vector Store 为 FLAT,表示不使用任何量化算法,而存储原始、未经压缩的向量。
53+
- PQ 指 Product Quantization,即乘积量化。
54+
- SQ 指 Scalar Quantization,即标量量化。
55+
- RQ 指 RaBit Quantization。
56+
57+
## 示例
58+
59+
我们提供了一个 Python 脚本,展示如何使用向量索引来加速向量检索。这个脚本执行的步骤如下:
60+
61+
1. 创建数据库 `demo`
62+
2. 创建表 `t`。表中包含一个向量列 `embed`,维度为 64。同时为该列指定 IVF_PQ 索引,同时设置构建索引的距离函数为 L2。
63+
3. 写入 5000 条随机数据。
64+
4. Flush 数据。
65+
5. 等待索引构建完成,默认等待 15 秒。
66+
6. 使用随机向量,执行向量检索。
67+
68+
``` python
69+
import http
70+
import json
71+
import random
72+
import time
73+
from http.client import HTTPConnection
74+
75+
76+
def main():
77+
host = "0.0.0.0"
78+
port = 8361
79+
url = "http://{}:{}/api/v1/sql".format(host, port)
80+
headers = {
81+
"Content-Type": "application/binary",
82+
"Authorization": "Basic YWRtaW46cHVibGlj"
83+
}
84+
conn = http.client.HTTPConnection(host=host, port=port)
85+
86+
# Create database `demo`.
87+
sql = "CREATE DATABASE IF NOT EXISTS demo;"
88+
conn.request(method="POST", url=url, headers=headers, body=sql)
89+
print_response("创建数据库", conn)
90+
91+
# Create table `t`.
92+
sql = '''
93+
CREATE TABLE IF NOT EXISTS `demo`.`t` (
94+
`ts` TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
95+
`sid` INT32 NOT NULL,
96+
`value` REAL,
97+
`flag` INT8,
98+
`embed` VECTOR(64),
99+
TIMESTAMP KEY(`ts`),
100+
VECTOR INDEX `my_vector_index`(`embed`) WITH (TYPE=IVF_PQ, DISTANCE=L2)
101+
)
102+
PARTITION BY HASH (`sid`) PARTITIONS 1
103+
ENGINE=TimeSeries
104+
WITH (
105+
MEMTABLE_SIZE=1024MB,
106+
STORAGE_TYPE=LOCAL,
107+
UPDATE_MODE=APPEND
108+
);
109+
'''
110+
conn.request(method="POST", url=url, headers=headers, body=sql)
111+
print_response("创建表", conn)
112+
113+
# 分批插入数据
114+
insert_data(
115+
conn, url, headers, total_rows=5000, batch_size=1000)
116+
117+
# Flush 数据
118+
flush_data(conn, url, headers)
119+
120+
# 等待索引构建完成
121+
print("等待索引构建完成...")
122+
time.sleep(15)
123+
124+
# Vector search with random query vector
125+
query_vector = generate_random_vector(64)
126+
sql = f"SELECT value FROM demo.t WHERE sid = 1 ORDER BY l2_distance(embed, {query_vector}) LIMIT 1"
127+
print(f"执行向量检索: {sql}")
128+
conn.request(method="POST", url=url, headers=headers, body=sql)
129+
print_query_result(conn)
130+
131+
132+
def generate_random_vector(dim: int) -> str:
133+
"""生成随机向量字符串表示"""
134+
vector = [round(random.uniform(-1.0, 1.0), 6) for _ in range(dim)]
135+
return "[" + ", ".join(map(str, vector)) + "]"
136+
137+
138+
def insert_data(conn: HTTPConnection, url: str, headers: dict, total_rows: int, batch_size: int):
139+
"""分批插入数据"""
140+
num_batches = total_rows // batch_size
141+
142+
print(f"开始插入 {total_rows} 条数据,分 {num_batches} 批次,每批 {batch_size}")
143+
144+
for batch in range(num_batches):
145+
print(f"插入第 {batch + 1}/{num_batches} 批次...")
146+
147+
values = []
148+
for _ in range(batch_size):
149+
sid = random.randint(0, 5000)
150+
value = round(random.uniform(0.0, 100.0), 2)
151+
flag = random.randint(0, 1)
152+
embed = generate_random_vector(64)
153+
154+
values.append(f"({sid}, {value}, {flag}, {embed})")
155+
156+
sql = f"INSERT INTO demo.t (sid, value, flag, embed) VALUES {', '.join(values)}"
157+
conn.request(method="POST", url=url, headers=headers, body=sql)
158+
159+
response = conn.getresponse()
160+
161+
if response.status == 200:
162+
print(f"✓ 第 {batch + 1} 批次插入成功")
163+
else:
164+
print(f"✗ 第 {batch + 1} 批次插入失败: {response.status} {response.reason}")
165+
166+
response.read()
167+
168+
time.sleep(0.5)
169+
170+
171+
def flush_data(conn: HTTPConnection, url: str, headers: dict):
172+
print("正在 Flush 数据")
173+
sql = "FLUSH TABLE demo.t SYNC"
174+
conn.request(method="POST", url=url, headers=headers, body=sql)
175+
176+
response = conn.getresponse()
177+
178+
if response.status == 200:
179+
print(f"Flush 数据成功")
180+
else:
181+
print(f"Flush 数据失败: {response.status} {response.reason}")
182+
183+
response.read()
184+
185+
186+
def print_response(msg: str, conn: HTTPConnection):
187+
with conn.getresponse() as response:
188+
if response.status == 200:
189+
print(f"{msg} 成功")
190+
else:
191+
print(f"{msg} 失败: {response.status} {response.reason}")
192+
193+
response.read()
194+
195+
def print_query_result(conn: HTTPConnection):
196+
print("检索结果:")
197+
198+
with conn.getresponse() as response:
199+
data = response.read().decode('utf-8')
200+
obj = json.loads(data)
201+
202+
columns = obj['result']['columns']
203+
rows = obj['result']['values']
204+
205+
print(columns)
206+
for row in rows:
207+
print(row)
208+
209+
if __name__ == "__main__":
210+
main()
211+
```
212+
213+
在测试机器上,为 5000 条数据构建向量索引大致需要 2 秒。为了确认在您的机器上索引已经构建完成,您可以通过执行 `SHOW TASKS` 命令检视当前正在执行、被挂起的索引构建任务。如果 `build_index` 任务的 `running``pending` 数量为 0,说明所有索引已经构建完毕。
214+
215+
``` sql
216+
> show tasks
217+
218+
+-------------+---------+---------+-------------------+-------------+----------------------------------------------------+
219+
| type | running | pending | concurrence_limit | queue_limit | description |
220+
+-------------+---------+---------+-------------------+-------------+----------------------------------------------------+
221+
| build_index | 1 | 1 | 1 | 10000 | Build index. |
222+
| compact | 0 | 0 | 3 | 10000 | Compaction, TTL clean. |
223+
| flush | 0 | 0 | 10 | 10000 | Flush memtable into file. |
224+
| gc | 0 | 0 | 100 | 10000 | Delete files when drop table, truncate table, etc. |
225+
| timer | 0 | 18 | 0 | 0 | Delayed tasks executed at specified time |
226+
| workflow | 0 | 0 | 10 | 10000 | Table DDL operation. |
227+
+-------------+---------+---------+-------------------+-------------+----------------------------------------------------+
228+
```
229+
230+
## 注意事项
231+
232+
- 构建索引时的距离函数与搜索时的距离函数必须一致,否则无法触发向量索引。
233+
- 目前仅支持为 32 维以上的向量列构建向量索引。
234+
- 目前不支持为索引配置构建参数、搜索参数,仅支持使用内部默认参数。

0 commit comments

Comments
 (0)