GA

2014/02/28

MySQLで「任意のテーブルのDROPは許可したい」けれど、「スキーマはDROPさせたくない」

MySQL分補充エントリー。

MyNA 15987のメール でそんな話題があっていろいろ考えてネタにしようと思ってたんですが、気が付けばこれ去年の7月じゃないですか。。

やりたいこと。

mysql56> SHOW GRANTS;
+------------------------------------------------+
| Grants for user1@localhost                     |
+------------------------------------------------+
| GRANT USAGE ON *.* TO 'user1'@'localhost'      |
| GRANT DROP ON `user1`.* TO 'user1'@'localhost' |
+------------------------------------------------+
2 rows in set (0.00 sec)
なユーザーに

mysql56> DROP TABLE user1.t1;
Query OK, 0 rows affected (0.07 sec)

は(マニュアルにあるとおり)成功させたいけれど、

mysql56> DROP DATABASE user1;
Query OK, 0 rows affected (0.15 sec)

は(フツーやると成功するところを)失敗させたい。


思い付いたやり方は2つ。

*** sql/sql_db.cc.orig  2014-01-15 00:38:00.000000000 +0900
--- sql/sql_db.cc       2014-02-28 17:13:19.383810416 +0900
***************
*** 44,49 ****
--- 44,51 ----
  #endif
  #include "debug_sync.h"

+ #include "sql_parse.h"    // check_global_access
+
  #define MAX_DROP_TABLE_Q_LEN      1024

  const char *del_exts[]= {".frm", ".BAK", ".TMD", ".opt", ".OLD", NullS};
