向量数据库:Qdrant

背景

向量数据库是一种专门设计用于存储、索引和检索向量数据的数据库。它适用于处理大规模的高维向量,这些向量通常代表了图像、视频、文本或音频内容的特征,甚至整个世界本身。向量数据库通常用于支持机器学习应用,例如检索增强生成(RAG)、图像检索、推荐系统等。

向量数据库的核心功能是相似度搜索(similarity search),即通过计算向量之间的相似度来快速检索出内容上相似的项目。常用的相似度度量包括欧氏距离、余弦相似度和点积。为了实现高效的大规模相似度搜索,向量数据库通常采用专门的数据结构和索引技术,以及高效的搜索算法(如近似最近邻搜索算法)。以基于检索增强生成的问答应用为例,当用户提出一个问题时,应用会首先将问题转换为向量,然后在向量数据库中检索与该向量相似的向量,即与问题相关的上下文。这些检索到的上下文信息用于增强大型语言模型的回答生成过程,使得生成的答案更为精确和详尽。

目前市面上存在数量众多的向量数据库产品,即便是开源向量数据库也有不少选择,例如 MilvusQdrantChromaWeaviate 等。关于这些向量数据库的比较,可以参阅博客 Vector databases (1): What makes each one different?

vector-db-lang

这里以 Qdrant 为例进行部署。Qdrant 官方提供了 Helm chart,部署更加简单、便捷。

qdrant

Qdrant 的一些特性如下:

  • 高效:有效利用计算资源。Qdrant 完全用 Rust 语言开发,实现了动态查询计划和有效载荷数据索引。也为企业提供了硬件感知的构建版本。
  • 快速且准确:实现了 HNSW 算法的独特自定义修改,用于近似最近邻搜索。以最先进的速度进行搜索,并在不影响结果的情况下应用搜索过滤器。
  • 易于使用的 API:提供 OpenAPI v3 规范来生成几乎任何编程语言的客户端库。或者,利用为 Python 或其他编程语言准备的现成客户端以获得额外功能。
  • 丰富的数据类型:向量负载支持大量的数据类型和查询条件,包括字符串匹配、数值范围、地理位置等。负载过滤条件允许你构建几乎任何自定义业务逻辑,这些逻辑应基于相似性匹配工作。
  • 分布式:云原生且横向可扩展。无论用户需要伺服多少数据,Qdrant 总是可以使用恰到好处的计算资源。

Qdrant 提供了 REST API 和 gRPC API 作为接口,所有与 Qdrant 的交互都通过这些 API 进行。此外,Qdrant 还提供了多种语言的客户端库和一个 Web UI 界面,使用部分将演示使用 Python 客户端以及 Web UI 与 Qdrant 交互。

部署

安装

进入 Notebook app 的终端,添加相应的 Helm Chart repository,列出 Chart qdrant/qdrant 的所有版本:

helm repo add qdrant https://qdrant.github.io/qdrant-helm

# 注意 CHART VERSION 和 APP VERSION(Qdrant 版本)之间的对应关系
# 例如 CHART VERSION 0.7.5 和 0.7.4 中的 Qdrant 版本都是 v1.7.3
helm search repo qdrant/qdrant --versions

安装指定版本的 Chart qdrant/qdrant 以部署 Qdrant 应用:

# 安装最新版本
helm repo update qdrant
helm install qdrant-demo qdrant/qdrant

# 安装指定 CHART VERSION,注意这不是 APP VERSION(Qdrant 版本)
helm install qdrant-demo qdrant/qdrant --version <CHART_VERSION>

配置

以上安装全部使用默认配置,完整的默认配置请参阅相应的 values.yaml

# 获取指定 CHART VERSION 的 values.yaml
helm show values qdrant/qdrant --version <CHART_VERSION> > values.yaml

如果想要修改默认配置,你可以将新配置(覆盖默认配置的字段)保存为一个 YAML 文件,通过 -f 选项提供给安装命令:

# 使用修改后的 values.yaml
helm install qdrant-demo qdrant/qdrant --version <CHART_VERSION> -f values.yaml

下面将分主题介绍部分关键配置(CHART VERSION 0.7.6)。

