[转帖]【MySQL】MySQL 8.0 redo log写入性能问题分析

mysql,redo,log,写入,性能,问题,分析 · 浏览次数 : 0

小编点评

**性能提升的原因:** 1. **log_writer独占1个核心,用户线程 CPU 利用率有所提高:**当log_writer独占1个核心时,用户线程的CPU 利用率有所提升,导致瓶颈。 2. **log_writer_no_waits从24110198下降到3395741:**由于隔离log_writer后,用户线程的CPU 利用率有所提高,log_writer拿不到CPU导致,对应的log_writes也从24891146降到4036257,降低了log_writer的写入效率。 3. **log_on_flush_no_waits从8616066下降到3011060:**由于隔离log_writer,用户线程在等待自己对应LSN刷盘的wait loops减少,从而提升了性能。 4. **log_on_flush_waits从8616066下降到3011060:**虽然log_on_flush_waits不变,但用户线程在等待recent_closed buffer回收空间次数变多,导致log_writer写的更快。 5. **log_on_recent_closed_wait_loops从0变成了17844432:**由于用户线程在mtr::commit时等待recent_closed buffer回收空间次数变多,导致log_writer写的更快,而log_closer由于与用户线程共享CPU,回收recent_closed不及时导致的,log_closer成为新的瓶颈。 **改进方案:** 1. **提高log异步线程的优先级使用RocksDB写WAL:**但是,目前个人觉得这种一把梭实现没有什么硬伤,反而更好。

正文

http://kernelmaker.github.io/MySQL_8_core

 

最近对比了MySQL 5.6和8.0在8核环境下oltp_write_only的性能,发现8.0写入性能(QPS 6-7万)反而低于5.6版本的(QPS 14万),所以进一步测试分析了下redo log这里可能导致性能降低的原因,整理记录一下

1. 测试方法

sysbench –mysql-host=IP –mysql-port=PORT –mysql-user=mysql –mysql-password=PASSWD –tables=250 –table_size=25000 –db_driver=mysql –threads=128 –report-interval=5 –rand-type=uniform prepare

sysbench –mysql-host=IP –mysql-port=PORT –mysql-user=mysql –mysql-password=PASSWD –tables=250 –table_size=25000 –db_driver=mysql –threads=128 –report-interval=5 –rand-type=uniform –max-time=360 –max-requests=3000000 run

sysbench –mysql-host=IP –mysql-port=PORT –mysql-user=mysql –mysql-password=PASSWD –table_size=25000 –db_driver=mysql –threads=128 cleanup

2. 测试结果

1. upstream 8.0 (8核)

SQL statistics:
    queries performed:
        read:                            0
        write:                           12000000
        other:                           6000000
        total:                           18000000
    transactions:                        3000000 (10792.71 per sec.)
    queries:                             18000000 (64756.24 per sec.)
    ignored errors:                      0      (0.00 per sec.)
    reconnects:                          0      (0.00 per sec.)

General statistics:
    total time:                          277.9637s
    total number of events:              3000000

CPU:

2. upstream 8.0(8核,CPU 8专门跑log_writer,其余线程跑在1-7)

SQL statistics:
    queries performed:
        read:                            0
        write:                           12000000
        other:                           6000000
        total:                           18000000
    transactions:                        3000000 (10705.28 per sec.)
    queries:                             18000000 (64231.71 per sec.)
    ignored errors:                      0      (0.00 per sec.)
    reconnects:                          0      (0.00 per sec.)

General statistics:
    total time:                          280.2336s
    total number of events:              3000000

CPU:

3. upstream 8.0(8核,CPU 8专门跑log_flusher,其余线程跑1-7)

SQL statistics:
    queries performed:
        read:                            0
        write:                           12000000
        other:                           6000000
        total:                           18000000
    transactions:                        3000000 (12860.01 per sec.)
    queries:                             18000000 (77160.08 per sec.)
    ignored errors:                      0      (0.00 per sec.)
    reconnects:                          0      (0.00 per sec.)

General statistics:
    total time:                          233.2794s
    total number of events:              3000000

CPU:

