程序边界头像
踩了三年烟囱式架构的坑,我终于理解了向量数据库和融合存储到底香在哪封面图

踩了三年烟囱式架构的坑,我终于理解了向量数据库和融合存储到底香在哪


兼容
是对前人努力的尊重
是确保业务平稳过渡的基石
然而
这仅仅是故事的起点

说真的,我干这行快八年了,前前后后换过四家公司,有互联网大厂也有传统企业,但凡是要做AI相关的项目,架构图画出来几乎一个模子刻出来的——关系型数据库存业务数据,旁边竖一个专用向量数据库存embedding,再搞个文档数据库塞非结构化内容,有时候还得加个时序数据库处理监控指标。DBA运维着四五套不同的数据库,开发在代码里写着五六个不同的driver,数据在各个库之间搬来搬去,ETL任务跑了一轮又一轮。每次出了问题,排查链路能绕地球半圈。

我之前一直觉得这就是AI时代该有的样子,直到去年有个项目让我接触到了KingbaseES的向量能力,才发现原来还能这么玩——一套数据库把关系、向量、文档、GIS、时序全给你装进去了。说实话刚听说的时候我是不信的,觉得不就是在关系库里面加了个vector类型嘛,能有多大花头?结果越用越觉得之前三年的架构简直是在给自己挖坑。

这篇文章我想跟你聊聊我这几年在"烟囱式"架构上踩的坑,以及后来用KingbaseES一体化融合架构做项目的真实体验。

先说个事儿:我们到底为什么需要向量数据库

我查过一些资料,向量数据库这个领域的发展其实经历了好几个阶段。最早大概是1989年到2000年前后,那时候的系统像Rasdaman,主要是给地球科学、天文航天、石油勘探这些领域用的,处理的是有序、密集的多维数据,查询的时候还伴随各种科学计算算子。这一阶段的系统跟现在我们说的AI向量数据库基本不是一回事,数据模型和查询模式差得很远。

然后到了2000年到2017年左右,出现了一批面向高维数据管理的系统,比如SciDB——这可是Micheal Stonebraker老爷子搞的,还有Samuel Madden牵头的Tile项目。这一阶段的研究重点转向了图像、视频、点云这类高维数据的存储和索引,Tile还专门做了面向稀疏向量优化的存储和索引结构。但老实说,这些系统在学术圈挺有名,在工业界真正大规模用起来的并不多。原因嘛,我觉得一方面是当时的应用场景确实有限,另一方面这些系统的产品化程度普遍不高,你让企业拿来跑核心业务,心里没底。

真正的转折点是2017年前后。那一年Milvus开源了,紧接着Pinecone在2019年推出了云服务。从那以后,向量数据库这个赛道突然就热了起来。为什么?因为大模型和embedding技术起来了。以前向量主要是学术界在玩,现在任何一个做AI应用的团队都需要把文本、图片、音频转成向量存起来做相似度检索,需求一下子就爆发了。

不过这里我想插一句自己的看法。现在市面上很多讨论把"向量数据库"和"专用向量数据库"画等号,好像你要存向量就必须用Milvus、Pinecone、Weaviate这些。这个假设其实是有问题的。向量检索本质上是一个数据类型加一个索引方法的问题,关系型数据库完全可以通过扩展来支持,不一定非得另起炉灶搞一套独立系统。KingbaseES走的就是这条路子——在KES之上做向量功能增强,以插件形式提供能力。这个选择到底好不好,我后面会结合实际体验详细说。

还有一个概念上的复杂性我觉得值得提一下。什么叫"向量数据库"?业界其实有好几种界定方式。最窄的定义是"专门用于存储和检索高维向量数据的数据库系统",Milvus、Pinecone属于这一类。稍微宽一点的定义是"支持向量数据类型和向量相似度检索的数据库",按照这个定义,KingbaseES加了向量组件之后也算。最宽泛的定义则把任何能存向量的系统都算进去,包括Elasticsearch带个dense_vector字段这种。我个人倾向于第二种界定,因为第一种把那些在关系库基础上做向量扩展的路线排除在外了,而这条路线恰恰可能是企业落地AI时最务实的选择。

烟囱式架构那些年我踩过的坑

第一个坑是ETL。业务方有个需求,要在问答结果里展示文档的创建部门、文档密级这些信息。这些信息在MySQL里,但相似度检索是在Milvus里做的。流程变成了:先去Milvus检索出最相似的top10向量,拿到对应的chunk_id和doc_id,然后拿着doc_id去MySQL查部门和密级信息,再根据当前用户的权限过滤掉无权查看的文档。