计算资源

默认配置没有指定计算资源,表示 Pod 可以无限制地使用节点的 CPU 和内存资源。你可以根据实际需求指定请求值和限制值。

# 默认配置
resources: {}

存储

默认配置指定的卷大小为 10Gi,并且没有指定存储类型。你可以根据数据规模修改卷大小,并选用高性能的存储类型。

# 默认配置
persistence:
  accessModes:
  - ReadWriteOnce
  annotations: {}
  size: 10Gi                # 卷大小
  storageClassName: ""      # 存储类型
config:
  storage: {}

此外,你还可以在 config.storage 字段下配置 Qdrant 实例的存储和索引的多个参数,请参阅 configuration file example

网络

默认配置没有启动 Ingress,你可以提供 Ingress 配置以提供外部访问。

# 默认配置
ingress:              # Ingress 配置
  enabled: false
  ingressClassName: ""
  annotations: {}
  hosts:
  - host: example-domain.com
    paths:
    - path: /
      pathType: Prefix
      servicePort: 6333
  tls: []

service:              # Service 配置              
  type: ClusterIP
  additionalLabels: {}
  annotations: {}
  loadBalancerIP: ""
  ports:
    - name: http
      port: 6333
      targetPort: 6333
      protocol: TCP
      checksEnabled: true
    - name: grpc
      port: 6334
      targetPort: 6334
      protocol: TCP
      checksEnabled: false
    - name: p2p
      port: 6335
      targetPort: 6335
      protocol: TCP
      checksEnabled: false

config:
  service: {}
  tls: {}

此外,你还可以在 config.serviceconfig.tls 字段下配置 Qdrant 实例的服务和 TLS 的多个参数,请参阅安全以及 configuration file example

分布式部署

Qdrant 支持分布式部署模式,在此模式下,多个 Qdrant 服务彼此通信,将数据分布到多个副本(replica)中,以扩展存储能力并增加稳定性。默认配置启用了分布式部署模式,并指定端口 6335 用于内部通信,但只创建了 1 个副本。你可以增加副本数量以实现分布式部署。

# 默认配置
replicaCount: 1

config:
  cluster:
    enabled: true
    p2p:
      port: 6335
    consensus:
      tick_period_ms: 100

关于分布式一致性协议、分片、复制(replication)和一致性保证的更多信息,请参阅 Distributed deployment

安全

默认配置没有为 Qdrant 实例提供任何保护,在投入生产使用之前需要启用安全措施。首先,启用 TLS 以加密连接:

config:
  service:
    enable_tls: true

  p2p:
    enable_tls: true  # 分布式部署下,对 peer 之间的通信启用 TLS

  tls:
    cert: ./tls/cert.pem
    key: ./tls/key.pem

在此基础上,可以使用静态的 API key 进行身份验证:

config:
  service:
    api_key: your_secret_api_key_here

更多信息请参阅 Security

应用架构

应用的系统架构如下图所示(CHART VERSION 0.7.6,默认配置):

architecture

创建的主要 Kubernetes 资源如下表所示:

类型名称作用备注
Serviceqdrant-demo暴露 Qdrant 服务
StatefulSetqdrant-demo部署 Qdrant默认计算资源为 {}
PVCqdrant-storage-qdrant-demo-*作为 Qdrant 的持久化存储默认卷大小为 10Gi

运维

查看应用的状态

helm status qdrant-demo

更新应用

# 更新到最新版本
helm upgrade qdrant-demo qdrant/qdrant

# 更新到指定版本
helm upgrade qdrant-demo qdrant/qdrant --version <VERSION_NUMBER>

# 回滚更新,首先查看历史版本
helm history qdrant-demo
helm rollback qdrant-demo [REVISION]

移除应用

helm delete qdrant-demo
kubectl delete pvc -l app.kubernetes.io/instance=qdrant-demo

使用

继续使用 Notebook app 的终端,获取应用 service 的公开的端口号:

kubectl get svc qdrant-demo -o jsonpath="{.spec.ports}"

输出应类似于:

