GA

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 に書いたことが全てなので特に書くことなかった。。

2020/01/10

explicit_defaults_for_timstampのONとOFFでエラーになるケースならないケース

TL;DR

explicit_defaults_fot_timestamp カラムのデフォルト値 INSERTでカラム未指定 INSERTでNULLを指定
ON なし エラー エラー
ON あり OK エラー
OFF なし エラー OK
OFF あり OK OK

explicit_defaults_for_timestamp というパラメーターがあって、これは「昔の古いTIMESTAMP型の挙動、SQL標準じゃないからちゃんとエラーにするようにする」というパラメーター。
これ自体は5.6で導入されていて、 “explicit_defaults_for_timestamp” でググるとワーニングの消し方(OFFだとエラーログにワーニングが出るので、ONにすれば消えるよ、というネタ)はいっぱい出てくるんだけど、結局どんなクエリーが影響を受けるのかイマイチまとまってるのを見つけられなかったのでメモ。
MySQL 8.0からはデフォルトでONになったことだし。

デフォルト値なしのカラム定義

mysql57 5> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `num` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `ts` timestamp NOT NULL,
  UNIQUE KEY `num` (`num`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

explicit_defaults_for_timestamp = OFF

mysql57 5> SELECT @@session.explicit_defaults_for_timestamp;
+-------------------------------------------+
| @@session.explicit_defaults_for_timestamp |
+-------------------------------------------+
|                                         0 |
+-------------------------------------------+
1 row in set (0.00 sec)

mysql57 5> INSERT INTO t1 VALUES (1, NULL);
Query OK, 1 row affected (0.00 sec)

mysql57 5> INSERT INTO t1 (num) VALUES (2);
ERROR 1364 (HY000): Field 'ts' doesn't have a default value

mysql57 5> SELECT * FROM t1;
+-----+---------------------+
| num | ts                  |
+-----+---------------------+
|   1 | 2020-01-10 19:13:54 |
+-----+---------------------+
1 row in set (0.00 sec)

explicit_defaults_for_timestamp = ON

mysql57 5> SELECT @@session.explicit_defaults_for_timestamp;
+-------------------------------------------+
| @@session.explicit_defaults_for_timestamp |
+-------------------------------------------+
|                                         1 |
+-------------------------------------------+
1 row in set (0.00 sec)

mysql57 5> INSERT INTO t1 VALUES (1, NULL);
ERROR 1048 (23000): Column 'ts' cannot be null

mysql57 5> INSERT INTO t1 (num) VALUES (2);
ERROR 1364 (HY000): Field 'ts' doesn't have a default value

デフォルト値ありのカラム定義

mysql57 5> SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `num` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY `num` (`num`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

explicit_defaults_for_timestamp = OFF

mysql57 5> SELECT @@session.explicit_defaults_for_timestamp;
+-------------------------------------------+
| @@session.explicit_defaults_for_timestamp |
+-------------------------------------------+
|                                         0 |
+-------------------------------------------+
1 row in set (0.00 sec)

mysql57 5> INSERT INTO t1 VALUES (1, NULL);
Query OK, 1 row affected (0.00 sec)

mysql57 5> INSERT INTO t1 (num) VALUES (2);
Query OK, 1 row affected (0.00 sec)

mysql57 5> SELECT * FROM t1;
+-----+---------------------+
| num | ts                  |
+-----+---------------------+
|   1 | 2020-01-10 19:18:24 |
|   2 | 2020-01-10 19:18:27 |
+-----+---------------------+
2 rows in set (0.00 sec)

explicit_defaults_for_timestamp = ON

mysql57 5> SELECT @@session.explicit_defaults_for_timestamp;
+-------------------------------------------+
| @@session.explicit_defaults_for_timestamp |
+-------------------------------------------+
|                                         1 |
+-------------------------------------------+
1 row in set (0.00 sec)

mysql57 5> INSERT INTO t1 VALUES (1, NULL);
ERROR 1048 (23000): Column 'ts' cannot be null

mysql57 5> INSERT INTO t1 (num) VALUES (2);
Query OK, 1 row affected (0.00 sec)

mysql57 5> SELECT * FROM t1;
+-----+---------------------+
| num | ts                  |
+-----+---------------------+
|   2 | 2020-01-10 19:18:08 |
+-----+---------------------+
1 row in set (0.00 sec)
というわけで最初のまとめどおり、
explicit_defaults_fot_timestamp カラムのデフォルト値 INSERTでカラム未指定 INSERTでNULLを指定
ON なし エラー エラー
ON あり OK エラー
OFF なし エラー OK
OFF あり OK OK

2019/12/24

CentOS 8.0にしたらいくつかのURLに error:141A318A:SSL routines:tls_process_ske_dhe:dh key too small でアクセスできなくなった

TL;DR

$ sudo update-crypto-policies --set LEGACY

CentOS 7.xのVMを壊してしまったのでCentOS 8.xのものにお引越ししていたら
$ curl -I https://www8.cao.go.jp
curl: (35) error:141A318A:SSL routines:tls_process_ske_dhe:dh key too small
こんな風になっていくつかアクセスできないURLが。
こちらをがたぶんそのまんまで、CentOS 8.0で入ってくるのが openssl 1.1.1 だから鍵長が短いのをリジェクトしているっぽい。内閣府さん…(www.cao.go.jpの方はいけるんだけどwww8.cao.go.jpの方がダメだった…)
$ rpm -q openssl
openssl-1.1.1-8.el8.x86_64
どうやってさまよってたどり着いたのか全く覚えていないけど、
を眺めていたら crypt-policies というのが設定できるらしく、これをLEGACYにすれば1024ビットのDHパラメーターでも許してくれるらしい。
$ sudo update-crypto-policies --set LEGACY
Setting system policy to LEGACY
Note: System-wide crypto policies are applied on application start-up.
It is recommended to restart the system for the change of policies
to fully take place.

$ curl -I https://www8.cao.go.jp
HTTP/1.1 200 OK
Date: Tue, 24 Dec 2019 06:45:17 GMT
Server: Apache
X-Frame-Options: SAMEORIGIN
Last-Modified: Tue, 20 Nov 2018 08:06:15 GMT
ETag: "1f10-57b141b87cbc0"
Accept-Ranges: bytes
Content-Length: 7952
Cache-Control: no-store
Expires: Tue, 24 Dec 2019 06:45:17 GMT
Pragma: no-cache
Content-Type: text/html
いったー。
自己責任でどうぞ。