GA

2020/03/19

MySQL 8.0のデュアルパスワードを使った記念メモ

TL;DR

  • デュアルパスワードの情報は mysql.user.User_attributes カラムにJSONで入ってくるのでその辺で確認できる
    • SHOW CREATE USER には入ってこないのでここで確認するしかない?
  • SELECTステートメントでデュアルパスワード持ってるアカウントだけを引っ張るなら mysql.user.user_attributesadditional_password って要素を持ってるかどうかで判定ができる
mysql80 7> SELECT user, host FROM mysql.user WHERE user_attributes->>'$.additional_password' IS NOT NULL;
+----------+------+
| user     | host |
+----------+------+
| yoku0825 | %    |
+----------+------+
1 row in set (0.00 sec)

「セカンダリーパスワード」とかそんな名前かと思ったら、「デュアルパスワード」が公式用語っぽいのも今回初めて気が付いた。
やり方そのものは日本語でも記事が存在するしドキュメントもある。
試しにやってみるだけなら、 IDENTIFIED WITH mysql_native_password BY '..' でmysql_native_passwordプラグインを使った方が出力が化けなくて良いかも ( print_identified_with_as_hexSELECT で直接のぞき込む時には使えないから)
mysql80 82> CREATE USER yoku0825 IDENTIFIED BY 'yoku0826';
Query OK, 0 rows affected (0.01 sec)

