CUBE SUGAR CONTAINER

技術系のこと書きます。

MariaDB Galera Cluster - Known Limitations について調べた

MariaDB Galera Cluster は MariaDB を同期レプリケーション型のマルチマスタ構成で冗長化するためのアプリケーションだ。 マスタ・スレーブ構成のようにフォールバック用のスクリプトを自分で用意する必要もないし、マルチマスタ構成なのでクラスタを構成するノードは全て書き込みできる (ただし、全てのノードに書き込みをロードバランスさせるとデッドロックの可能性が増える)。 MySQL Cluster のようにトランザクション分離レベルが落ちることもない。 障害が発生してもクラスタを構成する半分のノードの故障までなら耐えられるし、障害の復旧後は自動で正常なノードとの同期が走るようになっている。

一見すると良いところしかないように思える Galera Cluster だけど、メリットがあればもちろんデメリットもある。 MariaDB Galera Cluster の既知の制限 (Known Limitations) については MariaDB の公式ドキュメントにまとめられている。 今回は、このドキュメントを翻訳しながらその内容について調べてみることにする。

MariaDB Galera Cluster - Known Limitations

検証環境について

今回は検証環境のプラットフォームとして CentOS7 を選んだ。

$ cat /etc/redhat-release 
CentOS Linux release 7.1.1503 (Core) 
$ uname -r
3.10.0-229.11.1.el7.x86_64

また、MariaDB は安定版に 5.5 系と 10.0 系がある。 今回はその中でもより新しい 10.0 系を使用している。

$ rpm -qa | grep -i mariadb
MariaDB-common-10.0.21-1.el7.centos.x86_64
MariaDB-client-10.0.21-1.el7.centos.x86_64
MariaDB-Galera-server-10.0.21-1.el7.centos.x86_64

環境の具体的な構築方法については以下のエントリを参照のこと。

MariaDB Galera Cluster を CentOS7 で使ってみる

今回使う検証環境では、上記のエントリにあるように MariaDB Galera Cluster の最小構成であるノード 3 台を使ったクラスタを構築している。

MariaDB [(none)]> SHOW GLOBAL STATUS LIKE 'wsrep_cluster_size';
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| wsrep_cluster_size | 3     |
+--------------------+-------+
1 row in set (0.00 sec)

また、複数のノードにまたがった説明をする場合には、それぞれのノードを区別できるようにプロンプトの表示を変えている。

MariaDB [(none)]> prompt MariaDB1 > ;
MariaDB [(none)]> prompt MariaDB2 > ;
MariaDB [(none)]> prompt MariaDB3 > ;

データベースについては適当に 'sample' とかいう名前で作った。

MariaDB [(none)]> create database sample;
Query OK, 1 row affected (0.00 sec)

また、各項目毎に users という名前で検証用のテーブルを作っているが、その内容はそれぞれ異なるので項目の最後で毎回削除している。

MariaDB1 > drop table users;
Query OK, 0 rows affected (0.00 sec)

MariaDB Galera Cluster - Known Limitations

codership.com が公にしている制限について

Limitations from codership.com

codership.com (※ Galera Cluster の開発元) が公にしている制限

まずは codership.com が公にしている制限から。

サポートされているストレージエンジンは今のところ InnoDB だけ

Currently replication works only with the InnoDB storage engine. Any writes to tables of other types, including system (mysql.) tables are not replicated (this limitation excludes DDL statements such as CREATE USER, which implicitly modify the mysql. tables — those are replicated). There is however experimental support for MyISAM - see the wsrep_replicate_myisam system variable)

今のところレプリケーションは InnoDB ストレージエンジンでのみ動作します。 それ以外のタイプもしくはシステム (mysql.) のテーブルへの書き込みはレプリケーションされません。 (暗黙に mysql. テーブルを変更する CREATE USER のような DDL ステートメントについてはこの制限の対象外で、それらはレプリケーションされます) ただし、MyISAM には実験的なサポートが存在します。 システム変数 wsrep_replicate_myisam を参照してください。

まず、Galera Cluster は InnoDB ストレージエンジンでしか使えないらしい。 ただ、今どきは InnoDB くらいしか使われることがないのでこれは特に問題なさそう。

実験的にサポートされている MyISAM のレプリケーションについては 'wsrep_replicate_myisam' というシステム変数で機能の切り替えができるらしい。 確認すると、デフォルトでは OFF になっている。

MariaDB [(none)]> show variables like 'wsrep_replicate_myisam';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| wsrep_replicate_myisam | OFF   |
+------------------------+-------+
1 row in set (0.00 sec)

本当にレプリケーションされないのか、試しに MyISAM を使ったテーブルを作って確認してみよう。

MariaDB1 > create table users (id Integer primary key auto_increment, name Text not null) ENGINE=MyISAM;
Query OK, 0 rows affected (0.00 sec)

