GA

2020/02/26

MySQL 8.0.19現在のGroup Replicationで空パスワードのアカウントの認証プラグインだけを変えようとすると変になる

TL;DR

  • epelのsysbenchがcaching_sha2_passwordに対応してないので、root@localhostのパスワードを空のまま認証プラグインだけmysql_native_passwordに変更しようとした
  • プライマリーノード以外ではパスワードがEXPIREされて再変更を促された
  • プライマリーノードで SET PASSWORD = '' を実行したらセカンダリーノードでもEXPIRE状態じゃなくなった

パスワードが空っぽの時だけ再現するので、現用環境で問題になる可能性は低い。
再現手順。
mysql> CREATE USER yoku0825 IDENTIFIED BY '';
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT user, host, plugin, password_expired FROM mysql.user WHERE user = 'yoku0825';
+----------+------+-----------------------+------------------+
| user     | host | plugin                | password_expired |
+----------+------+-----------------------+------------------+
| yoku0825 | %    | caching_sha2_password | N                |
+----------+------+-----------------------+------------------+
1 row in set (0.00 sec)

mysql> ALTER USER yoku0825 IDENTIFIED WITH mysql_native_password BY ''; -- IDENTIFIED WITH .. BY .. で認証プラグインと一緒に新パスワードを指定しているので password_expired は Nになるはず
Query OK, 0 rows affected (0.01 sec)

primary> SELECT user, host, plugin, password_expired FROM mysql.user WHERE user = 'yoku0825'; -- PRIMARYノードでは確かにそうなっているが
+----------+------+-----------------------+------------------+
| user     | host | plugin                | password_expired |
+----------+------+-----------------------+------------------+
| yoku0825 | %    | mysql_native_password | N                |
+----------+------+-----------------------+------------------+
1 row in set (0.00 sec)

