向量数据库对比与推荐

引言

向量数据库是专门为存储和查询高维向量数据设计的数据库,广泛应用于机器学习和人工智能领域,如语义搜索、推荐系统和检索增强生成(RAG)。

1. Milvus

Milvus是一个开源的向量数据库,专为嵌入式向量搜索设计,同时提供Go和Python客户端。

优势:

  • 高性能、可扩展

  • 完善的Go和Python SDK

  • 支持多种索引类型

  • 活跃的社区支持

2. Qdrant

Qdrant是一个专注于向量相似性搜索的数据库,同样支持Go和Python。

优势:

  • 过滤器和有效的数据管理

  • 直观的API设计

  • 容易部署和使用

  • 开源且有商业支持

3. Weaviate

Weaviate是一个开源的向量搜索引擎,提供Go和Python客户端。

优势:

  • GraphQL和REST API

  • 集成了多种ML模型

  • 强大的模块化设计

  • 支持多模态数据

4. Chroma

Chroma虽然主要以Python为主,但通过REST API也可以与Go配合使用。

优势:

  • 极其简单的API

  • 针对AI应用进行了优化

  • 支持嵌入、存储和检索

  • 开源

代码示例

Milvus - Python示例

from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType, utility
import numpy as np

# 连接到Milvus
connections.connect(host="localhost", port="19530")

# 定义集合架构
fields = [
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True),
    FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=128)
]
schema = CollectionSchema(fields=fields, description="文档向量集合")

# 创建集合
collection_name = "documents"
if utility.has_collection(collection_name):
    utility.drop_collection(collection_name)
collection = Collection(name=collection_name, schema=schema)

# 创建索引
index_params = {
    "index_type": "IVF_FLAT",
    "metric_type": "L2",
    "params": {"nlist": 128}
}
collection.create_index(field_name="embedding", index_params=index_params)

# 插入数据
data = [
    [i for i in range(10)],  # id
    np.random.random((10, 128)).tolist()  # embedding
]
collection.insert(data)
collection.flush()

# 加载集合
collection.load()

# 搜索
vectors_to_search = np.random.random((1, 128)).tolist()
search_params = {"metric_type": "L2", "params": {"nprobe": 10}}
results = collection.search(
    vectors_to_search, 
    "embedding", 
    search_params, 
    limit=3, 
    output_fields=["id"]
)

print(results)

Milvus - Golang示例

package main

import (
	"context"
	"fmt"
	"log"
	"math/rand"

	"github.com/milvus-io/milvus-sdk-go/v2/client"
	"github.com/milvus-io/milvus-sdk-go/v2/entity"
)