MariaDB1 > desc users;
+-------+---------+------+-----+---------+----------------+
| Field | Type    | Null | Key | Default | Extra          |
+-------+---------+------+-----+---------+----------------+
| id    | int(11) | NO   | PRI | NULL    | auto_increment |
| name  | text    | NO   |     | NULL    |                |
+-------+---------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

ひとつレコードを追加しておく。

MariaDB1 > insert into users (name) values ("Foo");
Query OK, 1 row affected (0.00 sec)

MariaDB1 > select * from users;
+----+------+
| id | name |
+----+------+
|  1 | Foo  |
+----+------+
1 row in set (0.00 sec)

別のノードでレコードがあるか確認してみる。

MariaDB2 > use sample;
Database changed

見事にない。 やはり MyISAM のテーブルはレプリケーションされないようだ。

MariaDB2 > select * from users;
Empty set (0.00 sec)

ただし、mysql.* テーブルを暗黙に変更する CREATE USER などの DDL ステートメントはレプリケーションされるとある。 これも確認しておこう。

mysql 以下のテーブルにはストレージエンジンに MyISAM を使ったものが多い。

MariaDB [(none)]> select table_name, engine from  information_schema.tables where table_schema = 'mysql';
+---------------------------+--------+
| table_name                | engine |
+---------------------------+--------+
| column_stats              | MyISAM |
| columns_priv              | MyISAM |
| db                        | MyISAM |
| event                     | MyISAM |
| func                      | MyISAM |
| general_log               | CSV    |
| gtid_slave_pos            | InnoDB |
| help_category             | MyISAM |
| help_keyword              | MyISAM |
| help_relation             | MyISAM |
| help_topic                | MyISAM |
| host                      | MyISAM |
| index_stats               | MyISAM |
| innodb_index_stats        | InnoDB |
| innodb_table_stats        | InnoDB |
| plugin                    | MyISAM |
| proc                      | MyISAM |
| procs_priv                | MyISAM |
| proxies_priv              | MyISAM |
| roles_mapping             | MyISAM |
| servers                   | MyISAM |
| slow_log                  | CSV    |
| table_stats               | MyISAM |
| tables_priv               | MyISAM |
| time_zone                 | MyISAM |
| time_zone_leap_second     | MyISAM |
| time_zone_name            | MyISAM |
| time_zone_transition      | MyISAM |
| time_zone_transition_type | MyISAM |
| user                      | MyISAM |
+---------------------------+--------+
30 rows in set (0.01 sec)

ユーザ foo を追加してみる。

MariaDB1 > create user foo@'localhost';
Query OK, 0 rows affected (0.01 sec)

別のノードで確認すると、確かに CREATE USER で書き込まれた内容はレプリケーションされているようだ。

MariaDB2 > select User, Host from mysql.user where User like 'foo';
+------+-----------+
| User | Host      |
+------+-----------+
| foo  | localhost |
+------+-----------+
1 row in set (0.00 sec)

明示的なロックが使えない (グローバルロックを除く)

Unsupported explicit locking include LOCK TABLES, FLUSH TABLES {explicit table list} WITH READ LOCK, (GET_LOCK(), RELEASE_LOCK(),…). Using transactions properly should be able to overcome these limitations. Global locking operators like FLUSH TABLES WITH READ LOCK are supported.

LOCK TABLES, FLUSH TABLES {explicit table list} WITH READ LOCK, (GET_LOCK(), RELEASE_LOCK(),…) を含む明示的なロックはサポートされていません。 これらの制限は適切にトランザクションを使用することで克服できるはずです。 FLUSH TABLES WITH READ LOCK のようなグローバルロック操作はサポートされています。

どうやら Galera Cluster ではテーブルロックの類が使えないようだ。

それでは、実際に試してみよう。 まずは検証に使うテーブルを作る。

MariaDB1 > create table users (id Integer primary key auto_increment, name Text not null);
Query OK, 0 rows affected (0.01 sec)

作ったテーブルに対して MariaDB1 でロックをかける。 本来であれば、これで他のトランザクションはこのテーブルに書き込もうとするとブロックする。

MariaDB1 > lock tables users read;
Query OK, 0 rows affected (0.00 sec)

別のノードから行を挿入してみると、本来ならブロックするはずの insert 文がそのまま成功してしまう。

MariaDB2 > insert into users (name) values ("Foo");
Query OK, 1 row affected (0.00 sec)
MariaDB2 > select * from users;
+----+------+
| id | name |
+----+------+
|  2 | Foo  |
+----+------+
1 row in set (0.00 sec)

この時点では MariaDB1 からは MariaDB2 で挿入した行は見えない。

MariaDB1 > select * from users;
Empty set (0.00 sec)

ロックを手放した時点で見えるようになる。

MariaDB1 > unlock tables;
Query OK, 0 rows affected (0.00 sec)
MariaDB1 > select * from users;
+----+------+
| id | name |
+----+------+
|  2 | Foo  |
+----+------+
1 row in set (0.00 sec)

