实时历史库需求背景
在当今的数字化时代,随着业务的迅速发展,每天产生的数据量会是一个惊人的数量,数据库存储的成本将会越来越大,通常的做法是对历史数据做归档,即将长期不使用的数据迁移至以文件形式存储的廉价存储设备上,比如阿里云 OSS 或者阿里云数据库 DBS 服务。
然而在部分核心业务的应用场景下,针对几个月甚至几年前的“旧”数据依旧存在实时的,低频的查询甚至更新需求,比如淘宝 / 天猫的历史订单查询,企业级办公软件钉钉几年前的聊天信息查询,菜鸟海量物流的历史物流订单详情等。
• 如果这时从历史备份中还原后查询,那么查询时间将会是以天为单位,可接受度为 0
• 如果将这些低频但实时的查询需求的历史数据与近期活跃存储在同一套分布式数据库集群下,那么又会带来以下两大挑战
- 存储成本巨大,进而导致成本远大于收益,比如钉钉聊天信息数据量在高度压缩后接近 50PB,很难想象这些数据不做压缩会带来多大的资金开销
- 性能挑战巨大,随着数据量越来越大,即使针对数据做了分布式存储,单实例容量超过大概 5T 以后性能也会急剧下滑,进而影响到近期活跃数据的查询性能,拖垮整个集群
- 运维难度巨大,比如针对海量数据下发一个表数据结构变更操作,很难想象全部完成需要多长时间
实时历史库场景需求分析
通过上面的分析,不管是冷备份还是在线历史数据混合存储在同一张物理表上的方法都是不可取的,一般实时查询历史数据库的场景,一般需要有以下几个关键特性
- 成本可控,历史数据的存储成本无法接受和在线库一样线性增长
- 实时查询,历史数据的查询 RT 要做到与在线活跃库几乎一致
- 查询频次较低,一般来说,越“旧”的数据查询频率越低
- 统一查询入口,不管是活跃数据还是历史数据,查询入口保持一致
- 改造成本需要尽可能低,最好能做到不做任何应用代码修改,可以认为历史库对程序开发人员来说是完全透明的
- 可能存在历史数据更新需求
- 数据规模较大,一般在 100TB 以上
X-Engine 引擎介绍
X-Engine 简介
X-Engine 是阿里云数据库产品事业部自研的联机事务处理 OLTP(On-Line Transaction Processing)数据库存储引擎。作为自研数据库 POLARDB 的存储引擎之一,已经广泛应用在阿里集团内部诸多业务系统中,包括交易历史库、钉钉历史库等核心应用,大幅缩减了业务成本,同时也作为双十一大促的关键数据库技术,挺过了数百倍平时流量的冲击。
与传统的 InnoDB 引擎不同,X-Engine 使用分层存储架构(LSM-Tree)。分层存储有两个比较显著的优点:
- 需要索引的热点数据集更小,写入性能更高。
- 底层持久化的数据页是只读的,数据页采用紧凑存储格式,同时默认进行压缩,存储成本更低。
相比 InnoDB 引擎,依据数据特征,使用 X -Engine 存储空间可降低至 10%~50%,我们在著名的 Link-Bench 和阿里巴巴内部交易业务两个数据集上测试了 X -Engine 的存储空间效率。在测试中,对比开压缩的 InnoDB 引擎,X-Engine 有着 2 倍空间优势,而对比未开压缩的 InnoDB,X-Engine 则有着 3~5 倍的优势。
实时历史库方案,为何不是其他高压缩引擎
• 通常我们默认 MySQL 是当今最流行的开源数据库,大概率是在线核心数据库集群的首选。相比其他高压缩的存储引擎,引入 X -Engine 完全无需做任何 SQL 代码改造,并且支持事务,接入成本最低,学习成本几乎为 0
• 写入性能更强,X-Engine 相比同为 LSM-tree 架构的 Rocksdb,有超过 10 倍的性能提升。
• 在存储层引入数据复用技术等,优化 Compaction 的性能,降低传统 LSM-tree 架构中 Compaction 动作对系统资源的冲击,保持系统性能平稳
• 引入多个层级 Cache,同时结合 Cach 回填和预取机制,利用精细化访问机制和缓存技术,弥补传统 LSM-tree 引擎的读性能短板,X-Engine 的点查询能力几乎与 Innodb 持平
下图是 X -Engine 与主流历史数据存储方案对比
历史数据存储选型
备份至 OSS
开源 HBase
X-Engine
压缩率
高
高
高
是否支持查询
支持解析历史备份文件查询
高
高
实时性
N/A
较高
非常高
应用代码改造代价
N/A
很高
几乎不用修改
事务支持
N/A
仅支持单行事务
强
主要场景
冷备份
大数据生态
OLTP
实时历史数据库架构设计和实现
总体架构思路
基于上文对实时历史库和 X -Engine 的介绍,阿里云数据库团队推出以 X -Engine 引擎为历史数据存储核心,同时生态工具 DTS 作为在线 / 历史数据流转通道,DMS 作为历史数据无风险删除的完整“实时在线 - 历史库”方案,针对不同的业务场景和客户需求,在具体实现上可能会有所不同,我们提供了多种实时历史库方案的具体实现。主体架构图如下,核心思路为:
- 久经考验的 Innodb 引擎作为 OLTP 在线库核心引擎,主要处理高频查询 / 更新请求,满足在线活跃数据高并发,高性能,强范围查询的业务需求
- 阿里巴巴数据库团队自研的高压测存储引擎 X -Engine 作为历史库核心引擎,主要响应历史数据入库 / 查询 / 更新请求,满足历史数据冷热数据频次不一,低存储成本,高性能的业务需求 (范围查询可能性能受限)
- 统一 DB 接入层,根据设置的业务时间属性将请求分别转发至不同的存储引擎。针对特殊场景下的跨引擎访问,在应用层做聚合展示
- 在线 - 历史数据通过阿里云提供的生态体系工具做历史数据迁移和过期数据删除,确保链路更为稳定可靠
在线库 / 历史库拆分方案
一般来说,需要使用到实时历史库的场景,数据量都足够大到单台宿主机存放不了。在线数据库可能是根据业务水平或垂直拆分的多个 RDS,也可能是一个规模较大的 DRDS 集群。为了尽可能地保证在线库的性能,推荐将在线库 / 历史库完全拆分解耦
• 历史库集群存储全量数据
• 通过 DTS 链路打通在线库和历史库,实时同步
• DTS 链路过滤 Delete 操作
• 可直接使用新版 DMS 配置历史数据定期删除
源端为 DRDS 集群
数据同步链路走 RDS
• 多条 DTS 链路打通底层 RDS 节点,同步性能强
• RDS 数量较多可支持 API 批量创建和配置
• 链路稳定性更好
• 需要保证源端目标端库表数量一致,数据路由规则一致
数据同步链路走 DRDS
• 只需要配置一条 DTS 链路,方便省钱
• 数据同步性能较差
• 源端 DRDS 扩容会影响到 DTS 同步链路
• 源端目标端的实例数量和数据路由规则可自由配置
源端为多个 RDS
目标端为多个 RDS
• 业务代码无需任何改造
• 运行后期历史库节点磁盘容量存在风险
目标端为 DRDS 集群
- 可能涉及到业务代码轻量改造,目标端不走分库分表键性能无法保证
- 可将多个在线库业务合并至一套历史库集群,架构更加简洁
- DTS 链路也分为走 RDS 和 DRDS 两种
数据同步链路走 RDS
同实例混用存储引擎方案
在线库 / 历史库拆分方案相对较为复杂,RDS 支持同一实例混用存储引擎。针对总数据量不是特别大的场景,可以考虑同一实例下 Innodb&X-Engine 引擎混合使用
使用 DMS–> 数据工厂 –> 数据编排功能可以轻松地实现同一实例内的数据流动和过期数据删除,架构示意图如下。
- 实现简单灵活
- 混用存储引擎,在数据库内核参数优化上难以兼顾两者性能
- 历史数据 compact 阶段可能对整个实例产生性能抖动
- 同一实例下的库或者表无法重名,涉及到轻量业务改造
DTS 赋能在线 / 历史数据流转
DTS 不仅支持全量 & 增量同步,支持不同数据库产品之间的数据同步,在在线 / 历史库解决方案中,DTS 强大的 ” 条件过滤 ” 功能是非常重要的一环,通过配置 DTS 任务可以非常便捷地实现过滤 Delete 操作,动动鼠标点两下即可实现自定义的数据同步策略。
DMS 赋能在线库过期数据删除
在线库的过期数据删除既要保障删除效率,也要保证删除过程中对在线库不会造成性能上的抖动,新版 DMS 支持创建“历史数据清理”的数据变更任务,通过该任务可以非常方便地完成以下工作
• 历史数据定期删除,指定调度时间和一次调度时长
• 大事务拆分,减少事务执行过程中锁表时间过长,避免主备延迟
• 清理遭遇异常中断可重试
• 支持查看任务运行状态和失败原因分析
• 配置方面简洁
过期数据清理思路
如果没有使用 DMS 生态工具,也自行实现过期数据删除,但实现较为复杂。一般较为通用的设计思路为将表的主键按照大小做拆分,保证一次删除 ” 恰当数量 ” 的数据,既保证删除效率又不影响线上服务
• 在线库的历史数据删除策略 (假设主键为 id,数据保存 180 天,时间属性列为 date_col)
- 初始化数值 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 天,请求路由至历史库,由于链路异常,历史库查到了脏数据
解决方法
• 配置链路异常告警,及时发现及时处理
• 预计影响的数据范围为 DTS 链路恢复前的临界时间点附近数据,建议从业务逻辑上订正数据
• 建议过期数据删除设置保守一点,比如临界时间为 180 天,过期数据只删除 190 天以后的数据,方便在极端场景下对比源端目标端的数据情况进行数据订正
最佳实践参考
将 DRDS 中的 InnoDB 引擎转换为 X -Engine 引擎链接
X-Engine 如何支撑钉钉跃居 AppStore 第一链接
淘宝万亿级交易订单背后的存储引擎链接