4. upstream 8.0(8核,CPU 8专门用来跑log_writer和log_flusher,其余线程跑在1-7)

SQL statistics:
    queries performed:
        read:                            0
        write:                           12000000
        other:                           6000000
        total:                           18000000
    transactions:                        3000000 (15305.69 per sec.)
    queries:                             18000000 (91834.11 per sec.)
    ignored errors:                      0      (0.00 per sec.)
    reconnects:                          0      (0.00 per sec.)

General statistics:
    total time:                          196.0038s
    total number of events:              3000000

CPU:

3. 结果分析

  1. 不做任何隔离时,8核均无法跑不到100%,性能较低,QPS 64756.24
  2. 只隔离log_writer时,单独跑log_writer的核可以接近100%,但剩余7核不行,性能没有提升, QPS 64231.71
  3. 只隔离log_flusher时8核可以都大于90%,性能有所提升,QPS 77599.04
  4. 同时隔离log_writer和log_flusher时8核可以都接近100%,性能进一步提升,QPS 91834.11

从上面的结果可以看出来,

  1. log_flusher是否隔离对8核CPU利用率的影响较大,分析原因应该是在不隔离log_flusher时,由于和很多用户线程共享CPU,所以log_flusher得不到有效的CPU调度无法充分执行,flush_to_disk_lsn更新滞后反过来又影响到用户线程的推进(innodb_flush_log_at_trx_commit = 1),所以整体CPU利用率上不去。隔离log_flusher之后,CPU和性能都有所提升,但相比同时隔离log_writer和log_flusher又有低一些
  2. 单独隔离log_writer性能没有提升,分析原因应该还是同上,瓶颈在log_flusher上

综上,关于upstream 8.0写入性能较低的原因推测是log_writer和log_flusher由于和大量用户线程共享CPU核心,得不到充分调度成为瓶颈,影响整体写入性能,并且log_flusher更为严重。

4. 进一步验证

为了进一步验证上面的推测,我使用sysbench –max-requests=3000000分别对如下4中场景:

a.不隔离
b.同时隔离log_writer及log_flusher
c.单独隔离log_writer
d.单独隔离log_flusher

进行等量压测,每次都是300万次请求,另外为了LSN的可比较性,关掉了undo purge,基于此,我在sysbench run阶段对InnoDB log module进行打点记录,下面分组来看具体性能和打点信息:

性能

 a.不隔离b.隔离writer&flusherc.隔离writerd.隔离flusher
QPS 78739.83 93705.47 84082.08 81546.58
TIME 228.5992s 192.0894s 214.0747s 220.7310s

由于关掉了undo purge,除场景b外,其他场景QPS相较于之前的测试有所提升是符合预期的

打点信息

1. LSN

 a.不隔离b.隔离writer&flusherc.隔离writerd.隔离flusher
log_lsn_current 5736265204 5736254809 5736543934 5736474256

同样由于关掉了undo purge,对于sysbench –max-requests=3000000,lsn差不多

2. Buffer Wait

 a.不隔离b.隔离writer&flusherc.隔离writerd.隔离flusher
log_waits 0 0 0 0
log_on_buffer_space_no_waits 0 0 0 0
log_on_buffer_space_waits 0 0 0 0
log_on_buffer_space_waits_loops 0 0 0 0

上面的这4个监控信息都是

mtr_t::commit()->
mtr_t::Command::execute()->
log_buffer_reserve()->
log_wait_for_space_after_reserving()->
log_wait_for_space_in_log_buf()->
log_write_up_to(flush_to_disk=false)->
log_wait_for_write()

里的打点,它们全为0,也就是说明配置使用的64M大小log buffer是够用的,无论隔离与否,都不会出现因为log buffer不够用而进行回收时的wait

3. recent_XXX

 a.不隔离b.隔离writer&flusherc.隔离writerd.隔离flusher
log_on_recent_written_wait_loops 0 0 0 0
log_on_recent_closed_wait_loops 0 0 0 0
  1. log_on_recent_written_wait_loops是在
mtr_t::commit()->
mtr_t::Command::execute()->
mtr_write_log_t::()->
log_buffer_write_completed()

里如果由于recent_written不足等待回收则进行的打点

  1. log_on_recent_closed_wait_loops是在
mtr_t::commit()->
mtr_t::Command::execute()->
mtr_write_log_t::()->
log_wait_for_space_in_log_recent_closed()

里如果由于recent_closed不足等待回收则进行的打点

它们都为0说明recent_written和recent_closed当前配置够用,并且log_writer和log_closer对其回收及时。

重点关注下面的打点信息,首先先说下打点位置

4. Log Writes

 a.不隔离b.隔离writer&flusherc.隔离writerd.隔离flusher
log_write_requests 53837519 53772294 53987892 54287129
log_writes 24891146 22661060 40653491 4036257
  1. log_write_requests是
mtr_t::commit()->
mtr_t::Command::execute()->
log_buffer_reserve()

里打点,可以看到mtr的个数也差不多

  1. log_writes是
log_writer()->
log_writer_write_buffer()->
log_files_write_buffer()

里log_writer将log写进table cache并更新log_sys->write_lsn之后的打点

5. log_writer

 a.不隔离b.隔离writer&flusherc.隔离writerd.隔离flusher
log_writer_no_waits 24110198 21737096 38915995 3395741
log_writer_waits 706 525 341 233
log_writer_wait_loops 4505 4110 3084 1467
log_writer_on_file_space_waits 0 0 0 0
log_write_notifier_no_waits 96650 117586 141001 42684
log_write_notifier_waits 6364591 2184418 2405656 2168416
log_write_notifier_wait_loops 6702652 2520352 2439346 2846572
log_on_write_no_waits 0 0 0 0
log_on_write_waits 0 0 0 0
log_on_write_wait_loops 0 0 0 0
  1. log_writer_no_waits和log_writer_waits都是在
log_writer()->
os_event_wait_for(log.writer_event)

后的打点,log_writer_no_waits是在os_event_wait_for()时发现有连续可写的内容不需wait的次数,log_writer_waits是在os_event_wait_for()时发现没有连续可写的内容需要wait的次数

  1. log_write_notifier_no_waits和log_write_notifier_waits都是在
log_write_notifier()->
os_event_wait_for(log.write_notifier_event)

后的打点,log_writer_no_waits是在os_event_wait_for()时发现log.write_lsn向前推进所以不需wait的次数,log_write_notifier_waits是os_event_wait_for()时发现log.write_lsn没有向前推进所以需要wait的次数,待分析

  1. log_on_write_no_waits和log_on_write_waits都是在
XXX->
log_write_up_to()->
log_wait_for_write()->
os_event_wait_for(log.write_events[])

后的打点,可以看到都为0,说明并不存在用户线程在等待log_writer更新log.write_lsn到指定lsn的时候。

6. log_flusher

 a.不隔离b.隔离writer&flusherc.隔离writerd.隔离flusher
log_flusher_no_waits 219898 4639077 353433 843696
log_flusher_waits 11126 201415 215 59647
log_flusher_wait_loops 205948 224423 2083 2797221
log_flush_notifier_no_waits 7049 121679 2944 62289
log_flush_notifier_waits 128905 950814 231335 218895
log_flush_notifier_wait_loops 980173 1091132 1059829 958259
log_on_flush_no_waits 760 8934 972 1506
log_on_flush_waits 2993605 2975193 2993935 2991368
log_on_flush_wait_loops 8616066 3011060 6091019 6954663
log_flush_lsn_avg_rate 358652 11395 234238 94717
log_flush_total_time 181411112us (181s) 137893867us (138s) 209647279us (210s) 29879924us (30s)
log_flush_avg_time 683us 27us 642us 25us
  1. log_flusher_no_waits、log_flusher_waits和log_flusher_wait_loops都是在
log_flusher()->
os_event_wait_for(log.flusher_event)

后的打点,log_flusher_no_waits是在os_event_wait_for()时发现log.write_lsn向前推进所以不需要wait的次数,