这就导致一个很尴尬的问题:Milvus返回的top10里,可能有5条用户是没权限看的,你过滤掉之后只剩5条,上下文不够,回答质量就下降。你说可以在Milvus里存权限信息做预过滤?行啊,但权限是会变的,今天用户能看的文档明天可能就被收回权限了。你怎么同步?又回到数据一致性的问题了。

更麻烦的是,权限过滤条件可能很复杂——部门、角色、密级、文档归属人,这些条件组合起来,你很难在向量检索阶段高效处理。专用向量数据库虽然也支持标量过滤,但能力跟关系型数据库的WHERE子句比起来,差了不是一点半点。

第二个坑是运维。四套数据库,四套监控,四套备份策略,四个升级版本节奏。Milvus那套东西依赖etcd、MinIO、Pulsar,光本地开发环境搭起来就要半天。有一次测试环境的etcd挂了,Milvus整个不可用,我折腾了一下午才恢复。DBA同事本来只管MySQL,现在还得学Milvus的运维,苦不堪言。

第三个坑是事务。这个其实最致命但也最容易被忽视。在专用向量数据库里,向量的写入和查询是没有ACID事务保证的。

我给你看个最直观的例子。假设你有一个企业文档检索系统,在烟囱式架构下,你要先在Milvus里做向量相似度检索,拿到doc_id列表,然后去MySQL里查这些文档的元数据和权限,再去MongoDB里拿文档内容。三步,三个数据库,三次网络往返。代码大概长这样:

# 烟囱式架构:三步查询,三个数据库
# 第一步:Milvus向量检索
results = milvus_client.search(
    collection_name="doc_vectors",
    data=[query_vector],
    limit=20,
    output_fields=["doc_id", "chunk_id"]
)
doc_ids = [r["entity"]["doc_id"] for r in results[0]]

# 第二步:MySQL查元数据和权限
placeholders = ",".join(["%s"] * len(doc_ids))
cursor.execute(f"""
    SELECT d.id, d.title, d.department, d.security_level
    FROM documents d
    WHERE d.id IN ({placeholders})
    AND d.department IN ({user_dept_list})
    AND d.security_level <= %s
""", doc_ids + [user_clearance])
allowed_docs = {row["id"]: row for row in cursor.fetchall()}

# 第三步:MongoDB拿文档内容
contents = mongo_client.docs.find(
    {"_id": {"$in": list(allowed_docs.keys())}},
    {"content": 1, "title": 1}
)

看着不多对吧?但实际项目中,这三步之间的错误处理、重试逻辑、数据不一致处理,能写出几百行代码。而且这三步是串行的,每一步的延迟都会叠加。

换成KingbaseES一体化架构之后,一条SQL就搞定了:

-- 融合架构:一条SQL搞定向量检索+权限过滤+文档内容
SELECT
    d.id,
    d.title,
    d.department,
    d.content,
    v.chunk_text,
    1 - (v.embedding <=> :query_vector) AS similarity
FROM doc_vectors v
JOIN documents d ON v.doc_id = d.id
WHERE d.department = ANY(:user_depts)
  AND d.security_level <= :user_clearance
  AND v.embedding <=> :query_vector < 0.3
ORDER BY v.embedding <=> :query_vector
LIMIT 10;

索引:IVFFLAT和HNSW怎么选

IVFFLAT的原理是先把向量空间分成若干个聚类簇(用k-means),检索的时候只查离查询向量最近的几个簇。它的特点是索引构建快,内存占用小,但查询速度和准确率取决于你检查多少个簇——查得多准确率高但慢,查得少快但可能漏。

HNSW则是基于图的方法,构建一个多层的近邻图,检索时从顶层开始贪心搜索,逐层向下找到最相近的节点。它在高维空间里通常比IVFFLAT更快、准确率更高,代价是内存占用更大、索引构建更慢。

创建索引的语法:

-- IVFFLAT索引,lists是聚类数量
CREATE INDEX ON document_chunks
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);

-- HNSW索引,m是每层连接数,ef_construction是构建时搜索宽度
CREATE INDEX ON document_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

实际选择的时候,我一般这么判断:如果数据量在百万级以下,内存也够,直接上HNSW,体验最好。如果数据量特别大或者内存紧张,IVFFLAT更稳妥。不过有个坑要注意:IVFFLAT索引需要先有数据才能建——因为它需要数据来训练聚类中心。空表建IVFFLAT索引会报错或者效果很差。HNSW则没有这个问题。

查询的时候,你可以通过参数控制召回率和速度的平衡:

-- HNSW查询:ef_search越大召回越高但越慢
SET hnsw.ef_search = 100;

