向量数据库落地实践

京东云开发者
• 阅读 32

一、前言

本文基于京东内部向量数据库vearch进行实践。Vearch 是对大规模深度学习向量进行高性能相似搜索的弹性分布式系统。详见: https://github.com/vearch/zh_docs/blob/v3.3.X/docs/source/overview.rst

二、探索

初次认识向量数据库,一脸懵逼?



向量数据库落地实践



向量是什么?如何将文本转换为向量?如何确定维度?如何定义表结构?如何选择索引方式,建表参数如何配置?检索参数如何配置?分片数副本数如何选择等等

随着对文档的逐渐熟悉以及和vearch相关同事的沟通,以上问题迎刃而解,具体的不再赘述。主要记住以下几点:

1、 文本转向量:采用大模型网关接口 domain/embeddings 传入对应的模型如:text-embedding-ada-002-2和待转换的文本即可;

2、 向量维度:这个和向量转换所采用的模型有关,细节不用关注;

3、 建表参数的选择以及表结构:主要在于retrieval_type 检索模型的选择,具体的可以参考文档。经过综合考虑,决定采用 HNSW:

字段标识 字段含义 类型 是否必填 备注
metric_type 计算方式 string L2或者InnerProduct
nlinks 节点邻居数量 int 默认32
efConstruction 构图时寻找节点邻居过程中在图中遍历的深度 int 默认40
"retrieval_type": "HNSW",
"retrieval_param": {
    "metric_type": "InnerProduct",
    "nlinks": 32,
    "efConstruction": 40
}

注意: 1、向量存储只支持MemoryOnly
      2、创建索引不需要训练,index_size 值大于0均可

具体的建表示例见后文。

4、 分片数和副本数结合实际数据量评估,如果无法评估,按照最少资源申请即可,后续可扩展。

三、实践

1、 建表(space)

为了简化操作,实行db(库)-space(表)一对一的方案,弱化库的概念。经过一系列探索之后定义出了通用的space结构:

{
    "name": "demphah",
    "partition_num": 3,
    "replica_num": 3,
    "engine": {
        "name": "gamma",
        "index_size": 1,
        "id_type": "String",
        "retrieval_type": "HNSW",
        "retrieval_param": {
            "metric_type": "InnerProduct",
            "nlinks": 32,
            "efConstruction": 100,
            "efSearch": 64
        }
    },
    "properties": {
        "vectorVal": {
            "type": "vector",
            "dimension": 1536
        },
        "contentVal": {
            "type": "string"
        },
        "chunkFlagId": {
            "type": "string",
            "index": true
        },
        "chunkIndexId": {
            "type": "integer",
            "index": true
        }
    }
}

字段说明:

engine、partition_num等都是固定的参数,properties中所列字段皆为通用字段,如果有扩展字段如:skuId,storeId追加即可

字段名 含义 类型 说明
vectorVal 文本向量 vector 维度与选用模型有关
contentVal 源文本 string
chunkFlagId 文件唯一id string 文件的标识id,用于串联分块后的片段
chunkIndexId 文件分段位置 integer 从0开始,递增
skuId ... 扩展字段见上

这里file的概念可以理解为一个单元,可能是一个文件,也可能是一个url,总之就是一个数据整体。

2、 分段写入

这里针对通用文件描述,比如提供一个pdf文件如何导入向量库:

a. 首先上传文件到oss,然后根据对应的fileKey获取到文件数据流

b. 再根据各种拆分场景(按行、字节数、正则拆分等)分成片段

c. 分段写入向量库:

    /**
     * 将字符串转换为向量并插入数据库
     * <p>
     * 目前所有的知识库管理端写入全走这个方法
     *
     * @param dbName       数据库名称
     * @param spaceName    空间名称
     * @param str          字符串
     * @param flagId       标志ID
     * @param chunkIndexId 块索引ID
     * @param properties   属性
     */
    private void embeddingsAndInsert(String dbName, String spaceName, String str, String flagId, Integer chunkIndexId, Map<String, Object> properties) {
        // 先向数据库写入一条记录,记录当前文档的写入操作
        int success = knbaseDocRecordService.writeDocRecord(spaceName, flagId, chunkIndexId.longValue(), 0, str);
        if (success <= 0) {
            log.error("writeDocRecord失败 {},{},{}", spaceName, flagId, chunkIndexId);
        }

        // 分块转向量并写入
        List<Float> embeddings = GatewayUtil.baseEmbeddings(str);
        if (CollectionUtils.isEmpty(embeddings)) {
            return;
        }

        KnBaseVecDto knBaseVecDto = buildKnBaseVecDto(new FeaVector(embeddings),flagId,chunkIndexId,str);

        Map<String, Object> newPros = JsonUtil.obj2Map(knBaseVecDto);
        if (MapUtils.isNotEmpty(properties)) {
            newPros.putAll(properties);
        }
        // {"_index":"kn_base_file_db","_type":"kn_base_file_space","_id":"-8182839813441244911","status":200}
        String insert = VearchUtil.insert(dbName, spaceName, null, newPros);
        if (StringUtils.isBlank(insert) || !insert.contains("_index")) {
            log.error("写入失败的块:{},{}", chunkIndexId, insert);
        }
    }