mysql80 82> SHOW CREATE USER yoku0825;
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| CREATE USER for yoku0825@%                                                                                                                                                                                                                                                                   |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| CREATE USER 'yoku0825'@'%' IDENTIFIED WITH 'caching_sha2_password' AS '$A$005$]f|)Pd<I#(i
                                                                                                ij[HzpqNoMwkaEO.dS2orkm5nQ/2Mgulxlh.8aumbjfYa8' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT PASSWORD REQUIRE CURRENT DEFAULT |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql80 82> ALTER USER yoku0825 IDENTIFIED BY 'yoku0827' RETAIN CURRENT PASSWORD;
Query OK, 0 rows affected (0.00 sec)

mysql80 82> SHOW CREATE USER yoku0825;
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| CREATE USER for yoku0825@%                                                                                                                                                                                                                                                                      |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| CREATE USER 'yoku0825'@'%' IDENTIFIED WITH 'caching_sha2_password' AS '$A$005$V=]bHk2\r~l8f\"\ZpB]DoKqTjmFtEz20yaFYDbvkXx0kOOCmGzuhCByA5lXPi5' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT PASSWORD REQUIRE CURRENT DEFAULT |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
SHOW CREATE USER には以前のパスワードっぽいものは見えない。デュアルパスワードになってるのかどうかも見えない。
mysql.user あたりを眺めていたら user_attributes というカラムに突っ込んでいるっぽかった(caching_sha2_passwordプラグインだと authentication_string カラムはやっぱり潰れちゃうねえ…)
mysql80 82> SELECT user, host, authentication_string, user_attributes FROM mysql.user WHERE user = 'yoku0825'\G
*************************** 1. row ***************************
                 user: yoku0825
                 host: %
~l8f"pB]DoKqTjmFtEz20yaFYDbvkXx0kOOCmGzuhCByA5lXPi5
      user_attributes: {"additional_password": "$A$005$]\u000ff|)Pd<\u0017I#(i\u0006 \t\u000bij[HzpqNoMwkaEO.dS2orkm5nQ/2Mgulxlh.8aumbjfYa8"}
1 row in set (0.00 sec)

mysql80 82> ALTER USER yoku0825 DISCARD OLD PASSWORD;
Query OK, 0 rows affected (0.02 sec)

mysql80 82> SELECT user, host, authentication_string, user_attributes FROM mysql.user WHERE user = 'yoku0825'\G
*************************** 1. row ***************************
                 user: yoku0825
                 host: %
~l8f"pB]DoKqTjmFtEz20yaFYDbvkXx0kOOCmGzuhCByA5lXPi5
      user_attributes: NULL
1 row in set (0.00 sec)
user_attributesの構造体は このへん で、更新してるのは このへん 、使ってるフラグを管理してるのは このへん なので、8.0.19現在ではデュアルパスワードと ログインの失敗回数制御partial_revokes で使っているっぽい。
mysql80 7> SELECT user, host, authentication_string, user_attributes FROM mysql.user WHERE user = 'yoku0825'\G
*************************** 1. row ***************************
                 user: yoku0825
                 host: %
authentication_string: $A$005$LMJP\MS%cmy/Gv NIMCTPSdqYBmL2txa9mWq7ki0jYncHsWW99iyXk8CyD
      user_attributes: {"Restrictions": [{"Database": "mysql", "Privileges": ["SELECT"]}], "Password_locking": {"failed_login_attempts": 4, "password_lock_time_days": -1}, "additional_password": "$A$005$>PuHNir\n\t&\b\u0004B/Kf\u000b\u000e\u0010\u000bYZEIELtEpe7xFBDXqiQhC6EFNSOy.DQy.11P5iFl4v4"}
1 row in set (0.00 sec)
SELECTステートメントでデュアルパスワード持ってるアカウントだけを引っ張るならこうかな。
mysql80 7> SELECT user, host FROM mysql.user WHERE user_attributes->>'$.additional_password' IS NOT NULL;
+----------+------+
| user     | host |
+----------+------+
| yoku0825 | %    |
+----------+------+
1 row in set (0.00 sec)

2020/03/16

わざと件数をズラしたグループレプリケーションで、「遅延中」の動きを観察する

件数をズラした上で OPTIMIZE TABLE をかけるといくらでもセカンダリーが遅延した状況が作り出せる、というとことまでは良いとして。
node3のグループレプリケーションを一旦止める。
### node3
mysql> STOP GROUP_REPLICATION;
Query OK, 0 rows affected (4.49 sec)
またnode1から OPTIMIZE TABLE を5発くらい叩き込んでついでに更新SQLを入れておく。
### node1
mysql> OPTIMIZE TABLE sbtest.sbtest1; -- これを5回くらい

mysql> INSERT INTO d1.t1 VALUES (4, 'four');
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM d1.t1;
+-----+-------+
| num | val   |
+-----+-------+
|   1 | one   |
|   2 | two   |
|   3 | three |
|   4 | four  |
+-----+-------+
4 rows in set (0.00 sec)

### node2
mysql> SELECT * FROM d1.t1; -- OPTIMIZE TABLEにやられて遅れてる
+-----+-------+
| num | val   |
+-----+-------+
|   1 | one   |
|   2 | two   |
|   3 | three |
+-----+-------+
3 rows in set (0.00 sec)

### node3
mysql> SELECT * FROM d1.t1;  -- そもそもグループレプリケーション止めてるので同期されない
+-----+-------+
| num | val   |
+-----+-------+
|   1 | one   |
|   2 | two   |
|   3 | three |
+-----+-------+
3 rows in set (0.00 sec)
ここでnode3のグループレプリケーションを再開。
### node3
mysql> START GROUP_REPLICATION;
Query OK, 0 rows affected (4.93 sec)

$ for f in /var/lib/mysql/*.0000* ; do mysqlbinlog -vv $f | stdbuf -oL awk '{print "'$f'", $0}'; done | grep INSERT | grep d1
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/ip-172-31-1-48-relay-bin-group_replication_recovery.000003 ### INSERT INTO `d1`.`t1`
この時点で、「グループレプリケーション停止中に更新した行はrecoveryの方のリレーログに入る」のは実験済み。
じゃあ、 “RECOVERING” 中に新しく突っ込まれた更新の行は?
### node1
mysql> INSERT INTO d1.t1 VALUES (5, 'five');
Query OK, 1 row affected (0.01 sec)

### node3
$ for f in /var/lib/mysql/*.0000* ; do mysqlbinlog -vv $f | stdbuf -oL awk '{print "'$f'", $0}'; done | grep INSERT | grep d1
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/ip-172-31-1-48-relay-bin-group_replication_recovery.000003 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/ip-172-31-1-48-relay-bin-group_replication_recovery.000003 ### INSERT INTO `d1`.`t1`
これもrecoveryの方に入った。recoveryが動きを止める
遅延が完全に追い付いてからnode1にINSERTした値はapplierの方に入る。
### node1
mysql> INSERT INTO d1.t1 VALUES (6, 'six');
Query OK, 1 row affected (0.01 sec)

### node3
for f in /var/lib/mysql/*.0000* ; do mysqlbinlog -vv $f | stdbuf -oL awk '{print "'$f'", $0}'; done | grep INSERT | grep d1
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/ip-172-31-1-48-relay-bin-group_replication_applier.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/ip-172-31-1-48-relay-bin-group_replication_applier.000002 ### INSERT INTO `d1`.`t1`
ん? 何故かapplierの方に (5, ‘five’) と思しきイベントも入ってる
### node3
$ mysqlbinlog -vv /var/lib/mysql/ip-172-31-1-48-relay-bin-group_replication_applier.000002
..
SET @@SESSION.GTID_NEXT= '14b471b9-66e5-11ea-888f-12c40920c9ad:454'/*!*/;
### INSERT INTO `d1`.`t1`
### SET
###   @1=5
###   @2='five'
..
SET @@SESSION.GTID_NEXT= '14b471b9-66e5-11ea-888f-12c40920c9ad:455'/*!*/;
### INSERT INTO `d1`.`t1`
### SET
###   @1=6
###   @2='six'
recoveryの方のリレーログすぐ消えちゃうからな… relay_log_purge=OFF したら残ってくれるかしらん。
### node3
mysql> SELECT channel_name, service_state, last_applied_transaction AS last_gtid, last_applied_transaction_original_commit_timestamp AS orig_ts, last_applied_transaction_immediate_commit_timestamp AS my_ts FROM performance_schema.replication_applier_status_by_worker;
+----------------------------+---------------+------------------------------------------+----------------------------+----------------------------+
| channel_name               | service_state | last_gtid                                | orig_ts                    | my_ts                      |
+----------------------------+---------------+------------------------------------------+----------------------------+----------------------------+
| group_replication_applier  | ON            | 14b471b9-66e5-11ea-888f-12c40920c9ad:455 | 2020-03-15 18:23:10.568424 | 0000-00-00 00:00:00.000000 |
| group_replication_recovery | OFF           | 14b471b9-66e5-11ea-888f-12c40920c9ad:453 | 0000-00-00 00:00:00.000000 | 2020-03-15 18:18:30.913861 |
+----------------------------+---------------+------------------------------------------+----------------------------+----------------------------+
2 rows in set (0.00 sec)
performance_schemaで調べる限り、RECOVERING中に入ってきた2行はapplierの方が処理しているっぽい。
正直どっちが処理してくれても(recoveryが止まって初めてapplierが動くなら)構いはしない気がするけど、これがDMLだからなのかDDLなのかちょっと気になるね。
…と調べてみたら、DML/DDLに関わらずグループレプリケーションを再開した後に打たれたSQLはrecoveryのリレーログで一度受けた後にapplierの方に回すっぽい。
謎が深まった。

シングルプライマリーモードだろうとInnoDB Cluster内のデータをズラす方法

TL;DR

  • 単にプライマリーノードで SET sql_log_bin = 0 で効くんだからびっくりだ

動作確認用にd1.t1テーブルを作って2行くらい入れておく。
### node1
mysql> CREATE DATABASE d1;
Query OK, 1 row affected (0.01 sec)

mysql> CREATE TABLE d1.t1 (num SERIAL, val VARCHAR(32));
Query OK, 0 rows affected (0.02 sec)

mysql> INSERT INTO d1.t1 VALUES (1, 'one');
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO d1.t1 VALUES (2, 'two');
Query OK, 1 row affected (0.00 sec)
プライマリーノードで sysbench を使って100万行くらいのテーブルを作る。
### node1
$ sysbench --mysql-user=root oltp_common --table_size=1000000 prepare
sysbench 1.0.17 (using system LuaJIT 2.0.4)

Creating table 'sbtest1'...
Inserting 1000000 records into 'sbtest1'
Creating a secondary index on 'sbtest1'...

### node1
mysql> SELECT COUNT(*) FROM sbtest.sbtest1;                                                 
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.32 sec)

### node2
mysql> SELECT COUNT(*) FROM sbtest.sbtest1;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.14 sec)

### node3
mysql> SELECT COUNT(*) FROM sbtest.sbtest1;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.13 sec)
sql_log_bin=OFF でいけるかなと思ったらいけてしまった。
### node1
mysql> SET SESSION sql_log_bin = 0;
Query OK, 0 rows affected (0.00 sec)

mysql> DELETE FROM sbtest.sbtest1;
Query OK, 1000000 rows affected (9.65 sec)

### node1
mysql> SELECT COUNT(*) FROM sbtest.sbtest1;
+----------+
| COUNT(*) |
+----------+
|        0 |
+----------+
1 row in set (0.00 sec)

### node2
mysql> SELECT COUNT(*) FROM sbtest.sbtest1;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.24 sec)

### node3
mysql> SELECT COUNT(*) FROM sbtest.sbtest1;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.15 sec)
これで何が嬉しいかっていうと、この状態でnode1に対してテキトーな OPTIMIZE TABLE でも投げ続ければ、node2とnode3は十分遅れてくれるだろうっていうこと。
### node1
mysql> OPTIMIZE TABLE sbtest.sbtest1; -- これを5回くらい

mysql> SELECT MEMBER_ID, COUNT_TRANSACTIONS_IN_QUEUE AS queue, COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE FROM performance_schema.replication_group_member_stats;                                        +--------------------------------------+-------+--------------------------------------------+
| MEMBER_ID                            | queue | COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE |
+--------------------------------------+-------+--------------------------------------------+
| ec62dfb5-66e4-11ea-a8d3-12c40920c9ad |     0 |                                          0 |
| ee6bca41-66e4-11ea-b904-123209691891 |     0 |                                          5 |
| f35fec8f-66e4-11ea-9a69-12dd5d4a2e3d |     0 |                                          5 |
+--------------------------------------+-------+--------------------------------------------+
3 rows in set (0.00 sec)
溜まってるのが確認できる。
溜まってる間に追い越してApplyされないのは前々回くらいに試した通りなんだけど。
### node1
mysql> INSERT INTO d1.t1 VALUES (3, 'three');
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM t1;
+-----+-------+
| num | val   |
+-----+-------+
|   1 | one   |
|   2 | two   |
|   3 | three |
+-----+-------+
3 rows in set (0.00 sec)