SELECT id, chunk_text, embedding <=> '[0.1,0.2,...]' AS distance
FROM document_chunks
ORDER BY embedding <=> '[0.1,0.2,...]'
LIMIT 10;

-- IVFFLAT查询:probes是检查的簇数
SET ivfflat.probes = 10;

SELECT id, chunk_text, embedding <=> '[0.1,0.2,...]' AS distance
FROM document_chunks
ORDER BY embedding <=> '[0.1,0.2,...]'
LIMIT 10;

我做过一些对比测试,在百万级1024维向量、top10查询的场景下,HNSW的P99延迟大概在5毫秒左右,IVFFLAT在probes设为10的时候大概15毫秒。如果把probes降到1,IVFFLAT也能跑到3毫秒,但召回率直接掉到60%以下。所以大部分情况下我更推荐HNSW。

还有一个功能我觉得特别实用:表达式索引(也就是函数索引)。你可以只对向量的某一部分建索引:

-- 对向量的前256维建索引
CREATE INDEX ON document_chunks
USING hnsw ((embedding[1:256]) vector_cosine_ops);

这在什么场景下有用呢?比如你的embedding前半段是文本语义向量,后半段是元数据编码,你想只按语义部分做检索,这时候子向量索引就派上用场了。

为什么说一体化不只是"省一个组件"

聊到这儿你可能觉得,一体化的好处不就是少维护一套数据库嘛。我一开始也是这么想的,但用深了之后发现,好处远不止于此。

首先是事务。因为向量数据存在KES里面,它天然继承了KES的ACID事务能力。你可以在一个事务里同时写入业务数据和向量数据,要么一起成功要么一起回滚:

BEGIN;

-- 写入文档元数据
INSERT INTO documents (title, department, security_level, content)
VALUES ('Q3财务报告', '财务部', 3, '...')
RETURNING id INTO doc_id;

-- 写入文档切片和向量
INSERT INTO document_chunks (doc_id, chunk_index, chunk_text, embedding)
VALUES
    (doc_id, 0, '第一部分...', '[0.1,0.2,...]'),
    (doc_id, 1, '第二部分...', '[0.3,0.4,...]'),
    (doc_id, 2, '第三部分...', '[0.5,0.6,...]');

COMMIT;

这在专用向量数据库里是做不到的。你的向量写入和关系数据写入之间没有事务保证,一旦中间出了问题,就得靠补偿逻辑来修。在金融、医疗这些对数据一致性敏感的场景,ACID事务不是锦上添花,是硬性要求。

其次是混合查询能力。我前面给的那个例子已经展示了向量和关系数据的JOIN,但这还不是全部。KES支持向量与关系数据、文档数据、GIS数据、时序数据的混合检索。比如你可以在一条SQL里同时做向量相似度检索、GIS范围查询、时间范围过滤:

-- 多模融合查询:找最近的、语义相似的、特定时间段的事故报告
SELECT
    a.id,
    a.location,
    a.accident_time,
    a.description,
    a.geo_point <-> ST_MakePoint(116.4, 39.9) AS geo_distance,
    a.feature_vector <=> :query_vec AS semantic_distance
FROM accident_reports a
WHERE a.accident_time >= NOW() - INTERVAL '30 days'
  AND ST_DWithin(a.geo_point, ST_MakePoint(116.4, 39.9), 5000)
  AND a.feature_vector <=> :query_vec < 0.25
ORDER BY semantic_distance
LIMIT 20;

这些在专用向量数据库里你得自己在应用层实现,不仅开发量大,而且很容易出安全漏洞。

还有是高可用。KES从实例级、集群级到中心级都有完整的高可用方案。实例级有备份恢复、闪回;集群级有物理日志全同步复制、故障秒级切换、脑裂防护;中心级有同城双中心全同步、异地灾备。你的向量数据跟着KES一起享受这些能力,不用单独给向量数据库搭一套HA方案。

我之前用Milvus的时候,HA方案依赖K8s和etcd,运维复杂度很高。有一次etcd出了问题导致Milvus集群脑裂,两个节点都认为自己是leader,数据写了两份,排查了很久。KES用增强型一致性协议选主,从机制上避免脑裂,省心多了。

最后是兼容性。KES支持多种异构数据库的原生协议兼容——你可以用MySQL的客户端驱动直接连KES,也可以用Oracle的API。这意味着如果你的应用之前是跑在MySQL上的,要加上向量能力,基本不用改连接方式和数据访问层代码。

有问题或者不同意见的,欢迎在评论区聊聊。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/qq_32682301/article/details/164006653

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--