[{"name":"http","port":6333,"protocol":"TCP","targetPort":6333},{"name":"grpc","port":6334,"protocol":"TCP","targetPort":6334},{"name":"p2p","port":6335,"protocol":"TCP","targetPort":6335}]

首先使用 Python 客户端运行一个快速入门。安装 Python 客户端:

pip install qdrant-client

然后执行如下 Python 脚本,命令行参数中需要提供 gRPC 端口号:

quick_start.py
import sys

from qdrant_client import QdrantClient
from qdrant_client.http.models import (Distance, Filter, FieldCondition,
                                       MatchValue, PointStruct, VectorParams)

# 客户端在创建 collection 时存在 HTTP 超时问题,参考
# https://github.com/qdrant/qdrant-client/issues/394,采用 gRPC 作为临时解决方案
# client = QdrantClient(sys.argv[1])
client = QdrantClient(sys.argv[1], prefer_grpc=True)

# 创建一个 collection 以存储向量数据,设定向量维数为 4,使用点积度量距离
client.create_collection(
    collection_name="test_collection",
    vectors_config=VectorParams(size=4, distance=Distance.DOT),
)

# 添加一些向量
client.upsert(
    collection_name="test_collection",
    wait=True,
    points=[
        PointStruct(id=1,
                    vector=[0.05, 0.61, 0.76, 0.74],
                    payload={"city": "Berlin"}),
        PointStruct(id=2,
                    vector=[0.19, 0.81, 0.75, 0.11],
                    payload={"city": "London"}),
        PointStruct(id=3,
                    vector=[0.36, 0.55, 0.47, 0.94],
                    payload={"city": "Moscow"}),
        PointStruct(id=4,
                    vector=[0.18, 0.01, 0.85, 0.80],
                    payload={"city": "New York"}),
        PointStruct(id=5,
                    vector=[0.24, 0.18, 0.22, 0.44],
                    payload={"city": "Beijing"}),
        PointStruct(id=6,
                    vector=[0.35, 0.08, 0.11, 0.44],
                    payload={"city": "Mumbai"}),
    ],
)

# 查询与向量 [0.2, 0.1, 0.9, 0.7] 最相似的 3 个向量
search_result = client.search(collection_name="test_collection",
                              query_vector=[0.2, 0.1, 0.9, 0.7],
                              limit=3)
print(search_result)

# 进一步过滤结果
search_result = client.search(
    collection_name="test_collection",
    query_vector=[0.2, 0.1, 0.9, 0.7],
    query_filter=Filter(
        must=[FieldCondition(key="city", match=MatchValue(value="London"))]),
    with_payload=True,
    limit=3,
)
print(search_result)
python quick-start.py qdrant-demo:<GRPC_PORT>

脚本创建了一个 collection,添加了一些数据,并进行了两次基本的查询。输出应类似于:

[ScoredPoint(id=4, version=0, score=1.3619999885559082, payload={'city': 'New York'}, vector=None, shard_key=None), ScoredPoint(id=1, version=0, score=1.2730000019073486, payload={'city': 'Berlin'}, vector=None, shard_key=None), ScoredPoint(id=3, version=0, score=1.2079999446868896, payload={'city': 'Moscow'}, vector=None, shard_key=None)]
[ScoredPoint(id=2, version=0, score=0.8709999918937683, payload={'city': 'London'}, vector=None, shard_key=None)]

接下来进入 Web UI 与 Qdrant 向量数据库交互。如果在部署应用时配置了 Ingress,那么直接在浏览器中访问相应的地址即可,否则需要进行端口转发。通过 t9k-pf 进行端口转发:

t9k-pf -n <APP_PROJECT> pod qdrant-demo-0 6333:6333

在浏览器中访问 http://127.0.0.1:6333/dashboard 进入 Web UI。在 Console 页面中,用户可以直接调用 REST API 并得到响应结果:

loss

在 Collections 页面中,用户可以进行以下操作:

  • 浏览 collection 的所有数据点
  • 查看 collection 的基本信息
  • 制作和上传快照
  • 对数据点进行可视化
  • 删除 collection
loss
loss
loss

Web UI 的其他功能这里不再一一介绍,用户可以自行探索。

更多使用教程请进一步参阅 Qdrant 文档

参考