POLARDB · 最佳实践 · POLARDB不得不知道的秘密(二)

时间:2020-12-07 19:29:19

前言

POLARDB For MySQL(下文简称POLARDB)目前是阿里云数据库团队主推的关系型数据库。线上已经有很多企业用户在使用并且稳定运行了很久。当然,由于POLARDB是为云上环境专门打造的数据库,与原生的官方MySQL相比,有一些需要注意的事项。前几个月的月报介绍了一些,详见这篇[月报](http://mysql.taobao.org/monthly/2018/10/01/),结合笔者最近几个月一线的开发和运维经验,总结出以下几点新的注意事项并给出了建议。

空表/空实例空间问题

由于POLARDB除了实例费用外,用户还需要支付实例占用的存储费用,并且是按照使用量来收费,即当前使用多少磁盘就付多少钱。这一点与RDS MySQL兼容版的预付费模式不太一样,所以很多用户对磁盘使用量非常敏感,除了上篇月报中提到的空间问题外,还有以下两类问题:空实例空间问题和空表空间问题。

空实例空间问题。常常有用户会问,我刚买了一个POLARDB实例,但是才刚创建完,空间就占用了将近3GB(实际是2.8G左右),这是啥东西?我的磁盘被什么东西占用了?这里解释一下一个空实例大概有哪些文件以及他们占用的磁盘大小。

* 系统表ibdata1文件。可以查看系统变量`innodb_data_file_path`,ibdata默认大小就是200M,即在初始化后,ibdata就占用了200M空间,后续会按需自动扩展。
* MySQL系统库。目前权限信息,表的元信息等都放在MySQL库下,由于空表的空间占用问题(下一小节介绍),将近60个文件,占了230M左右的空间。
* Redolog文件。默认有两个,每个1G(Redolog也是预分配空间),其中一个是当前正在用的日志文件,另外一个是为了提高性能而提前分配的redolog,主要是为了减少Redolog切换时候的性能抖动。
* Undo文件。默认有8个,每个10M。Undo空间可以放在ibdata里面,也可以挪出来(ibdata里面只留一个),POLARDB默认是把undo空间挪出来以独立文件的形式存在,主要是为了方便后续清理。
* Performance_schema表空间。虽然目前POLARDB 5.6兼容版本,不太建议使用Performance_schema,但是我们还是在编译的时候把相关的功能给加上了,这就导致在初始化时候就会把Performance_schema相关的表空间给初始化好,占用了一部分空间,由于空表的空间占用问题(下一小节介绍),50多个文件,占了210M左右的空间。
* auto.cnf文件,存放server uuid。由于空表的空间占用问题(下一小节介绍),占用4M。
* ib_checkpoint文件,存放checkpoint信息。因为Redolog是类似binlog顺序递增的,所以第一个ib_logfile可能被删掉,所以checkpoint信息不能放在那里。由于空表的空间占用问题(下一小节介绍),占用4M。
* innodb_repl.info,存放物理复制相关的信息。主要是POLARDB数据库内部使用,记录复制位点,切换信息等。由于空表的空间占用问题(下一小节介绍),占用4M。

综上所述,空实例的主要空间是被Redolog给占用了,当然这个也是为了性能考虑。每个日志文件大小1G,能在大多数场景下保证实例正常运行。随着DML的执行,Redolog会被使用,使用完后会上传OSS,以便恢复到时间点任务使用。

空表空间问题。这个问题也常常被用户提起,我创建了很多表,但是数据还没导入,为啥空间增长那么快,差不多一千张空表就占用了40G的空间。这个问题的主要原因是POLARDB底层使用了自研的文件系统PolarFS,为了提高性能,默认的文件块被设置为4M(Ext4文件系统这个值为4K),换句话说,由于一个文件至少占用一个块,所以文件大小最小为4M,而在InnoDB上,一张普通的表默认有两个文件,一个是FRM文件,一个是IBD文件,所以只要一张表成功创建,就会占用8M的空间。当然,这些空间可以理解为提前分配预留的空间,当后续数据被写入的时候,首先先使用这些预留的空间,只有被用完后,才会再分配空间。这个行为对大多数用户来说影响不大,但对某些需要提前创建大量表但是数据量不大的用户来说,影响比较大,会导致用户存储成本较高。不过幸运的是,针对这点,我们的文件系统已经做了相应的优化,目前在内部测试阶段,达到预定的稳定性要求好,我们会尽快发布到线上,目标是,在保证高性能和稳定性的情况下,尽可能的减少磁盘空间使用量。

复制延迟问题
主备数据库的复制延迟是一个老生常谈的问题,在传统的主备架构下,只要主要压力稍微大一点或者做了一个DDL,备库有很大概率的发生延迟。发生延迟后,不仅会影响应用从备库读取数据的正确性,也影响主备切换。由于POLARDB在架构上的优势(只有一份数据),因此大部分用户可能会误认为在POLARDB上理论上不应该发生延迟。因为主备都读取一份数据,当然都应该看到一致的数据啦。但是实际上,复制延迟也还是有的,因为虽然磁盘上的状态一致了,但是内存上的状态不一定一致。在POLARDB上,主库和只读库通过物理复制来同步内存中的状态,由于同步的数据比较少,因此发生复制延迟的概率相比传统的MySQL复制还是小很多的。接下来简单介绍一下复制的过程:

Primary节点会定期告诉Replica节点,可安全读取的日志位点上限,Replica在这个周期内,可以安全读取到这个位点以下的日志,如果超过这个位点,可能会读到Primary节点正在写的日志。Replica节点定期反馈应用日志的位点,表示自己应用到的日志位点,小于这个位点的一定已经应用完,大于这个的可能还没应用或者正在应用。Primary节点当前写到的日志位点和Replica节点应用到的位点之差即为复制延迟,如果复制延迟很大,就会导致Replica跟不上Primary。

另外,在POLARDB上,所有主库上执行完的DDL都要等所有Replica应用完相应日志后才能返回成功。换句话说,如果Replica有很大的复制延迟,可能会导致主库执行DDL不成功。这一点可以详见上一篇月报中的“DDL与大事务”小节。

由此可看,我们要尽可能的减小复制延迟。物理复制有两个阶段,一个是日志解析的阶段,另外一个是日志应用的阶段。任何一个阶段慢了,都可能导致复制延迟。日志解析,目前是单线程解析,在我们测试情况下,只有当写入量超过每秒20W(最大规格)的情况下,解析线程才会成为瓶颈。日志应用阶段,由于应用比较慢,目前是多线程应用,不过在POLARDB中,我们只需要应用在内存中的数据页即可,换句话说,如果日志涉及的数据页不在内存中,我们就不需要应用这些日志(目前的方案是把这些日志存在内存中),直到这些数据页被读入内存,我们在IO线程中把这些拉下的日志都应用掉。

用户可以在只读节点上查询`Innodb_replication_delay`这个status变量来获取当前复制延迟。如果复制延迟比较大并且有不断增大的趋势,可以调大参数`loose_innodb_slave_recv_hash_cells_max`和`loose_innodb_slave_recv_hash_cells_min`来减少延迟。这个参数的主要作用就是增加日志hash桶的数量,从而减少日志查找时候的开销。另外,我们的Proxy支持Session一致读,即同一个session内的读和写保证是逻辑相关的,在写之后立即读,一定会读到最新的数据(只有由于读写分离,会把读请求发到有延迟的只读节点,从而导致读取到历史版本的数据)。

内存问题
有部分用户发现,相比RDS MySQL兼容版,POLARDB的内存使用量会更多,这里需要说明一下,这里的主要原因是我们在很多地方使用了以空间换时间的方法,所以内存使用量上会有一定的上升。如果用户发现内存占用过多,可以从以下几个方面诊断:

* 调小`table_open_cache`,同时使用`flush tables`命令关闭所有表空间。这招对打开表数量很多且用户连接很多的用户来说,效果比较明显。
* 关闭自适应哈希。`innodb_adaptive_hash_index`,这个功能可能会占用很多的内存,但是在现在的SSD磁盘下,性能提升有限。
* 适当的调小buffer pool。这点比较适用于连接数多,同时又很多复杂查询的用户。不过目前调整Buffer pool需要联系售后。

大部分用户的实例,在内存上涨到一定数量后,会停止上涨,这种情况下,即使内存占比比较高,也不用担心,因为我们是尽可能的使用内存,把内存尽可能的都用起来,缓存数据,提高效率。但是如果发现实例内存不断上涨,然后直到OOM,这种情况,请立刻联系我们,我们会帮您在后台开启一个内存监控工具(依赖Jemalloc profiling工具,但是需要重启数据库),可以用来发现这些内存去哪儿了,到底是您使用的问题,还是内存泄露问题,如果是我们的问题,我们会尽快解决并给与一定的补偿,如果是您使用的问题,我们也会给您指出,给出优化的建议等。

在后续的POLARDB版本上,我们会增加更多的内存监控功能,方便用户自行进行排查。

IOPS问题

目前POLARDB的每种规格的实例,都有IOPS限制。同时,RDS MySQL的实例也有IOPS限制。往往有很多用户把这个两个IOPS做比较,其实意义不大。

在RDS中,这个IOPS是指数据文件(后台刷脏线程触发)的写盘次数,日志文件每秒的写盘次数(即主要是Redolog和Binlog加起来的写盘次数限制)是不统计在里面的。但是在POLARDB中,这个IOPS限制不但包括了日志文件的写盘次数限制,也包括了数据文件的写盘限制,即两者加起来每秒写盘的次数不能超过这个值。数据和日志一起限制,一起隔离,这样更加合理,也减少了因为磁盘压力而产生的性能抖动。

因此,我们在设置同一类型的规格IOPS参数时,POLARDB上的IOPS一般都比RDS上大好几倍。多出来的IOPS主要是给日志文件刷盘预留的。

目前临时表的磁盘还是普通的NVME本地磁盘,没有放在自研的磁盘上,因此临时表的IOPS暂时不计入总的IOPS,而RDS上这一部分是放在数据盘上,所以这部分IO会记录到IOPS限制上。同时,我们在MySQL内核代码中,也有部分限制,主要是为了防止某些用户临时表使用过多从而影响了其他用户。

错误日志,审计日志,慢日志的写盘操作同样在本地盘,没有计入IOPS限制。

另外,我们在IO带宽上,目前还没有硬的限制(目前POLARDB的实例达不到自研存储的带宽上线),但是很快,为了资源隔离的更加彻底,我们也会加上IO带宽的限制和隔离。

升级问题
由于POLARDB是阿里云自研的关系数据库产品,我们在上面不但开发了数据库系统,还开发了一套对应的文件系统和块存储系统。不少用户对我们的期望很高,也提出了许多优化建议,因此POLARDB的系统升级频率会比RDS家族的产品高。一方面,我们需要修复一些极端情况下会被触发的BUG,另外一方面,我们需要满足用户形形色色的需求。当然,我们的系统升级都是热升级,我们竭尽所能尽可能减少对用户的影响,例如在系统升级时期的性能抖动时间,数据库不可服务时间等等。如果系统要升级,我们会提前给用户发短信/邮件以及大客户的电话通知,一般系统升级都在凌晨用户设定的可运维时间,正常的升级流程可能会造成秒级的服务中断,客户只需要在应用端保证能重连数据库即可。如果那天晚上,升级不太方便,请尽快提工单给我们,我们再约其他时间。我们后台也在开发一套主动运维系统,有了这套系统之后,用户就可以在控制台上在指定的时间点内主动升级数据库,同时不同版本的数据库也会有详尽的releasenote,便于用户按需升级。

总结
这篇文件简单解答了几个用户常常问到的问题。希望对大家有所帮助。

最后,欢迎使用POLARDB。