4

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

 3 years ago
source link: https://my.oschina.net/BaikalDB/blog/5219326
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

作者简介:李国强,百度商业平台研发部资深研发工程师。工作方向包括分布式数据库 BaikalDB、分布式SQL 中间件等,对构建高性能、高可用分布式文件系统有较多实践和较深入的理解。

欢迎关注 Star github.com/baidu/BaikalDB 国内加速镜像库gitee

1. 商业存储需求背景与现状

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

  • 新业务发展迅速,从广告主投放的业务系统发展为托管页、观星盘、用商一体、运营营销、数据洞察等多矩阵系统。
  • 广告物料规模在持续上涨,从18到20年增长了一倍以上。
  • 业务场景更加复杂,从B端业务发展为B端+C端,增加各种活动、秒杀、分析等场景。

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

图1 | 商业存储原有架构

以上这些专用系统,架构混杂,数量庞多,运维扩容成本极高,并且内存占用过多造成资源浪费与混布困难。 过去10年,为了解决各种业务问题,我们存储团队一直在做加法,开发运维了大量的存储模块。为了解决这个问题,我们在2017年开始研发BaikalDB,旨在解决上述问题,把存储架构统一,希望能够支持更多新业务的存储需求。

1.1 面向商业产品系统的新一代存储系统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+。

2. 研发运维过程中遇到的困难和挑战

BaikalDB项目我们已经做了近4年,是逐步克服困难的4年。和其他数据库团队不同的是,BaikalDB一直是研发人员自运维,因此很多时候遇到问题不只是功能上,还有高效运维方面的。 索引是数据库的灵魂,不同的业务需求本质上是需要采用不同的索引来实现的。 而稳定性是数据库的基础,服务不稳定意味着业务方无法安心使用。 性能是数据库的关键,高性能意味着使用较少的资源支持大量的业务。 本次主要分享BaikalDB在构建索引、稳定性与安全性、性能方面的三大挑战与一些解决思路。

2.1 名词解释

  • 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有一些共同点:查询计划相同,索引大概率是同一个,一般具有相同的业务逻辑

2.2 挑战一:统一存储如何构建高效多功能的索引系统来满足业务高速迭代

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

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

基于这些统计信息进行分析,我们发现有5%的低效SQL占了42%的扫描资源。因此,如果优化这个5%的SQL,对线上的性能和稳定非常重要。我们重点分析了这些低效SQL,发现主要有两个原因导致: - 系统早期只做了基于规则的索引选择,线上部分表索引非常多,导致部分SQL选了低效索引。 - 业务RD对SQL优化并不熟悉,并且能参考的优化信息不够全,导致部分表缺少合适的索引。

b. 业务检索需求激增

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

c. BaikalDB索引变更能力不足

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

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

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

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

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

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

  • 列直方图可以用来估计某一区间在整个范围内的占比,用于范围查询;
  • Count-Min Sketch可以用来估计某一元素的出现频率,用于等值查询;
  • distinct count可以用来评估该列的区分度。

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

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

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

图7 | 检索实现

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

  • realtime层其实存储的并不是拉链,而是每条数据,进行切词后的term直接打平写入,这个写入完全没有归并过程,因此可以基本保障写入速度。
  • base层则是永久存储了倒排拉链,里面的拉链长度可能比较长,长链会在几十万级别,因此合并拉链过程的开销并不小。
  • buffer层是为了降低base层的合并压力,定期会合并realtime层的数据,到达阈值和才与base层合并,降低base层的合并开销。

目前拉链采用了arrow格式:arrow.apache.org 。这个格式最大的特点是不需要解析,因此速度比protobuf更快,但是构造单行性能比pb差很多。因此我们realtime使用的是pb,buffer和base层使用的是arrow格式。 BaikalDB内置布尔引擎,基于like或match against语法,可以实现不同的算子组合归并,方便业务定制功能。

c. 基于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.3 挑战二:稳定性和安全性是存储的基础,如何让业务安心使用

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

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

  • 流量突增问题占比41%,包括业务慢SQL,索引操作出错,部分业务流量变高,压力过高等情况
  • 事务问题占比25%,包括死锁问题,事务延迟问题,两阶段过程中遇到的雪崩问题
  • 禁写问题占比17%,包括数据迁移太快,sata盘io性能差,rocksdb发生禁写
  • 其他问题,包括偶发core,网络超时等

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

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

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

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

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

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

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

