暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

高性能存储引擎--支撑人工智能热潮下推荐与搜索的基石

rivernjl 2021-12-29
350


1. 背景


 

高频海量数据更新 在搜索推荐中经常会涉及到海量数据的批量更新单表记录数在亿级别是非常常见的并且大批量数据更新很频繁很多公司可能选择Redis\Hbase之类的一些开源的存储系统但是它们都并不太适合推荐及推荐这种场景下使用如一张100G的亿级别数据导入到Redis或者Hbase中在高并发的情况下这可能是一种灾难

 

检索数据量大并且对性能要求高 单次请求过程中召回数万级的数据响应性在5-10毫秒以内这个是一个比较低的要求了这万级别的数据可能还要去关联检索物品属性特征等等一般来说推荐过程中单次响应时长会要求在100毫秒左右所以对检索的性能要求非常高还比如排序过程中排量查询1000甚至更多物品特征单特征表字段80-100个字段想象一下这种性能慢得会有多夸张

 

检索类型多需要一套架构支持多种引擎: KV倒排向量这三种检索能力在搜索及推荐当中这是最基本的了通过一套架构方案集成多种检索引擎大量的减少了系统的维护成本及系统的复杂度

 

基于上面的一些原因的总合我们选择在开源的一些引擎下自研更贴合搜索与推荐的存储引擎

2. 总体设计

 

2.1. 系统架构 - 如何支撑亿级索引、毫秒级的检索


 

ClusterManager

是整个服务的中心控制台所有的过引构建集群资源分析线上集群扩缩容索引更新等都由ClusterManger发送控制命令

 

BS

索引构建服务进行离线及实时数据处理及索引构建如果是向量索引的话还提供向量索引模型训练如聚类模型训练

 

线上服务

 

 

整套储存框架以可嵌入式提供上层的服务可以将储存框架嵌入自身的服务当中则这个服务就具备了存储检索能力系统管控能力集群管理等等能力

 

线上服务通过检索API提供KV\向量\倒排等数据检索Engines里集成了不同的检索库NodeManager提供集群结点管理功能如结点状态管理TableManager提供表的更新下线上线等等管理功能Console提供统一的管理界面使用人员可以通过图形化的界面进行相关的操作

 

2.2. 系统扩展 - 单机资源有限数据装不下怎么办

 


 

数据分片进行存储同一张表的不同分片数据可以分布在相同的机器第一个分片的数据存在多个副本也可以分布在不同的机器上面每台机器可以做为一个proxy的角色请求过来之后proxy根据当前检索的数据计算出数据的分片信息根据分片取出当前分片所在的机器列表通过负载均衡相关的算法获取其中一台机器进行数据检索检索完成后proxy负责对数据进行合并并进行数据返回.

2.3. 如何支撑亿级别的离线数据批量更新

2.3.1.  索引生成


 

 

1. ClusterManager扫描数据表变更,生成数据版本信息数据版本可以作废作废后不再会发送到线上),并发送指令给BuildService构建索引

 

2. 如果有指定的表有最新数据则根据当前BS机器负载情况将任务分配给最闲的机器进行build

 

3. buildService监听zk结点发现自已有任务需要做则启动构建索引任务,同时上报自已的心跳信息

 

4. buildService构建完索引将索引上传到hdfs

 

 

2.3.2. 线上数据更新

 

1.  ClusteManager收集最新产生的索引信息如果有表有更新则将表的最新版本更新到zk

2. 线上服务监听对应结点发现指定表有索引更新则判定本地状态如果当前表正在更新则停止正在更新动作如果没有正在更新这一步则不做),然后下载指定远程索引并进行加载加载完成后跟随心跳上报状态

3. ClusteManager检测表指定版本的所有分片及分片的副本都被加载了向zk写入切换命令

4. 线上服务监听结点收到监听命令后更切换索引(切换索引只能涉及到指引的更变时间微秒级)

5. 同时根随心跳上报当前状态

 

 

2.3.3. 服务扩索容

2.3.3.1. 线上服务扩列及缩列管理

1. 用户通过console进行扩列操作同时生成一条扩列任务信息

 

2. 当前集群上的表都需要重新构建

 

3. 当所有表构建流程和线上索引加载过程与前面列的流程一致

 

4. ClusteManager根据当前扩列信息检测所有表的最新索引列是否都已被线上加载了如果都加载了则发送索引切换命令

 

5. 如果没有处理完则等待前面的流程

2.3.3.2. 线上服务扩缩行

扩缩行相对简单只需要指定列的行将索引加载完成后即可提供服务如果未加载完成当前机器只是一个proxy的角色将所有请求都分发到其它结点进行服务

 

 

2.4. 离线+增量数据更新

2.4.1. 离线全量索引构建流程

 

 

在全量离线索引构建完成后检查表是否带有增量数据如果带有增量数据则根据数据产生者指定的追数据起始时间开始追回增量数据如果数据生产者没有产生数据则如果是天分区那就从当前分区的的天的0:00:00开始追增量数据如果是小时分区则从当前小时的整点开始追数据,增量数据追到没数据或者当前时间点的前1分钟的数据则停止之后写入最后数据时间戳BS本地关闭表并上传文件到远程

2.4.2. 增量数据备份

 


 

clusterManager根据当前线上用到的表及表版本信息根据bs的情况分配实时索引备份任务优先将任务分配给本地存在索引的bs结点bs获取到任务后开始追增量数据更新到本地索引所有表数据按配置的固定时间间隔将本地备份索引上传到hdfs如果bs机器当机或者下线bs任务将被重新分配到新结点新结点以hdfs上的最后索引数据时间重新追加数据

 

 

