写点什么

BaikalDB 在大规模数据场景的挑战和实践

发布于: 2 小时前
BaikalDB在大规模数据场景的挑战和实践

 商业存储需求背景与现状 

 

商业平台研发部目前面临三大业务特点:

 

√ 新业务发展迅速,从广告主投放的业务系统发展为托管页、观星盘、用商一体、运营营销、数据洞察等多矩阵系统。

 

√ 广告物料规模在持续上涨,从 18 到 20 年增长了一倍以上。

 

√ 业务场景更加复杂,从 B 端业务发展为 B 端+C 端,增加各种活动、秒杀、分析等场景。

 

商业存储原有架构不统一:采用了 MySQL 满足事务性需求;采用各种 OLAP 类系统满足在线分析需求;采用自研的内存存储(字面服务,加速镜像服务等)满足各种加速查询场景;采用 redis、表格系统、基于 SSD 的 KV 系统等满足各种 KV 场景;采用建库、倒排基础查询、推荐模块、ElasticSearch 满足各种检索需求;采用搜索词 PV 仓库满足大容量 PV 查询需求。



图 1 | 商业存储原有架构

 

以上这些专用系统,架构混杂,数量庞多,运维扩容成本极高,并且内存占用过多造成资源浪费与混布困难。

过去 10 年,为了解决各种业务问题,我们存储团队一直在做加法,开发运维了大量的存储模块。为了解决这个问题,我们在 2017 年开始研发 BaikalDB,旨在解决上述问题,把存储架构统一,希望能够支持更多新业务的存储需求。

 

面向商业产品系统的新一代存储系统 BaikalDB

 

BaikalDB 是一个兼容 MySQL 协议的分布式可扩展存储系统,支持 PB 级结构化数据的随机实时读写,整体系统架构如下:

 


图 2 | BaikalDB 架构

 

BaikalDB 基于 RocksDB 实现单机存储,基于 Multi Raft 协议(braft 库)保障副本数据一致性,基于 brpc 实现节点通讯交互,其中:

 

√ BaikalStore 负责数据存储,用 Region 组织,三个 Store 的 三个 Region 形成一个 Raft Group 实现三副本,多实例部署,Store 实例宕机可以自动迁移 Region 数据。

 

√ BaikalMeta 负责元信息管理,包括分区,容量,权限,均衡等, Raft 保障的 3 副本部署,- Meta 宕机只影响数据无法扩容迁移,不影响数据读写。

 

√ Baikaldb 负责前端 SQL 解析,查询计划生成执行,无状态全同构多实例部署,宕机实例数不超过 qps 承载极限即可。

 

BaikalDB 的核心特性

 

全自主化的容量管理:可以自动扩容和自动数据均衡,应用无感知,很容易实现云化,目前运行在 Paas 虚拟化平台之上。

 

高可用,无单点:支持自动故障恢复和迁移。

 

面向查询优化:支持各种二级索引,包括全文索引,支持多表 join,支持常用的 OLAP 需求。

 

兼容 mysql 协议,支持分布式事务:对应用方提供 SQL 界面,支持高性能的 Schema 和索引变更。

 

支持多租户:meta 信息共享,数据存储完全隔离。

 

在系统研发过程中,BaikalDB 以业务需求为导向规划快速迭代,在业务使用中深度打磨优化,随业务成长而成长,关键功能的时间节点如下:



图 3 | BaikalDB 关键功能时间点 

 

从 2018 年上线以来, BaikalDB 已部署 1.5K+数据表,数据规模达到 600+TB,存储节点达到 1.7K+。

 

 研发运维过程中 

 遇到的困难和挑战 

 

BaikalDB 项目我们已经做了近 4 年,是逐步克服困难的 4 年。和其他数据库团队不同的是,BaikalDB 一直是研发人员自运维,因此很多时候遇到问题不只是功能上的,还有高效运维方面的。

索引是数据库的灵魂,不同的业务需求本质上是需要采用不同的索引来实现的。

而稳定性是数据库的基础,服务不稳定意味着业务方无法安心使用。

性能是数据库的关键,高性能意味着使用较少的资源支持大量的业务。

本次主要分享 BaikalDB 在构建索引、稳定性与安全性、性能方面的三大挑战与一些解决思路。

 

名词解释

 

 SQL 聚类是指把 SQL 中的常量使用问号替换后,聚合出来的一类 SQL,类似 preparestmt:

→select xx from xx where id = 3 and name in (1,3,4) =>select xx from xx where id =? and name in (?)

 

√ 相同类别的 SQL 有一些共同点:查询计划相同,索引大概率是同一个,一般具有相同的业务逻辑。

挑战 1:

统一存储如何构建高效多功能的索引系统