物理隔离:即拆分集群,基于meta的调度与raft的addpeer能力,BaikalDB支持不同表直接一键拆分存储集群,整个过程中业务无感知。 但是物理隔离无法解决全部问题,集群拆分过多的话,资源消耗会增多,并且运维压力也会变大。况且单个表多个SQL相互影响这个也无法通过拆分集群解决。 因此我们增加了基于令牌桶流控实现的逻辑隔离。

图9 | 逻辑隔离

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

图10 | 单速双桶策略

b. 参考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 | 事务实现

c. 基于RocksDB状态的迁移限速方案

rocksdb达到stall状态时,hold addpeer/index操作,达到根据rocksdb压力动态调控的目的。 对于大region,进行拆分写sst操作,做到每个sst大约是128M。 增加预估大小分裂机制,减少大region。 简单来说,就是根据rocksdb压力来动态调控:压力小,全速迁移,压力大,不迁移。

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

BaikalDB直接操作sst数据,彻底解决计划开销的问题。并且恢复的时候直接ingest sst到rocksdb中,解决memtable写入开销问题,恢复速度提升百倍以上。 采用了COW机制,只有数据变更才更新备份,减少备份压力(一个百亿行的表,每天更新数据占比很小)。 图12 | 基于SST的备份恢复

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

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

  • binlog的存储使用raft保证多副本安全,binlog数据与主表一样安全
  • 采用分布式事务保证binlog与数据表强一致性
  • 由于binlog数据也是顺序追加的,因此binlog数据与raft log共享存储(都叫log),节约一倍存储,并且减少数据复制开销 图13 | Binlog 实时备份

2.4 挑战三:统一存储额外开销大,性能比不上专用存储,如何优化满足业务需求

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

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

业务方 性能需求 基木鱼C端H5渲染页 几十万 qps 查询 百度统计url历史 几十万 qps写入(sata盘) 商阿C端物料查询 几十万 qps查询 搜索词PV仓库,关键词推荐 百万级 qps查询

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

b. 业务OLAP查询性能差

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

业务方 性能需求 性能现状 Optlog(表格存储) 毫秒级 若干秒 物料列表(ES)(大户千万物料) 十秒级查询 经常超时 星云业务(宽表500列) 一天数据秒级分析 新业务 观星盘人群画像离线(HDFS) 分钟级 小时级

为解决以上问题,我们采取的方案是:

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

我们从火焰图分析,结合KV场景取单行数据,显然查询计划在整个查询过程中消耗很高。 我们对于性能优化的核心点是抓大放小。 小:火焰图发现查询计划开销好高,开始优化查询计划,内存池,对象池等等 大:每天几十亿pv,可以聚合成几千条SQL;cache查询计划,几十亿次缩减成约几百万次(几千sql*实例线程数)

图14 | 火焰图

图15 | KV性能优化

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

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

  • 与观星盘合作支持HyperLogLog,BITMAP,Tdigest分位值类型,极大降低存储开销,从而减少大量扫描
  • 与报告合作提供内部分片逻辑,实现报告集群分布式并行查询BaikalDB集群多个分片
  • 基于rocksdb实现了简单列存,大幅提升了大宽表扫描性能

3. 经验和建议

  • 基于优秀的开源组件和参考优秀系统的设计
  • 有效的方案:基准+增量
  • 分裂,DDL,meta更新,raft复制
  • 高效Profile:brpc支持的火焰图,DOT图
  • 性能优化分析:抓大放小

本文先简单介绍了商业系统的业务背景与存储需求。通过持续迭代BaikalDB过程中遇到的一些问题挑战进行分析与思考,并给出我们的一些解决方案。

主要包括针对索引方面的问题,我们开发了代价索引选择,开箱即用的检索功能,在线索引变更;针对稳定性与安全性方面,我们主要采用了物理+逻辑隔离的多租户方案,基于sst的全量备份恢复方案,以及binlog方案来保证;针对性能方面,分享了一些我们的性能优化的方案。

这些问题都是过去我们遇到的典型问题,希望对大家有所启发。随着系统的发展迭代,我们还在不断遇到新问题新思路,欢迎大家持续关注BaikalDB的开源项目github.com/baidu/BaikalDB


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK