MySQL中MGR中SECONDARY节点磁盘满,导致mysqld进程被OOM Killed

在MGR测试中,人为制造磁盘满问题后,节点被oom killed

问题描述

在对MySQL 8.0.26 vs GreatSQL 8.0.25的对比测试过程中,有一个环节是人为制造磁盘满的场景,看看MGR是否还能正常响应请求。

在实测过程中,最后发现磁盘满的那个节点,持续时间足够久后,会因为内存消耗过大而最终被OS给OOM Kill。

这个问题我已报告BUG(#104979),下面是该过程的详细记录。

首先,直接利用dd复制空文件填满磁盘。

MySQL 8.0.26 测试过程

disk full报告过程及何时被oom killed

来看下MySQL 8.0.26遇到disk full时日志都输出哪些内容:

# 首次提示disk full的时刻是 09:44:10.052558,这时其实还能写入日志,只是不能写数据和binlog
2021-09-18T09:44:10.052558+08:00 10 [ERROR] [MY-000035] [Server] Disk is full writing './yejr-mgr3-relay-bin-group_replication_applier.000046' (OS errno 28 - No space left on device). Waiting for someone to free space... Retry in 60 secs. Message reprinted in 600 secs.
2021-09-18T09:44:10.052558+08:00 15 [ERROR] [MY-000035] [Server] Disk is full writing '/data/MySQL/binlog.000039' (OS errno 28 - No space left on device). Waiting for someone to free space... Retry in 60 secs. Message reprinted in 600 secs.
2021-09-18T09:54:10.109075+08:00 15 [ERROR] [MY-000035] [Server] Disk is full writing '/data/MySQL/binlog.000039' (OS errno 28 - No space left on device). Waiting for someone to free space... Retry in 60 secs. Message reprinted in 600 secs.
2021-09-18T09:54:10.109828+08:00 10 [ERROR] [MY-000035] [Server] Disk is full writing './yejr-mgr3-relay-bin-group_replication_applier.000046' (OS errno 28 - No space left on device). Waiting for someone to free space... Retry in 60 secs. Message reprinted in 600 secs.
2021-09-18T09:56:15.020870+08:00 152 [ERROR] [MY-010907] [Server] Error writing file '/data/MySQL/slow.log' (errno: 28 - No space left on device)
# 最后一次提示disk full时是10:04:10.166349,这时候彻底无法写入日志了
2021-09-18T10:04:10.166349+08:00 15 [ERROR] [MY-000035] [Server] Disk is full writing '/data/MySQL/binlog.000039' (OS errno 28 - No space left on device). Waiting for someone to free space... Retry in 60 secs.

从disk full时刻开始,大约过了2.5小时,mysqld进程内存消耗持续上升,最终引发oom kill

Sep 18 12:56:28 mgr3 kernel: docker-containe invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=-500
...
Sep 18 12:56:29 mgr3 kernel: Out of memory: Kill process 9539 (mysqld) score 902 or sacrifice child
Sep 18 12:56:29 mgr3 kernel: Killed process 9539 (mysqld), UID 27, total-vm:17020364kB, anon-rss:14644556kB, file-rss:0kB, shmem-rss:0kB

在这期间某个时刻抓到的待认证事务堆积,在被oom kill前实际不止这么多:

+--------------------------------------+----------+
| id                                   | trx_que  |
+--------------------------------------+----------+
| dbe4f563-1622-11ec-8cc8-525400e802e2 | 87863918 |
+--------------------------------------+----------+

关注mysqld进程内存消耗变化

下面是mysqld进程内存消耗变化情况

# 一开始3G
 9539 3144872 /usr/local/mysql-8.0.26-linux-glibc2.12-x86_64/bin/mysqld --defaults-file=/data/MySQL/my.cnf
...
# 涨到3.47G
 9539 3641984 /usr/local/mysql-8.0.26-linux-glibc2.12-x86_64/bin/mysqld --defaults-file=/data/MySQL/my.cnf
...
# 涨到7G
 9539 7416908 /usr/local/mysql-8.0.26-linux-glibc2.12-x86_64/bin/mysqld --defaults-file=/data/MySQL/my.cnf
...
# 不断增长,直至最后被oom killed前,大约飙到14G
 9539 14638256 /usr/local/mysql-8.0.26-linux-glibc2.12-x86_64/bin/mysqld --defaults-file=/data/MySQL/my.cnf

OS层oom-killer相关日志:

# mysqld进程内存大约飙到14G
Sep 18 12:56:29 mgr3 kernel: Out of memory: Kill process 9539 (mysqld) score 902 or sacrifice child
Sep 18 12:56:29 mgr3 kernel: Killed process 9539 (mysqld), UID 27, total-vm:17020364kB, anon-rss:14644556kB, file-rss:0kB, shmem-rss:0kB

GreatSQL 8.0.25测试过程

作为对比,我用GreatSQL 8.0.25也做了同样的测试。

# 首次报告disk full
2021-09-18T23:07:49.264992+08:00 89 [ERROR] [MY-000035] [Server] Disk is full writing '/data/GreatSQL/binlog.000085' (OS errno 28 - N
o space left on device). Waiting for someone to free space... Retry in 60 secs. Message reprinted in 600 secs.
2021-09-18T23:07:49.264992+08:00 82 [ERROR] [MY-000035] [Server] Disk is full writing './yejr-mgr3-relay-bin-group_replication_applie
r.000147' (OS errno 28 - No space left on device). Waiting for someone to free space... Retry in 60 secs. Message reprinted in 600 se
cs.

# 发出报错,提示因为disk full,无法写入data/binlog
2021-09-18T23:07:49.838301+08:00 84 [ERROR] [MY-011071] [Repl] Plugin group_replication reported: 'io full on data or binlog director
y'
# 本节点自动改为RO
2021-09-18T23:07:49.852475+08:00 84 [ERROR] [MY-011712] [Repl] Plugin group_replication reported: 'The server was automatically set i
nto read only mode after an error was detected.'
2021-09-18T23:07:49.852633+08:00 0 [ERROR] [MY-011486] [Repl] Plugin group_replication reported: 'Message received while the plugin i
s not ready, message discarded.'
# 接下来准备退出集群了
2021-09-18T23:07:49.856907+08:00 0 [Note] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] Re-using server node 0 host 17
2.16.16.53'
2021-09-18T23:07:49.856937+08:00 0 [Note] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] Re-using server node 1 host 17
2.16.16.16'
2021-09-18T23:07:49.856950+08:00 0 [Note] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] pid 5211 Installed site start={dba4e34 1394386 1} boot_key={dba4e34 1394375 1} event_horizon=10 node 4294967295 chksum_node_list(&site->nodes) 704906340'
2021-09-18T23:07:55.380111+08:00 0 [Note] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] Installing leave view.'
2021-09-18T23:07:55.380169+08:00 0 [Note] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] ::install_view():: No exchanged data'
2021-09-18T23:07:55.380188+08:00 0 [Note] [MY-011071] [Repl] Plugin group_replication reported: 'on_view_changed is called'
# 发生view change,正式退出集群
2021-09-18T23:07:55.380242+08:00 0 [System] [MY-011504] [Repl] Plugin group_replication reported: 'Group membership changed: This member has left the group.'
2021-09-18T23:07:55.382254+08:00 0 [Note] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] Rejecting this message. The gr
oup communication engine has already stopped.'
...中间几条相同的日志
2021-09-18T23:07:55.382325+08:00 0 [Note] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] Rejecting this message. The group communication engine has already stopped.'
2021-09-18T23:07:55.382333+08:00 0 [Note] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] Rejecting this message. The group....日志没写完,磁盘彻底填满了
# 下面是第二天我清理磁盘空间后的新日志
2021-09-19T07:10:52.071942+08:00 82 [ERROR] [MY-013309] [Repl] Plugin group_replication reported: 'Transaction '1:38481943' does not exist on Group Replication consistency manager while receiving remote transaction prepare.'
2021-09-19T07:10:52.071990+08:00 82 [ERROR] [MY-011452] [Repl] Plugin group_replication reported: 'Fatal error during execution on the Applier process of Group Replication. The server will now leave the group.'
2021-09-19T07:10:52.072032+08:00 82 [Warning] [MY-011646] [Repl] Plugin group_replication reported: 'Skipping leave operation: member already left the group.'
2021-09-19T07:10:52.072049+08:00 82 [ERROR] [MY-011712] [Repl] Plugin group_replication reported: 'The server was automatically set into read only mode after an error was detected.'

从日志详情中可以看到,当磁盘空间满了之后,GreatSQL会将那个节点主动退出集群,对整个集群的影响非常小。

此外,从集群退出后,也不会再接收认证事务了,所以也没发生内存持续暴涨最终被oom killed的情况,实际观察过程中发现内存反倒还下降了

# 还在集群中的内存消耗
 5211 2790736 /usr/local/GreatSQL-8.0.25-15-Linux-glibc2.17-x86_64-minimal/bin/mysqld
 ...
 # 退出集群后的内存反倒降低了
 5211 2801696 /usr/local/GreatSQL-8.0.25-15-Linux-glibc2.17-x86_64-minimal/bin/mysqld
 5211 968876 /usr/local/GreatSQL-8.0.25-15-Linux-glibc2.17-x86_64-minimal/bin/mysqld
 ...
 # 此后内存一直保持这个值
  5211 969172 /usr/local/GreatSQL-8.0.25-15-Linux-glibc2.17-x86_64-minimal/bin/mysqld

这样对比来看,GreatSQL的可靠性还真是可以的,官方的MySQL MGR的可靠性还有待进一步加强呀。

P.S,本文即将推送前,收到MySQL官方bug团队的回复,认为这不是一个bug,而应该优先解决磁盘满的问题。我补充回复说加个事务缓存上限阈值或许更合理,人继续傲娇的表示我应该先关注磁盘问题。。。

Enjoy MySQL & GreatSQL :)

简单测试MySQL 8.0.26 vs GreatSQL 8.0.25的MGR稳定性表现

MySQL 8.0.26下MGR表现如何?用实测数据说话。
此外,MySQL 8.0.26还存在一个严重缺陷。

MySQL 8.0.26发布差不多两个月了,一直还没对它进行测评,看到release notes中涉及到几个MGR相关的Bug fixed,最近抽空对其简单测试一番,下面说说结果吧。

本文后半段还会爆出MySQL 8.0.26的一个严重缺陷。

本次测试采用sysbench,测试模式选择楼方鑫提供的mix-load方案:

require("oltp_common")

local runtype = 0;

function prepare_statements()
   -- use 1 query per event, rather than sysbench.opt.point_selects which
   -- defaults to 10 in other OLTP scripts
   sysbench.opt.point_selects=1

   runtype = (10 * sysbench.tid + 10) / sysbench.opt.threads

   if runtype <= 6 then
     prepare_point_selects()
   else
     prepare_non_index_updates()
   end
end

function event(thread_id)
   if runtype <= 6 then
     execute_point_selects()
   else
     execute_non_index_updates()
   end
end

下面是压测相关的几个指标参数:

  • –tables=10
  • –table_size=100000
  • –threads=16
  • –report-interval=1

下面是InnoDB & MGR相关的几个主要参数选项值:

innodb_buffer_pool_size = 256M

slave_parallel_type = LOGICAL_CLOCK
slave_parallel_workers = 64
binlog_transaction_dependency_tracking = WRITESET
slave_preserve_commit_order = 1
slave_checkpoint_period = 2

group_replication_flow_control_mode = "DISABLED"

备注:由于测试机配置一般,所以压测的数据量并不大,并发也不高。

接下来针对 group_replication_consistency 几个不同可选项,我拿GreatSQL 8.0.25-15 和 MySQL 8.0.26进行对比,主要关注tps和latency数据。

1. group_replication_consistency=EVENTUAL

2. group_replication_consistency=BEFORE_ON_PRIMARY_FAILOVER

3. group_replication_consistency=BEFORE

4. group_replication_consistency=AFTER

5. group_replication_consistency=BEFORE_AND_AFTER

从上面的几个测试数据可以看到:

  1. MySQL 8.0.26的tps还是很不平稳,波动很大。
  2. MySQL 8.0.26的latency也是波动很大。