func main() {
	// 连接到Milvus
	ctx := context.Background()
	c, err := client.NewGrpcClient(ctx, "localhost:19530")
	if err != nil {
		log.Fatal("连接失败:", err.Error())
	}
	defer c.Close()

	// 定义集合
	collectionName := "documents"
	dim := int64(128)

	// 检查并删除已存在的集合
	has, err := c.HasCollection(ctx, collectionName)
	if err != nil {
		log.Fatal("检查集合错误:", err.Error())
	}
	if has {
		err = c.DropCollection(ctx, collectionName)
		if err != nil {
			log.Fatal("删除集合错误:", err.Error())
		}
	}

	// 创建集合
	schema := &entity.Schema{
		CollectionName: collectionName,
		Description:    "文档向量集合",
		Fields: []*entity.Field{
			{
				Name:       "id",
				DataType:   entity.FieldTypeInt64,
				PrimaryKey: true,
				AutoID:     false,
			},
			{
				Name:     "embedding",
				DataType: entity.FieldTypeFloatVector,
				TypeParams: map[string]string{
					"dim": fmt.Sprintf("%d", dim),
				},
			},
		},
	}

	err = c.CreateCollection(ctx, schema, int64(1))
	if err != nil {
		log.Fatal("创建集合错误:", err.Error())
	}

	// 创建索引
	idx, err := entity.NewIndexIvfFlat(entity.L2, 128)
	if err != nil {
		log.Fatal("创建索引错误:", err.Error())
	}

	err = c.CreateIndex(ctx, collectionName, "embedding", idx, false)
	if err != nil {
		log.Fatal("创建索引错误:", err.Error())
	}

	// 加载集合
	err = c.LoadCollection(ctx, collectionName, false)
	if err != nil {
		log.Fatal("加载集合错误:", err.Error())
	}

	// 插入数据
	idColumn := make([]int64, 10)
	embeddingColumn := make([][]float32, 10)

	for i := 0; i < 10; i++ {
		idColumn[i] = int64(i)
		vec := make([]float32, dim)
		for j := 0; j < int(dim); j++ {
			vec[j] = rand.Float32()
		}
		embeddingColumn[i] = vec
	}

	idColEntity := entity.NewColumnInt64("id", idColumn)
	embeddingColEntity := entity.NewColumnFloatVector("embedding", int(dim), embeddingColumn)
	_, err = c.Insert(ctx, collectionName, "", idColEntity, embeddingColEntity)
	if err != nil {
		log.Fatal("插入数据错误:", err.Error())
	}

	// 搜索
	vectors := make([][]float32, 1)
	vectors[0] = make([]float32, dim)
	for i := 0; i < int(dim); i++ {
		vectors[0][i] = rand.Float32()
	}

	sp, _ := entity.NewIndexIvfFlatSearchParam(10)
	searchResult, err := c.Search(
		ctx,
		collectionName,
		[]string{},
		"",
		[]string{"id"},
		vectors,
		"embedding",
		entity.L2,
		3,
		sp,
	)

	if err != nil {
		log.Fatal("搜索错误:", err.Error())
	}

	fmt.Printf("搜索结果: %v\n", searchResult)
}

Qdrant - Python示例

from qdrant_client import QdrantClient
from qdrant_client.http import models
import numpy as np

# 连接到Qdrant
client = QdrantClient(host="localhost", port=6333)

# 创建集合
client.recreate_collection(
    collection_name="documents",
    vectors_config=models.VectorParams(
        size=128,
        distance=models.Distance.COSINE
    )
)

# 上传向量
vectors = np.random.rand(10, 128).tolist()
payload = [{"text": f"文档 {i}"} for i in range(10)]

client.upsert(
    collection_name="documents",
    points=models.Batch(
        ids=[i for i in range(10)],
        vectors=vectors,
        payloads=payload
    )
)

# 搜索相似向量
search_vector = np.random.rand(128).tolist()
search_result = client.search(
    collection_name="documents",
    query_vector=search_vector,
    limit=3
)

print(search_result)

Qdrant - Golang示例

package main

import (
	"context"
	"fmt"
	"log"
	"math/rand"

	"github.com/qdrant/go-client/qdrant"
	"google.golang.org/grpc"
)

func main() {
	// 连接到Qdrant
	ctx := context.Background()
	conn, err := grpc.Dial("localhost:6334", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("无法连接到Qdrant: %v", err)
	}
	defer conn.Close()

	client := qdrant.NewCollectionsClient(conn)
	pointsClient := qdrant.NewPointsClient(conn)

	// 创建集合
	collectionName := "documents"
	vectorSize := uint64(128)

	// 删除已存在的集合
	_, err = client.Delete(ctx, &qdrant.DeleteCollection{
		CollectionName: collectionName,
	})
	// 忽略错误,如果集合不存在

	// 创建新集合
	_, err = client.Create(ctx, &qdrant.CreateCollection{
		CollectionName: collectionName,
		VectorsConfig: &qdrant.VectorsConfig{
			Config: &qdrant.VectorsConfig_Params{
				Params: &qdrant.VectorParams{
					Size:     vectorSize,
					Distance: qdrant.Distance_Cosine,
				},
			},
		},
	})
	if err != nil {
		log.Fatalf("创建集合失败: %v", err)
	}

	// 上传向量
	points := make([]*qdrant.PointStruct, 10)
	for i := 0; i < 10; i++ {
		// 创建随机向量
		vector := make([]float32, vectorSize)
		for j := 0; j < int(vectorSize); j++ {
			vector[j] = rand.Float32()
		}

		// 创建点
		points[i] = &qdrant.PointStruct{
			Id: &qdrant.PointId{
				PointIdOptions: &qdrant.PointId_Num{
					Num: uint64(i),
				},
			},
			Vectors: &qdrant.Vectors{
				VectorsOptions: &qdrant.Vectors_Vector{
					Vector: &qdrant.Vector{
						Data: vector,
					},
				},
			},
			Payload: map[string]*qdrant.Value{
				"text": {
					Kind: &qdrant.Value_StringValue{
						StringValue: fmt.Sprintf("文档 %d", i),
					},
				},
			},
		}
	}

	// 批量上传点
	_, err = pointsClient.Upsert(ctx, &qdrant.UpsertPoints{
		CollectionName: collectionName,
		Points:         points,
	})
	if err != nil {
		log.Fatalf("上传点失败: %v", err)
	}

	// 搜索相似向量
	searchVector := make([]float32, vectorSize)
	for i := 0; i < int(vectorSize); i++ {
		searchVector[i] = rand.Float32()
	}

	searchResult, err := pointsClient.Search(ctx, &qdrant.SearchPoints{
		CollectionName: collectionName,
		Vector:         searchVector,
		Limit:          3,
	})
	if err != nil {
		log.Fatalf("搜索失败: %v", err)
	}

	fmt.Printf("搜索结果: %v\n", searchResult)
}