log_flusher_waits是在os_event_wait_for()时发现log.write_lsn没有向前推进所以需要wait的次数,log_flusher_wait_loops则是在wait里被唤醒或timeout之后发现condition还不满足而继续wait的loop次数

  1. log_flush_notifier_no_waits和log_flush_notifier_waits都是在
log_flush_notifier()->
os_event_wait_for(log.flush_notifier_event)

后的打点,log_flush_notifier_no_waits是在os_event_wait_for()时发现log.flushed_to_disk_lsn更新所以不需要wait的次数,log_flush_notifier_waits是在os_event_wait_for()时发现log.flushed_to_disk_lsn没更新所以需要wait的次数

  1. log_on_flush_no_waits、log_on_flush_waits和log_on_flush_wait_loops都是在
XXX->
log_write_up_to(flush_to_disk=true)->
log_wait_for_flush()

后的打点,这里是用户线程在(innodb_flush_log_at_trx_commit = 1)等待flush_to_disk_lsn更新到自己对应的LSN,log_on_flush_no_waits是当用户线程对应的LSN的redo log已被log_flusher刷盘,无需wait的次数,log_on_flush_waits是当用户线程对应LSN的暂未不刷盘,需要wait的次数,log_on_flush_wait_loops是在wait中被唤醒或timeout后发现condition还不满足继续wait的loop次数

  1. log_flush_lsn_avg_rate是每30次flush的内容平均每秒的长度
  2. log_flush_total_time是整体flush的用时
  3. log_flush_avg_time单次flush的平均用时

基于以上信息,分析如下,以a.不隔离作为基准

  1. 隔离log_flusher(场景d),可以看到隔离log_flusher之后,由于log_flusher独占1个核心,此时log_flusher_no_waits从219898增长到843696(4倍),说明log_flusher被调度执行到的次数更多,刷盘更及时,甚至因为CPU太过充分导致log_flusher_waits、log_flusher_wait_loops出现大幅增长。而log_on_flush_no_waits和log_on_flush_waits没有太大变化说明即使刷盘效率有所提高,但用户线程在自己对应LSN刷盘时还是需要等待,不过log_on_flush_wait_loops有所减少,从8616066减少到6954663,虽然wait次数没变,但wait里的loop变少,说明一定程度上用户线程推进速度还是有所提升,这点也符合QPS和CPU利用率提升的现象。另外log_flusher大约100万次(log_flusher_no_waits+log_flusher_waits)的刷盘操作,总用时30s,单次刷盘25us,效率最高。不过为什么整体性能提升有限呢?因为此时log_writer成为了瓶颈,此时CPU利用率有所提升,log_writer与大量用户线程共享核心,得不到充分调度,更新write_lsn不及时,这点可以从log_writer_no_waits和log_writes看出来,log_writer_waits没变的基础上,log_writer_no_waits从24110198降到3395741(1/6),正是因为隔离log_flusher后,用户线程CPU利用率有所提高(不隔离时会在等待自己对应LSN刷盘上wait较久,这样还会让出CPU给log_writer来用),log_writer拿不到CPU导致,对应的log_writes也从24891146降到4036257(1/6),log_writer的写入效率变低,瓶颈。
  2. 隔离log_writer(场景c),可以看到隔离log_writer之后,由于log_writer独占1个核心,此时log_writer_no_waits从24110198增长到38915995(近2倍),说明log_writer被调度到的次数更多,write_lsn推进更及时。而log_writer_waits没什么变化说明即使log_writer独占1个核心,CPU也是刚刚够用,基本每次写完page cache后就有新的log要再写入。不过log_on_flush_no_waits和log_on_flush_waits没有太大变化说明即使写page cache效率有所提高,但用户线程在等自己对应LSN刷盘时还是需要等待,不过log_on_flush_wait_loops有所减少,从8616066减少到6091019,一定程度上用户线程推进速度有所提升。至于整体性能提升不明显的原因,是因为此时log_flusher是瓶颈。按理说log_writer执行频率和效率大幅提高,唤醒log_flusher的频率也会很高,但log_flusher_no_waits从219898仅增长至353433(1.7倍,还不如隔离log_flusher带来的提升),log_flusher_waits甚至降低,另外可以看到log_flusher大约刷盘35万次,总用时210s,单次平均用时642us,(即使刨除由于单次fsync的内容变多导致可能的fsync用时变长之外)远高于隔离log_flusher时的用时,都说明此时log_flusher得不到有效的CPU调度,执行效率低下,瓶颈。
  3. 同时隔离log_writer和log_flusher(场景b),二者共用1个核心,此时log_flusher_no_waits从219898提升至4639077(23倍),被调度的更多,执行更充分,另外log_writer_no_waits没有太大变化(应该是log_flusher被调度更多了,执行效率变高,所以与其共享一个核心的log_writer相比不隔离时被调度的次数稍少一些),这样在log_writer执行效率没有太大变化的情况下,log_flusher效率大幅度提升,一共刷盘500万次,总用时138s,单次平均用时27us。这种情况,在log_on_flush_waits不变的基础上,log_on_flush_no_loops从8616066减少到3011060(1/3),用户线程在等待自己LSN落盘的wait loops减少最多,所以整体性能也提升最多。

