Author: 巴彦
目前undo log读取有这么几个方面:旧版本数据读取;purge时扫描undo log record;崩溃恢复时扫描undo log record;大事务回滚。 目前线上关于undo log遇到这样两个case:
针对第一个问题,我们开始想:因为重启时扫描undo log record只是为了读取其中的table id,所以如果把这些table id记录到一个undo page中;那么崩溃重启恢复时就不用再去扫描其他undo log record。
针对第二个问题,我们开始想把一个trx用到的所有undo page no记录到一个数据结构中(如vector);等需要purge时,对这些undo page下发预读请求,加快读取速度。
针对第三个问题,就是扫描undo log record太慢导致回滚速度太慢。
虽然看似解决了上面三个问题;但是究其根本,还是因为读取undo page太慢的原因导致读取undo log太慢;所以我们又想出一种方案可以同时解决上面两个问题:
现在我们没有办法对undo page进行预读,是因为TRX_UNDO_PAGE_LIST 是存储在文件中;只有读取了当前的page,才知道下一个undo page在何处;所以我们设计了一个方案,在一个undo page中同时存储了这个page在TRX_UNDO_PAGE_LIST前面几个和后面几个page的位置;这样我们在读取这个page时,就可以对这个page后面几个或前面几个page进行预读;详细设计如下。
详细实现如下: 假设我们现在每次预读的page数为 n。 首先新增两个数据结构
具体流程如下:
如何使用TRX_UNDO_PAGE_JUMP_LIST?
关于undo page的删除 关于undo page从TRX_UNDO_PAGE_LIST删除只有三种情况:
关于 n 的取值 n的取值涉及到undo log读取速度;undo page预留空间空间的问题; 原则上我们希望尽可能的提高undo log读取速度的同时,尽可能的减少对undo page的占用。 所以具体效果需要测试;查看n在不同取值时,对undo log读取速度,和undo page空间占用的影响。
兼容性的问题 在undo page中,我们需要通过一个字段来判断这个undo page是新版本的,还是旧版本的; 我们了解到,在undo log segment header中的字段,其中TRX_UNDO_STATE字段的内容其实用一个byte就可以存储,但是却分配了2个byte;所以我们用第二个byte来标记这个undo log是不是新版本的,取名为TRX_UNDO_NEW。 所以当启动实例时如果TRX_UNDO_NEW值为0,则采取老版本undo log格式来解析,否则用新版本格式来解析。 但是尽管这样,我们解决也仅仅解决了向下兼容的问题,无法解决向上兼容的问题:如果用一个老版本实例来读取新版本的undo log则会发生crash。
场景 | 插入耗时 | 更新耗时 | 恢复时间 | |
---|---|---|---|---|
upstream | 1 hour 50 min 27.04 sec | 12 hours 48 min 28.16 sec | 849s | |
read_ahead_pages=2 | 1 hour 25 min 43.66 sec | 12 hours 41 min 50.84 sec | 627s | |
read_ahead_pages=4 | 1 hour 53 min 48.22 sec | 12 hours 19 min 15.08 sec | 477s | |
read_ahead_pages=6 | 2 hours 1 min 28.73 sec | 12 hours 50 min 51.06 sec | 382s | |
read_ahead_pages=8 | 3 hours 3 min 58.53 sec | 12 hours 44 min 46.06 sec | 320s |
从上面的测试结果来看,在使用了这个方案来优化了undo log IO后;大事务场景下,crash recovery的时间有了非常明显的提升,但是随着read ahead page的增多,插入时间有着明显的增加;所以在使用时,我们兼顾插入耗时,和undo log磁盘的消耗,可以设置read_ahead_pages = 4或者read_ahead_pages = 6来使用。