$ for f in /var/lib/mysql/*.0000* ; do mysqlbinlog -vv $f | stdbuf -oL awk '{print "'$f'", $0}'; done | grep INSERT | grep d1
/var/lib/mysql/binlog.000003 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/binlog.000003 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/binlog.000003 ### INSERT INTO `d1`.`t1`

### node2
mysql> SELECT * FROM t1;
+-----+------+
| num | val  |
+-----+------+
|   1 | one  |
|   2 | two  |
+-----+------+
2 rows in set (0.00 sec)

$ for f in /var/lib/mysql/*.0000* ; do mysqlbinlog -vv $f | stdbuf -oL awk '{print "'$f'", $0}'; done | grep INSERT | grep d1                              [22/19445]
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/ip-172-31-1-120-relay-bin-group_replication_applier.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/ip-172-31-1-120-relay-bin-group_replication_applier.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/ip-172-31-1-120-relay-bin-group_replication_applier.000002 ### INSERT INTO `d1`.`t1`

### node3
mysql> SELECT * FROM t1;
+-----+------+
| num | val  |
+-----+------+
|   1 | one  |
|   2 | two  |
+-----+------+
2 rows in set (0.00 sec)

$ for f in /var/lib/mysql/*.0000* ; do mysqlbinlog -vv $f | stdbuf -oL awk '{print "'$f'", $0}'; done | grep INSERT | grep d1                               [22/18451]
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/binlog.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/ip-172-31-1-48-relay-bin-group_replication_applier.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/ip-172-31-1-48-relay-bin-group_replication_applier.000002 ### INSERT INTO `d1`.`t1`
/var/lib/mysql/ip-172-31-1-48-relay-bin-group_replication_applier.000002 ### INSERT INTO `d1`.`t1`
Applierの方のリレーログはまではちゃんと受け取っている。
DMLだと、DML適用の番になった時にフツーのレプリケーションと同じようにDUP_KEYなりROW_NOT_FOUNDでエラーになって面白くないだけだと思うので、この状態で「グループレプリケーションが遅延しまくっている」のを再現させるのが使いどころじゃないかなあとは思う。
遅延させて何を試していたかはタイトルにそぐわなくなってきたのでまた今度。

続きはこっち → 日々の覚書: わざと件数をズラしたグループレプリケーションで、「遅延中」の動きを観察する

2020/03/13

グループレプリケーションの group_replication_applier と group_replication_recovery のリレーログ

TL;DR

  • グループレプリケーションは(形式上かも知れないけど) group_replication_applier というレプリケーションチャンネルと group_replication_recovery というレプリケーションチャンネルを持っている
  • グループレプリケーションの通信が問題なく行われている間は GCS(Group Communication System) から group_replication_applier のリレーログに書き込まれて group_repliation_applierのSQLスレッドが処理
    • この時、プライマリーノードはGCSから応答が返ってきた後にバイナリログに書く
  • グループレプリケーションが途切れてGCSから直接受け取っていない間に発生した書き込みはバイナリログを経由して group_replication_recovery チャンネルを通じて受け取る
    • こっちは割とフツーのGTIDベースなAsyncレプリケーションっぽく振る舞う
    • group_replication_recoveryチャンネルが動いている間はgroup_replication_applierチャンネルが動かないような制御は入ってるっぽい

実験

  • シングルプライマリーの3台構成

まずは3台ともONLINE

### node1 (PRIMARY)
mysql> CREATE DATABASE d1;
Query OK, 1 row affected (0.01 sec)

mysql> CREATE TABLE d1.t1 (num serial, val varchar(32));
Query OK, 0 rows affected (0.02 sec)

mysql> INSERT INTO d1.t1 VALUES (1, 'one');
Query OK, 1 row affected (0.01 sec)
リレーログとバイナリログと、 INSERT が記録されているファイルを雑に調べる( *.0000* はバイナリログとリレーログの連番部分だけを引っ掛けてるつもり、 binlog_rows_query_log_events の部分だけ抜粋)
### node1 (PRIMARY)
$ for f in /var/lib/mysql/*.0000* ; do mysqlbinlog -vv $f | stdbuf -oL awk '{print "'$f'", $0}'; done | grep INSERT | grep t1
/var/lib/mysql/binlog.000004 # INSERT INTO d1.t1 VALUES (1, 'one')

### node2
$ for f in /var/lib/mysql/*.0000* ; do mysqlbinlog -vv $f | stdbuf -oL awk '{print "'$f'", $0}'; done | grep INSERT | grep t1
/var/lib/mysql/binlog.000002 # INSERT INTO d1.t1 VALUES (1, 'one')
/var/lib/mysql/node2-relay-bin-group_replication_applier.000002 # INSERT INTO d1.t1 VALUES (1, 'one')

### node3
$ for f in /var/lib/mysql/*.0000* ; do mysqlbinlog -vv $f | stdbuf -oL awk '{print "'$f'", $0}'; done | grep INSERT | grep t1
/var/lib/mysql/binlog.000002 # INSERT INTO d1.t1 VALUES (1, 
/var/lib/mysql/node3-relay-bin-group_replication_applier.000002 # INSERT INTO d1.t1 VALUES (1, 'one')
プライマリーではバイナリログのみ、node2とnode3は group_replication_applier のリレーログとバイナリログに記録。

ノード停止中に発生した更新

### node3
$ sudo systemctl stop mysqld

### node1
mysql> DELETE FROM t1 WHERE num = 1;
Query OK, 1 row affected (0.00 sec)

### node1
$ for f in /var/lib/mysql/*.0000* ; do mysqlbinlog -vv $f | stdbuf -oL awk '{print "'$f'", $0}'; done | grep DELETE | grep t1
/var/lib/mysql/binlog.000004 # DELETE FROM t1 WHERE num = 1

### node2
$ for f in /var/lib/mysql/*.0000* ; do mysqlbinlog -vv $f | stdbuf -oL awk '{print "'$f'", $0}'; done | grep DELETE | grep t1
/var/lib/mysql/binlog.000002 # DELETE FROM t1 WHERE num = 1
/var/lib/mysql/node2-relay-bin-group_replication_applier.000002 # DELETE FROM t1 WHERE num = 1

### node3
$ sudo systemctl start mysqld
$ for f in /var/lib/mysql/*.0000* ; do mysqlbinlog -vv $f | stdbuf -oL awk '{print "'$f'", $0}'; done | grep DELETE | grep t1
/var/lib/mysql/binlog.000003 # DELETE FROM t1 WHERE num = 1
個人的には node3-relay-bin-group_replication_recovery の方でも観測できると良かったんだけど、なんかすぐパージされるのか何なのか観測できなかった。
performance_schema.replication_applier_status_by_worker テーブルからは、node3だけ group_replication_recovery チャンネルが仕事をしたのがわかる。
### node3
 MySQL  node3:33060+ ssl  SQL > SELECT channel_name, service_state, last_applied_transaction AS last_gtid, last_applied_transaction_original_commit_timestamp AS orig_ts, last_applied_transaction_immediate_commit_timestamp AS my_ts FROM performance_schema.replication_applier_status_by_worker;
+----------------------------+---------------+-----------------------------------------+---------------------+----------------------------+
| channel_name               | service_state | last_gtid                               | orig_ts             | my_ts                      |
+----------------------------+---------------+-----------------------------------------+---------------------+----------------------------+
| group_replication_applier  | ON            |                                         | 0000-00-00 00:00:00 | 0000-00-00 00:00:00        |
| group_replication_recovery | OFF           | 96bb7988-64ec-11ea-b013-122c3190731d:58 | 0000-00-00 00:00:00 | 2020-03-13 05:45:03.879562 |
+----------------------------+---------------+-----------------------------------------+---------------------+----------------------------+
2 rows in set (0.0020 sec)

### node2
 MySQL  node2:33060+ ssl  SQL > SELECT channel_name, service_state, last_applied_transaction AS last_gtid, last_applied_transaction_original_commit_timestamp AS orig_ts, last_applied_transaction_immediate_commit_timestamp AS my_ts FROM performance_schema.replication_applier_status_by_worker;
+----------------------------+---------------+-----------------------------------------+---------------------+--------------------------+
| channel_name               | service_state | last_gtid                               | orig_ts             | my_ts                    |
+----------------------------+---------------+-----------------------------------------+---------------------+--------------------------+
| group_replication_applier  | ON            | 96bb7988-64ec-11ea-b013-122c3190731d:58 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00      |
| group_replication_recovery | OFF           | 96bb7988-64ec-11ea-b013-122c3190731d:39 | 0000-00-00 00:00:00 | 2020-03-13 05:37:00.4302 |
+----------------------------+---------------+-----------------------------------------+---------------------+--------------------------+
2 rows in set (0.0007 sec)

### node1
 MySQL  localhost:33060+ ssl  SQL > SELECT channel_name, service_state, last_applied_transaction AS last_gtid, last_applied_transaction_original_commit_timestamp AS orig_ts, last_applied_transaction_immediate_commit_timestamp AS my_ts FROM performance_schema.replication_applier_status_by_worker;
+---------------------------+---------------+-----------------------------------------+---------------------+---------------------+
| channel_name              | service_state | last_gtid                               | orig_ts             | my_ts               |
+---------------------------+---------------+-----------------------------------------+---------------------+---------------------+
| group_replication_applier | ON            | 96bb7988-64ec-11ea-b013-122c3190731d:58 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 |
+---------------------------+---------------+-----------------------------------------+---------------------+---------------------+
1 row in set (0.0004 sec)
言われてみれば、 グループレプリケーションを手で組む時 って CHANGE MASTER TO .. FOR CHANNEL 'group_replication_recovery' だけ設定するので、コイツらは比較的「いつものレプリケーション」に近い仕組みをしていそう。
折角なのでgdbで止めて、group_replication_recoveryチャンネルのリレーログに入っているところを見てみた(バイナリログ書き出し Binlog_event_writer::write の手前で止めればまだリレーログからは消えてないだろうという雑なアレ)
### node3
$ sudo gdb --args /usr/sbin/mysqld --user=mysql
(gdb) b Binlog_event_writer::write
Breakpoint 1 at 0x1bc0f80: file /export/home/pb2/build/sb_0-37170812-1575922551.75/rpm/BUILD/mysql-8.0.19/mysql-8.0.19/sql/binlog.cc, line 1260.
(gdb) r
Starting program: /usr/sbin/mysqld --user=mysql
..
Breakpoint 1, Binlog_event_writer::write (this=0x7fffc0095fb0, buffer=0x7fffc0095ce0 "LUk^!Wk\002\235R", length=19)
    at /export/home/pb2/build/sb_0-37170812-1575922551.75/rpm/BUILD/mysql-8.0.19/mysql-8.0.19/sql/binlog.cc:1260
1260    /export/home/pb2/build/sb_0-37170812-1575922551.75/rpm/BUILD/mysql-8.0.19/mysql-8.0.19/sql/binlog.cc: No such file or directory.

$ for f in /var/lib/mysql/*.0000* ; do mysqlbinlog -vv $f | stdbuf -oL awk '{print "'$f'", $0}'; done | grep DELETE | grep t1
/var/lib/mysql/node3-relay-bin-group_replication_recovery.000003 # DELETE FROM t1 WHERE num = 1
group_replication_recoveryのリレーログに入ってるところが見えて、そのままgdb側の処理を進めてやるとすぐにrelay-bin-group_replication_recovery.000003ファイルそのものが消えた。たぶんrelay_log_purge (group_replication_applierの方のリレーログが割と残るのは何なんだろう…?
ともあれ、グルーレプリケーションは2つのチャンネルを使っているのが何となくわかった。