前言

AI 带火了向量数据库,来对比几个不同的向量数据库,使用感受和差异,个人非标测试,仅供参考。

具体每个我就不多介绍了,官网都有

使用场景

正所谓不设定场景的测试和比较都是耍流氓,所以先说明一下使用场景。由于大厂的技术选型往往就直接冲着数据量、扩展性等等方向去了,而这次我们的角度不一样,更贴近与个人用户使用,准备用在个人项目中,并且直接安装在个人电脑上。

  • 本来就是单点,不需要考虑什么扩展
  • 数据量不大,一个人撑死能有多大的数据
  • 使尽可能减少资源占用

安装

全部使用 docker 一把梭,使用的宿主机相同配置,都是足够的。

milvus

其中 attu 是专门用来查看的,默认自带的功能比较少,没法直接操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
version: '3.5'
services:
etcd:
container_name: milvus-etcd
image: quay.io/coreos/etcd:v3.5.18
environment:
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1000
- ETCD_QUOTA_BACKEND_BYTES=4294967296
- ETCD_SNAPSHOT_COUNT=50000
command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
healthcheck:
test: ["CMD", "etcdctl", "endpoint", "health"]
interval: 30s
timeout: 20s
retries: 3

minio:
container_name: milvus-minio
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
environment:
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
ports:
- "9001:9001"
- "9000:9000"
command: minio server /minio_data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3

standalone:
container_name: milvus-standalone
image: milvusdb/milvus:v2.5.5
command: ["milvus", "run", "standalone"]
security_opt:
- seccomp:unconfined
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
interval: 30s
start_period: 90s
timeout: 20s
retries: 3
ports:
- "19530:19530"
- "9091:9091"
depends_on:
- "etcd"
- "minio"

attu:
container_name: milvus-attu
image: zilliz/attu:v2.4
environment:
MILVUS_URL: milvus:19530
ports:
- "7000:3000"
networks:
- default

networks:
default:
name: milvus

pgvector

pgvector 是 postgresql 的一个插件,所以安装之后需要执行命令去启用

1
$ docker run --name postgresql -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=password -p 5432:5432 -d pgvector/pgvector:pg17
1
2
3
$ docker exec -it postgresql psql -h 127.0.0.1 -p 5432 -U postgres
psql (17.4 (Debian 17.4-1.pgdg120+2))
Type "help" for help.
1
2
3
4
5
$ postgres=# select * from pg_extension;
oid | extname | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition
-------+---------+----------+--------------+----------------+------------+-----------+--------------
13569 | plpgsql | 10 | 11 | f | 1.0 | |
(1 row)
1
2
3
4
5
6
7
8
9
$ postgres=# CREATE EXTENSION vector;
CREATE EXTENSION

$ postgres=# select * from pg_extension;
oid | extname | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition
-------+---------+----------+--------------+----------------+------------+-----------+--------------
13569 | plpgsql | 10 | 11 | f | 1.0 | |
16388 | vector | 10 | 2200 | t | 0.8.0 | |
(2 rows)

qdrant

1
docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant

elasticsearch

1
2
3
4
5
6
7
8
docker run -d \
--name elasticsearch \
-e "ES_JAVA_OPTS=-Xms2048m -Xmx2048m" \
-e "discovery.type=single-node" \
--privileged \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.17.7

测试

前提

  • 相同的向量数据,是同一个模型 embedding 生成相同的向量数据,维度 1024
  • 相同的索引算法, HNSW (Hierarchical Navigable Small World)
  • 相同的计算方法,COSINE 余弦相似度

查询速度

小数据量下区别不大,大数据量下这个差距会扩大

  • milvus 执行时间:452.41ms
  • pgvector 执行时间:180.65ms
  • qdrant 执行时间:69.00ms
  • elasticsearch 执行时间:314.35ms

CPU

pgvector 的 CPU 波动最不明显,qdrant 和 elasticsearch 相差不大,而 milvus 显然 CPU 敏感,测试多次效果基本一致。

vectordb-test-cpu-usage

内存

vectordb-test-cpu-usage

从内存的角度上来说,很明显 elasticsearch 和 milvus 占用更多,而 qdrant 和 pgvector 从图片中很难看出区别,但实际数据值可以看到一个是 172MiB(pgvector) 一个是 152.9MiB(qdrant) 也区别不大。