***************
*** 772,777 ****
--- 774,782 ----

  bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent)
  {
+   if (check_global_access(thd, SUPER_ACL))
+     DBUG_RETURN(0);
+
    ulong deleted_tables= 0;
    bool error= true;
    char        path[2 * FN_REFLEN + 16];

安定の、いつものパターン。

mysql56> DROP TABLE user1.t1;
Query OK, 0 rows affected (0.02 sec)

mysql56> DROP DATABASE user1;
ERROR 1227 (42000): Access denied; you need (at least one of) the SUPER privilege(s) for this operation

書き足したのもシンプルだし、俺にしては珍しく(!)ちゃんとmtrやったので、どこかで使う機会があったら使ってもいいなくらいの出来。というか必要があったら使うわこのパッチ。

あともう1つ、ソースコードをいじることなく出来る汚い手としては、

$ pwd
/usr/mysql/5.6.16/data/user1

$ touch drop_database_prevention

$ ll
合計 112
-rw-rw---- 1 yoku0825 yoku0825    61  2月 28 17:36 2014 db.opt
-rw-rw-r-- 1 yoku0825 yoku0825     0  2月 28 17:43 2014 drop_database_prevention
-rw-rw---- 1 yoku0825 yoku0825  8558  2月 28 17:42 2014 t1.frm
-rw-rw---- 1 yoku0825 yoku0825 98304  2月 28 17:42 2014 t1.ibd

mysql56> DROP TABLE user1.t1;
Query OK, 0 rows affected (0.02 sec)

mysql56> DROP DATABASE user1;
ERROR 1010 (HY000): Error dropping database (can't rmdir './user1/', errno: 17)

$ ll
合計 0
-rw-rw-r-- 1 yoku0825 yoku0825     0  2月 28 17:43 2014 drop_database_prevention

DROP DATABASEがMySQLのテーブルに関するファイルを全部消したあとにrmdirを呼んでいるのを逆手に取った嫌がらせ 小ネタでした。

2014/02/14

デブサミ2014のコミュニティーLTしてきました

ずさー。





日本MySQLユーザ会のひととしてLTさせてもらいました。
みんな、自分のコミュニティーにまつわるStory(発祥からの経緯とか、最近の活動とか)とか話してたのに、俺だけ自分のStory語っててやっぱりすべりました。どうしてこうなった。

すべり駆動コミュニティーでもいいじゃないみたいな話になってますが、出会い厨でコミュニティー参加しても楽しいもんですよ、という話をしたかったです本当は。

Chiba.pmにはすたじおさんに会いたくて行ったし
Groongaを囲む夕べ には斯波さんに会いたくてGroonga使ってないのに行ったし
MyNA会 2013年3月 には SH2さん に会いたくて(強引にネタを作って)行ったし
JAWS-UG(第何回か忘れた…) にはSH2さん(again)とこんまめさんに会いたくて行ったし(でもこんまめさんのセッション聞かずに帰った…orz)
MySQL Cluster Casual Talks は「企画したら誰か会えるかなー」と思っていたらみんな来てくれて嬉しかったし
OracleのMySQL Tech Tour Tokyoはやまさきさんに会いに行ってるようなもんだし

不純な動機ではじめても、結構楽しいものですよ :)
Have fun!

2014/02/13

MySQLでUDFを含んだクエリーをクエリーキャッシュに載せるライフハック

kazeburoさん のツイートを見てふとやってみたくなった。
反省はしていない。



取りあえずmroonga_snippetで試してみようと思って、mroonga 2.07のリリースノート をまるっとテストケースにする。

mysql56> CREATE TABLE snippet_test (id int NOT NULL, text text, PRIMARY KEY(id), FULLTEXT KEY(text)) Engine= mroonga;
Query OK, 0 rows affected (0.10 sec)

mysql56> INSERT INTO snippet_test (id, text) VALUES (1, 'An open-source fulltext search engine and column store.');
Query OK, 1 row affected (0.01 sec)

mysql56> INSERT INTO snippet_test (id, text) VALUES (2, 'An open-source storage engine for fast fulltext search with MySQL.');
Query OK, 1 row affected (0.01 sec)

mysql56> INSERT INTO snippet_test (id, text) VALUES (3, 'Tritonn is a patched version of MySQL that supports better fulltext search function with Senna.');
Query OK, 1 row affected (0.00 sec)

mysql56> SELECT @@query_cache_type;
+--------------------+
| @@query_cache_type |
+--------------------+
| DEMAND             |
+--------------------+
1 row in set (0.00 sec)

mysql56> SHOW GLOBAL STATUS LIKE 'Qcache_queries_in_cache';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Qcache_queries_in_cache | 0     |
+-------------------------+-------+
1 row in set (0.00 sec)

mysql56> SELECT * FROM snippet_test;
+----+-------------------------------------------------------------------------------------------------+
| id | text                                                                                            |
+----+-------------------------------------------------------------------------------------------------+
|  1 | An open-source fulltext search engine and column store.                                         |
|  2 | An open-source storage engine for fast fulltext search with MySQL.                              |
|  3 | Tritonn is a patched version of MySQL that supports better fulltext search function with Senna. |
+----+-------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)

mysql56> SHOW GLOBAL STATUS LIKE 'Qcache_queries_in_cache';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Qcache_queries_in_cache | 0     |
+-------------------------+-------+
1 row in set (0.00 sec)

mysql56> SELECT sql_cache * FROM snippet_test;
+----+-------------------------------------------------------------------------------------------------+
| id | text                                                                                            |
+----+-------------------------------------------------------------------------------------------------+
|  1 | An open-source fulltext search engine and column store.                                         |
|  2 | An open-source storage engine for fast fulltext search with MySQL.                              |
|  3 | Tritonn is a patched version of MySQL that supports better fulltext search function with Senna. |
+----+-------------------------------------------------------------------------------------------------+
3 rows in set (0.01 sec)

mysql56> SHOW GLOBAL STATUS LIKE 'Qcache_queries_in_cache';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Qcache_queries_in_cache | 1     |
+-------------------------+-------+
1 row in set (0.00 sec)

query_cache_type= 2なので、sql_cacheオプションをつけたときだけクエリーキャッシュに入る。