选择建议

  1. Milvus - 如果您需要高性能和可扩展性,特别适合大规模生产环境。它的两种语言客户端都很成熟。

  2. Qdrant - 如果您注重易用性和灵活的过滤功能,Qdrant是很好的选择,同样有优秀的Go和Python支持。

  3. Weaviate - 如果您的应用涉及更复杂的语义关系或需要多模态功能,Weaviate值得考虑。

性能对比

根据近期研究,三个数据库在加载和搜索性能上有显著差异。以下是基于一个语义搜索应用的测试结果(加载3680个向量,搜索前十个最相似的向量):

方面

Milvus

Pinecone

Weaviate

加载时间(每向量ms)

10 ms,需单独创建索引

4.3 ms,支持批量加载至1000向量

5 ms,支持批量处理

搜索时间(平均秒)

0.95 秒,L2距离默认,质量0.028

0.88 秒,余弦距离默认,质量0.03

0.12 秒,余弦距离默认,质量0.03

索引类型

支持HNSW、DiskANN等,需手动选择

隐含HNSW,无枚举选项

支持HNSW、Product Quantization

从表中可以看出,Weaviate在搜索时间上表现最佳,而Pinecone在加载时间上领先。Milvus的性能稍逊,但通过优化(如增加计算资源)可以提升。

功能对比

功能是选择数据库的重要考量因素,以下是三个数据库的功能差异:

功能

Milvus

Pinecone

Weaviate

混合搜索

不支持,限制较大

近期支持,闭源实现

支持,结合向量和关键词搜索

知识图谱支持

有限,主要聚焦向量搜索

不支持,专注于向量操作

支持,通过GraphQL和数据类

多租户支持

支持,通过分区和权限控制

支持,通过命名空间

支持,通过数据隔离

高级查询

支持元数据过滤,无GraphQL

基本查询,支持过滤

支持GraphQL,查询灵活

Weaviate因其GraphQL支持和混合搜索功能在复杂查询场景中表现突出。Milvus在多租户和元数据过滤方面有优势,但缺乏混合搜索。Pinecone近期增加了混合搜索,但作为闭源服务,灵活性受限。

可扩展性和部署

部署方式和可扩展性是另一个关键因素:

  • Milvus:开源,支持从本地到分布式系统的部署,适合自定义基础设施。Zilliz提供托管服务,定价为99美元/月(标准性能优化版)。

  • Pinecone:全托管服务,serverless架构,无需管理基础设施,定价为70美元/月(s1.x1 pod)。

  • Weaviate:开源,支持本地和云部署,定价为25美元/月(标准性能优化版),适合中小型项目。

Pinecone的托管服务适合快速部署,而Milvus和Weaviate更适合需要控制基础设施的场景。

语言支持

最后更新于