来满足业务高速迭代

 

1.低效 SQL 消耗大,优化困难

BaikalDB 线上有几十亿 pv,可以聚合成几千类 SQL,每类 SQL 我们会统计扫描行数、过滤行数、平响、pv、错误数等信息。

 


图 4 | SQL 统计信息分析 

 

基于这些统计信息进行分析,我们发现有 5%的低效 SQL 占了 42%的扫描资源。

因此,如果优化这个 5%的 SQL,对线上的性能和稳定非常重要。我们重点分析了这些低效 SQL,发现主要有两个原因导致:

→ 由于因此早期只做了基于规则的索引选择,线上部分表索引非常多,导致部分 SQL 选了低效索引。

→ 由于业务 RD 对 SQL 优化并不熟悉,并且能参考的优化信息不够全,导致部分表缺少合适的索引。

 

2.业务检索需求激增

 

业务系统对检索功能需求较多:物料检索,账户检索,图片检索,电商检索,文档检索等。需求量增长 4 倍。业务系统原有检索能力无法复用,需要搭建 redis,建库,倒排基础查询,高级检索等检索专用模块,而且对不同业务需要适配不同的代码。线上光各种专用检索模块就达到十几个。因此急需一套开箱即用的检索功能,快速满足大部分基础检索需求。

 

3.BaikalDB 索引变更能力不足

 

业务需求多变,每个月都有十多个由于业务需求,优化需求的各种加索引需求。老流程加索引需要建表、导数据、禁写等,单次变更耗时 1-3pd。

 

以上这些问题,我们都归类为索引相关问题。数据库的核心之一,就是支持各种索引来满足业务需求。与此相对的各种专用存储,实际上就是把某项索引能够做好,来满足某一类业务需求,不够通用。

 

我们采用了如下方案来解决这些问题:

 

√ 实现基于代价的索引选择与索引推荐,不需要业务参与来优化低效 SQL。

 


图 5 | 基于代价的索引选择与索引推荐 

 

为了应对索引选错的情况,BaikalDB 增加了基于代价的索引选择。对于能匹配到多个索引的 SQL,需要选择一个最优的代价最小的去执行。何为最优索引,检索量是个很重要的指标,一条 SQL 获取的数据量固定,检索量越少说明该索引越高效,为了选出最优的索引,需要预估出使用某个索引的检索量,那么我们就需要一些统计信息。BaikalDB 维护的统计信息包括列直方图、Count-Min Sketch、distinct count 等等。

 

→ 列直方图可以用来估计某一区间在整个范围内的占比,用于范围查询;

 

→ Count-Min Sketch 可以用来估计某一元素的出现频率,用于等值查询;

 

→ distinct count 可以用来评估该列的区分度。

 

查询条件(范围,等值,IN)在采样数据中的占比我们称之为选择率,再乘以表的总行数可以预估该条件需要检索的行数。对于单列索引可以直接计算出索引的检索行数,对于联合索引涉及多列,假设每列是相互独立的,那么将每一列的选择率相乘即可得到联合索引的选择率,进而可以预估联合索引的检索行数。

 


图 6 | BaikalDM 索引推荐方案 

 

有部分表缺少合适的索引,导致 SQL 低效。为了应对这个问题,BaikalDB 实现了一套索引推荐方案。通过对每类 SQL 的多种信息进行统计,包括:扫描行数,过滤行数,平响,pv。计算出过滤率=过滤行数/扫描行数,过滤率越大,扫描行数越大则越低效。然后再结合过滤率,平响,和统计哪个条件过滤最多,通过这些信息来综合推荐索引。

 

√ 实现定制化切词的倒排索引和多功能布尔引擎,支持一键添加检索功能。

 


图 7 | BaikalDB 检索功能

 

为了应对业务不断增加的检索需求,BaikalDB 实现了 FULLTEXT 索引,内置了 7 种切词类型,依然通过 RocksDB 存储倒排拉链。倒排拉链分为 3 层,realtime 层,buffer 层,base 层。

realtime 层其实存储的并不是拉链,而是每条数据,进行切词后的 term 直接打平写入,这个写入完全没有归并过程,因此可以基本保障写入速度。base 层则是永久存储了倒排拉链,里面的拉链长度可能比较长,长链会在几十万级别,因此合并拉链过程的开销并不小。buffer 层是为了降低 base 层的合并压力,定期会合并 realtime 层的数据,到达阈值和才与 base 层合并,降低 base 层的合并开销。目前拉链采用了 arrow 格式:https://arrow.apache.org。这个格式最大的特点是不需要解析,因此速度比 protobuf 更快,但是构造单行性能比 pb 差很多。因此我们 realtime 使用的是 pb,buffer 和 base 层使用的是 arrow 格式。 