1
2
3
4
5
6
7
8
9
10
11
12
CONTAINER ID   NAME                CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS

4c3b1922b36b elasticsearch 0.46% 1.619GiB / 5.126GiB 31.59% 378MB / 31.9MB 201MB / 7.75GB 100

1c0febeff728 postgresql 0.00% 172MiB / 5.126GiB 3.28% 1.28GB / 125MB 342MB / 6.08GB 12

58fa2a1d8558 qdrant 1.37% 152.9MiB / 5.126GiB 2.91% 648MB / 9.41MB 291MB / 8.23GB 52

bb05db1d3099 milvus-standalone 6.92% 368.5MiB / 5.126GiB 7.02% 18.9GB / 14GB 776MB / 5.62GB 88
b0c0ff71478e milvus-attu 0.36% 28.27MiB / 5.126GiB 0.54% 9.66MB / 27.3MB 41.7MB / 96.6MB 23
edad780e6197 milvus-minio 14.68% 133.1MiB / 5.126GiB 2.54% 13.8GB / 18.1GB 18.5GB / 17.5GB 27
fc8e6e0012ae milvus-etcd 0.53% 30.41MiB / 5.126GiB 0.58% 156MB / 118MB 67.9MB / 12.3GB 15

使用

我使用的 go 连接的每个数据库,具体代码就不展示了,每个仓库的使用方式的各有不同,但好在 sdk 都完整所以直接将官方的案例拷贝过来都能用。说几个实际中遇到的问题。

milvus

相比于其他几个,它的使用需要额外在查询之前 LoadCollection ,需要手动 load 到内存里面去。然后如果使用完之后不用了,可以关掉。

解析结果的时候一定注意查询结果还在内侧,而非最外面

1
2
3
4
5
searchResult, err := m.client.Search(

// 需要两个循环
for i := 0; i < len(searchResult); i++ {
for j := 0; j < searchResult[i].ResultCount; j++ {

别问我怎么知道的,说多了也难受,不知道为什么要这样设计…

pgvector

如果没有使用 orm 的话需要手写 SQL 相较于其他的 API 使用会有一点点门槛,比如:

1
2
3
4
SELECT title, 1 - (embedding <=> $1) as similarity
FROM embeddings
ORDER BY embedding <=> $1
LIMIT $2

其他

一定注意使用的索引算法向量的维度,有的时候创建的维度和插入数据维度不一样也不会异常报错的,需要特别注意。特别是在测试不同模型下生成不同维度向量数据效果的时候。

查询结果的说明

从理论上来说在使用相同算法的情况下,无论哪一个数据库查询结果应该都是相同的

实际测试下来发现

  • milvus
  • qdrant
  • elasticsearch

相同的查询条件下,以上三者的查询结果相同,而 pgvector,在一些查询条件下结果与其他会有一两个差异,相似度越高差异越小,比如 top5 就没差异,top10 可能就相差一个。这是由于 pgvector 实现的算法逻辑应该是近似的,而非 100%,不过不影响实际使用,后面会有说明。

总结

  1. 注意,搜索结果与使用数据库无关,仅与算法有关。
  2. 由于有索引存在,不同的向量数据库在个人使用的场景下均能满足对于查询速度的要求。
  3. 插入,虽然这里的测试并没有提到插入数据,因为实际的使用中对于插入数据来说并不敏感。但由于测试的时候插入的数据量巨大,导致明显 qdrant 遥遥领先于其他插入的时间,对于测试会友好很多。

个人总结:

在其他条件类似的情况下,显然使用不同底层语言实现的,计算和内存资源的使用上 Java(elasticsearch) > golang(milvus) > rust(qdrant) ≈ c(pgvector) 感受太明显了,所以那么多人才会用 rust 去重写各种中间件。没办法,底层就直接决定了能力所不同。

单一向量方式的的相似度对于搜索来说有效,但还不够,一方面取决与向量化模型本身,另一方面用过的都知道,仅用 COSINE 余弦相似度去比较,最终的查询结果没有那么的符合人的直觉。所以才会有那么多的搜索系统才有召回、粗排、精排、匹配。所以,单一能力并不能足以胜任工作,如果做的更,还有很多工作要做的。当然,对于个人使用者来说,就我的使用感受来说,显然比一些正常的分词后做匹配要来的更好用。