2.4.3. 线上机器扩容或者机器替换时增量处理

 

1线上机器扩容时在下载文件时检测增量表的最后数据时间是否大于指定时间如半小时如果大于则告知clusterMananager需要通知bs将最新的索引上传bs收到指令后上传文件

2线上服务重复上面流程如果发现索引已更新则下载最新索引文件

3所有索引都切换ok后向线上提供服务

2.4.4. 线上服务表切换

 

线上服务先从hdfs上下载索引文件下载完成后根据最索引文件中的最新时间戳追加新的数据追加数据完成后切换索引上线

 

 

2.5. 存储引擎选型及优化

 

2.5.1. ANN检索

ANN检索引擎选型

目前ANN检索有很多比较优秀的开源库 nmslib、hnswlib、faiss 等hnswlib库性能检索性能最为优秀Cosine similarity上召回率能达到95%以上能满足大部分推荐场景向量检索需求同时整个库代码简单集成起来相对比较方便

 


                    性能对比图

 

hnswlib支持空间距离如下:

 


 

扩展HNSWLIB节约内存资源丰富使用场景

 

1: 很多场景下我们的数据label需要的是string类型的比如一个词向量词为字符串类型但是原生的label只支持长整型

2: 原生的索引结构中label是直接加载到内存中的这样索引加载后占用大量内存我们需要的是冷数据存储在磁盘热的label数据存储在内存中

3: 在很多推荐场景下算法做物品间的embbding 是全局的数据但是在小业务场景下只限制在固定的数据池内进行推荐如果将label->vector单独存储同时针对小场景下构建向量索引的话label->vector的数据及索引数据需要做数据版本对齐整体系统会复杂

 

针对上面我们对hnswlib进行了扩展

 

原生的存储结构如下:

 


 

改造后的数据存储结构

 

 



label从纯内存中迁移到了文件+内存模式进行存储同时label从原来的索引结构迁出后可以支持string类型的数据同时在API层面支持只保存向量到文件存储系统中这样就解决了我们所面临的问题上线后整体系统的内存占用比起原生的库节省了1半以上同时在检索性能上比原稍慢一些但是整体在内存与性能的平衡上这种情能差距还是可以接受的

 

 

2.5.2. KV存储引擎

 

KV引擎选型

 

目前比较常见的几类存储引擎读写性能及存储空间占比性能比较如下:

 




通过综合的比较我们最终选择RocksDB做为我们的存储引擎


1:Rocksdb开源并且是一种可嵌入式、持久化存储,方便我们进行定制化开发与自身的系统进行集成

2:Rocksdb基于c++进行编写c++当前主流语言团队成员容易对它进行扩展

3:各方面综合比较来说Rocksdb是比较均衡的写入及检索性能相对来说表现的还算比较优秀

 

优化存储节省存储空间提升检索性能

 

1:优化存储数据的序列化及反序列化

 


 

每张表有一个独立的schema用于定义表的字段数据类型等信息每条记录的数据按数据类型以二进制的方式紧

 


 

每张表有一个schema定义表的字段及字段类型等信息表的数据类型支持常用的数字类型string,list,map等数据结构如果遇到空字段用一位byte标识为空整个数据数据紧凑没有太多冗余的信息减少了存储空间反序列化过程是一个顺序读取内存数据过程整体性能消耗很低

 

2优化数据存储模式提升检索性能

 

mmapBlock Cache从上线之初为了性能我们使用了mmap模式但是效果并没有达到我们的预确在没有大数据量更新的情况下系统检索性能确实表现还比较优异但是每当有大数据表更新如一张几十G的表进行更新时就会间断性的出现检索超时

 


 

经分析发现文件写入本地磁盘会占用一部分的cache会将线上cache在内存中的数据换出去这样造成大量数据需要通过磁盘检索导致系统性能下降于是我们采用BlockCache模式进程独占内存块不受其它影响使用BlockCache后线上检索响应不再出现超时现象

 


 

同时为了更好的提升性能在对数据块进行Cache的同时我们分配出一块小内存进行RowCache,RowCache采用LRU存储模式将最热的数据放入缓存块中增加RowCache后系统整体检索性能提升5%左右.

 

 

3. 总结与展望

整个平台研发完成后被推广到推荐的各个子系统中使用相对之前使用第三方存储库我们取得了如下的成效

 

检索性能的提升 整体系统的召回能力比以前提升了2-3倍以上整体在线预测流程性能提升了2倍以上

 

数据更新效率的提升  线上数据更新单表记录如30-40G的特征数据更新整体提升了10

 

资源成本的降低 新的服务上线后整体的资源成本节省比以前降低2倍以上

 

系统维护成本及平台技术架构复杂度的降低 以前我们需要维护三套存储引擎每套存储引擎有不同的技术架构整个系统的复杂度及系统的维护成本非常高新的引擎上线后统一成一套架构架构体系降低了系统的维护成本整个系统的技术架构的复杂度也降底了很多

 

后续系统还需要持续的进行优化以满足公司业务发展的需要我们还需要持续的在如下的一些方向上前行

 

存储引擎还需要扩充向量检索的能力以满足更多的应用场景比如HNSWInner product距离计算上召回的准确率并不如人意我们需要支持聚类等检索模型在更大规模数据集下我们需要通过更好的算法对索引数据进行压缩以支撑更大规模的向量检索 KV检索上我们需要支持更丰富的存储模式KKV数据检索模式超大规模集群下更新大索引时索引下载性能优化同时需要在索引层面支持部分字段检索以提升超大字段表的检索性能等等在集群管理方面资源可以动态调度达到分钟级界面一键快速扩缩容及新集群的创建.

 

 

 


文章转载自rivernjl,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论