mysql56> SHOW GLOBAL STATUS LIKE 'Qcache_queries_in_cache';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Qcache_queries_in_cache | 1     |
+-------------------------+-------+
1 row in set (0.00 sec)

mysql56> SELECT sql_cache id, mroonga_snippet(text, 8, 2, 'utf8_general_ci', 1, 1, '...', '...
',
    ->                                        'fulltext', '', '',
    ->                                        'MySQL', '', '',
    ->                                        'search', '', '')
    -> FROM snippet_test WHERE MATCH(text) AGAINST('fulltext');
+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| id | mroonga_snippet(text, 8, 2, 'utf8_general_ci', 1, 1, '...', '...
',
                                       'fulltext', '', '',
                                       'MySQL', '', '',
                       |
+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|  1 | ...fulltext...
... search ...
                                                                                                                                                                             |
|  2 | ...fulltext...
... search ...
                                                                                                                                                                             |
|  3 | ...f MySQL ...
...fulltext...
                                                                                                                                                                             |
+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)

mysql56> SHOW GLOBAL STATUS LIKE 'Qcache_queries_in_cache';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Qcache_queries_in_cache | 1     |
+-------------------------+-------+
1 row in set (0.00 sec)

おー、ホントだ、載らない。

昔取った杵柄 で、何とかすれば何とでもなりそうな気配がしたので、キューピー3分ハッキングといきます。