5. 其他发现

  1. 在不做任何隔离的情况下,打开undo purge的性能低于关闭时的QPS,(64756.24 : 78739.83),分析原因应该是打开undo purge时,由于undo purge truncate会写入更多的log,导致LSN变大(5736265204 :7692486960),在插入这么多新的redo log后,由于log_flusher效率并没有提升,log变多了,用户线程在等待自己对应LSN刷盘时会等更久(log_on_flush_wait_loops变化,13015383 : 8616066),所以性能低一些。
  2. 在32核,1024并发场景下,不隔离时QPS是274509.11,隔离时(log_writer和log_flusher同时绑定在CPU32)QPS反而降为254697.57,分析了下打点数据,隔离时log_writer_no_waits从8175104将为5261245,说明当在更多核心更高压力时,将log_writer、log_flusher同时放在一个核心会因为此时log_flusher相比不隔离时刷盘更快而导致log_writer被调度执行的机会更少,log_writer成为瓶颈
  3. 在2的场景下,进一步测试,保持用户使用的核心数不变(CPU 1-31),多加一个核心,将log_writer绑定CPU32,log_flusher绑定CPU33,发现此时性能和不隔离是差不太多(281093.45 :274509.11),分析打点数据,此时log_writer_no_waits(11153587)和log_flusher_no_waits(1600971)都大幅提升,说明二者并不是瓶颈(另外观察到此时log_flusher所在核心的CPU利用率也都有70%左右了),但性能为什么没上去呢?发现此时log_on_recent_closed_wait_loops从之前的0变成了17844432,证明用户线程在mtr::commit时等待recent_closed buffer回收空间次数变多,应该是此时log_writer写的更快,而log_closer由于与用户线程共享CPU,回收recent_closed不及时导致的,log_closer成为新的瓶颈
  4. 在3的场景下,进一步测试,保持用户使用的核心数(CPU 1-31)和log_writer(CPU 32)和log_flusher(CPU32)不变的基础上,再加一个核心,绑定log_closer(CPU33),此时性能提升较多310020.40,不过此时log_on_recent_closed_wait_loops并不为0(758048),因为log_closer并不是由谁唤醒而是周期性的执行,所以在这种情况下,默认的sleep时间(1000us)也显得太久,需要调小
  5. 在2的场景下,只是用32个核心,用户线程使用(1-29),log_flusher、log_writer、log_closer使用(30-32),这样即使用户线程使用的核心相比场景2少了2个,但整体性能是299089.22,相比场景2反而有所提升

6. 总结

MySQL 8.0将写redo log拆分成多个线程异步来做的方式,可能并不是理想的优化,

  1. 当并发比较低(用户线程少)时,用户线程与这些异步线程之间的交互等待反而不如直接像RocksDB那样全有用户线程来做更核实
  2. 在并发比较高(用户线程多)时,又会因为这些异步线程(log_writer、log_flusher、log_closer)得不到充分的CPU调度而成为写入瓶颈。这样的异步设计需要确保这些异步线程有足够的CPU时间才能提高性能