説明にあった通り Galera Cluster では明示的なロックがサポートされないため、別のトランザクションからロック中であっても行を挿入できてしまうことがわかった。

また、select ... for update を使った排他ロックも、ノードをまたぐと別のトランザクションをブロックできない。 これについては別の記事にまとめた。

blog.amedama.jp

テーブルには主キーがあった方が良い

All tables should have a primary key (multi-column primary keys are supported). DELETE operations are unsupported on tables without a primary key. Also, rows in tables without a primary key may appear in a different order on different nodes.

すべてのテーブルは主キーを持つべきです (複合主キーはサポートされています)。 主キーがないテーブルの DELETE 操作はサポートされていません。 また、主キーがないと行の表示が異なるノードで異なる順序になる可能性があります。

テーブルは主キーを持っていた方が良いらしい。 主キーがないと挿入した行を DELETE できなくなってしまうそうだ。

実際に主キーのないテーブルを作って試してみよう。

MariaDB1 > create table users (name Text not null);
Query OK, 0 rows affected (0.00 sec)
MariaDB1 > desc users;
+-------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------+------+-----+---------+-------+
| name  | text | NO   |     | NULL    |       |
+-------+------+------+-----+---------+-------+
1 row in set (0.00 sec)

適当に行を追加する。

MariaDB1 > insert into users (name) values ("Foo");
Query OK, 1 row affected (0.01 sec)

レプリケーションされて別のノードからも見える。

MariaDB2 > select * from users;
+------+
| name |
+------+
| Foo  |
+------+
1 row in set (0.00 sec)

では、追加した行を削除してみよう!

MariaDB1 > delete from users where name like "Foo";
Query OK, 1 row affected (0.01 sec)

Galera Cluster では主キーがないと DELETE できないので、消えないはず…あれ、消えてる?

MariaDB2 > select * from users;
Empty set (0.00 sec)

えっと、何故か消えてしまったけど本家がサポートしていないと言っているのでテーブルには主キーがあった方が良いんだと思う。

クエリログはファイルにしか残せない

The query log cannot be directed to a table. If you enable query logging, you must forward the log to a file: log_output=FILE

クエリログをテーブルに向けることはできません。 もしあなたがクエリーログを取りたいなら、ログはファイルに転送する必要があります: log_output=FILE 。

通常はクエリログをテーブルに残すこともできるんだけど、Galera Cluster ではファイルにしか残せないらしい。 こちらの検証については省略。

XA トランザクションはサポートされていない

XA transactions are not supported.

XA トランザクションはサポートされていません。

初めて知ったんだけど XA トランザクションというのは二相コミットを利用した分散トランザクションのことらしい。 複数のデータベースにまたがって更新をかけるときに必要になるみたい。

サポートされていないみたいだし今のところ使う予定はないけど一応試してみる。 まずは検証用のテーブルを作るところから。

MariaDB1 > create table users (id Integer primary key auto_increment, name Text not null);
Query OK, 0 rows affected (0.01 sec)

XA トランザクションでは xa start でトランザクションを開始する。

MariaDB1 > xa start 'xatest';
Query OK, 0 rows affected (0.00 sec)

トランザクションの中で行を挿入する。

MariaDB1 > insert into users (name) values ("foo");
Query OK, 1 row affected (0.00 sec)
MariaDB1 > select * from users;
+----+------+
| id | name |
+----+------+
|  4 | foo  |
+----+------+
1 row in set (0.00 sec)

これで一旦トランザクションから抜ける。

MariaDB1 > xa end 'xatest';
Query OK, 0 rows affected (0.00 sec)

この状態は IDLE と呼ばれるみたいでコマンドが実行できない。

MariaDB1 > select * from users;
ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global transaction is in the  IDLE state

xa prepare と xa commit で二相コミットを完了すると挿入した行が見えるようになる。

MariaDB1 > xa prepare 'xatest';
Query OK, 0 rows affected (0.00 sec)
MariaDB1 > xa commit 'xatest';
Query OK, 0 rows affected (0.00 sec)
MariaDB1 > select * from users;
+----+------+
| id | name |
+----+------+
|  4 | foo  |
+----+------+
1 row in set (0.00 sec)

では別のノードではどうなっているかというと、追加したはずの行が見えない。

MariaDB2 > select * from users;
Empty set (0.00 sec)

やはり XA トランザクションはサポートされていないため動作しないようだ。

大きなトランザクションは実行できない

Transaction size. While Galera does not explicitly limit the transaction size, a writeset is processed as a single memory-resident buffer and as a result, extremely large transactions (e.g. LOAD DATA) may adversely affect node performance. To avoid that, the wsrep_max_ws_rows and wsrep_max_ws_size system variables limit transaction rows to 128K and the transaction size to 1Gb by default. If necessary, users may want to increase those limits. Future versions will add support for transaction fragmentation.