另外,从测试的直观感受来看,在MySQL 8.0.26以前的版本中存在的几个问题略有改善:

  1. 被kill后的SECONDARY节点重新加回集群,分布式事务恢复较快(快辄20-30秒左右),不像以往要很久(最少1-2分钟)。
  2. 把SECONDARY节点kill后,集群tps波动的时长变短了,之前大概需要20-30秒,现在大概10-20秒。
  3. 把SECONDARY节点kill后,集群还是大约要20多秒才能将其踢出,这个没改善。
  4. 磁盘空间满之后会导致MGR事务被阻塞,在8.0.26里依然会阻塞事务,时间太久就没及时处理的话,还会因为待认证事务堆积等原因导致mysqld进程被oom killed,这个算是更严重了(BUG#104979),后面我再整理文章。

接下里说下MySQL 8.0.26的严重缺陷吧(BUG#104980)。

复现方案:

  1. 设置 group_replication_consistency = BEFORE_AND_AFTER | AFTER(二选一,其余模式暂未出现问题)。
  2. 启动sysbench对MGR集群进行持续压力测试。
  3. 压测过程中,随机kill某个SECONDARY节点。
  4. 经多次重试,会有相当大概率出现该SECONDARY节点无法重新加回集群的问题。报错信息类似下面这样:
[ERROR] [MY-013309] [Repl] Plugin group_replication reported: 'Transaction '2:39976870' does not exist on Group Replication consistency manager while receiving remote transaction prepare.'
[ERROR] [MY-011452] [Repl] Plugin group_replication reported: 'Fatal error during execution on the Applier process of Group Replication. The server will now leave the group.'
[ERROR] [MY-011712] [Repl] Plugin group_replication reported: 'The server was automatically set into read only mode after an error was detected.'"

同样的测试,在GreatSQL 8.0.25中未出现,还是相当给力的呀。

再报告个小问题(BUG#104974),在线设置 group_replication_consistency 选项值时,如果设置为 BEFORE,则必须加引号才可以,否则会报错,其他几个模式则没问题:

mysql>set global group_replication_consistency=EVENTUAL;
Query OK, 0 rows affected (0.00 sec)

mysql>set global group_replication_consistency=BEFORE_ON_PRIMARY_FAILOVER;
Query OK, 0 rows affected (0.00 sec)

mysql>set global group_replication_consistency=BEFORE;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'BEFORE' at line 1

mysql>set global group_replication_consistency='BEFORE';
Query OK, 0 rows affected (0.00 sec)

mysql>set global group_replication_consistency=AFTER;
Query OK, 0 rows affected (0.00 sec)

mysql>set global group_replication_consistency= BEFORE_AND_AFTER;
Query OK, 0 rows affected (0.00 sec)

一周碎碎念,2021.9.12,之前说的MGR课程上线啦

叨叨最近遇到的一些事以及见闻、思考。

1. GreatSQL/MGR课程上线啦

向大家报告下GreatSQL/MGR专题课程的进度,该课程现已上线,不过还差两个视频没录完。

第一期内容相信有很多不足,甚至错漏的地方,也欢迎各位不吝留言帮忙提建议、意见,帮忙改进完善,感谢。

扫码

或复制链接在浏览器中打开
GreatSQL社区《实战MGR》

2. 在MGR中,开启writeset和组提交会冲突吗

这是来自一位群友的问题。

这是不冲突的,采用writeset仍然可以组提交。

3. MySQL有全同步模式吗?

严格上讲,目前是没有全同步模式架构的。

不过,在MGR里,选择 BEFORE_AND_AFTER 也勉强可以算全同步模式吧。

4. MGR单主模式下,需要另外两个节点全部确认才能提交,还是只需要一个节点确认就可以提交?

MGR单主模式下,虽然只有一个节点能进行写入,但其实其他节点也要参与事务认证的,所以还是需要达成多数派共识才行。

预告下,GreatSQL分支计划推出一个新功能,在单主模式下,可以选择只在本地节点认证,无需多数派认证,效率会更高。不过同时要求其他节点不能手动关闭 super_read_only 模式写数据,否则可能会造成数据不一致风险。

5. 执行show databases 特别慢怎么回事

大概率是这个实例下的数据对象太多了,第一次执行时要加载这些数据对象,所以特别慢。可以尝试以下几个方法:

  • 用mysql客户端登入时,加上 -A (–no-auto-rehash) 选项,就无须在第一次执行时全部读取这些数据对象元数据了
  • 提高table-open-cache
  • 加大innodb-buffer-pool-size
    如果还是不行,那就要具体问题具体分析了。

其他无甚大小事,就这样。

技术分享 | 为什么MGR一致性模式不推荐AFTER

引子

某次测试过程中,发现在 AFTER 级别下,节点故障会导致集群无法进行事务提交,同时,当事务进入提交阶段后,其它节点无法开启只读事务。整个集群无法正常提供服务,直到故障节点被踢出集群。

以下首先复现上述故障场景的步骤。

  1. 初始化一个3节点的集群。集群信息如下:

    mysql> select * from performance_schema.replication_group_members;
    +—————————+————————————–+————-+————-+————–+————-+—————-+
    | CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
    +—————————+————————————–+————-+————-+————–+————-+—————-+
    | group_replication_applier | c223cde5-0719-11ec-8295-ec5c6826bca3 | 127.0.0.1 | 13000 | ONLINE | PRIMARY | 8.0.25 |
    | group_replication_applier | c22847e0-0719-11ec-b30b-ec5c6826bca3 | 127.0.0.1 | 13004 | ONLINE | PRIMARY | 8.0.25 |
    | group_replication_applier | c22c3752-0719-11ec-98ef-ec5c6826bca3 | 127.0.0.1 | 13002 | ONLINE | PRIMARY | 8.0.25 |
    +—————————+————————————–+————-+————-+————–+————-+—————-+
    3 rows in set (0.00 sec)

    mysql> select @@group_replication_member_expel_timeout;
    +——————————————+
    | @@group_replication_member_expel_timeout |
    +——————————————+
    | 1000 |
    +——————————————+
    1 row in set (0.00 sec)

  2. 在 AFTER 级别下创建表并插入一条数据

    13000-conn1
    mysql> set session group_replication_consistency=’AFTER’;
    Query OK, 0 rows affected (0.00 sec)

    mysql> create table t1 (c1 int primary key, c2 int);
    Query OK, 0 rows affected (0.12 sec)

    mysql> insert into t1 values (1,1);
    Query OK, 1 row affected (0.03 sec)

  3. 强杀一个节点,由于设置的 expel_timeout 设置为1000秒,故障节点变成 UNREACHABLE 状态

    mysql> select * from performance_schema.replication_group_members;
    +—————————+————————————–+————-+————-+————–+————-+—————-+
    | CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
    +—————————+————————————–+————-+————-+————–+————-+—————-+ | group_replication_applier | c223cde5-0719-11ec-8295-ec5c6826bca3 | 127.0.0.1 | 13000 | ONLINE | PRIMARY | 8.0.25 |
    | group_replication_applier | c22847e0-0719-11ec-b30b-ec5c6826bca3 | 127.0.0.1 | 13004 | UNREACHABLE | PRIMARY | 8.0.25 |
    | group_replication_applier | c22c3752-0719-11ec-98ef-ec5c6826bca3 | 127.0.0.1 | 13002 | ONLINE | PRIMARY | 8.0.25 |
    +—————————+————————————–+————-+————-+————–+————-+—————-+
    3 rows in set (0.00 sec)

  4. 此时,再次插入一条数据,无法返回,语句处于等待提交阶段

    13000-conn1
    mysql> insert into t1 values (2,2);

    13000-conn2
    mysql> select time, state, info from information_schema.processlist;
    +——+——————————————————–+————————————————————–+
    | time | state | info |
    +——+——————————————————–+————————————————————–+
    | 0 | executing | select time, state, info from information_schema.processlist | | 193 | waiting for handler commit | Group replication applier module |
    | 228 | Waiting on empty queue | NULL | | 193 | Slave has read all relay log; waiting for more updates | NULL |
    | 50 | waiting for handler commit | insert into t1 values (2,2) |
    +——+——————————————————–+————————————————————–+
    5 rows in set (0.01 sec)

  5. 再次登录另一个活着的节点,无法执行查询操作

    13002-conn1
    mysql> set @@group_replication_consistency=’eventual’;
    Query OK, 0 rows affected (0.00 sec)

    mysql> select * from t1;

    13002-conn2
    mysql> select time, state, info from information_schema.processlist;
    +——+————————————–+————————————————————–+
    | time | state | info | +——+————————————–+————————————————————–+
    | 0 | executing | select time, state, info from information_schema.processlist |
    | 354 | waiting for handler commit | Group replication applier module |
    | 403 | Waiting on empty queue | NULL | | 225 | waiting for handler commit | NULL |
    | 13 | Executing hook on transaction begin. | select * from t1 |
    +——+————————————–+————————————————————–+
    5 rows in set (0.01 sec)

AFTER 的写一致性

上述故障的第一个问题是,在 MGR 集群中,当事务读写一致性级别设置为 AFTER 后,任何单点的故障都会导致集群不可用。在默认设置情况下,节点从故障到被踢出集群,一般需要5至10秒的时间,在这段时间内,整个数据库是无法进行写事务的提交的,当然,如上述测试一般,如果将expel_timeout设置的更大,则会有更长的时间无法正常工作。这对线上业务来说,将是一次灾难。

查看MGR读写一致性的原始worklog,发现上述现象是符合设计预期的,并不是一个bug。该 worklog 的以下需求即解释了上述的问题。

FR-08: When group_replication_consistency=AFTER or BEFORE_AND_AFTER,
       if there are unreachable members but the group still has a
       reachable majority, the transaction will wait until that
       members are reachable or leave the group.

如此处理的一个好处是,可以满足部分业务对严格数据一致性的需求,但对一般的业务却是极度不友好的。因此,对于一般的业务,并不推荐使用 AFTER 机制。然而,由于非 AFTER 机制下,事务消息只是通过 paxos 协议在内存层面达成多数派,而并不要求数据落盘,因此,如果多数节点同时故障,是存在丢失数据风险的。

AFTER 的读一致性

上述故障的第二个问题是,当节点1 处于事务commit阶段过程中,在节点2上,甚至都无法在 eventual 级别下开启一个只读事务。此时,在故障节点被踢出集群之前,节点2无法提供任何的读写服务。

还是从 worklog 中,解释了这个问题。

FR-06: When group_replication_consistency=AFTER or BEFORE_AND_AFTER,
       since the remote ONLINE members do acknowledge the
       transaction on prepare, the new transactions on those members
       shall be held until the preceding prepared are committed.

也就是说,如果事务在 remote 节点进入了 prepared 阶段,则必须等待该事务完成提交才能开启新的事务,不论 consistency 是何种级别。但是如此处理的一个弊端是,AFTER 级别下,不光会导致执行节点性能吞吐降低,其它节点作为只读节点性能也会降低。在多主写部署中,性能的影响可能更大。另一个问题是,如果用户恶意开启 AFTER 级别执行一个大事务操作,会导致其它节点长时间无法开启新的事务。官方 worklog 中也提到这个问题。

SECURITY CONTEXT
================
From a point of view of malicious attack to the group, since when
group_replication_consistency=AFTER or BEFORE_AND_AFTER a
transaction will wait for a acknowledge from all ONLINE members, a
UNREACHABLE member will block a transaction execution until that
member is reachable or leaves the group.

A malicious user can set group_replication_consistency=AFTER or
BEFORE_AND_AFTER on long lived transactions, which may block new
transactions while those long lived transactions are being applied.

AFTER 执行流程

首先,在事务执行节点上的流程如下:

  1. 首先,事务进入提交阶段后,会执行一个before_commit的HOOK,在 mgr 中,对应的实现是 group_replication_trans_before_commit。AFTER 的一致性保证通过该接口实现。
  2. 假设事务 T1 在节点 M1 上执行,如果是 AFTER 级别,会通过 paxos 发送一个携带事务全部数据的Transaction_with_guarantee_message消息,消息类型为 CT_TRANSACTION_WITH_GUARANTEE_MESSAGE
  3. 节点接收到该消息并处理时,首先会获取当前集群中的 online_members。这里需要注意的是,即使节点状态变为 UNREACHABLE,只要没有踢出集群,也会认为是 online_members。
  4. 节点 M1 需要等待其它节点的消息反馈
  5. 节点M1只有收到上述 online_members 中所有节点的 prepared 消息时,才能继续完成提交

接下来,看一下其它节点(以M2节点为例)处理 AFTER 事务的流程:

  1. 首先,paxos 接收到事务,并进入事务执行阶段
  2. 事务T1在M2进入提交阶段时,调用 before_hook进行处理,不同于 M1的用户线程,M2上的复制线程是在 GR_APPLIER_CHANNEL 上执行
  3. 将事务加入到 prepared 事务列表
  4. 发送 transaction_prepared 消息给所有的节点,并等待处理
  5. 接收到其它节点对 transaction_prepared 消息确认后,从 prepared 事务列表中移除该事务,并继续提交

对于 AFTER 模式,所有的节点在处理事务时,均需要发送一个 transaction_prepared 消息并等待所有节点的确认,之后,用户线程执行的事务才能成功提交。排除用户线程等待所有节点事务提交的时间开销,这些消息处理的网络开销也会对性能造成一定的影响。

另一个需要注意的是,如果在M2节点,事务T1还为进入prepared阶段,此时开启新的事务并不会阻塞。在DEBUG版本下,可以通过如下步骤进行验证。

connect 13000:
mysql> select * from performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| group_replication_applier | 1ca5023b-0a1d-11ec-82f9-c8f7507e5048 | 127.0.0.1   |       13000 | ONLINE       | PRIMARY     | 8.0.25         |
| group_replication_applier | 1cab1e2b-0a1d-11ec-9eb9-c8f7507e5048 | 127.0.0.1   |       13004 | ONLINE       | PRIMARY     | 8.0.25         |
| group_replication_applier | 1caf096c-0a1d-11ec-a241-c8f7507e5048 | 127.0.0.1   |       13002 | ONLINE       | PRIMARY     | 8.0.25         |
| group_replication_applier | 1cbc3cf7-0a1d-11ec-955d-c8f7507e5048 | 127.0.0.1   |       13006 | ONLINE       | PRIMARY     | 8.0.25         |
| group_replication_applier | 1cc6f5eb-0a1d-11ec-8e81-c8f7507e5048 | 127.0.0.1   |       13008 | ONLINE       | PRIMARY     | 8.0.25         |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
5 rows in set (0.03 sec)

mysql> set session group_replication_consistency='AFTER';
Query OK, 0 rows affected (0.00 sec)

mysql> create table t1 (c1 int primary key, c2 int); 
 Query OK, 0 rows affected (0.17 sec)

mysql> insert into t1 values (1,1); 
Query OK, 1 row affected (0.07 sec)

kill -9 13008
mysql> select * from performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| group_replication_applier | 1ca5023b-0a1d-11ec-82f9-c8f7507e5048 | 127.0.0.1   |       13000 | ONLINE       | PRIMARY     | 8.0.25         |
| group_replication_applier | 1cab1e2b-0a1d-11ec-9eb9-c8f7507e5048 | 127.0.0.1   |       13004 | ONLINE       | PRIMARY     | 8.0.25         |
| group_replication_applier | 1caf096c-0a1d-11ec-a241-c8f7507e5048 | 127.0.0.1   |       13002 | ONLINE       | PRIMARY     | 8.0.25         |
| group_replication_applier | 1cbc3cf7-0a1d-11ec-955d-c8f7507e5048 | 127.0.0.1   |       13006 | ONLINE       | PRIMARY     | 8.0.25         |
| group_replication_applier | 1cc6f5eb-0a1d-11ec-8e81-c8f7507e5048 | 127.0.0.1   |       13008 | UNREACHABLE  | PRIMARY     | 8.0.25         |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
5 rows in set (0.00 sec)

connect 13002,使用DEBUG_SYNC,控制节点不进入prepared阶段:
mysql> set global debug='+d,group_replication_before_commit_hook_wait';
Query OK, 0 rows affected (0.00 sec)

connect 13000,插入新的事务,没有返回
mysql> insert into t1 values (2,2);

connect 13002,可以发现,事务被 DEBUG_SYNC 阻塞:
mysql> select command, time, state, info from information_schema.processlist;
+---------+------+----------------------------+-----------------------------------------------------------------------+
| command | time | state                      | info                                                                  |
+---------+------+----------------------------+-----------------------------------------------------------------------+
| Connect |  189 | waiting for handler commit | Group replication applier module                                      |
| Query   |    0 | executing                  | select command, time, state, info from information_schema.processlist |
| Sleep   |    7 |                            | NULL                                                                  |
| Daemon  |  240 | Waiting on empty queue     | NULL                                                                  |
| Query   |   64 | debug sync point: now      | NULL                                                                  |
+---------+------+----------------------------+-----------------------------------------------------------------------+
5 rows in set (0.01 sec)

此时,可以查询到数据:

mysql> set session group_replication_consistency='eventual';
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t1;
+----+------+
| c1 | c2   |
+----+------+
|  1 |    1 |
+----+------+
1 row in set (0.00 sec)

BEFORE 执行流程

与 AFTER 相对应的是 BEFORE,如果一个 session 开启了 BEFORE 级别,则在事务开启时,需要等待所有已经提交的事务已经在本地完成提交,这是通过 WAIT_FOR_GTID_SET 实现。在事务的开启时刻,获取到节点已经同步接收到的所有的事务 gtid 集合,并等待集合内所有的 gtid 完成提交,即可保证事务执行时,读取到最新的数据。

但是在获取 gtid 集合之前,节点需要通过 paxos 发送一个 SYNC_BEFORE_EXECUTION 类型的消息。由于 paxos 会对消息进行排队,因此,当 SYNC_BEFORE_EXECUTION 处理完成时,可以保证该消息发送之前的所有的事务消息均完成在 paxos 中的处理。由于该消息是本次事务开启时产生的,因而此时节点收到的 gtid 集合符合 BEFORE 级别。

如果节点不发送 SYNC_BEFORE_EXECUTION 消息,则 BEFORE 级别未必能够读取到最新数据。假设当前存在网络分区,总共三个节点A,B,C,网络分区后,A,B节点组成多数派,C节点为少数派,此时,A,B节点上新的写入事务将不会继续同步到C节点。在 C 节点被踢出集群之前,如果 C 开启了 BEFORE 级别,却未发送 SYNC_BEFORE_EXECUTION 消息,那么 C 中不能读取到新的数据,违背了 BEFORE 的设计宗旨。但是发送该消息后,由于无法达成消息一致性,那么新的事务将失败、或者一直等待消息返回,而不会返回用户过时的数据。

如下示例则显示了多数节点故障下,BEFORE 级别的执行行为。

开始阶段:

mysql> select * from performance_schema.replication_group_members; 
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| group_replication_applier | dd6398ec-09fe-11ec-95de-c8f7507e5048 | 127.0.0.1   |       13002 | ONLINE       | PRIMARY     | 8.0.25         |
| group_replication_applier | dd64424b-09fe-11ec-aeeb-c8f7507e5048 | 127.0.0.1   |       13000 | ONLINE       | PRIMARY     | 8.0.25         |
| group_replication_applier | dd65b9de-09fe-11ec-9d06-c8f7507e5048 | 127.0.0.1   |       13004 | ONLINE       | PRIMARY     | 8.0.25         |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
3 rows in set (0.06 sec)

mysql> select @@group_replication_member_expel_timeout;
+------------------------------------------+
| @@group_replication_member_expel_timeout |
+------------------------------------------+
|                                       60 |
+------------------------------------------+
1 row in set (0.00 sec)

查询数据:

mysql> set session group_replication_consistency='BEFORE';
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t1;
+----+------+
| c1 | c2   |
+----+------+
|  2 |    2 |
+----+------+
1 row in set (0.01 sec)

使用kill -9模拟多数节点故障:

mysql> select * from performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
| group_replication_applier | dd6398ec-09fe-11ec-95de-c8f7507e5048 | 127.0.0.1   |       13002 | UNREACHABLE  | PRIMARY     | 8.0.25         |
| group_replication_applier | dd64424b-09fe-11ec-aeeb-c8f7507e5048 | 127.0.0.1   |       13000 | UNREACHABLE  | PRIMARY     | 8.0.25         |
| group_replication_applier | dd65b9de-09fe-11ec-9d06-c8f7507e5048 | 127.0.0.1   |       13004 | ONLINE       | PRIMARY     | 8.0.25         |
+---------------------------+--------------------------------------+-------------+-------------+--------------+-------------+----------------+
3 rows in set (0.00 sec)

此时,再次查询数据,一直未返回

mysql> select * from t1;

使用另一个客户端查看状态,一直处于 before hook 阶段:

mysql> select command, time, state, info from information_schema.processlist;
+---------+------+--------------------------------------------------------+-----------------------------------------------------------------------+
| command | time | state                                                  | info                                                                  |
+---------+------+--------------------------------------------------------+-----------------------------------------------------------------------+
| Connect |  253 | waiting for handler commit                             | Group replication applier module                                      |
| Daemon  |  318 | Waiting on empty queue                                 | NULL                                                                  |
| Query   |  238 | Slave has read all relay log; waiting for more updates | NULL                                                                  |
| Query   |  170 | Executing hook on transaction begin.                   | select * from t1                                                      |
| Query   |    0 | executing                                              | select command, time, state, info from information_schema.processlist |
+---------+------+--------------------------------------------------------+-----------------------------------------------------------------------+
5 rows in set (0.00 sec)

当然,由于 BEFORE 级别下,额外发送了一次 SYNC_BEFORE_EXECUTION 消息,增加了一次网络开销,对性能是有一定的影响的。

一些思考

  1. AFTER 模式下,当其它节点事务进入到 prepared 阶段,但用户线程并未完成提交,此时要求新的事务开启时必须等待事务提交是否必要 ?当前设计下,这一要求会造成一定程度上的节点不可用。
  2. AFTER 模式需要等待所有的节点提交成功,这是一个强一致的数据同步方案,但同时会导致集群不可用。同时由于参数 group_replication_consistency 是一个 session 级的控制变量,即使某一个用户连接开启 AFTER 模式,都可能导致整个集群不可用。一个备选方案是,采用开源 greatsql 版本的 majority-after 模式,可以规避上述问题。

参考文档

  1. MySQL 设计文档
  2. MySQL 用户文档

面向金融级应用的GreatSQL正式开源

经过几个月的紧张筹备,GreatSQL宣布正式开源。

GreatSQL是源于Percona Server的分支版本。GreatSQL在Percona Server已有的稳定可靠、高效、管理更方便等优势基础上,进一步提升了MGR(MySQL Group Replication)的性能及可靠性,新增金融级应用场景需求特性并修复数个影响可靠性的严重bug。

GreatSQL可以作为MySQL或Percona Server的可选替代方案,用于线上生产环境。

GreatSQL完全免费并兼容MySQL或Percona Server。

GreatSQL由万里数据库发起、主导、维护,也欢迎广大MySQL使用者、爱好者下载使用,或者提交代码、issue等。

1. 使用MySQL社区版存在什么风险

万里数据库核心研发团队深入研究MGR架构,并在不断的BUG修复实践中总结出了一套完善、流畅的BUG修复流程,将MGR的缺陷分为BUG和性能两类,整理出共16种BUG及性能缺陷问题。

搜索MySQL官方bug站,可以看到MGR分类下未修复的bug数量还是比较多的:

当服务器配置高,网络环境好,业务量小的时候,这些MGR相关的bug可能不容易碰到。如果是网络环境稍微复杂一些,例如同城多数据中心环境,甚至跨交换机,都可能会遇到网络分区条件下的一些bug。或者当业务量较大,负载较高时,可能会产生丢数据、OOM,或事务频繁回滚、死锁等问题。

由于MGR自身的复杂性,以及复现BUG场景也更困难,所以MySQL社区版针对MGR的BUG修复工作通常比较缓慢,堆积较多。这也就造成了不少用户不太敢放心使用MySQL社区版的MGR,担心遇到各种不可控的BUG,甚至较严重的线程、事务hang住等问题,感觉还是不那么可靠。

而GreatSQL已经有效解决了绝大多数较严重的问题,可以更放心地在金融级应用场景使用MGR架构。

2. GreatSQL的优势及展望

在金融级应用场景中,对数据的可靠性和架构的容错性要求都更高,对多数据中心甚至多活都有较高需求。此外,业务系统中经常会有定期跑批计算任务,而MySQL在这方面存在明显的性能瓶颈,很难满足大数据量跑批需求。为此,GreatSQL未来会在以下几方面着重发力。

2.1 增加更多金融级场景需求特性

  • 增加地理标签功能。当在多机房部署MGR时,可以保证每个机房中至少有个节点都参与事务认证,确保该节点总有最新事务,这可用于解决多机房数据同步的问题。
  • 采用全新的流控机制,流控阈值计算更合理、细致,不会出现频繁性能抖动问题。
  • (下个版本计划)增加single-primary模式下快速单主机制,省略事务认证过程,效率更高。
  • (下个版本计划)增加投票节点功能,该节点仅参与MGR投票仲裁,不存放实际数据也无需执行DML操作,在保证MGR可靠性的同时还能降低服务器成本。

2.2 提升同城双机房和跨城架构部署的可靠性

  • 支持AFTER模式下多数派写机制。发生网络分区时,只要多数派节点已经回放完毕,集群就可以继续处理新的事务,依然可以保障集群的高可用性。
  • 解决磁盘空间爆满时导致MGR集群阻塞的问题。当发现某节点磁盘空间满了,就会让这个节点主动退出集群,避免像MySQL社区版那样整个集群被阻塞的问题。
  • 解决多主模式下或切主时可能导致丢数据的问题。调整了事务认证处理流程,改成放到 applier queue 里按照paxos顺序处理,这就解决了在多主模式下或切主时可能导致丢数据的问题。
  • 解决节点异常退出集群时导致性能抖动的问题。优化paxos通信机制,发生异常时只会产生约1~3秒的性能小抖动,最差时TPS可能只损失约20% ~ 30%。而MySQL社区版本可能会造成约20~30秒的性能抖动,最差时TPS可能有好几秒都降为0。下面两个图非常明显体现了GreatSQL针对这种情况所做的优化。

MySQL社区版:耗时约29秒才完全恢复。

GreatSQL版本:耗时约3秒即完全恢复,时效提升约90% – 节点异常状态判断更完善,比MySQL社区版本能更快发现、判断节点异常状态,有效减少切主和异常节点的等待耗时。下面两个图体现了GreatSQL针对节点状态异常做出更快速准确的判断。

MySQL社区版:5秒发现问题,22秒后将其踢出。

GreatSQL版本:2秒发现问题,9秒后将其踢出,时效提升约60%

2.3 MGR性能提升

  • 优化事务认证队列清理算法。MySQL社区版本中,认证数据库采用类似全表扫描的方式,效率极低。优化后,采用基于类似索引机制,有效解决清理效率低、性能抖动大的问题。
  • 提高MGR吞吐量。经过优化,有效提升MGR的吞吐量,并减少网络延迟对访问性能的影响。
  • 提升强一致性读性能,并降低从库只读延迟。

2.4 InnoDB事务锁以及并行查询优化

合并了由华为鲲鹏计算团队贡献的两个Patch,分别针对OLTP和OLAP两种业务场景。 – 优化InnoDB事务锁机制,将原来的红黑树改为无锁哈希结构,在高并发场景中有效提升事务并发性能至少10%以上。

  • 实现对InnoDB底层B+树多个子树的并行扫描机制,极大提升聚合查询效率,TPC-H测试中,最高可提升30倍,平均提升15倍。特别适用于周期性数据汇总报表之类的SAP、财务统计等业务。

2.5 其他更多企业级特性展望

未来我们还计划将一部分企业级特性也开放出来,包括且不仅限于国产化硬件适配、等保合规、安全加密、Oracle兼容等众多特性。

3. GreatSQL VS MySQL社区版

特性 GreatSQL MySQL社区版
地理标签
全新流控算法
InnoDB并行查询优化
InnoDB事务锁优化
网络分区异常应对 ⭐️⭐️⭐️⭐️⭐️ ⭐️
大事务处理 ⭐️⭐️⭐️⭐️⭐️ ⭐️
节点异常退出处理 ⭐️⭐️⭐️⭐️⭐️ ⭐️
一致性读性能 ⭐️⭐️⭐️⭐️⭐️ ⭐️
提升MGR吞吐量 ⭐️⭐️⭐️⭐️⭐️ ⭐️
多写模式下可能丢数据 ⭐️⭐️⭐️⭐️⭐️ /
单主模式下切主丢数据 ⭐️⭐️⭐️⭐️⭐️ /
MGR集群启动效率 ⭐️⭐️⭐️⭐️⭐️ /
集群节点磁盘满处理 ⭐️⭐️⭐️⭐️⭐️ /
TCP self-connect问 题 ⭐️⭐️⭐️⭐️⭐️ /

GreatSQL代码已上传到gitee上,项目地址 https://gitee.com/GreatSQL/GreatSQL,欢迎围观、加星,也欢迎来“找茬”,提patch。

Enjoy GreatSQL :)