所以目前我能想到的改进方案有如下2种:

  1. 提高log异步线程的优先级
  2. 使用RocksDB写WAL的做法,不过暂时还不确定,目前个人觉得这种一把梭实现没有什么硬伤,反而更好

与[转帖]【MySQL】MySQL 8.0 redo log写入性能问题分析相似的内容:

[转帖]【MySQL】MySQL 8.0 redo log写入性能问题分析

http://kernelmaker.github.io/MySQL_8_core 最近对比了MySQL 5.6和8.0在8核环境下oltp_write_only的性能,发现8.0写入性能(QPS 6-7万)反而低于5.6版本的(QPS 14万),所以进一步测试分析了下redo log这里可能导致性

[转帖]第一章 MySQL 8.0 介绍及安装配置

第一章 MySQL 8.0 介绍及安装配置 https://www.jianshu.com/p/d190c6b3520d 本课程,适合具备一定Linux运维或者开发基础的朋友,课程定级中、高级DBA。只要掌握80%,轻松助力薪资15k-25K。课程内容均来自与MySQL官网+MySQL源码。配套精品

[转帖]MySQL 8.0: When to use utf8mb3 over utf8mb4?

https://dev.mysql.com/blog-archive/mysql-8-0-when-to-use-utf8mb3-over-utf8mb4/ MySQL 8.0: When to use utf8mb3 over utf8mb4? Posted on May 19, 2017 by 

[转帖]MySQL 8.0.19 instant add column,亿级数据秒速增加字段

一、MySQL DDL 的方法 MySQL 在大型表上的 DDL 会带来耗时较久、负载较高、额外空间占用、MDL、主从同步延时等情况。需要特别引起重视,而MySQL 的 DDL 有很多种方法。 MySQL 本身自带三种方法,分别是:copy、inplace、instant。 copy 算法为最古老的

[转帖]MySQL 8.0 Instant Add Column功能解析

https://zhuanlan.zhihu.com/p/408702204 概述 DDL(Data Definition Language)是数据库内部的对象进行创建、删除、修改的操作语言,主要包括:加减列、更改列类型、加减索引等类型。数据库的模式(schema)会随着业务的发展不断变化,如果没有

[转帖]MySQL 8.0新特性和性能数据

https://plantegg.github.io/2022/07/03/MySQL8.0%E7%9A%84%E4%B8%80%E4%BA%9B%E6%95%B0%E6%8D%AE/ MySQL 8.0带来了很多新特性 针对性能方面介绍全在这个PPT( http://dimitrik.free.f

【转帖】MySQL 8.0.32如期而至

MySQL 8.0版本计划 MySQL 8.0开始采用快速迭代开发模式,基本上是每隔3个月就发布一个新的小版本。去年1月18日(2022.1.18)发布MySQL 8.0.28,今年1月17日发布MySQL 8.0.32,再看看其他几个版本的时间,还真是贼守时啊。 版本发布时间上一年版本上一年发布时

【转帖】MySQL 8.0 hash join有重大缺陷?

我并不这么看。 友情提醒:本文建议在PC端阅读。 徐春阳老师发文爆MySQL 8.0 hash join有重大缺陷。 文章核心观点如下:多表(比如3个个表)join时,只会简单的把表数据量小的放在前面作为驱动表,大表放在最后面,从而导致可能产生极大结果集的笛卡尔积,甚至耗尽CPU和磁盘空间。 就此现

[转帖]MySQL Performance : 8.0 and UTF8 impact

http://dimitrik.free.fr/blog/posts/mysql-performance-80-and-utf8-impact.html 2018-04-26 00:58 | MySQL, Performance, UTF8 by Dimitri The world is movin

[转帖]【MySQL 8】MySQL 5.7都即将停只维护了,是时候学习一波MySQL 8了

https://www.cnblogs.com/paul8339/p/17026571.html 阅读目录 账户与安全 索引增强 原子DDL操作 通用表达式(CTE) 其他 MySQL 8新特性选择MySQL 8的背景:MySQL 5.6已经停止版本更新了,对于 MySQL 5.7 版本,其将于 2