トランザクションサイズについて。 Galera は明示的にトランザクションサイズを制限しているわけではありませんが、ライトセットは単一のメモリ常駐バッファで処理されるため、極めて大きなトランザクション (例えば LOAD DATA) はノードのパフォーマンスに影響を与えかねます。 その対策として、デフォルトで wsrep_max_ws_rows と wsrep_max_ws_size のシステム変数はトランザクション行数を 128K に、トランザクションサイズを 1Gb に制限しています。 必要に応じてユーザはこの上限を引き上げることができます。 将来のバージョンではトランザクションフラグメンテーションのサポートが追加されます。

大きなトランザクションはシステム変数で制限されているらしい。

一応、システム変数 'wsrep_max_ws_rows' の値を見ておこう。

MariaDB [(none)]> show variables like 'wsrep_max_ws_rows';
+-------------------+--------+
| Variable_name     | Value  |
+-------------------+--------+
| wsrep_max_ws_rows | 131072 |
+-------------------+--------+
1 row in set (0.00 sec)

128 * 1024 = 131072 なのでデフォルトのトランザクション最大行数は確かに 128K のようだ。

同様に 'wsrep_max_ws_size' についても。

MariaDB [(none)]> show variables like 'wsrep_max_ws_size';
+-------------------+------------+
| Variable_name     | Value      |
+-------------------+------------+
| wsrep_max_ws_size | 1073741824 |
+-------------------+------------+
1 row in set (0.00 sec)

1024 ** 3 = 1073741824 だから 1Gb (小文字だからビット?) だね。

その他の知見

Other observations, in no particular order:

その他の知見 (順不同)

ここからは既知の色々な問題とか制限とか Tips など諸々。

ステートトランスファーに mysqldump を使っているときはエラーログの内容に注意

If you are using mysqldump for state transfer, and it failed for whatever reason (e.g. you do not have the database account it attempts to connect with, or it does not have necessary permissions), you will see an SQL SYNTAX error in the server error log. Don't let it fool you, this is just a fancy way to deliver a message (the pseudo-statement inside of the bogus SQL will actually contain the error message).

ステートトランスファーに mysqldump を使っていて、それが何らかの理由 (例えば接続に必要なデータベースアカウントがなかったり、権限がない) で失敗した場合、それはサーバのエラーログに SQL SYNTAX エラーとして表示されます。 これは単にエラーメッセージを提供するためのファンシーなやり方にすぎないため、その表示にだまされないでください。 (偽の SQL の仮想的なステートメントの内部に真のエラーメッセージが含まれています)

Galera Cluster のステートトランスファーには rsync とか mysqldump とか複数のやり方があるんだけど、その中でも mysqldump を使っているときはエラーログがちょっとばかし分かりにくいものになっているらしい。 外見上は SQL の文法エラーとして見えるんだけど、中に本当のエラーメッセージが隠れているらしい。 気をつけよう。

大きなトランザクションは使わない方がいい