secondary> SELECT user, host, plugin, password_expired FROM mysql.user WHERE user = 'yoku0825'; -- SECONDARYノードでは認証プラグインは更新されているもののpassword_exipred が Y、認証プラグインが変更された後にパスワードが更新されていないと認識されている
+----------+------+-----------------------+------------------+
| user     | host | plugin                | password_expired |
+----------+------+-----------------------+------------------+
| yoku0825 | %    | mysql_native_password | Y                |
+----------+------+-----------------------+------------------+
1 row in set (0.00 sec)
group_replication_applierのリレーログを確認すると
# at 13356
#200226  2:48:29 server id 2412749166  end_log_pos 0    GTID    last_committed=61       sequence_number=62      rbr_only=no     original_committed_timestamp=1582687438763140   immediate_commit_timestamp=0    transaction_length=229
# original_commit_timestamp=1582687438763140 (2020-02-26 03:23:58.763140 UTC)
# immediate_commit_timestamp=0 (1970-01-01 00:00:00.000000 UTC)
/*!80001 SET @@session.original_commit_timestamp=1582687438763140*//*!*/;
/*!80014 SET @@session.original_server_version=80019*//*!*/;
/*!80014 SET @@session.immediate_server_version=80019*//*!*/;
SET @@SESSION.GTID_NEXT= '662c9473-5842-11ea-8c75-12458a6f001d:66'/*!*/;
# at 13436
#200226  3:23:58 server id 2412749166  end_log_pos 0    Query   thread_id=65    exec_time=0     error_code=0    Xid = 541
SET TIMESTAMP=1582687438/*!*/;
ALTER USER 'yoku0825'@'%' IDENTIFIED WITH 'mysql_native_password'
/*!*/;
空文字の部分が握りつぶされている。
group_replication_applierはバイナリログを受け取っているわけではない(んですよ実は)ので見ても仕方ないような気がするけれど、プライマリーノードのバイナリログを覗いても同じように握りつぶされているので、リレーログとして空文字パスワードが省略されるのは仕様としては正しそう。
# at 37750
#200226  3:23:58 server id 2412749166  end_log_pos 37830        GTID    last_committed=65       sequence_number=66      rbr_only=no     original_committed_timestamp=1582687438763140   immediate_commit_timestamp=1582687438765388  transaction_length=229
# original_commit_timestamp=1582687438763140 (2020-02-26 03:23:58.763140 UTC)
# immediate_commit_timestamp=1582687438765388 (2020-02-26 03:23:58.765388 UTC)
/*!80001 SET @@session.original_commit_timestamp=1582687438763140*//*!*/;
/*!80014 SET @@session.original_server_version=80019*//*!*/;
/*!80014 SET @@session.immediate_server_version=80019*//*!*/;
SET @@SESSION.GTID_NEXT= '662c9473-5842-11ea-8c75-12458a6f001d:66'/*!*/;
# at 37830
#200226  3:23:58 server id 2412749166  end_log_pos 37979        Query   thread_id=65    exec_time=0     error_code=0    Xid = 541
SET TIMESTAMP=1582687438/*!*/;
ALTER USER 'yoku0825'@'%' IDENTIFIED WITH 'mysql_native_password'
/*!*/;
しかしそうなると、SQLを実行したノードではSQLをパースしている段階で IDENTIFIED BY '' が同時指定されているからパスワードを更新したことになり、それ以外(セカンダリーノードだったり、フツーのレプリケーションのスレーブだったり)は認証プラグインの更新後にパスワードの変更が行われてないように見えるからEXPIREする、と。
「認証プラグインを変更しつつパスワードを空文字にする」なんてことがない限りは着火しないので、検証環境以外でお目にかかることはないはず。たぶん。
( ´-`).oO(アカウントのもとのパスワードがNotからっぽだとマスターとスレーブの authentication_string がズレるから、 password_require_current が有効だとレプリケーションが死にそうな予感…

2020/02/25

InnoDB Clusterの全ノードを正常に停止させたあとの復旧方法

TL;DR

  • MySQL Shellで dba.rebootClusterFromCompleteOutage()

深く考えずにGroup Replicationの全ノードを停止すると、いざ次回起動した時に
2020-02-25T09:14:08.497656Z 0 [ERROR] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] Error on opening a connection to xxx.xxx.xxx.xxx:33061 on local port: 33061.'
のようなエラーを吐き続けて最終的に
2020-02-25T09:14:08.497685Z 0 [ERROR] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] Error connecting to all peers. Member join failed. Local port: 33061'
2020-02-25T09:14:09.500209Z 0 [ERROR] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] The member was unable to join the group. Local port: 33061'
2020-02-25T09:14:12.789547Z 2 [ERROR] [MY-011640] [Repl] Plugin group_replication reported: 'Timeout on wait for view after joining group'
2020-02-25T09:14:12.789633Z 2 [ERROR] [MY-011735] [Repl] Plugin group_replication reported: '[GCS] The member is leaving a group without being on one.'
GRの起動に失敗する。
タイムアウトとは言っているけれど、「過半数を満たしたグループに接続しようと思ったけれど、接続先がみんな過半数を満たしたグループにいない」からこうなっているんだとは思う。
### node1 
$ mysql -e "SELECT member_host, member_state, member_role FROM performance_schema.replication_group_members" 
+------------------------------+--------------+-------------+ 
| member_host | member_state | member_role | 
+------------------------------+--------------+-------------+ 
| node1 | OFFLINE | | 
+------------------------------+--------------+-------------+ 

### node2
$ mysql -e "SELECT member_host, member_state, member_role FROM performance_schema.replication_group_members" 
+------------------------------+--------------+-------------+ 
| member_host | member_state | member_role | 
+------------------------------+--------------+-------------+ 
| node2 | OFFLINE | | 
+------------------------------+--------------+-------------+ 

### node3
$ mysql -e "SELECT member_host, member_state, member_role FROM performance_schema.replication_group_members" 
+------------------------------+--------------+-------------+ 
| member_host | member_state | member_role | 
+------------------------------+--------------+-------------+ 
| node3 | OFFLINE | | 
+------------------------------+--------------+-------------+
performance_schema.replication_group_members をのぞき込む限りはみんな自分のことしか見えてない。
 MySQL  localhost:33060+ ssl  JS > dba.getCluster()
Dba.getCluster: This function is not available through a session to a standalone instance (metadata exists, instance belongs to that metadata, but GR is not active) (RuntimeError)
MySQL Shellもこの通りエラるが、どれか1台のノードで dba.rebootClusterFromCompleteOutage() を実行すればOK。
 MySQL  localhost:33060+ ssl  JS > dba.rebootClusterFromCompleteOutage()
Reconfiguring the default cluster from complete outage...

The instance 'node2:3306' was part of the cluster configuration.
Would you like to rejoin it to the cluster? [y/N]: y

The instance 'node3:3306' was part of the cluster configuration.
Would you like to rejoin it to the cluster? [y/N]: y

Disabling super_read_only mode on instance 'node1:3306'.
The cluster was successfully rebooted.

<Cluster:myfabric>
残っているメタデータから「他のノードもこのクラスターにrejoinさせる?」と聞いてくれる充実っぷりなので復旧(というのかこの場合)はらくちん。

壊れはしなかった(オフラインの間にゴニョゴニョしたのが競合してしまった…)

2020/02/21

MultiPrimaryModeのGroup Replication環境を崩壊させるテスト

TL;DR

  • 完全崩壊した時の復旧シナリオを考えたりするには、やっぱり崩壊した状態を再現させられると便利だよね
  • cluster.switchToMultiPrimaryMode() してから2つの別のノードに「1回目は成功するけど2回流すと必ず失敗するALTER TABLE」を投げると崩壊させられる

node1> ALTER TABLE sbtest.sbtest1 ADD KEY idx_pad (pad);
Query OK, 0 rows affected, 1 warning (10.36 sec)
Records: 0  Duplicates: 0  Warnings: 1

-- 5秒くらい待つ

node2> ALTER TABLE sbtest.sbtest1 ADD KEY idx_pad (pad);
Query OK, 0 rows affected, 1 warning (10.60 sec)
Records: 0  Duplicates: 0  Warnings: 1
  1. node1 のALTER TABLEが終わり、 node2node3 に渡される
  2. node2 では手で打ったALTER TABLEがメタデータをロックしているので node1 から渡ってきたALTER TABLEは待たされる
  3. node3 では node1 由来のALTER TABLEが実行される
  4. node2 の手で打ったALTER TABLEが終わり、 node1 由来のALTER TABLEを適用しようとするが同じ名前のインデックスは作成できないのでエラー
  5. node2 由来のALTER TABLEが node1node3 に到達して、やっぱり同じ名前のインデックスは作成できないのでエラー
結果として完全崩壊する。
### node1
$ mysql -e "SELECT member_host, member_state, member_role FROM performance_schema.replication_group_members"
+------------------------------+--------------+-------------+
| member_host                  | member_state | member_role |
+------------------------------+--------------+-------------+
| node1 | ERROR        |             |
| node2  | ONLINE       | PRIMARY     |
| node3 | ONLINE       | PRIMARY     |
+------------------------------+--------------+-------------+

### node2
$ mysql -e "SELECT member_host, member_state, member_role FROM performance_schema.replication_group_members"
+-----------------------------+--------------+-------------+
| member_host                 | member_state | member_role |
+-----------------------------+--------------+-------------+
| node2 | ONLINE       | PRIMARY     |
+-----------------------------+--------------+-------------+

### node3
$ mysql -e "SELECT member_host, member_state, member_role FROM performance_schema.replication_group_members"
+------------------------------+--------------+-------------+
| member_host                  | member_state | member_role |
+------------------------------+--------------+-------------+
| node1 | ONLINE       | PRIMARY     |
| node2 | ONLINE       | PRIMARY     |
| node3 | ERROR        |             |
+------------------------------+--------------+-------------+
エラーログはどのノードも同じようなことを言っていた。
2020-02-21T09:10:32.562676Z 13 [ERROR] [MY-011451] [Repl] Plugin group_replication reported: 'The applier thread execution was aborted. Unable to process more transactions, this member will now leave the group.'
2020-02-21T09:10:32.562690Z 13 [ERROR] [MY-010586] [Repl] Error running query, slave SQL thread aborted. Fix the problem, and restart the slave SQL thread with "SLAVE START". We stopped at log 'FIRST' position 0
2020-02-21T09:10:32.562921Z 9 [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.'
2020-02-21T09:10:32.562975Z 9 [ERROR] [MY-011712] [Repl] Plugin group_replication reported: 'The server was automatically set into read only mode after an error was detected.'
ここまで来るともうまともにMySQL ShellやGroup Replication関連のステートメントが使えなくなるので、たっぷり復旧方法を考えたり試したりできる。

2020/02/19

CentOS 7のAMIでEC2を起動してGroup Replicationを組むところまでを何も考えずに

TL;DR

  • 毎回ちょこちょこ打ち込むのが面倒になったのでコピペ用に
  • まだGroup Replicationを調べるのがメインなのでMySQL Routerはどうにもしてない

Group Replicationに加わる全てのノードで

sudo -i
setenforce 0
yum install -y https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm
yum install -y mysql-community-server mysql-shell
mysqld --initialize-insecure --user=mysql
systemctl start mysqld

cluster_user="gradmin"
cluster_pass="grtest"
cluster_name="myfabric"

mysqlsh -- shell.options set_persist history.autoSave true
mysqlsh --uri=root:""@localhost -- dba configureInstance '' { --clusterAdmin=$cluster_user --clusterAdminPassword=$cluster_pass --interactive=false --restart=true }
until mysqladmin ping ; do
  sleep 1
done

最初の1台になるノードでだけ

  • 変数は引き継いでるつもりで
mysqlsh --uri=$cluster_user:$cluster_pass@localhost -- dba createCluster $cluster_name

### 追加したいノードぶんこれやる
node=xxx.xxx.xxx.xxx
mysqlsh --uri=$cluster_user:$cluster_pass@localhost --cluster -- cluster addInstance "$cluster_user:$cluster_pass@$node" { --recoveryMethod=clone }

忘れそうなポイントだけ。
  • mysqlsh --cluster
    • 接続先がInnoDB Clusterに属していれば、 cluster = dba.getCluster() 相当のことを勝手にやってくれるのでいきなり cluster オブジェクトが使える
  • mysqlsh -- ..
    • 名前空間を区切っているドットは書かない。
    • MySQL Shell内部だと {"param":"value","hoge":"fuga"} みたいな形式で渡すやつは不思議形式で記述しないといけなくなる
      • { --param=value --hoge=fuga }

2020/02/18

InnoDB ClusterはPKがないテーブルに対する更新を "ERROR 3098 (HY000): The table does not comply with the requirements by an external plugin." のエラーにする

TL;DR

  • InnoDB Cluster(というかGroup Replication)環境で ERROR 3098 (HY000): The table does not comply with the requirements by an external plugin. と言われたら、テーブルにPRIMARY KEYがあるかどうかを疑ってみても良いかも
  • InnoDB Cluster環境に mysqlslap --auto-generate-sql を叩き込もうとしたら気が付いた
  • このエラーメッセージを使っているのはMySQL 8.0.19現在ではGroup Replicationくらいだけれど、このエラーを返すインターフェイス自体は汎用的に作られているので将来は変わる鴨

InnoDB Clusterで遊ぼうと テキトーにMySQL ShellでInnoDB Clusterを作って mysqlslap を叩いたらこれ。
$ mysqlslap80 -P3306 -h127.0.0.1 -uroot --auto-generate-sql --auto-generate-sql-execute-number=10000 --auto-generate-sql-load-type=l
/usr/mysql/8.0.19/bin/mysqlslap: Cannot run query INSERT INTO t1 VALUES (1804289383,'mxvtvmC9127qJNm06sGB8R92q2j7vTiiITRDGXM9ZLzkdekbWtmXKwZ2qG1llkRw5m9DHOFilEREk3q7oce8O3BEJC0woJsm6uzFAEynLH2xCsw1KQ1lT4zg9rdxBL') ERROR : The table does not comply with the requirements by an external plugin.
前にも同じエラーに当たったことがあるような気がしつつ、取り敢えず手で叩いても同じエラーになるかどうかをかくにn
$ mysql80 -P3306 -h127.0.0.1 -uroot

mysql80 52> use mysqlslap
Database changed

mysql80 52> show tables;
+---------------------+
| Tables_in_mysqlslap |
+---------------------+
| t1                  |
+---------------------+
1 row in set (0.03 sec)

mysql80 52> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `intcol1` int DEFAULT NULL,
  `charcol1` varchar(128) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
この時点でオチに気が付いてしまった。PRIMARY KEYがない。
mysql80 52> INSERT INTO t1 VALUES (1804289383,'mxvtvmC9127qJNm06sGB8R92q2j7vTiiITRDGXM9ZLzkdekbWtmXKwZ2qG1llkRw5m9DHOFilEREk3q7oce8O3BEJC0woJsm6uzFAEynLH2xCsw1KQ1lT4zg9rdxBL');
ERROR 3098 (HY000): The table does not comply with the requirements by an external plugin.

$ perror 3098
MySQL error code MY-003098 (ER_BEFORE_DML_VALIDATION_ERROR): The table does not comply with the requirements by an external plugin.
エラーコード的には Before DML Vlidation Error らしいので、Group Replicationに限らず返す ここのフック を通れば同じエラーが返る可能性はある。
というか、テストスイートの中に replication_observers_example_before_dml とかいうのがあってその中でこのエラーを期待しているらしいので、レプリケーションプラグイン全般で使うことを想定しているのであろう、たぶん。
$ grep -r ER_BEFORE_DML_VALIDATION_ERROR
sql/sql_base.cc:  ER_BEFORE_DML_VALIDATION_ERROR
sql/sql_base.cc:  if (out_value) my_error(ER_BEFORE_DML_VALIDATION_ERROR, MYF(0));
Binary file GRTAGS matches
mysql-test/suite/rpl/t/rpl_replication_observers_example_before_dml.test:--error ER_BEFORE_DML_VALIDATION_ERROR
mysql-test/suite/rpl/t/rpl_replication_observers_example_before_dml.test:--error ER_BEFORE_DML_VALIDATION_ERROR
mysql-test/suite/rpl/t/rpl_replication_observers_example_before_dml.test:--error ER_BEFORE_DML_VALIDATION_ERROR
mysql-test/suite/rpl/t/rpl_replication_observers_example_before_dml.test:--error ER_BEFORE_DML_VALIDATION_ERROR
mysql-test/suite/rpl/t/rpl_replication_observers_example_before_dml.test:--error ER_BEFORE_DML_VALIDATION_ERROR
mysql-test/suite/rpl/t/rpl_replication_observers_example_before_dml.test:--error ER_BEFORE_DML_VALIDATION_ERROR
mysql-test/suite/rpl/t/rpl_replication_observers_example_before_dml.test:--error ER_BEFORE_DML_VALIDATION_ERROR
mysql-test/suite/rpl/t/rpl_replication_observers_example_before_dml.test:--error ER_BEFORE_DML_VALIDATION_ERROR
mysql-test/suite/rpl/t/rpl_replication_observers_example_before_dml.test:--error ER_BEFORE_DML_VALIDATION_ERROR
^C
sql_require_primary_key と違って分かりやすいエラーが返るわけではないことにだけ注意。
mysql80 20> SELECT @@session.sql_require_primary_key;
+-----------------------------------+
| @@session.sql_require_primary_key |
+-----------------------------------+
|                                 1 |
+-----------------------------------+
1 row in set (0.00 sec)

mysql80 20> CREATE TABLE t3 (num int);
ERROR 3750 (HY000): Unable to create or change a table without a primary key, when the system variable 'sql_require_primary_key' is set. Add a primary key to the table or unset this variable to avoid this message. Note that tables without a primary key can cause performance problems in row-based replication, so please consult your DBA before changing this setting.
いないとは思うけれど、InnoDB Clusterに mysqlslap --auto-generate-sql を叩き込みたい人は --auto-generate-sql-guid-primary オプションを足せばPRIMARY KEY用のカラムを作るのでちゃんと通るようになるますよ。
$ mysqlslap80 -P3306 -h127.0.0.1 -uroot --auto-generate-sql --auto-generate-sql-execute-number=10000 --auto-generate-sql-load-type=l --auto-generate-sql-guid-primary
Benchmark
        Average number of seconds to run all queries: 19.517 seconds
        Minimum number of seconds to run all queries: 19.517 seconds
        Maximum number of seconds to run all queries: 19.517 seconds
        Number of clients running queries: 1
        Average number of queries per client: 10000

MySQL WorkbenchからのLOAD DATA LOCAL INFILEが失敗する場合

TL;DR

“Edit Connection” -> “Connection” -> “Advanced” -> “Others” のところに OPT_LOCAL_INFILE=1


日本MySQLユーザ会 のメーリングリストに寄せられたお便り(?) mysql:16565 の件なんですが、添付ファイルがML Archiveから上手く見えていなかったのでブログ版です。
“Error Code: 3948. Loading local data is disabled; this must be enabled on both the client and server sides” または “Error Code: 1148. The used command is not allowed with this MySQL version” で失敗する場合のはなし。

カラムの数が違うとか値が不正だとけ権限が足りない系のエラーの対処ではないので注意。
というか TL;DR に書いたことが全てなので特に書くことなかった。。