BaikalDB 内置布尔引擎,基于 like 或 match against 语法,可以实现不同的算子组合归并,方便业务定制功能。

 

√ 基于 google F1 思想,实现高性能 online schema change(索引变更)。

 


图 8 | 索引变更 

 

Online Schema Change 的核心思想是通过状态跳变,实现异步变更,整个过程无需锁表。整个状态分为 NONE、DELETE_ONLY、WRITE_ONLY、WRITE_LOCAL、PULBIC 这几个状态。

 

→ 在状态到了 WRITE_ONLY 时,对于业务的实时写 SQL,都会写入这个索引。

 

→ 在状态到了 WRITE_LOCAL 时,BaikalDB 会把历史数据读出,然后回写到需要变更索引上,整个过程中通过锁主键保证历史数据不会覆盖掉新增数据,保证数据一致性。

 

→ 当所有数据全部回写成功后,把索引状态置位 PUBLIC,后续读请求可以使用到该索引。

 

→ BaikalMeta 会协调整个状态跳变过程,并且会控制并发,做到让业务无感知。

 

挑战 2:

稳定性和安全性是存储的基础

如何让业务安心使用

 

1.存储的稳定性问题会严重损害业务

为了解决稳定性不足的问题,我们对线上影响稳定性的问题总了分类总结:

 

→ 这些影响稳定性的问题,会严重损害业务,造成业务 pvlost,客户投诉、赔款等。因此我们根据问题发生的频率与严重程度,制定不同优先级逐步解决。

 

→ 流量突增问题占比 41%,包括业务慢 SQL,索引操作出错,部分业务流量变高,压力过高等情况。

 

→ 事务问题占比 25%,包括死锁问题,事务延迟问题,两阶段过程中遇到的雪崩问题。

 

→ 禁写问题占比 17%,包括数据迁移太快,sata 盘 io 性能差,rocksdb 发生禁写。

 

→ 其他问题,包括偶发 core,网络超时等。

 

2.全量备份能力不足导致故障恢复慢

之前的备份系统直接基于 SQL 读写,性能很差,导入 1 亿行(147G)数据,需要耗费 8 小时以上。而 BaikalDB 本身是个分布式数据库,数据量很大,因此这种恢复速度显然不能满足业务需求。

 

3.缺少实时输出能力,业务无法实时输出

 

实时输出能力的缺失,导致业务无法像使用 mysql 那样,把数据实时同步到各个系统(例如 redis,udw,做备份表,etl 后流给检索系统),无法满足业务多样化的需求。

 

为了能让业务更安心的使用,我们采用了下述方案来进行解决:

 

√ 物理、逻辑双隔离的多租户能力,解决突发流量带来的稳定性问题。

物理隔离:即拆分集群,基于 meta 的调度与 raft 的 addpeer 能力,BaikalDB 支持不同表直接一键拆分存储集群,整个过程中业务无感知。

但是物理隔离无法解决全部问题,集群拆分过多的话,资源消耗会增多,并且运维压力也会变大。况且单个表多个 SQL 相互影响这个也无法通过拆分集群解决。

因此我们增加了基于令牌桶流控实现的逻辑隔离。

 


图 9 | 增加令牌桶实现逻辑隔离

 

令牌桶的分配也是通过 SQL 聚类进行,线上几千类,每类 SQL 都会统计最近 1 小时的 qps,扫描量等信息,以此来分配令牌。除此之外为了突发流量,BaikalDB 采取了单速双桶策略,在承诺令牌之外预留部分超额令牌,保障额外的突增流量可以获取到令牌。

 


图 10 | 单速双桶策略

 

√ 参考 Percolator 的事务方案,保证事务稳定性 

BaikalDB 选取了第一条 DML 指令的某一个 region 为 primary region(sync point),在执行 COMMIT/ROLLBACK 的时候首先向 primary region 发送请求,保证 primary region 执行成功,再向其他 region 发送 COMMIT/ROLLBACK,让 primary region 来充当事务协调者的角色。

BaikalDB 分布式事务采用两阶段提交,prepare 后节点故障,节点恢复后反查 Primary Region。

Store 为了防止行锁卡 raft 状态机,采用 Leader 持锁成功后 raft 同步的方式进行单语句复制。

 


图 11 | BaikalDB 参考 Percolator 事务方案

 

√ 基于 RocksDB 状态的迁移限速方案。

 

rocksdb 达到 stall 状态时,hold addpeer/index 操作,达到根据 rocksdb 压力动态调控的目的。

对于大 region,进行拆分写 sst 操作,做到每个 sst 大约是 128M。

增加预估大小分裂机制,减少大 region。