Do not use transactions of any essential size. Just to insert 100K rows, the server might require additional 200-300 Mb. In a less fortunate scenario it can be 1.5 Gb for 500K rows, or 3.5 Gb for 1M rows. See MDEV-466 for some numbers (you'll see that it's closed, but it's not closed because it was fixed).

巨大なトランザクションを使ってはいけません。 10 万行を挿入するにはサーバは追加で 200 ~ 300 Mb を必要とするでしょう。 不運なシナリオでは 50 万行に対して 1.5Gb または 100 万行に対して 3.5Gb になります。 それ以外の数値については MDEV-466 を参照してください。 (このチケットは閉じられていますが、修正されたので閉じられてはいません)

前述した通りトランザクションのサイズはシステム変数で制限されているんだけど、そもそも Galera Cluster は大きなトランザクションを扱うのが苦手みたい。 大きなトランザクションを実行するとメモリを大量に消費するみたいなので、大きくならないように注意した方が良さそうだ。

DDL/DML ステートメントは混ぜるな危険

Locking is lax when DDL is involved. For example, if your DML transaction uses a table, and a parallel DDL statement is started, in the normal MySQL setup it would have waited for the metadata lock, but in Galera context it will be executed right away. It happens even if you are running a single node, as long as you configured it as a cluster node. See also MDEV-468. This behavior might cause various side-effects, the consequences have not been investigated yet. Try to avoid such parallelism.

DDL が含まれるとロックはガバガバになります。 例えば、もしテーブルを使用する DML トランザクションと平行して DDL ステートメントが開始された場合、通常の MySQL であればメタデータのロックを待ちますが、Galera ではすぐに実行されます。 これはクラスタノードとして設定している限り、実行が単一ノード上であっても発生します。 MDEV-468 も参照してください。 この挙動は幾つかの副作用も引き起こしえますが、それらの重要性はまだ調査されていません。 こういった並行処理は避けてください。

DDL と DML を混ぜて実行した場合、スタンドアロンな MySQL であればちゃんと考えてロックしてくれるんだけど Galera Cluster の場合は DDL が即座に実行されてしまうらしい。

まずは検証用のテーブルと行を作っておく。

MariaDB1 > create table users (id Integer primary key auto_increment, name Text not null);
Query OK, 0 rows affected (0.00 sec)
MariaDB1 > insert into users (name) values ("foo");
Query OK, 1 row affected (0.00 sec)

追加した行を MariaDB1 のノードで暗黙にロックする。

MariaDB1 > begin;
Query OK, 0 rows affected (0.00 sec)
MariaDB1 > select * from users where name like "foo" for update;
+----+------+
| id | name |
+----+------+
|  1 | foo  |
+----+------+
1 row in set (0.00 sec)

それと平行して MariaDB2 の方からテーブルを削除する DDL を発行してみよう。 スタンドアロンな MySQL であれば DROP を実行したところでブロックするはずなんだけど、残念ながら即座に実行されてしまう。

MariaDB2 > begin;
Query OK, 0 rows affected (0.00 sec)
MariaDB2 > drop table users;
Query OK, 0 rows affected (0.00 sec)

MariaDB2 的にはテーブルが消えてなくなった。

MariaDB2 > show tables;
Empty set (0.00 sec)

MariaDB1 からはどう見えるかというと、トランザクションの最中にテーブルが消えてしまったため処理が続行できずデッドロックを起こした。

MariaDB1 > show tables;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

Galera Cluster で DDL と DML を混ぜて使うとロックがガバガバになるので気をつけよう。

自動インクリメントの値には連続性がない

Do not rely on auto-increment values to be sequential. Galera uses a mechanism based on autoincrement increment to produce unique non-conflicting sequences, so on every single node the sequence will have gaps. See http://codership.blogspot.com/2009/02/managing-auto-increments-with-multi.html

自動インクリメント (auto-increment) の値に連続性があることを期待してはいけません。 Galera は一意で衝突を起こさないシーケンスを提供するために自動インクリメント・インクリメントのメカニズムにもとづいているため、どのノードにおいてもシーケンスには隙間があります。 こちらのページ (http://codership.blogspot.com/2009/02/managing-auto-increments-with-multi.html) も参照してください。

Galera Cluster では自動インクリメントの値がノード間で衝突を起こさないように飛び飛びの値になって連続性を持たない。

確認のために検証用のテーブルに同じノードからいくつか行を挿入してみよう。

MariaDB1 > create table users (id Integer primary key auto_increment, name Text not null);
Query OK, 0 rows affected (0.06 sec)
MariaDB1 > insert into users (name) values ("foo");
Query OK, 1 row affected (0.01 sec)
MariaDB1 > insert into users (name) values ("bar");
Query OK, 1 row affected (0.00 sec)
MariaDB1 > insert into users (name) values ("baz");
Query OK, 1 row affected (0.00 sec)

ご覧の通り、規則性はあるものの見事に飛び飛びだ。

MariaDB1 > select * from users;
+----+------+
| id | name |
+----+------+
|  1 | foo  |
|  4 | bar  |
|  7 | baz  |
+----+------+
3 rows in set (0.00 sec)

障害時の挙動について

A command may fail with ER_UNKNOWN_COM_ERROR producing 'WSREP has not yet prepared node for application use' (or 'Unknown command' in older versions) error message. It happens when a cluster is suspected to be split and the node is in a smaller part — for example, during a network glitch, when nodes temporarily lose each other. It can also occur during state transfer. The node takes this measure to prevent data inconsistency. Its usually a temporary state which can be detected by checking wsrep_ready value. The node, however, allows SHOW and SET command during this period.

コマンドは ER_UNKNOWN_COM_ERROR と共に 'WSREP has not yet prepared node for application use' (または古いバージョンでは 'Unknown command') のエラーメッセージで失敗することがあります。 これはクラスタが分断されたことが疑われる場合に、その小さい方にいるノードで発生します (これは例えばネットワーク障害でノードが一時的にお互いを見失っているときです)。 これはステートトランスファーの最中にも発生しえます。 ノードはデータの不整合を防ぐためにこの対策を取ります。 この通常一時的な状態は wsrep_ready の値を確認することで検出できます。 ただし、この期間であってもノードは SHOW と SET コマンドについては許可します。

ネットワークが分断されるなどした際には、スプリットブレインを起こしてデータの不整合が起こらないようにクラスタの小さい方は更新できなくなるようだ。 Galera Cluster は最低でもノード 3 台構成で、奇数台のノードでクラスタを組むことになる。 自身が同期しているノードの数がクラスタ全体のノード数 ÷ 2 未満になった場合に上記の挙動を取るということだろう。 クラスタ全体のノード数 ÷ 2 を越える数のノードが障害を起こした場合には、全てのノードで自身がスプリットブレインを起こしていない保証ができなくなるためサービスの継続はできないはずだ。

障害時を模倣して動作を確認しておこう。 まず、今の時点では全てのノードで同期が上手くいっている。

MariaDB1 > show global status like 'wsrep_ready';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wsrep_ready   | ON    |
+---------------+-------+
1 row in set (0.00 sec)

動作確認用のテーブルを作っておく。

MariaDB1 > create table users (id Integer primary key auto_increment, name Text not null);
Query OK, 0 rows affected (0.06 sec)

そして、MariaDB1 のノードで、別のノードとの同期ができないように firewalld で通信を遮断してしまう。

$ sudo firewall-cmd --zone=drop --add-source=192.168.33.0/24
success

少しするとシステム変数 'wsrep_ready' が OFF になって 'wsrep_cluster_size' も 1 になる。

MariaDB1 > show global status like 'wsrep_ready';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wsrep_ready   | OFF   |
+---------------+-------+
1 row in set (0.00 sec)
MariaDB1 > show global status like 'wsrep_cluster_size';
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| wsrep_cluster_size | 1     |
+--------------------+-------+
1 row in set (0.00 sec)

この状況で行を挿入しようとすると、前述したエラーメッセージになる。

MariaDB1 > insert into users (name) values ("foo");
ERROR 1047 (08S01): WSREP has not yet prepared node for application use

ちなみに、障害を起こしていない残りのノードは継続して使用できる。

MariaDB2 > insert into users (name) values ("foo");
Query OK, 1 row affected (0.00 sec)

障害から復旧する際の挙動について

After a temporary split, if the 'good' part of the cluster was still reachable and its state was modified, resynchronization occurs. As a part of it, nodes of the 'bad' part of the cluster drop all client connections. It might be quite unexpected, especially if the client was idle and did not even know anything wrong was happening. Please also note that after the connection to the isolated node is restored, if there is a flow on the node, it takes a long time for it to synchronize, during which the "good" node says that the cluster is already of the normal size and synced, while the rejoining node says it's only joined (but not synced). The connections keep getting 'unknown command'. It should pass eventually.

一時的なクラスタの分断が起こった後、クラスタの「良い」部分にまだ疎通性があって、その状態が変更された場合には再同期が発生します。 その一環として、クラスタの「悪い」部分はすべてのクライアントとの接続が遮断されます。 これはクライアントがアイドル状態で、何が起こったのか何も知らないときには特に予期し得ない挙動です。 分断されたノードへのコネクションが復旧した後、そのノードにフローがある場合には同期に長い時間がかかります。 同期している間、「良い」ノードはクラスタは同期されていて平常時のサイズだと答えます。 参加し直している最中のノードは参加している (ただし同期していない) と答えます。 コネクションは 'unknown command' を出し続けますが、これは気にしなくても構いません。

障害から復旧した際には、クラスタのサイズはすぐに復旧するものの、同期が完了するまでは障害を起こしていたノードは使えないらしい。

先ほどの環境を引き続き使用して検証していく。 まずは、別ノードとの通信をフィルタしていた firewalld のルールを削除する。

$ sudo firewall-cmd --zone=drop --remove-source=192.168.33.0/24
success

すると、クラスタサイズはすぐに元に戻った。

MariaDB1 > show global status like 'wsrep_cluster_size';
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| wsrep_cluster_size | 3     |
+--------------------+-------+
1 row in set (0.00 sec)

しかし、'wsrep_ready' の値はしばらくしないと ON にならない。 ドキュメントに記述があったように、障害が起こっている間に更新された内容を MariaDB1 が別ノードとの間で同期しているためだ。

MariaDB1 > show global status like 'wsrep_ready';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wsrep_ready   | OFF   |
+---------------+-------+
1 row in set (0.00 sec)

...

MariaDB1 > show global status like 'wsrep_ready';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wsrep_ready   | ON    |
+---------------+-------+
1 row in set (0.00 sec)

バイナリログのフォーマットは ROW 一択

While binlog_format is checked on startup and can only be ROW (see Binary Log Formats), it can be changed at runtime. Do NOT change binlog_format at runtime, it is likely not only cause replication failure, but make all other nodes crash.

binlog_format は起動と同時にチェックされ ROW (Binary Log Formats を参照してください) だけが使えますが、ランタイムで変更できます。 絶対に binlog_format をランタイムで変更してはいけません。 レプリケーションの失敗の原因となるだけでなく、他のすべてのノードをクラッシュさせます。

バイナリログのフォーマットは ROW だけが使えるらしい。 起動中に変更すると相当ヤバイみたい。

ステートトランスファーに rsync を使っていて、完了する前にクラッシュした場合のリカバリ方法について

If you are using rsync for state transfer, and a node crashes before the state transfer is over, rsync process might hang forever, occupying the port and not allowing to restart the node. The problem will show up as 'port in use' in the server error log. Find the orphan rsync process and kill it manually.

ステートトランスファーに rsync を使っていて、ステートトランスファーが完了する前にノードがクラッシュした場合、rsync のプロセスがポートを専有したまま永久にハングしてノードを再始動できなくなる可能性があります。 この問題はサーバのエラーログに 'port in use' を表示して現れます。 孤児となった rsync のプロセスを見つけて手動で殺してください。

前述した通りステートトランスファーには rsync とか mysqldump とか色々な方法が選べる。 その中でも rsync を使っている場合で、ステートトランスファーが完了する前にノードがクラッシュすると rsync のプロセスがポートを専有したままになるらしい。 その状態だとノードを再起動できなくなっちゃうので、その時はプロセスを自分で見つけて kill すればいいとのこと。

スタンドアロンの MySQL と比べるとパフォーマンスはぶっちゃけ低い

Performance: by design performance of the cluster cannot be higher than performance of the slowest node; however, even if you have only one node, its performance can be considerably lower comparing to running the same server in a standalone mode (without wsrep provider). It is particularly true for big enough transactions (even those which are well within current limitations on transaction size quoted above).

パフォーマンス: 理論上、クラスタのパフォーマンスはその中で最も遅いノードのパフォーマンスを上回ることはありません。 ただし、単一ノードで動かしてもそのパフォーマンスは同じサーバを (wsrep プロバイダを使わない) スタンドアロンモードで動かした場合に比べるとかなり低くなります。 これは十分に大きなトランザクションでは特にそうなります (上記で括弧書きになっているトランザクションサイズの現在の制限に十分収まるものであっても)。

Galera Cluster は同期型のレプリケーションなので、更新作業があれば全てのノードでそれが反映されるまで完了できないはず。 だとすれば、クラスタの中にネットワークが遅かったり計算リソースの少ないノードがいれば当然クラスタ全体のパフォーマンスが落ちることになる。 また、仮にクラスタを構成するノードが 1 台であったとしてもスタンドアロンで動かしている MySQL に比べるとパフォーマンスは落ちるらしい。

それでは実際に、スタンドアロンな MariaDB とパフォーマンスを比較してみよう。 ベンチマークには sysbench を使用する。 sysbench を使った MySQL のベンチマーク方法はこちらのエントリに書いた。

sysbench を使って MySQL をベンチマークする

違いはスタンドアロンなだけで MariaDB の同じバージョンで設定も同じマシンを用意してみた。 サクサクと sysbench を実行する。

$ mysqladmin -u root create sbtest
$ sysbench --test=oltp --mysql-user=root --db-driver=mysql prepare
sysbench 0.4.12:  multi-threaded system evaluation benchmark

Creating table 'sbtest'...
Creating 10000 records in table 'sbtest'...
$ sysbench --test=oltp --mysql-user=root --db-driver=mysql run
    sysbench 0.4.12:  multi-threaded system evaluation benchmark

    Running the test with following options:
    Number of threads: 1

    Doing OLTP test.
    Running mixed OLTP test
    Using Special distribution (12 iterations,  1 pct of values are returned in 75 pct cases)
    Using "BEGIN" for starting transactions
    Using auto_inc on the id column
    Maximum number of requests for OLTP test is limited to 10000
    Threads started!
    Done.

    OLTP test statistics:
        queries performed:
            read:                            140000
            write:                           50000
            other:                           20000
            total:                           210000
        transactions:                        10000  (346.14 per sec.)
        deadlocks:                           0      (0.00 per sec.)
        read/write requests:                 190000 (6576.60 per sec.)
        other operations:                    20000  (692.27 per sec.)

    Test execution summary:
        total time:                          28.8903s
        total number of events:              10000
        total time taken by event execution: 28.8365
        per-request statistics:
             min:                                  2.21ms
             avg:                                  2.88ms
             max:                                 28.32ms
             approx.  95 percentile:               3.56ms

    Threads fairness:
        events (avg/stddev):           10000.0000/0.00
        execution time (avg/stddev):   28.8365/0.00

実行には 28.83 秒かかった。

続いて Galera Cluster を組んだ方。

$ mysqladmin -u root create sbtest
$ sysbench --test=oltp --mysql-user=root --db-driver=mysql prepare
sysbench 0.4.12:  multi-threaded system evaluation benchmark

Creating table 'sbtest'...
Creating 10000 records in table 'sbtest'...
$ sysbench --test=oltp --mysql-user=root --db-driver=mysql run
sysbench 0.4.12:  multi-threaded system evaluation benchmark

Running the test with following options:
Number of threads: 1

Doing OLTP test.
Running mixed OLTP test
Using Special distribution (12 iterations,  1 pct of values are returned in 75 pct cases)
Using "BEGIN" for starting transactions
Using auto_inc on the id column
Maximum number of requests for OLTP test is limited to 10000
Threads started!
Done.

OLTP test statistics:
    queries performed:
        read:                            140000
        write:                           50000
        other:                           20000
        total:                           210000
    transactions:                        10000  (195.06 per sec.)
    deadlocks:                           0      (0.00 per sec.)
    read/write requests:                 190000 (3706.08 per sec.)
    other operations:                    20000  (390.11 per sec.)

Test execution summary:
    total time:                          51.2671s
    total number of events:              10000
    total time taken by event execution: 51.1870
    per-request statistics:
         min:                                  3.83ms
         avg:                                  5.12ms
         max:                                356.12ms
         approx.  95 percentile:               5.96ms

Threads fairness:
    events (avg/stddev):           10000.0000/0.00
    execution time (avg/stddev):   51.1870/0.00

こちらは 51.18 秒かかっている。

28.83 / 51.18 = 0.5633 ということで、スタンドアロンな MariaDB に比べると約 56% のパフォーマンスとなった。 これは高いと見るべきか低いと見るべきか。 ちなみに今回使用した環境で各ノードは同じ L2 上に配置されているためネットワークの遅延は少ない。 Galera Cluster にとっては理想的な状況と言えるはず。

Windows はサポート外

Windows is not supported.

Windows はサポートされていません。

Galera Cluster を Windows で使おうなんて人はいないから問題ないね。

レプリケーションフィルタを使うつもりなら慎重に!

Replication filters: Within Galera cluster, replication filters should be used with caution. As a general rule except for InnoDB DML updates, the following replication filters are not honored in a Galera cluster : binlog-do-db , binlog-ignore-db, replicate-wild-do-db, replicate-wild-ignore-db. However, replicate-do-db, replicate-ignore-db filters are honored for DDL and DML for both InnoDB & MyISAM engines. Having said that, caution must be taken while using replication filters as they might create discrepancies and replication may abort (see MDEV-421, MDEV-6229).

レプリケーションフィルタ: Galera クラスタにおいてレプリケーションフィルタは慎重に使用する必要があります。 InnoDB は原則として DML の更新を除いて次のレプリケーションフィルタが通用しません: binlog-do-db, binlog-ignore-db, replicate-wild-do-db, replicate-wild-ignore-db ただし、replicate-do-db, replicate-ignore-db フィルタは InnoDB と MyISAM エンジンの両方で DDL と DML で通用します。 とはいえ、不整合やレプリケーションの中断を招きかねないことからレプリケーションフィルタの使用には注意を払わなければいけません (MDEV-421 と MDEV-6229 を参照してください)。

レプリケーションフィルタは、特定のテーブルだけレプリケーションの対象にするみたいなことができる機能らしい。 ただ、この機能は「実践ハイパフォーマンスMySQL 第3版」の「10.3.6 レプリケーションフィルタ」でスゲー Dis られてる。 MariaDB から忠告を受ける以前に、これだけで使う気は全くなくなった。

FLUSH PRIVILEGES はレプリケーションされない

FLUSH PRIVILEGES is not replicated.

FLUSH PRIVILEGES はレプリケーションされません。

権限周りを変更するために mysql. テーブルを直接編集すると、それを反映するのには FLUSH PRIVILEGES 文が必要になるようだ。 ただ、CREATE USER とか GRANT 文を使って暗黙に編集する分には必要ないみたい。 mysql. 以下を直接編集すること自体が危なっかしくてそうそうやらないだろうから、これは心の片隅にでもとどめておく程度でいいかな。

古いバージョンではクエリキャッシュを無効にする

Prior to MariaDB Galera Cluster versions 5.5.40-galera and 10.0.14-galera, the query cache needed to be disabled.

MariaDB Galera Cluster のバージョンが 5.5.40-galera および 10.0.14-galera 以前であれば、クエリキャッシュを無効にする必要があります。

以前のバージョンではクエリキャッシュは無効 (query_cache_size=0) に指定する必要があるっぽい。

MariaDB の公式 yum リポジトリから降ってくるバージョンはより新しいものなので、特に気にする必要はなさげ。

$ rpm -qa | grep -i mariadb
MariaDB-client-10.0.21-1.el7.centos.x86_64
MariaDB-shared-10.0.21-1.el7.centos.x86_64
MariaDB-common-10.0.21-1.el7.centos.x86_64
MariaDB-Galera-server-10.0.21-1.el7.centos.x86_64

Galera Cluster をスレーブにするときは並列レプリケーションできない

In an asynchronous replication setup where a master replicates to a galera node acting as slave, parallel replication (slave-parallel-threads

1) on slave is currently not supported (see MDEV-6860). 非同期レプリケーションでスレーブとして動作する Galera ノードにマスターを複製する設定では、並列レプリケーション (slave-parallel-threads > 1) はスレーブにおいて現時点でサポートされていません (MDEV-6860 を参照してください)。

これは前提がマスタ・スレーブ構成でスレーブ側を Galera Cluster にする場合、ということかな。 どういったメリットがあってそんな構成にするんだろう。 もしやることがあれば気をつけよう。

まとめ

今回は Galera Cluster の Known Limitations についてテキトーな翻訳と共に挙動を調べてみた。 良いところばかりに見える Galera Cluster にも、やはり幾つかの制限はあって、それを実際に動かして確認することができた。