まずはsql/sql_cache.cc の中のどこかで、UDFを使ったクエリーはキャッシュしない、と判定されていいるはずなのでそこを探す。たぶん、is_cacheable とかいう名前の何かがあったはず。
mysql-5.6.16/sql/sql_cache.cc
..
3707 TABLE_COUNTER_TYPE
3708 Query_cache::is_cacheable(THD *thd, size_t query_len, const char *query,
3709                           LEX *lex,
3710                           TABLE_LIST *tables_used, uint8 *tables_type)
3711 {
3712   TABLE_COUNTER_TYPE table_count;
3713   DBUG_ENTER("Query_cache::is_cacheable");
3714
3715   if (query_cache_is_cacheable_query(lex) &&
3716       (thd->variables.query_cache_type == 1 ||
3717        (thd->variables.query_cache_type == 2 && (lex->select_lex.options &
3718                                                  OPTION_TO_QUERY_CACHE))))
3719   {
..
ここかなー。
thd->variables.query_cache_typeはSET SESSION query_ache_type= ..で指定するやつだし、lex->select_lex.optionsは"sql_cache", "sql_no_cache"とかを持つところだから違うし、明らかにquery_cache_is_cacheable_queryが怪しい。
mysql-5.6.16/sql/sql_cache.h
..
562 #define query_cache_is_cacheable_query(L) \
563   (((L)->sql_command == SQLCOM_SELECT) && (L)->safe_to_cache_query && \
564    !(L)->describe)
..
lex->safe_to_cache_queryかな?
mysql-5.6.16/sql/item_create.cc
..
2733 Item*
2734 Create_udf_func::create(THD *thd, udf_func *udf, List<Item> *item_list)
2735 {
..
2826   thd->lex->safe_to_cache_query= 0;
2827   DBUG_RETURN(func);
2828 }
..
ここっぽいので取り敢えず= 1に書き換えてmakeする。


mysql56> SHOW GLOBAL STATUS LIKE 'Qcache_queries_in_cache';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Qcache_queries_in_cache | 0     |
+-------------------------+-------+
1 row in set (0.00 sec)

mysql56> SELECT sql_cache id, mroonga_snippet(text, 8, 2, 'utf8_general_ci', 1, 1, '...', '...
',
    ->                                        'fulltext', '', '',
    ->                                        'MySQL', '', '',
    ->                                        'search', '', '')
    -> FROM snippet_test WHERE MATCH(text) AGAINST('fulltext');
+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| id | mroonga_snippet(text, 8, 2, 'utf8_general_ci', 1, 1, '...', '...
',
                                       'fulltext', '', '',
                                       'MySQL', '', '',
                       |
+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|  1 | ...fulltext...
... search ...
                                                                                                                                                                             |
|  2 | ...fulltext...
... search ...
                                                                                                                                                                             |
|  3 | ...f MySQL ...
...fulltext...
                                                                                                                                                                             |
+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)

mysql56> SHOW GLOBAL STATUS LIKE 'Qcache_queries_in_cache';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Qcache_queries_in_cache | 1     |
+-------------------------+-------+
1 row in set (0.00 sec)

載った。満足。

2014/01/24

groonga-index-dumpで遊んでみる

これがやりたくてtweets.csvをMroongaに食わせていた

$ time /usr/ruby/2.0.0/bin/groonga-index-dump /usr/mysql/5.6.15/data/mroonga.mrn

real    0m33.430s
user    0m29.649s
sys     0m2.924s

$ ll
合計 16
drwxr-xr-x 2 root     root     4096  7月  3 19:14 2013 hsperfdata_root
drwxr-xr-x 2 yoku0825 yoku0825 4096 11月  6 11:49 2013 hsperfdata_ttanaka
drwxrwxr-x 3 yoku0825 yoku0825 4096  1月 24 12:04 2014 index-dump
drwx------ 2 yoku0825 tmux    4096  1月 10 15:19 2014 tmux-3012

--output-directoryで指定しない場合はカレントディレクトリの下にindex-dumpディレクトリを掘る。

$ cd index-dump/

$ ll
合計 2012
drwxrwxr-x 2 yoku0825 yoku0825 2056192  1月 24 12:04 2014 tweets-text.index

$ cd tweets-text.index/

$ ll | wc
  47997  479962 3723665

index-dumpの下に<テーブル名>-<インデックス名>.indexディレクトリが作られるぽい。
ls -l叩くと泣きたくなるくらいにファイルの数が多い。

$ ll | head
合計 201992
-rw-rw-r-- 1 yoku0825 yoku0825    222  1月 24 12:04 2014 %21%21%21%21%21%21%21%21%21%21%21%21%21%21.dump
-rw-rw-r-- 1 yoku0825 yoku0825    218  1月 24 12:04 2014 %21%21%21%21%21%21%21%E3%80%8D.dump
-rw-rw-r-- 1 yoku0825 yoku0825    256  1月 24 12:04 2014 %21%21%21%21%21%21%21.dump
-rw-rw-r-- 1 yoku0825 yoku0825    337  1月 24 12:04 2014 %21%21%21%21%21%21.dump
-rw-rw-r-- 1 yoku0825 yoku0825    216  1月 24 12:04 2014 %21%21%21%21%21%E3%80%8D.dump
-rw-rw-r-- 1 yoku0825 yoku0825    417  1月 24 12:04 2014 %21%21%21%21%21.dump
-rw-rw-r-- 1 yoku0825 yoku0825    218  1月 24 12:04 2014 %21%21%21%21%E3%80%8D%E3%80%8C.dump
-rw-rw-r-- 1 yoku0825 yoku0825    417  1月 24 12:04 2014 %21%21%21%21.dump
-rw-rw-r-- 1 yoku0825 yoku0825    212  1月 24 12:04 2014 %21%21%21%28.dump

なんかざっと見ただけで、1つのトークンに対して1つの<トークン>.dumpファイルが出来上がっている予感。

$ cat %21%21%21%21%21%21%21%21%21%21%21%21%21%21.dump
index: tweets-text.index        term: <!!!!!!!!!!!!!!>  domain: tweets-text     range: tweets   have_section: false     have_weight: false    have_position: true
  weight        position        term_frequency  record
  0     74      1       tweets[398641294714499072].text

$ cat %E3%81%84%E3%81%BC.dump
index: tweets-text.index        term: <いぼ>    domain: tweets-text     range: tweets   have_section: false     have_weight: false    have_position: true
  weight        position        term_frequency  record
  0     40      1       tweets[198244626002481152].text

やりたいことは myisam_ftdump 的なことなので、この.dumpファイルをテキトーにパースしてやらないといけないっぽい。

取りあえず、投げやりなPerlワンライナーで。

$ cat `ls | head -1000` | perl -nle 'if (/\sterm:\s<(.+)>\s/) {printf("%d\t%s\n", $count, $term); $term= $1; $count= 0;} elsif (/\s([0-9]+)\s+tweets\[[0-9]+\]\.text$/) {$count+= $1;}' | sort -nr | head
12305   、
11013   。
9295    @
7042    /
4599    :
3583    。。
3479    #
2577    ://
2561    (
2243    「

cat *でやろうとしたらなんか怒られた(´・ω・`)

$ cat * | perl -nle 'if (/\sterm:\s<(.+)>\s/) {printf("%d\t%s\n", $count, $term); $
term= $1; $count= 0;} elsif (/\s([0-9]+)\s+tweets\[[0-9]+\]\.text$/) {$count+= $1;}' | sort -nr | head
cat: オプションが違います -- '%'
詳しくは `cat --help' を実行して下さい.

これで索引の中の無駄なトークンをあぶりだしてメンテしてやるのがゴールのつもり。


【2014/01/24 13:46】
catがダメなのは"-"で始まるファイルっぽい。

$ for f in *; do cat $f > /dev/null || (echo $f; break); done
cat: オプションが違います -- '%'
詳しくは `cat --help' を実行して下さい.
-%23.dump
cat: オプションが違います -- '%'
詳しくは `cat --help' を実行して下さい.
-%25%27.dump
cat: オプションが違います -- '%'
詳しくは `cat --help' を実行して下さい.
-%26.dump
..

cat ./*にしたら食えた。

$ cat ./* | perl -nle 'if (/\sterm:\s<(.+)>\s/) {printf("%d\t%s\n", $count, $term); $term= $1; $count= 0;} elsif (/\s([0-9]+)\s+tweets\[[0-9]+\]\.text$/) {$count+= $1;}' | sort -nr | head
12305   、
11013   。
10217   _
9295    @
7042    /
6456    って
4599    :
4514    mysql
4147    ない
3801    5

うん、mysql多いなオイ。

全文検索のテスト用にtweets.csvを食わせるSQL

毎回手打ちするのが面倒なのでメモ。

mysql> CREATE TABLE tweets (tweet_id bigint unsigned primary key, timestamp timestamp NOT NULL, text text NOT NULL, FULLTEXT KEY(text)) Engine= mroonga;
Query OK, 0 rows affected (0.05 sec)

mysql> LOAD DATA INFILE '/tmp/tweets.csv' INTO TABLE tweets FIELDS TERMINATED BY ',' ENCLOSED BY '"' IGNORE 1 ROWS (tweet_id, @dummy, @dummy, @timestamp, @dummy, text, @dummy) SET timestamp= DATE_ADD(@timestamp, INTERVAL 9 HOUR);
Query OK, 14410 rows affected, 28821 warnings (0.35 sec)
Records: 14410  Deleted: 0  Skipped: 0  Warnings: 28821

CSVのtext以降の部分をTRUNCATEしたってワーニングがごちゃっと出る。
timestampは"2013-12-17 03:06:59 +0000"(=UTC)の形で入っていて、SET TIME_ZONEで頑張ってみたけど上手く行かなかったのでSETで無理矢理9時間足してみた。

2014/01/23

Galera Arbitratorというもの(garbd)

Galera Cluster(俺が実際に色々やってるのはPercona XtraDB Cluster)はスプリットブレイン対策としてQuorum方式を採用している。クラスターを構成するノードの *過半数* とコミュニケーションが取れていない場合、自身がネットワークから切り離されているとみなして *全ての操作を* 拒否するようになる。Galera Clusterはそもそも全てのノードのデータが「仮想完全同期」されているという前提でマルチマスターのトポロジーを提供しているため、コミュニケーションパスが途切れて同期できていないかもしれない状態は、マルチマスターを提供するための下地が崩れるからこうなっている。

mysql> use test
ERROR 1047 (08S01): Unknown command

mysql> SELECT * FROM d1.t1;
ERROR 1047 (08S01): Unknown command

出力はこんな感じで、何をしてもError: 1047のUnknown commandが返ってくるようになる。流石にmysqlコマンドラインクライアントだけで完結するコマンド(helpとかcharsetとかteeとか)は実行できるけど、サーバー側から情報を引っ張って何かするコマンド(statusとuseくらい?)は実行できなくなる。

で、コミュニケーションパスが生きている必要があるノードはクラスターを構成するノードの *過半数* なので、2台構成にしてしまうと1台お亡くなりになった時に 残った1台> 2* 50% が成り立たないため、生き残った1台はこの状態に突入する。これを避けるために、Galera Clusterは最低3台での構成を推奨 している。

SET GLOBAL wsrep_provider_options= 'pc.ignore_sb= ON'; という手もあるけれど(名前の通り、スプリットブレイン状態になっててもコマンドを拒否しなくなる)、本当にネットワークがおかしくなった時に何が起こるかわからない(2つのmysqldそれぞれに矛盾する更新がかかったら、とか、更新が伝達されずにレコードを読んじゃったら、とか)ので、あまりやりたくない。かといって物理サーバー3台…うーん、微妙。

そんなわたしに贈る、Galera Arbitrator。プロセス名はgarbd。

$ ll /usr/local/mysql5534_pxc/bin/garbd
-rwxr-xr-x 1 root root 18015250 12月  2 21:20 /usr/local/mysql5534_pxc/bin/garbd

Percona XtraDB Cluster 5.5.34の.tar.gzのバイナリーを解凍した版にはbinの下に入ってた。ソースからコンパイルした人にはいない。どこから手に入れるのかもよく判らないけど、ソースからコンパイルした人、libの下にlibgalera_smm.soもなくて別途codershipから取ってきて入れないといけないから、そこから入れるのかな。PXCなら.tar.gz版がオールインワンで扱いやすいと思う余談。MariaDB Galera Clusterは(入ってるのか入ってないのか)知らない。

$ /usr/local/mysql5534_pxc/bin/garbd --help

Usage: /usr/local/mysql5534_pxc/bin/garbd [options] [group address]

Configuration:
  -d [ --daemon ]       Become daemon
  -a [ --address ] arg  Group address
  -g [ --group ] arg    Group name
  --sst arg             SST request string
  --donor arg           SST donor name
  -o [ --options ] arg  GCS/GCOMM option list
  -l [ --log ] arg      Log file
  -c [ --cfg ] arg      Configuration file


Other options:
  -v [ --version ]      Print version
  -h [ --help ]         Show help message

FATAL: Exit

ざっくりと、必要最低限っぽいオプションは-a(PXCのwsrep_cluster_addressに相当)と-g(PXCのwsrep_cluster_nameに相当)かしら。

$ /usr/local/mysql5534_pxc/bin/garbd -a "gcomm://xxx.xxx.xxx.xxx:13333" -g "cluster_name" -o "gmcast.listen_addr= tcp://0.0.0.0:13334"
2014-01-23 12:13:20.944  INFO: Read config:
        daemon:  0
        address: gcomm://xxx.xxx.xxx.xxx:13333
        group:   cluster_name
        sst:     trivial
        donor:
        options: gmcast.listen_addr= tcp://0.0.0.0:13334; gcs.fc_limit=9999999; gcs.fc_factor=1.0; gcs.fc_master_slave=yes
        cfg:
        log:

2014-01-23 12:13:20.952  INFO: protonet asio version 0
2014-01-23 12:13:20.953  INFO: backend: asio
2014-01-23 12:13:20.967  INFO: GMCast version 0
2014-01-23 12:13:20.972  INFO: (50019916-83dc-11e3-a310-bfb42459a728, 'tcp://0.0.0.0:13334') listening at tcp://0.0.0.0:13334
2014-01-23 12:13:20.972  INFO: (50019916-83dc-11e3-a310-bfb42459a728, 'tcp://0.0.0.0:13334') multicast: , ttl: 1
2014-01-23 12:13:20.992  INFO: EVS version 0
2014-01-23 12:13:21.000  INFO: PC version 0
2014-01-23 12:13:21.000  INFO: gcomm: connecting to group 'cluster_name', peer 'xxx.xxx.xxx.xxx:13333'
2014-01-23 12:13:21.007  INFO: (50019916-83dc-11e3-a310-bfb42459a728, 'tcp://0.0.0.0:13334') turning message relay requesting on, nonlive peers: tcp://xxx.xxx.xxx.xxx:13332
2014-01-23 12:13:21.489  INFO: (50019916-83dc-11e3-a310-bfb42459a728, 'tcp://0.0.0.0:13334') turning message relay requesting off
2014-01-23 12:13:21.505  INFO: declaring 1663d468-79c2-11e3-a5e5-2f016f2db075 stable
2014-01-23 12:13:21.505  INFO: declaring bc9b8196-83d6-11e3-b4d0-1a4766eec11e stable
2014-01-23 12:13:21.506  INFO: Node 1663d468-79c2-11e3-a5e5-2f016f2db075 state prim
2014-01-23 12:13:21.506  INFO: view(view_id(PRIM,1663d468-79c2-11e3-a5e5-2f016f2db075,43) memb {
        1663d468-79c2-11e3-a5e5-2f016f2db075,
        50019916-83dc-11e3-a310-bfb42459a728,
        bc9b8196-83d6-11e3-b4d0-1a4766eec11e,
} joined {
} left {
} partitioned {
})
2014-01-23 12:13:22.005  INFO: gcomm: connected
2014-01-23 12:13:22.005  INFO: Changing maximum packet size to 64500, resulting msg size: 32636
2014-01-23 12:13:22.005  INFO: Shifting CLOSED -> OPEN (TO: 0)
2014-01-23 12:13:22.005  INFO: Opened channel 'cluster_name'
2014-01-23 12:13:22.006  INFO: New COMPONENT: primary = yes, bootstrap = no, my_idx = 1, memb_num = 3
2014-01-23 12:13:22.006  INFO: STATE EXCHANGE: Waiting for state UUID.
2014-01-23 12:13:22.006  INFO: STATE EXCHANGE: sent state msg: 5055781a-83dc-11e3-b9e1-e2687a7bb5a6
2014-01-23 12:13:22.006  INFO: STATE EXCHANGE: got state msg: 5055781a-83dc-11e3-b9e1-e2687a7bb5a6 from 0 (xxxx_3333)
2014-01-23 12:13:22.006  INFO: STATE EXCHANGE: got state msg: 5055781a-83dc-11e3-b9e1-e2687a7bb5a6 from 2 (xxxx_3332)
2014-01-23 12:13:22.007  INFO: STATE EXCHANGE: got state msg: 5055781a-83dc-11e3-b9e1-e2687a7bb5a6 from 1 (garb)
2014-01-23 12:13:22.007  INFO: Quorum results:
        version    = 2,
        component  = PRIMARY,
        conf_id    = 33,
        members    = 2/3 (joined/total),
        act_id     = 29266,
        last_appl. = -1,
        protocols  = 0/4/2 (gcs/repl/appl),
        group UUID = 64863b36-62f7-11e3-bef2-a746941be182
2014-01-23 12:13:22.007  INFO: Flow-control interval: [9999999, 9999999]
2014-01-23 12:13:22.007  INFO: Shifting OPEN -> PRIMARY (TO: 29266)
2014-01-23 12:13:22.007  INFO: Sending state transfer request: 'trivial', size: 7
2014-01-23 12:13:22.007  INFO: Node 1 (garb) requested state transfer from '*any*'. Selected 0 (xxxx_3333)(SYNCED) as donor.
2014-01-23 12:13:22.007  INFO: Shifting PRIMARY -> JOINER (TO: 29266)
2014-01-23 12:13:22.008  INFO: 0 (xxxx_3333): State transfer to 1 (garb) complete.
2014-01-23 12:13:22.008  INFO: 1 (garb): State transfer from 0 (xxxx_3333) complete.
2014-01-23 12:13:22.008  INFO: Shifting JOINER -> JOINED (TO: 29266)
2014-01-23 12:13:22.008  INFO: Member 0 (xxxx_3333) synced with group.
2014-01-23 12:13:22.008  INFO: Member 1 (garb) synced with group.
2014-01-23 12:13:22.008  INFO: Shifting JOINED -> SYNCED (TO: 29266)

"gmcast.listen_addr= tcp://0.0.0.0:13334"は、このノード既にPXCが3インスタンスいるのでポートがカブらないように調整しているだけで、フツーにPXCのmysqldを起動させた時と同じように、コミュニケーションパスを使って自分のステータスをSYNCEDまで持って行ってる。SST(=フル同期)はしない。

他のノードからSHOW GLOBAL STATUS LIKE 'wsrep_cluster_size'で見ると、確かに増えている。これでmysqldが3つなくても、1台落ちただけなら過半数を満たせる数が確保できるという算段。APサーバーの先頭か何かにgarbdだけ入れておけばね。


……MHA-Managerみたい。。

2014/01/22

Percona XtraDB Cluster設定中に気付いたwsrep_certify_nonPK

Primary Keyが無いテーブル + Galera Clusterで悲惨な目に なんていうことが昔ありました。

PXC(wsrepの要求なので、Gelara Cluster for MySQL, MariaDB Galera Cluster も同じ)はbinlog_format= ROW が必須パラメーターになっているのでPrimary Keyが無いと悲惨な目に遭うんですが、これを強制的に禁止するオプション(wsrep_certify_nonPK)を発見。

mysql> SELECT @@wsrep_certify_nonPK;
+-----------------------+
| @@wsrep_certify_nonPK |
+-----------------------+
|                     1 |
+-----------------------+
1 row in set (0.00 sec)

デフォルトはON。

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

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

mysql> UPDATE d1.t1 SET val= 'eins' WHERE num= 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM d1.t1;
+------+------+
| num  | val  |
+------+------+
|    1 | eins |
+------+------+
1 row in set (0.00 sec)

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

Primary Keyが無くてもINSERTもUPDATEもDELETEもできる。フツーの動作。

mysql> SET GLOBAL wsrep_certify_nonPK= OFF;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@wsrep_certify_nonPK;
+-----------------------+
| @@wsrep_certify_nonPK |
+-----------------------+
|                     0 |
+-----------------------+
1 row in set (0.00 sec)

これをOFFる。と、想像通り、

mysql> INSERT INTO d1.t1 VALUES (1, 'one');
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

容赦なく蹴られるように。
Error 1213のDeadlock found.. が表示されるのは、全ノード間でデータの同期を保証する為の2相コミットっぽい仕組み(実際には2相コミットじゃない)で失敗したから(これをwsrepではCertificationと呼んでいる)で、Local Certification Failure(XA Prepare的なのに失敗)はError 1213 Deadlock found..で返される仕様になっているから。

なお飽くまで"Local" Certification Failureなので、他の(wsrep_certify_nonPK= ONな)ノードでINSERTするとINSERTできる。よって、設定する時は全てに設定しておくこと。


ところでこの動作、MariaDBには標準で無かったっけ? とか思ったけど違った。RBRの時にPkeyが無いテーブルへの更新は拒否する、じゃなくて、RBRの時でPkeyが無いテーブルへの更新でも最適化してスレーブに適用してくれる、という風情らしい。

Row-based replication with no primary key

Perconaがオリジナルだっていうのも知らなかった。