简单来说,就是根据 rocksdb 压力来动态调控:压力小,全速迁移,压力大,不迁移。 

√ 基于 SST 的备份恢复,实现亿行数据分钟级恢复。

BaikalDB 直接操作 sst 数据,彻底解决计划开销的问题。并且恢复的时候直接 ingest sst 到 rocksdb 中,解决 memtable 写入开销问题,恢复速度提升百倍以上。

采用了 COW 机制,只有数据变更才更新备份,减少备份压力(一个百亿行的表,每天更新数据占比很小)。

 


图 12 | 基于 SST 的备份恢复

 

√ 实现 Binlog 实时备份,业务可以实时输出数据去其他系统。

类似 MySQL,BaikalDB 实现了 Binlog 来实时输出数据。这里面有几个设计点可供参考:

 

→ binlog 的存储使用 raft 保证多副本安全,binlog 数据与主表一样安全。

 

→采用分布式事务保证 binlog 与数据表强一致性。

 

→由于 binlog 数据也是顺序追加的,因此 binlog 数据与 raft log 共享存储(都叫 log),节约一倍存储,并且减少数据复制开销。

 


图 13 | 实现 Binlog 实时备份 

 

挑战 3:

统一存储额外开销大,性能比不上专用存储,

如何优化满足业务需求

 

√ 业务对存储系统有很高的 KV 性能

 

随着 C 端业务的不断增多,对系统的 KV 性能要求也越来越高。

 


然后由于 BaikalDB 是一个通用存储,因此性能对比基于 SSD 的 KV 系统存在明显的差距。在相同资源下,对比了基于 SSD 的 KV 系统发现只有其 1/4 左右的性能,急需优化。

 

√ 业务 OLAP 查询性能差。

 

商业这边有许多千万物料的大户,还有各种对客户画像,人群画像的分析系统,需要处理较多数据的查询。目前基于表格系统、ElasticSearch、Spark+HDFS 等不同系统的查询无法满足业务需求,现状如下:

 


√ 基于查询计划 cache 的 KV 性能优化,性能媲美专用磁盘 KV。

我们从火焰图分析,结合 KV 场景取单行数据,显然查询计划在整个查询过程中消耗很高。

我们对于性能优化的核心点是抓大放小。

小:火焰图发现查询计划开销好高,开始优化查询计划,内存池,对象池等等。

大:每天几十亿 pv,可以聚合成几千条 SQL;cache 查询计划,几十亿次缩减成约几百万次(几千 sql*实例线程数)。

 



图 14 | 基于查询计划 cache 的 KV 性能优化

 

√ 业务合作的 OLAP 性能优化,满足多样化业务需求。

BaikalDB 设计特点:在满足 OLTP 的基础上尽可能支持 OLAP。我们与业务方进行深度合作,尽量满足业务的查询需求。具体如下:

 

→ 与观星盘合作支持 HyperLogLog,BITMAP,Tdigest 分位值类型,极大降低存储开销,从而减少大量扫描。

 

→与报告合作提供内部分片逻辑,实现报告集群分布式并行查询 BaikalDB 集群多个分片。

 

→ 基于 rocksdb 实现了简单列存,大幅提升了大宽表扫描性能。

 

 经验和建议 

 

√ 基于优秀的开源组件和参考优秀系统的设计

 

√ 有效的方案:基准+增量

→分裂,DDL,meta 更新,raft 复制

 

√ 高效 Profile:brpc 支持的火焰图,DOT 图

 

√ 性能优化分析:抓大放小

 

 总结 

 

本文先简单介绍了商业系统的业务背景与存储需求。通过持续迭代 BaikalDB 过程中遇到的一些问题挑战进行分析与思考,并给出我们的一些解决方案。主要包括:针对索引方面的问题,我们开发了代价索引选择,开箱即用的检索功能,在线索引变更;针对稳定性与安全性方面,我们主要采用了物理+逻辑隔离的多租户方案,基于 sst 的全量备份恢复方案,以及 binlog 方案来保证;针对性能方面,分享了一些我们的性能优化的方案。

 

这些问题都是过去我们遇到的典型问题,希望对大家有所启发。

 

随着系统的发展迭代,我们还在不断遇到新问题新思路,欢迎大家持续关注 BaikalDB 的开源项目 github.com/baidu/BaikalDB 。


点击进入了解更多技术资讯~~


发布于: 2 小时前阅读数: 3
用户头像

关注百度开发者中心,收获一手技术干货。 2018.11.12 加入

汇聚百度所有对外开放技术、平台和服务资源,提供全方位支持,助力开发者加速成功,实现开发者、消费者和百度三方共赢。https://developer.baidu.com/

评论

发布
暂无评论
BaikalDB在大规模数据场景的挑战和实践