一小时手搓轻量级可代替 Qdrant 的向量数据库

一个"向量版的 SQLite"?

前两天我直在优化goRAG那个项目,当我想RAG寻找一个存储方案时,我傻眼了:

  • Qdrant / Milvus / Weaviate?它们非常强大,但都是为了云原生和海量集群设计的。让一个本地桌面 App 启动时去跑一个几百 MB 内存占用的 Docker 容器?这绝对是反人类的设计。
  • SQLite / PostgreSQL (pgvector)?这确实是个好主意,但引入 C/C++ 依赖会让跨平台编译(尤其是 Go 的交叉编译)变成一场灾难。

所以我只能妥协,现在只能让goRAG同时支持各个主流的向量数据库,要用起来还得靠Docker,实在太不优雅了!

我真正想要的,是一个"纯粹的、无 CGO 依赖的、可以直接嵌在 Go 代码里,而且能够把数据持久化到本地单文件"的向量数据库。

我不死心在网上又收刮了一通,最后只能放弃,竟然没有人考虑要做个轻量的可嵌入式的 Qrant ?

既然找不到完全满意的轮子,那就自己造一个!我的目标很明确:

  1. 纯 Go 实现 (CGO-Free) :一个 go build 走天下。
  2. 持久化存储:程序重启,向量不丢。
  3. 支持元数据过滤 (Payload) :搜索向量时,能加上 category == "tech" 这样的硬性条件。
  4. 兼容 Qdrant API:以后如果业务做大了,代码不用改,无缝把后端 URL 切到真正的 Qdrant 集群。

2. 架构设计:拆解一个向量数据库

要在一小时内干完这件事,我们不能去造那些艰深的底层算法轮子,而是要做一位优雅的"架构整合大师"。

一个现代向量数据库,核心其实就三大块:

  1. 存储引擎 (Storage Engine):负责把向量和附带的 JSON 元数据(Payload)落盘。
  2. 索引引擎 (Index Engine):负责在海量向量中快速找到"最近邻"。
  3. API 层 (Serving Layer):负责对外提供通信接口。

技术选型:

原则:必须小!

  • 存储引擎 :我选择了 go.etcd.io/bbolt (BoltDB)。它是一个纯 Go 写的 KV 数据库,极其轻量,性能极佳,数据全保存在本地一个 .db 文件里,简直是嵌入式存储的完美之选。
  • 索引引擎 :暴力轮询(Flat Index)在几百条数据时还凑合,上万条就拉胯了。我们需要 HNSW(分层可导航小世界) 算法。我在这里引入了 Coder 团队开源的纯 Go HNSW 库 github.com/coder/hnsw
  • API 层 :直接使用 Go 1.22 原生的 http.ServeMux 增强路由,零框架依赖。

3. 核心代码实战:如何将它们组装起来?

步骤一:定义数据模型 (对齐 Qdrant)

为了能兼容 Qdrant,我们的数据结构必须和它长得一样:一个点(Point)包含 ID、向量(Vector)以及元数据(Payload)。

go 复制代码
// Payload 模拟 Qdrant 的 JSON 元数据
type Payload map[string]interface{}

type PointStruct struct {
	ID      string    `json:"id"`
	Vector  []float32 `json:"vector"` 
	Payload Payload   `json:"payload,omitempty"` // 用于条件过滤
}

步骤二:实现带 Payload 过滤的 HNSW 搜索

这是最核心的部分。单纯的 HNSW 只能做空间上的最近邻搜索,但如果用户要求"帮我找最相似的 10 篇文章,但作者必须是 Alice"怎么办?

我们的解法是 后置过滤 (Post-Filtering)

当带有 Filter 时,我们让 HNSW 引擎超额召回 (Over-fetch)(比如取 Top 100),然后在内存中进行精准的 Payload 匹配,剔除不符合条件的结果,最后再截取 Top 10 返回。

go 复制代码
func (h *HNSWIndex) Search(query []float32, filter *Filter, topK int) ([]ScoredPoint, error) {
	// 如果带有过滤条件,放大召回深度以防止结果被全部过滤掉
	fetchK := topK
	if filter != nil {
		fetchK = topK * 10 
	}

	// 1. O(log N) 极速图搜索
	neighbors := h.graph.Search(query, fetchK)

	var results []ScoredPoint
	for _, neighbor := range neighbors {
		point := h.points[neighbor.Key]
		
		// 2. 精确过滤 Payload 元数据
		if !MatchFilter(point.Payload, filter) {
			continue // 作者不是 Alice,跳过!
		}

		// 3. 计算并格式化最终得分
		score := CalculateDistance(h.metric, query, point.Vector)
		results = append(results, ScoredPoint{ID: point.ID, Score: score, Payload: point.Payload})

		if len(results) == topK {
			break
		}
	}
	return results, nil
}

