深度 | 实时历史数据库存储成本惊人,怎么破?
作者:胡刀 阿里云运维专家
舟济 阿里云解决方案架构师
实时历史库需求背景
存储成本巨大 ,进而导致成本远大于收益,比如钉钉聊天信息数据量在高度压缩后接近50PB,很难想象这些数据不做压缩会带来多大的资金开销 性能挑战巨大 ,随着数据量越来越大,即使针对数据做了分布式存储,单实例容量超过大概5T以后性能也会急剧下滑,进而影响到近期活跃数据的查询性能,拖垮整个集群
运维难度巨大 ,比如针对海量数据下发一个表数据结构变更操作,很难想象全部完成需要多长时间
实时历史库场景需求分析
成本可控 ,历史数据的存储成本无法接受和在线库一样线性增长
实时查询 ,历史数据的查询RT要做到与在线活跃库几乎一致
查询频次较低 ,一般来说,越"旧"的数据查询频率越低
统一查询入口 ,不管是活跃数据还是历史数据,查询入口保持一致
改造成本需要尽可能低 ,最好能做到不做任何应用代码修改,可以认为历史库对程序开发人员来说是完全透明的
可能存在历史数据更新需求
数据规模较大 ,一般在100TB以上
X-Engine引擎介绍
与传统的InnoDB引擎不同,X-Engine使用分层存储架构(LSM-Tree)。分层存储有两个比较显著的优点:
需要索引的热点数据集更小,写入性能更高 。
底层持久化的数据页是只读的,数据页采用紧凑存储格式,同时默认进行压缩,存储成本更低。
实时历史库方案,为何选择 X-Engine
2.写入性能更强,X-Engine相比同为LSM-tree架构的Rocksdb,有超过10倍的性能提升。
3.在存储层引入数据复用技术等,优化Compaction的性能,降低传统LSM-tree架构中Compaction动作对系统资源的冲击,保持系统性能平稳
4.引入多个层级Cache,同时结合Cach回填和预取机制,利用精细化访问机制和缓存技术,弥补传统LSM-tree引擎的读性能短板,X-Engine的点查询能力几乎与Innodb持平
实时历史数据库架构设计和实现
总体 架构思路
基于上文对实时历史库和X-Engine的介绍,阿里云数据库团队推出以X-Engine引擎为历史数据存储核心,同时生态工具DTS作为在线/历史数据流转通道,DMS作为历史数据无风险删除的完整"实时在线-历史库"方案,针对不同的业务场景和客户需求,在具体实现上可能会有所不同,我们提供了多种实时历史库方案的具体实现。主体架构图如下,核心思路为:
久经考验的Innodb引擎作为OLTP在线库核心引擎,主要处理高频查询/更新请求,满足在线活跃数据高并发,高性能,强范围查询的业务需求
阿里巴巴数据库团队自研的高压测存储引擎X-Engine作为历史库核心引擎,主要响应历史数据入库/查询/更新请求,满足历史数据冷热数据频次不一,低存储成本,高性能的业务需求(范围查询可能性能受限)
统一DB接入层,根据设置的业务时间属性将请求分别转发至不同的存储引擎。针对特殊场景下的跨引擎访问,在应用层做聚合展示
在线-历史数据通过阿里云提供的生态体系工具做历史数据迁移和过期数据删除,确保链路更为稳定可靠
在线库/历史库拆分方案
源端为DRDS集群
a.数据同步链路走RDS
• RDS数量较多可支持API批量创建和配置
• 链路稳定性更好
• 需要保证源端目标端库表数量一致,数据路由规则一致
b.数据同步链路走DRDS
• 数据同步性能较差
• 源端DRDS扩容会影响到DTS同步链路
• 源端目标端的实例数量和数据路由规则可自由配置
源端为多个RDS
a.目标端为多个RDS
• 运行后期历史库节点磁盘容量存在风险
b.目标端为DRDS集群
可能涉及到业务代码轻量改造,目标端不走分库分表键性能无法保证
可将多个在线库业务合并至一套历史库集群,架构更加简洁
DTS链路也分为走RDS和DRDS两种
实现简单灵活
混用存储引擎,在数据库内核参数优化上难以兼顾两者性能
历史数据compact阶段可能对整个实例产生性能抖动
同一实例下的库或者表无法重名,涉及到轻量业务改造
DMS赋能在线库过期数据删除
在线库的过期数据删除既要保障删除效率,也要保证删除过程中对在线库不会造成性能上的抖动,新版DMS支持创建"历史数据清理"的数据变更任务,通过该任务可以非常方便地完成以下工作
• 大事务拆分,减少事务执行过程中锁表时间过长,避免主备延迟
• 清理遭遇异常中断可重试
• 支持查看任务运行状态和失败原因分析
• 配置方面简洁
如果没有使用DMS生态工具,也自行实现过期数据删除,但实现较为复杂。一般较为通用的设计思路为将表的主键按照大小做拆分,保证一次删除"恰当数量"的数据,既保证删除效率又不影响线上服务
初始化数值Y=select min(id) from $table_name
到了业务低峰期以后,DELETE FROM $table_name WHERE date_col< SUBDATE(CURDATE(),INTERVAL 180 DAY) and id >= Y and id <=
Y+100000 ,执行成功后代码层重新赋值 Y=Y+100000程序sleep 3s,重复步骤b
时间到了业务高峰期以后,停止执行,记录下当前的数值Y,第二天执行时直接从Y开始注意
代码上注意不要出现高并发删除的情况,即步骤b还没跑完,新的步骤b又进来了 程序sleep的具体秒数,要通过测试,取一个最合适的数值,主要看主备是否存在较大延迟,3只是估值 100000也是一个估值,实际取值最好也通过测试,取一个效率最高,对业务影响最小的数值。因为drds的序列不是单点递增1的,所以这里的10w不代表10w条记录。 假设删除程序中途崩溃了,或者执行很多天后发现部分数据没有删除。 那么可以手工先删除一小部分残留的数据,比如预估下id<100w的记录还有多少条,不多的话直接执行DELETE FROM logs_trans WHERE reqdate < SUBDATE(CURDATE(),INTERVAL 30 DAY) and id<100w 然后初始化整个程序,这样保证重新初始化以后不会做很多无用功,即反复执行删除条目很少的sql
极端场景分析
在临界时间处理上,实时历史库方案可能遭遇极端场景导致业务可能存在历史库的脏读问题,假设在线库数据保存180天
更新179天前23时59分59秒的数据,请求路由至在线库
数据同步链路异常中断或链路存在延迟,该更新请求未能及时到达历史库
这时业务查询该数据时,由于已经数据已经"旧"到超过180天,请求路由至历史库,由于链路异常,历史库查到了脏数据
• 建议过期数据删除设置保守一点,比如临界时间为180天,过期数据只删除190天以后的数据,方便在极端场景下对比源端目标端的数据情况进行数据订正
最佳实践参考
1.X-Engine如何支撑钉钉跃居AppStore第一
2.淘宝万亿级交易订单背后的存储引擎
3.将DRDS中的InnoDB引擎转换为X-Engine引擎
链接: https://help.aliyun.com/document_detail/161316.html