3、 数据记录

上文写知识库的过程有个 knbaseDocRecordService.writeDocRecord 的逻辑,用于记录写入的片段。下文详细介绍其中用到的mysql表:

1、 表1 space记录表

注:主要用于记录创建的space,以及查询管控,如禁用某个space等

CREATE TABLE `xxx_vearch_spaces` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `type` tinyint(3) NOT NULL COMMENT '类型',
  `status` tinyint(3) NOT NULL COMMENT '状态',
  `space` varchar(127) NOT NULL COMMENT '空间标识',
  `db` varchar(127) NOT NULL COMMENT '库标识',
  `desc` varchar(127) NOT NULL COMMENT '空间描述',
  `ext` varchar(4095) NOT NULL DEFAULT '' COMMENT '扩展字段',
  `creator` varchar(127) NOT NULL DEFAULT '' COMMENT '创建人',
  `created` timestamp NOT NULL COMMENT '创建时间',
  `modifier` varchar(127) NOT NULL DEFAULT '' COMMENT '修改人',
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `deleted` tinyint(3) NOT NULL DEFAULT '0' COMMENT '已删除(0:否;1:是)',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uniq_space_db` (`space`,`db`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COMMENT='xxx向量空间'

2、 表2 file记录表

注:主要用于记录space下的file,以及查询管控,如禁用某个file,以及关联查询对应的全部片段。

CREATE TABLE `xxx_spaces_knbase` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `status` tinyint(3) NOT NULL COMMENT '状态',
  `space` varchar(127) NOT NULL COMMENT '空间标识',
  `file_name` varchar(255) NOT NULL COMMENT '文件名',
  `file_desc` varchar(511) NOT NULL COMMENT '文件描述',
  `byte_num` bigint(20) unsigned NOT NULL COMMENT '字符数',
  `hit_count` int(10) unsigned NOT NULL COMMENT '命中次数',
  `ext` varchar(4095) NOT NULL DEFAULT '' COMMENT '扩展字段',
  `creator` varchar(127) NOT NULL DEFAULT '' COMMENT '创建人',
  `created` timestamp NOT NULL COMMENT '创建时间',
  `modifier` varchar(127) NOT NULL DEFAULT '' COMMENT '修改人',
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `deleted` tinyint(3) NOT NULL DEFAULT '0' COMMENT '已删除(0:否;1:是)',
  `file_flag_id` varchar(255) DEFAULT NULL COMMENT '文件唯一标识',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_space` (`space`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COMMENT='xxx空间知识库'

3、 表3 paragraph记录表

注:主要用于记录file拆分的片段,包括当前位置,查询命中数等。

CREATE TABLE `xxx_knbase_doc_record` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `space` varchar(127) NOT NULL COMMENT '空间标识',
  `file_flag_id` varchar(255) NOT NULL COMMENT '文件标识',
  `d_index` bigint(20) unsigned NOT NULL COMMENT '文档位置',
  `hit_count` int(10) unsigned NOT NULL COMMENT '命中次数',
  `ext` varchar(4095) NOT NULL DEFAULT '' COMMENT '扩展字段',
  `creator` varchar(127) NOT NULL DEFAULT '' COMMENT '创建人',
  `created` timestamp NOT NULL COMMENT '创建时间',
  `modifier` varchar(127) NOT NULL DEFAULT '' COMMENT '修改人',
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  `deleted` tinyint(3) NOT NULL DEFAULT '0' COMMENT '已删除(0:否;1:是)',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uniq_space_file_idx` (`space`,`file_flag_id`,`d_index`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COMMENT='xxx知识文档记录'

四、总结

向量数据库对于大模型应用落地来说至关重要,有些不可外露的内部数据可以存储在向量库中,用于内部检索。随着向量库中数据的丰富,大模型推理回答的能力也将更加精准。

上文的设计比如space中的chunkFlagId可以关联出原始的整个文件;chunkIndexId可以控制数据的查询范围,另一方面可以通过此字段实现分页(vearch目前不支持分页查询)以及全文导出。xxx_knbase_doc_record表中记录了片段的记录,可用于计算片段的chunkIndexId,一方面避免重复,另一方面保证属性的递增,可用于扩展很多能力。

目前向量数据库的检索只支持基本的向量检索和关键字检索,后续会逐步优化混合检索等方案以提高检索准确率等。

点赞
收藏
评论区
推荐文章
Karen110 Karen110
2年前
人工智能数学基础-线性代数1:向量的定义及向量加减法
一、向量1.1、向量定义向量也称为欧几里得向量、几何向量、矢量,指具有大小(magnitude)和方向的量。它可以形象化地表示为带箭头的线段。箭头所指:代表向量的方向;线段长度:代表向量的大小。与向量对应的量叫做数量(物理学中称标量),数量(或标量)只有大小,没有方向。1.在物理学和工程学中,几何向量更常被称为矢量。2.一般印刷用黑体的小写
ElasticSearch深度分页详解
1前言ElasticSearch是一个实时的分布式搜索与分析引擎,常用于大量非结构化数据的存储和快速检索场景,具有很强的扩展性。纵使其有诸多优点,在搜索领域远超关系型数据库,但依然存在与关系型数据库同样的深度分页问题,本文就此问题做一个实践性
大数据
课程安排 一、大数据概述 二、大数据处理架构Hadoop 三、分布式文件系统HDFS 四、分布式数据库HBase 五、MapReduce 六、Spark 七、IPythonNotebook运行PythonSpark程序 八、PythonSpark集成开发环境 九、PythonSpark决策树二分类与多分类 十、PythonSpark支持向量机 十一
Stella981 Stella981
2年前
ClickHouse在京东流量分析的应用实践
前言ClickHouse是一款开源列式存储的分析型数据库,相较业界OLAP数据库系统,其最核心优势就是极致的查询性能。它实现了向量化执行和SIMD指令,对内存中的列式数据,一个batch调用一次SIMD指令,大幅缩短了计算耗时,带来数倍的性能提升。目前国内社区火热,各大厂也纷纷进入该技术领域的探索。引言本文主要讨论京东黄
Stella981 Stella981
2年前
OpenTSDB时序数据库的应用
OpenTSDBOpenTSDB是基于Hbase的时序数据库,它是一个时间序列专用数据库,只能存储时序数据。官方定位是一个分布式、可伸缩的监控系统。譬如收集大规模集群(包括网络设备、操作系统、应用程序)的监控数据并进行存储,查询。官网(https://www.oschina.net/action/GoToLink?urlhtt
Wesley13 Wesley13
2年前
D3D编程必备的数学知识(2)
向量相加我们能够通过分别把两个向量的各个分量相加得到向量之和,注意在相加之前必须保证它们有相同的维数。uv(_ux_\_vx_,_uy_\_vy_,_uz_\_vz_)图5显示的是几何学上的向量相加。!(http://static.oschina.net/uploads/img/201
Stella981 Stella981
2年前
Python技巧之函数拆包裹
函数参数拆包裹有时我们把参数打包传递给函数所以有必要告知函数如何拆包裹defmyfunc(x,y,z):print(x,y,z)元祖向量、字典向量tuple_vec(1,0,1)dict_vec{'x':1,
Wesley13 Wesley13
2年前
PHP
<?php/\\ \数据分析引擎 \分析向量的元素必须和基准向量的元素一致,取最大个数,分析向量不足元素以0填补。 \求出分析向量与基准向量的余弦值 \@author(http://my.oschina.net/arthor)Foyon \//\\ \获得向量的模 \
Wesley13 Wesley13
2年前
2. 文本相似度计算
1\.文本相似度计算文本向量化(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.cnblogs.com%2Fhuangyc%2Fp%2F9785420.html)2\.文本相似度计算距离的度量(https://www.oschina.net/a
精彩分享 | 欢乐游戏 Istio 云原生服务网格三年实践思考
作者吴连火,腾讯游戏专家开发工程师,负责欢乐游戏大规模分布式服务器架构。有十余年微服务架构经验,擅长分布式系统领域,有丰富的高性能高可用实践经验,目前正带领团队完成云原生技术栈的全面转型。导语欢乐游戏这边对istio服务网格的引进,自2019开始,从调研到规模化落地,至今也已近三年。本文对实践过程做了一些思考总结,期望能给对网格感兴趣的同学们以参考