步骤三:暴露 Qdrant 兼容的 REST API

api/server.go 中,我们仅需几行代码就能复刻 Qdrant 的搜索接口:

go 复制代码
// 拦截 POST /collections/{name}/points/search
func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
    // 解析 Qdrant 格式的请求体 (包含 vector 和 filter)
    var req struct {
        Vector []float32    `json:"vector"`
        Filter *core.Filter `json:"filter,omitempty"` 
        Limit  int          `json:"limit"`
    }
    json.NewDecoder(r.Body).Decode(&req)

    // 调用底层核心执行搜索
    results, _ := col.Search(req.Vector, req.Filter, req.Limit)

    // 返回标准 Qdrant 响应格式
    json.NewEncoder(w).Encode(map[string]interface{}{
        "status": "ok",
        "result": results,
    })
}

4. 暴力美学:性能基准测试 (Benchmark)

1小时后 .... 代码写完了,它到底有多快?

我写了一个 Benchmark 脚本,在纯内存下生成了 10,000 条 128维 的向量,并生成 1,000 条查询,对比普通的暴力轮询(Flat)和我们引入的 HNSW 图索引。

索引策略 构建耗时 平均查询延迟 吞吐量 (QPS)
Flat Index (线性扫描) 0 ms 4.267 毫秒 234 次/秒
HNSW Index (图搜索) 1688 ms 0.058 毫秒 (58微秒) 17,150 次/秒

结果极其惊艳!

HNSW 带来了高达 73倍 的性能跃升。在单机单核下,轻松跑出了 1.7万 QPS ,每次检索仅需 58微秒

想象一下,大模型生成一个词都需要几十毫秒,而你的知识库检索时间连 0.1 毫秒都不到,完全消除了 RAG 流程中的检索延迟瓶颈!


5. 成果展示:三种绝佳的食用方式

你可以通过三种方式使用我写的这个轮子:

方式一:微服务模式 (平替本地 Qdrant)

直接跑起来,它就是一个监听 18080 端口的微服务。

bash 复制代码
go run cmd/govector-server/main.go -port 18080 -db ./govector.db -hnsw=true

然后你在 Python 代码里原来请求 Qdrant 的地方,直接把端口改成 18080,一切照旧运转!

方式二:内嵌模式 (极致性能)

如果你写的是 Go 服务,连 HTTP 都不需要!直接把它像 SQLite 一样 import 进去。

go 复制代码
import "github.com/yourusername/govector/core"

// 本地落盘,零网络开销
store, _ := core.NewStorage("app.db")
col, _ := core.NewCollection("docs", 128, core.Cosine, store, true)

// 直接查!
results, _ := col.Search(queryVector, filter, 10)

方式三: home brew

macOS上只要去 brew 一下就能用

bash 复制代码
 $ brew tap DotNetAge/govector
 $ brew install govector

就这么简单!

结语

这个名为 GoVector 的项目我已经开源到了 GitHub,没有任何外部服务的依赖绑定,非常纯粹。如果国内的开发者兄弟们也在苦恼本地 AI 缺乏好用的嵌入式向量库,源码编译包都一应准备好了,欢迎来白嫖代码或者点个 Star!

👉 源码地址: https://github.com/DotNetAge/govector

(如果这篇文章对你有启发,欢迎在评论区交流你对本地向量存储的看法!)

相关推荐
aircrushin2 小时前
国产大模型全球逆袭的技术与商业逻辑
人工智能
iceiceiceice2 小时前
从零开始构建 RAG + DeepSeek Demo
人工智能·llm
掘金安东尼3 小时前
养龙虾之前?先搞懂 Skills!
人工智能
chaors3 小时前
从零学RAG0x03第一个实战应用:医疗知识混合检索实战
人工智能·aigc·ai编程
阿聪谈架构4 小时前
第02章:Prompt 工程 —— 用语言精准指挥 AI
人工智能
suke4 小时前
AI 界的 npm 惨案重演?聊聊 龙虾OpenClaw skills那些带毒的“骚操作
人工智能·程序员·aigc
明明如月学长4 小时前
OpneClaw 总挂?配个"保镖"自动修,7x24小时不用管
人工智能
万少5 小时前
用 OpenClaw 实现小红书自动发帖
人工智能
阿聪谈架构5 小时前
第01章:从零开始调用 LLM —— 入门 Qwen 大模型 API
人工智能