很多时候业务架构设计里面最重要的一环就是数据库模型设计。由于单机MySQL 的限制, 很多业务架构师不得不考虑对大表进行拆分, 通过中间件或者其他手段进行分库分表。本文
背景
很多业务在快速发展阶段,开始考虑数据拆分的原因其实并不是计算能力遇到了瓶颈,而是海量数据的存储到达了单实例的上限,但是由于最初设计的时候没有考虑到海量数据的使用方式,或是在业务逻辑中,数据无法进行清理或归档。
运维团队要对业务的稳定性负责,随着数据量还是每天上涨,不得不开始考虑数据拆分的问题,由于分库分表的兼容性问题需要业务修改业务的代码, 需要按照分库分表的形式重写SQL,这就要所有开发团队投入到架构改造。但业务团队更多的考虑业务的发展,这个时候是没有精力做这些事情的, 那么拆分只能无限推迟到不得不做的那天,这期间整体系统的稳定性一直运行在风险之下。
当然, 有一些老的DBA 还记得在很早的时候, 坊间流传的是在MySQL里面单表不要超过500万行,其实规定是有其历史背景的。资源方面来说早期服务器IO能力都比较低,单表过大会增加Btree 的高度,导致IO问题;同时磁盘容量也比较低,要考虑到存储上限以及备份空间的问题;运维层面来说旧版本的MySQL(5.5以前)基本不支持online ddl,大表运维时可能导致业务异常。但是现在无论是软件还是硬件都有非常显著的提高, 在PolarDB体系中,上述问题已经基本得到解决。目前PolarDB相对MySQL来说,大表已经不再是问题,目前公共云上客户的生产系统表最大已经有几十T的容量,业务都在平稳运行。
一般来说,分库分表解决方案解决了如下的问题:
本地存储不足
计算能力不足
大表运维困难
那么PolarDB 怎么解决这些问题的呢?
如果业务只是因为空间不够, 那么由MySQL直接升级为PolarDB 是最兼容、最轻松的解决方案, 业务不需要修改任何代码就可以迁移到兼容性100%产品上,同时磁盘风险可以立即消除。在应用侧流量巨大时,单个数据库实例因为访问量往往会资源用尽,导致业务出现性能卡点。但从我们服务的客户来看, 大部分业务的访问量都是来自于读取,MySQL可以通过添加只读节点的方式减轻写实例的压力,但有可能会出现复制延迟导致业务数据不一对待的问题。PolarDB使用共享存储,同时使用物理复制技术,基本消除了复制时数据延迟的影响。同时容器化技术可以快速添加只读节点,可以做到分钟级别计算能力扩展,同时智能Proxy可以在各节点上按需分流,使读流量完全平衡在各节点之间,消除了此类流量洪峰的风险。
一张百G乃至T级别的大表,给运维侧带来的难度也是日益增长。比如在做表结构变更时,大表往往需要更多的时间,可能会以天级别计算,这会给数据库本身的资源带来压力 ,同时会伴随着业务风险,PolarDB已经支持并行DDL功能,大大缩短了变更所需要的时间;从数据备份还原的角度说,PolarDB使用的是快照级别备份,秒级就可以完成,数据库还原也使用快照技术,无论数据量多大,基本都可以做到分钟级别还原,完全不用担心由于数据量带来的RTO问题。
PolarDB物理架构
图1.PolarDB MySQL物理架构图
存储
存储PolarDB的存储是由一组Chunk Server通过RDMA硬件连接在一起(PFS技术), 对上层提供块设备接口,而计算节点通过RDMA网络将存储挂载到计算节点中,从上往下看,底层的Chunk对于计算是完全透明的,目前PolarDB提供的最大规格还是限制在100T,主要是考虑一般业务中很少有能超过的oltp业务数据容量,同时过大的存储的确还是会存在一定的运维问题,所以这里的上限限制只是软限制,基于PFS的构成原理,我们其实可以近似认为PFS可以为计算节点提供无限容量的存储。
在支持了海量存储之后,使超大规模的表成为了可能,但由于单机版MySQL时代的大表容量只停留在理论上,没有真正投入生产,所以在出现史无前例的大表后,性能方面的问题才开始逐渐浮出水面,包括且不限于索引分裂,海量更新时数据刷盘等问题。
例如:
大表索引B-tree一定会出现比较一般表高很多层的情况,此时进行SMO操作时,索引上的锁会影响系统并发,我们将Btree 改进成了Blink-Tree,可以以最小代价完成索引层的维护,大大减小SMO的时间和性能成本。
redo落盘时,原生MySQL的方案是尽量使用顺序写以提高情况IO效率,但由PFS的架构可以看出,分布式的写不会带来性能提升,反而会造成热点写的问题,我们将redo改为分桶的方式在不同chunk上分散写,充份利用了分布式存储的优势。
计算
对比常见的数据库架构, 分库分表存在很多的兼容性问题,例如不支持自定义函数、数据类型、临时表、变量等等, 对业务有一定的侵入性,特别是在进行集中式向分布式系统改造的过程中,需要业务团队进行代码调整, 这是很多业务团队不愿意的。如果说存在一种超级MySQL,与原生业务的兼容性100%,并且有足够的资源可以不再让存储和计算资源成为瓶颈,那其实原业务平行迁移是最快、快方便的选择,PolarDB恰恰承担这个【超级MySQL】的角色。
由图1可以看出PolarDB采用了计算存储分离的架构,也就是说,计算节点间的资源也是像传统分库分表了样是完全物理隔离的,也就是说在已有计算资源不够的情况下,可以通过容器化技术分钟级别迅速添加新的计算节点进行横向扩展,目前可以支持到最多16个节点,也可以快速升级计算节点的资源进行纵向扩展。
另外PolarDB在业内也率先实现了并行查询功能,通过Parallel Query , 将MySQL one SQL one connection 的访问模式打破, 一个请求可以有多个线程同时进行请求,在有大量数据扫描的需求时,会提升几倍的性能。
未使用并行查询
使用并行查询
运维
运维超大规格实例的问题,主要是表变更以及备份恢复的时间太久,无法保证在非高峰窗口内完成,影响日常业务。
目前由于MySQL5.6官方已经不再维护,所以大量客户在转向5.7与8.0版本,所以我们在PolarDB 5.7中也merge了8.0 instant方式添加字段的能力,可以让大表字段添加在秒级完成。同时对于无法使用instant算法完成的变更,开发出了Parallel DDL的能力 ,通过并行化可以让原生单进程的表维护变为多进程同时操作,底层分布式存储的带宽有多大, 我们就可以跑多快。
图2.不同并行度大DDL效果
做为数据库必备的能力,备份与恢复能力对业务稳定和安全性来说重中之重。
如果实例中存在大表,无论使用逻辑备份或是物理备份,时间都会被此类表影响导致时间完全无法预期。PolarDB通过快照备份的方式,直接在PFS层进行快照备份,并且无论真实数据量多少,都可以在秒级别内完成,可以做到业务完全无感。
在数据库还原情况下,如果只需求还原一些小表,在使用传统物理备份实现时,也只能做全量恢复,此时如果存在T级别的大表,也必须要等它还原完成数据才可用,会使数据库的RTO无限增长。PolarDB提供了库表恢复的能力,在进行小表还原时完全不用影响大表对RTO的影响,按需快速还原需要内容即可。
同时基于快照备份的能力,进行实例级别还原时,只需要将快照挂载到新的计算节点上就可以完成整个实例的还原,无论数据量大小,都可以将还原时间控制在30分钟以内,大大提升了RPO,可以在最快速提供一个完整数据的新环境给业务方,无论是进行业务测试还是数据异常时快速止血的效率都大大提升。
性能
如果说大表容量不再受到存储限制,同时计算资源也不再成为瓶颈,那么单表的性能将会成为是否需要拆分的重要依据,我们通过如下的方式对单节点小表与大表的性能对比测试,确认单计算节点的性能阈值以供业务参考。
测试设计
主要是分别对单个大表(60亿行),单小表(5W)的读写性能进行测试。
测试资源:
Polardb 64核 512GB
ECS ecs.ic5.3xlarge(12C 12G * 2)
测试工具:
sysbench
测试模型:考虑并发1024,512,256,128时的read only, read write,write only性能表现
测试表使用tpch模型中的orders表,并生成5万和60亿的测试数据
CREATE TABLE `sbtest1` (
`L_ORDERKEY` bigint(20) NOT NULL,
`L_PARTKEY` int(11) NOT NULL,
`L_SUPPKEY` int(11) NOT NULL,
`L_LINENUMBER` int(11) NOT NULL,
`L_QUANTITY` decimal(15,2) NOT NULL,
`L_EXTENDEDPRICE` decimal(15,2) NOT NULL,
`L_DISCOUNT` decimal(15,2) NOT NULL,
`k` decimal(15,2) NOT NULL,
`L_RETURNFLAG` char(1) NOT NULL,
`L_LINESTATUS` char(1) NOT NULL,
`L_SHIPDATE` date NOT NULL,
`L_COMMITDATE` date NOT NULL,
`L_RECEIPTDATE` date NOT NULL,
`L_SHIPINSTRUCT` char(25) NOT NULL,
`L_SHIPMODE` char(10) NOT NULL,
`c` varchar(44) NOT NULL,
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`pad` varchar(44) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `L_ORDERKEY` (`L_ORDERKEY`,`L_LINENUMBER`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
结果记录
单表 |
ro |
CPU |
rw |
CPU |
wo |
CPU |
---|---|---|---|---|---|---|
1024 |
47.9 |
99.19 |
20.9 |
74.5 |
6.9 |
57.2 |
512 |
50.6 |
99.16 |
22.8 |
77.3 |
8.3 |
62.3 |
256 |
50.6 |
94.6 |
30.1 |
82.7 |
14.3 |
74.3 |
128 |
39.3 |
64.3 |
29.1 |
69.9 |
12 |
61 |
单大表 |
ro |
CPU |
rw |
CPU |
wo |
CPU |
---|---|---|---|---|---|---|
1024 |
44.8 |
99.2 |
18.4 |
72 |
6.5 |
48 |
512 |
47.2 |
99.2 |
14.7 |
47 |
3.8 |
31 |
256 |
47.4 |
96.2 |
8.1 |
28 |
2.1 |
20 |
128 |
37.9 |
69.9 |
4.5 |
18.8 |
1.1 |
14 |
结果分析
只读
-
小表
单个小表的读取效率最,主要瓶颈基本都出在点查时CPU使用上,可以看出来并发256时,整个节点的效率最高,低于256并发,整体压力上不去,资源使用不充分。
大表
大表时,由于b-tree高度问题,性能有下降趋势,此为预期内情况。
整体看大表的确会由于b-tree问题导致性能下降,但整体性能对于小表没有断崖式的下跌。
混合
-
小表
热点出现在内存的s_lock,同时在大并发下的transaction出现行锁情况比较严重,并发256时行锁阻塞的情况相对表现好一些。
大表
由于未关闭flush_neighbors,出现小热点在buf_flush_page_and_try_neighbors,但整体表现还好,但小并发场景下,性能整体表现不高,由于数据量较大,会导致脏页率较高,如果不是非常密集的压写,一般不会出现此类瓶颈,并发增大时,由于不像小表会出现频繁的锁阻塞,会让整体情况有一定提升。
纯写
-
小表
同时行锁比较严重,并发256的时候到达性能高点,之后并发增长会出现比较严重的行锁问题。
大表
大表随着并发增长会慢慢上涨,主要原因是行锁不像小表一样过于频繁,但同样由于数据量较大,内存频繁置换出现一定瓶颈。
性能总结
可以看到, 虽然单表数据行数从5万上升到了60亿, 但是PolarDB 在读取测试中,由于btree深度问题会有一定下降,但基本在预期之内;写入的场景下,随着并发增长,性能逐渐逼近,在并发小的场景下,大表写入会有一定的性能衰退,但整体上看单大表的性能和单小表的性能在大并发场景下基本趋近,并且一般来说真实生产环境中不会像压测场景下出现如此密集的写入导致脏页维持在非常高的水位, 完全可以承载一般业务流量,目前已知在公共云上一个客户单表的大小有已达到43T并且业务稳定运行。
总结
由现阶段云厂商产品的演进,可以看到几乎全部提供了兼容性很强的解决方案并做为云上核心产品。据了解, Google cloud 内部也同样在做类似解决方案,他们吸收了大量MySQL InnoDB 内核人才推动产品的开发上线。
对云厂商来说,兼容性是最高的要求,100% 兼容对于大量的中小型客户来说,是重要的参考指标。很多时候, 客户迁移上云的时候,会考虑目前开发人员的技术栈以及存量代码的问题,所以一旦有不兼容的情况,开发人员的学习成本以及存量业务的改造成本都会造成客户额外的支出。所以在云数据库. 一定要记得:
100% 兼容, 和99% 兼容是不一样的!
100% 兼容, 和99% 兼容是不一样的!
100% 兼容, 和99% 兼容是不一样的!
所以架构师在业务发展的阶段应该选择对开发人员更友好的解决方案,而不是过早投入到业务改造中。对于资金、运维、人工成本来说,过早优化是万恶之源。
大部分的项目早期不需要分库分表解决存储以及性能问题,如果可能的话选择云厂商(PolarDB)解决大表问题、让业务精力全部投入在业务快速迭代与发展的最快速有效解决方案。
当然,后期业务能够发展非常壮大,大表已经影响到业务的发展,性能完全不满足业务需求并且没有任务数据清理的方案,到时候再考虑也来得及。