GA

2014/12/02

MySQL Fabricつらい(FabricサーバーがMySQLプロトコルのポートでしゃべってる内容を見てみる)

tcpdump&Wiresharkでさくっと。
お手製MySQL Fabric対応mysqlコマンドラインクライアント からMySQL FabricのMySQLプロトコルをしゃべる口に接続して、ちょこちょことステートメントを叩いてみる。


$ client/mysql -P 32275 -uadmin -pxxxx --protocol=tcp -h 127.0.0.1 --fabric-group=my_second_fabric --fabric-real-user=msandbox --fabric-real-password=msandbox
mysql> \F
Current FABRIC_OPT_DEFAULT_MODE is ro

mysql> show tables;
ERROR 1046 (3D000): No database selected

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
| tpcc               |
+--------------------+
5 rows in set (0.07 sec)

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

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

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

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

mysql> SELECT @@port;
+--------+
| @@port |
+--------+
|  14723 |
+--------+
1 row in set (0.02 sec)

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

mysql> \F rw
FABRIC_OPT_DEFAULT_MODE sets to rw

mysql> create database if not exists d1;
Query OK, 1 row affected (0.01 sec)

こんなのをキャプチャしてみた結果が、コレ(クライアントとFabricの32275ポートをMySQLプロトコルとしてデコードさせてフィルターした)



ものの見事にログイン(グリーティング, 認証, 認証応答)とストアドプロシージャ(dump.fubric_nodes, group.lookup_groups, dump.servers) しか叩いてない。その後はRequist QUITまで沈黙。

ということは一度結果さえキャッシュしてしまえば、


$ mysqlfabric manage stop
Password for admin:
Fabric UUID:  5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

Success (empty result set)


$ mysqlfabric group lookup_servers my_second_fabric
Password for admin:
[Errno 111] Connection refused

mysql> \F
Current FABRIC_OPT_DEFAULT_MODE is ro

mysql> SELECT @@port;
+--------+
| @@port |
+--------+
|  14724 |
+--------+
1 row in set (0.03 sec)

mysql> \F rw
FABRIC_OPT_DEFAULT_MODE sets to rw

mysql> SELECT @@port;
+--------+
| @@port |
+--------+
|  14723 |
+--------+
1 row in set (0.01 sec)

mysql> create table d1.t1 (num int, val varchar(32));
Query OK, 0 rows affected (0.19 sec)

$ client/mysql -P 32275 -uadmin -pxxxx --protocol=tcp -h 127.0.0.1 --fabric-group=my_second_fabric --fabric-real-user=msandbox --fabric-real-password=msandbox d1
mysql: [Warning] Using a password on the command line interface can be insecure.
Using Fabric for MYSQL connection
ERROR 2064 (HY000): Fabric error: Error connecting to Fabric host: Can't connect to MySQL server on '127.0.0.1' (111) (2003)

この通りクエリーは通る(もちろん、新しくコマンドラインクライアントを立ち上げようとしても32275が空いてないのでそれ以上の接続はできないけど)
TTLとかも関係してくる話なんだろうなぁ。。

余談だけど、このお手製クライアントからuse(つまり、mysql_select_db())を呼んでもことごとくエラーになることに気が付いた。MySQL Fabricの制約なのかConnector/Cがまだ対応してないのか、俺のパッチが何か考慮漏れてるのかはまだ不明。。


mysql> use d1
ERROR:

mysql>

取り敢えず、今日はこれまで。

2014/12/01

pt-query-digest --type tcpdumpに3306以外のポートのダンプを食わせる方法

ハマったのでメモ。

サンドボックスな環境でMySQLのポートを64055にしていたら、pt-query-digest --type tcpdumpでそのまま食ってくれなかった。

結論、--portとかじゃなく、--watch-serverで指定するんだった。


$ sudo tcpdump -i any -p /tmp/test.cap -c 100
$ tcpdump -r /tmp/test.cap -n -x -q -tttt | pt-query-digest --type tcpdump
reading from file /tmp/test.cap, link-type LINUX_SLL (Linux cooked)

# No events processed.

あっれぇ。。
取り敢えずまずはPTDEBUG=1にして様子を見てみる。


$ tcpdump -r /tmp/test.cap -n -x -q -tttt | PTDEBUG=1 pt-query-digest --type tcpdump
reading from file /tmp/test.cap, link-type LINUX_SLL (Linux cooked)
# /usr/bin/perl 5.010001
# Linux dev-personal-04 2.6.32-279.19.1.el6.x86_64 #1 SMP Wed Dec 19 07:05:20 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux
# Arguments: _[--type]_ _[tcpdump]_
# OptionParser:1522 29673 Option rule: This tool accepts additional command-line arguments. Refer to the SYNOPSIS and usage information for details.
..
# Pipeline:11539 29673 Pipeline restarting early after MySQLProtocolParser
# Pipeline:11527 29673 Pipeline process prep
# Pipeline:11527 29673 Pipeline process input
# Pipeline:11527 29673 Pipeline process TcpdumpParser
# TcpdumpParser:3658 29673 packet: $VAR1 = {
#   ack => 3530724024,
#   complete => 1,
#   data => '',
#   data_len => 0,
#   dgram_len => 40,
#   dst_host => '127.0.0.1',
#   dst_port => '42872',
#   fin => 1,
#   ip_hlen => 5,
#   rst => 0,
#   seq => 2831704995,
#   src_host => '127.0.0.1',
#   src_port => '64055',
#   syn => 0,
#   tcp_hlen => 5,
#   ts => '2014-12-01 16:40:11.599600'
# };
#
# Pipeline:11527 29673 Pipeline process MySQLProtocolParser
# MySQLProtocolParser:3916 29673 Packet is not to or from a MySQL server
# pt_query_digest:13442 29673 No more events, input EOF
..

どばー。
取り敢えず、MySQLProtocolParserの中で"Packet is not to or from a MySQL server"って言われてる。3916行目あたりにダイブ。


3894 my $src_host = "$packet->{src_host}:$packet->{src_port}";
3895 my $dst_host = "$packet->{dst_host}:$packet->{dst_port}";
..
3905 my $packet_from;
3906 my $client;
3907 if ( $src_host =~ m/:$self->{port}$/ ) {
3908 $packet_from = 'server';
3909 $client = $dst_host;
3910 }
3911 elsif ( $dst_host =~ m/:$self->{port}$/ ) {
3912 $packet_from = 'client';
3913 $client = $src_host;
3914 }
3915 else {
3916 PTDEBUG && _d('Packet is not to or from a MySQL server');
3917 return $self->{null_event};
3918 }

パケットの127.0.0.1:64055が$self->{port}を含んでいないのがいけないらしい。$self->{port}は何になってるかというと


$ tcpdump -r /tmp/test.cap -n -x -q -tttt | perl -d /usr/bin/pt-query-digest --type tcpdump
reading from file /tmp/test.cap, link-type LINUX_SLL (Linux cooked)

Loading DB routines from perl5db.pl version 1.32
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(/usr/bin/pt-query-digest:65):
65:     package Percona::Toolkit;
  DB<1> c 3916
MySQLProtocolParser::parse_event(/usr/bin/pt-query-digest:3916):
3916:         PTDEBUG && _d('Packet is not to or from a MySQL server');
  DB<2> p Dumper $self
$VAR1 = bless( {
  fake_thread_id => '4294967296',
  null_event => undef,
..
  }, 'OptionParser' ),
  port => '3306',
  server => undef,
  sessions => {},
  version => '41'
}, 'MySQLProtocolParser' );

3306。オプションで--port 64055とかDSN形式でP=64055とか渡してやってもずっと3306。むぅ。デフォルトで3306になるのはMySQLProtocolParser::newでそうなってるからで、


3870 sub new {
3871 my ( $class, %args ) = @_;
3872
3873 my $self = {
3874 server => $args{server},
3875 port => $args{port} || '3306',
3876 version => '41', # MySQL proto version; not used yet
3877 sessions => {},
3878 o => $args{o},
3879 fake_thread_id => 2**32, # see _make_event()
3880 null_event => $args{null_event},
3881 };
3882 PTDEBUG && $self->{server} && _d('Watching only server', $self->{server});
3883 return bless $self, $class;
3884 }

これがどこから呼ばれてるかというとmainの中のここだった。


13393 my ($server, $port);
13394 if ( my $watch_server = $o->get('watch-server') ) {
13395 # This should match all combinations of HOST and PORT except
13396 # "host-name.port" because "host.mysql" could be either
13397 # host "host" and port "mysql" or just host "host.mysql"
13398 # (e.g. if someone added "127.1 host.mysql" to etc/hosts).
13399 # So host-name* requires a colon between it and a port.
13400 ($server, $port) = $watch_server
13401 =~ m/^((?:\d+\.\d+\.\d+\.\d+|[\w\.\-]+\w))(?:[\:\.](\S+))?/;
13402 PTDEBUG && _d('Watch server', $server, 'port', $port);
13403 }
13404
13405 foreach my $module ( @$type ) {
13406 my $parser;
13407 eval {
13408 $parser = $module->new(
13409 server => $server,
13410 port => $port,
13411 o => $o,
13412 );
13413 };

--portとかじゃなくて、--watch-serverから取ってたのか。。


$ tcpdump -r /tmp/test.cap -n -x -q -tttt | /usr/bin/pt-query-digest --watch-server 127.0.0.1.64055 --type tcpdump
reading from file /tmp/test.cap, link-type LINUX_SLL (Linux cooked)

# 410ms user time, 30ms system time, 25.84M rss, 212.79M vsz
# Current date: Mon Dec  1 17:44:12 2014
# Hostname: xxxx
# Files: STDIN
# Overall: 8 total, 6 unique, 0.35 QPS, 0.02x concurrency ________________
# Time range: 2014-12-01 16:39:48.680810 to 16:40:11.597656
# Attribute          total     min     max     avg     95%  stddev  median
# ============     ======= ======= ======= ======= ======= ======= =======
# Exec time          399ms       0   150ms    50ms   148ms    48ms    65ms
# Rows affecte           3       0       1    0.38    0.99    0.48       0
# Query size           220      16      36   27.50   34.95    6.66   30.22
# Warning coun           0       0       0       0       0       0       0
..

無事食えた。確かにマニュアルにもフツーに書いてあった。。
http://www.percona.com/doc/percona-toolkit/2.2/pt-query-digest.html#cmdoption-pt-query-digest--watch-server

2014/11/27

MySQL 5.7.5-labsのQuery Rewrite Plugin

Query Rewrite pluginの一番ベーシックな使い方はこちら。
The Query Rewrite Plugins | MySQL Server Blog

MySQL 5.7.5-labsに$basedir/lib/plugin/install_rewriter_plugin.sqlを食わせてやれば取り敢えず有効になる。MySQL 5.7.5-m15にはこのプラグインは存在しないので注意。

$ mysql57 < install_rewriter_plugin.sql

mysql57> SELECT @@version;
+------------------------+
| @@version              |
+------------------------+
| 5.7.5-labs-preview-log |
+------------------------+
1 row in set (0.00 sec)

mysql57> show plugins;
+----------------------------+--------+--------------------------+-------------+---------+
| Name                       | Status | Type                     | Library     | License |
+----------------------------+--------+--------------------------+-------------+---------+
..
| Rewriter                   | ACTIVE | QUERY REWRITE POST PARSE | rewriter.so | GPL     |
+----------------------------+--------+--------------------------+-------------+---------+
43 rows in set (0.00 sec)

mysql57> show databases like 'query_rewrite';
+--------------------------+
| Database (query_rewrite) |
+--------------------------+
| query_rewrite            |
+--------------------------+
1 row in set (0.00 sec)

mysql57> show tables;
+-------------------------+
| Tables_in_query_rewrite |
+-------------------------+
| rewrite_rules           |
+-------------------------+
1 row in set (0.00 sec)


このquery_rewrite.rewrite_rulesにクエリー書き換えのルールを記述していく感じ。
記述したら、query_rewrite.flush_rewrite_rulesを呼んで更新してやる(FLUSH PRIVILEGESみたいな感じだ)

mysql57> INSERT INTO query_rewrite.rewrite_rules (pattern, replacement) VALUES ('INSERT INTO t1 VALUES (?)', 'INSERT INTO t2 VALUES (? + 1)');
Query OK, 1 row affected (0.01 sec)

mysql57> SELECT * FROM query_rewrite.rewrite_rules;
+---------------------------+------------------+-------------------------------+---------+---------+
| pattern                   | pattern_database | replacement                   | enabled | message |
+---------------------------+------------------+-------------------------------+---------+---------+
| INSERT INTO t1 VALUES (?) | NULL             | INSERT INTO t2 VALUES (? + 1) | Y       | NULL    |
+---------------------------+------------------+-------------------------------+---------+---------+
1 row in set (0.00 sec)

mysql57> CALL query_rewrite.flush_rewrite_rules();
Query OK, 1 row affected (0.01 sec)

mysql57> SELECT * FROM query_rewrite.rewrite_rules;
+---------------------------+------------------+-------------------------------+---------+--------------------------------------------------+
| pattern                   | pattern_database | replacement                   | enabled | message                                          |
+---------------------------+------------------+-------------------------------+---------+--------------------------------------------------+
| INSERT INTO t1 VALUES (?) | NULL             | INSERT INTO t2 VALUES (? + 1) | N       | Parse error in pattern: >>No database selected<< |
+---------------------------+------------------+-------------------------------+---------+--------------------------------------------------+
1 row in set (0.00 sec)


あれ、なんか怒られた。 "No database selected" なので、pattern_databaseがNULLなのがいけないのか…ってテーブル名を指定しない時はNULLでも大丈夫ぽい流れが冒頭のブログには書いてあった(´・ω・`)

mysql57> UPDATE query_rewrite.rewrite_rules SET pattern_database= 'd1', enabled= 'Y';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql57> SELECT * FROM query_rewrite.rewrite_rules;
+---------------------------+------------------+-------------------------------+---------+--------------------------------------------------+
| pattern                   | pattern_database | replacement                   | enabled | message                                          |
+---------------------------+------------------+-------------------------------+---------+--------------------------------------------------+
| INSERT INTO t1 VALUES (?) | d1               | INSERT INTO t2 VALUES (? + 1) | Y       | Parse error in pattern: >>No database selected<< |
+---------------------------+------------------+-------------------------------+---------+--------------------------------------------------+
1 row in set (0.00 sec)


enabledは自分で'Y'に戻さないと、次にflush_rewrite_rulesを呼んでもそもそもFLUSHしようとしてくれなくなる(はまった)

mysql57> CALL query_rewrite.flush_rewrite_rules();
Query OK, 1 row affected (0.00 sec)

mysql57> SELECT * FROM query_rewrite.rewrite_rules;
+---------------------------+------------------+-------------------------------+---------+---------------------------------+
| pattern                   | pattern_database | replacement                   | enabled | message                         |
+---------------------------+------------------+-------------------------------+---------+---------------------------------+
| INSERT INTO t1 VALUES (?) | d1               | INSERT INTO t2 VALUES (? + 1) | N       | Pattern not a select statement. |
+---------------------------+------------------+-------------------------------+---------+---------------------------------+
1 row in set (0.00 sec)


(´・ω・`) SELECTステートメントしか食ってくれないようなことを言っている。かなしい。

mysql57> DELETE FROM query_rewrite.rewrite_rules;
Query OK, 1 row affected (0.00 sec)

mysql57> INSERT INTO query_rewrite.rewrite_rules (pattern, pattern_database, replacement) VALUES ('SELECT * FROM t1', 'd1', 'SELECT * FROM t1 LIMIT 1');
Query OK, 1 row affected (0.00 sec)

mysql57> SELECT * FROM query_rewrite.rewrite_rules;
+------------------+------------------+--------------------------+---------+---------+
| pattern          | pattern_database | replacement              | enabled | message |
+------------------+------------------+--------------------------+---------+---------+
| SELECT * FROM t1 | d1               | SELECT * FROM t1 LIMIT 1 | Y       | NULL    |
+------------------+------------------+--------------------------+---------+---------+
1 row in set (0.00 sec)

mysql57> CALL query_rewrite.flush_rewrite_rules();
Query OK, 1 row affected (0.01 sec)

mysql57> SELECT * FROM query_rewrite.rewrite_rules;
+------------------+------------------+--------------------------+---------+---------+
| pattern          | pattern_database | replacement              | enabled | message |
+------------------+------------------+--------------------------+---------+---------+
| SELECT * FROM t1 | d1               | SELECT * FROM t1 LIMIT 1 | Y       | NULL    |
+------------------+------------------+--------------------------+---------+---------+
1 row in set (0.00 sec)


という訳で、d1.t1のテーブルを単純SELECTした時に"LIMIT 1"を押し込むようなやつを作って(まともに読み込まれたときは、messageはNULLのままで見た目変化がないっぽい)

mysql57> SELECT * FROM t1;
+------+
| num  |
+------+
|    1 |
+------+
1 row in set, 1 warning (0.00 sec)

mysql57> SHOW WARNINGS;
+-------+------+---------------------------------------------------------------------------------------+
| Level | Code | Message                                                                               |
+-------+------+---------------------------------------------------------------------------------------+
| Note  | 1105 | Query 'SELECT * FROM t1' rewritten to 'SELECT * FROM t1 LIMIT 1' by plugin: Rewriter. |
+-------+------+---------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

おお、書き換えてくれたぽい。 クエリーキャッシュぽく、クエリーの文字列で判定しているだけぽいけど


mysql57> select * FROM t1;
+------+
| num  |
+------+
|    1 |
+------+
1 row in set, 1 warning (0.00 sec)

mysql57> SELECT /* dummy */  * FROM t1;
+------+
| num  |
+------+
|    1 |
+------+
1 row in set, 1 warning (0.00 sec)

mysql57> SELECT *
    -> FROM t1;
+------+
| num  |
+------+
|    1 |
+------+
1 row in set, 1 warning (0.00 sec)

mysql57> SELECT sql_cache * FROM t1;
+------+
| num  |
+------+
|    1 |
|    2 |
|    3 |
|    4 |
|    5 |
+------+
5 rows in set (0.00 sec)


大文字小文字の吸収と、コメント, スペースや改行文字くらいは無視してくれるっぽい。SQL構文として有効なsql_cacheとか入るとさすがにダメ。簡単なプレースホルダー(?文字)は使えるけど、テーブル名の書き換えとかはできない。

SELECT以外の何かをゴニョゴニョしたかったり、テーブル名の書き換えをやりたかったら、Rewrite Pluginを自作することになるっぽい。
Write Yourself a Query Rewrite Plugin: Part 1 | MySQL Server Blog

取り敢えずrewrite_example(クエリーを全て小文字に書き換える)というのが付属しているので、

mysql57> INSTALL PLUGIN rewrite_example SONAME 'rewrite_example.so';
Query OK, 0 rows affected (0.01 sec)

mysql57> use d1
Database changed, 1 warning

mysql57> INSERT INTO t1 VALUES (100);
Query OK, 1 row affected, 1 warning (0.01 sec)

mysql57> SHOW WARNINGS;
+-------+------+------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                    |
+-------+------+------------------------------------------------------------------------------------------------------------+
| Note  | 1105 | Query 'INSERT INTO t1 VALUES (100)' rewritten to 'insert into t1 values (100)' by plugin: rewrite_example. |
| Note  | 1105 | Query 'SHOW WARNINGS' rewritten to 'show warnings' by plugin: rewrite_example.                             |
+-------+------+------------------------------------------------------------------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

うむ、INSERTもSHOWも軒並み全部イケる。こっちはC++で文字列を書き換えてるだけだから結構融通が利く感じ。

Enjoy :)

2014/11/26

MySQL Sandboxで./use_allをrootでやりたい

いっつも忘れるのだ!
前に使ってても忘れるのだ!
http://yoku0825.blogspot.jp/2014/11/mysql-fabric_29.html

こたえ: MYCLIENT_OPTIONS

$ ./use_all "select current_user"
# server: 1:
current_user
msandbox@localhost
# server: 2:
current_user
msandbox@localhost
# server: 3:
current_user
msandbox@localhost
# server: 4:
current_user
msandbox@localhost

$ export MYCLIENT_OPTIONS="-uroot"

$ ./use_all "select current_user"
# server: 1:
current_user
root@localhost
# server: 2:
current_user
root@localhost
# server: 3:
current_user
root@localhost
# server: 4:
current_user
root@localhost

2014/11/25

MySQL Fabricつらい(FABRIC_OPT_MODE = roでスレーブだけを見に行かせる方法が見つかったと思ったのに失敗した)

FABRIC_OPT_MODE = roはスレーブだけを見に行くわけじゃない の時にはweightにしたがってコネクションがラウンドロビンされるから、スレーブのweightを大きくして誤魔化す的なことをしていたけど、なんかそれっぽいものを見つけた。


$ mysqlfabric group lookup_servers my_first_fabric
Password for admin:
Fabric UUID:  5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

                         server_uuid         address    status       mode weight
------------------------------------ --------------- --------- ---------- ------
47cf54df-63fc-11e4-942e-fa163e020fd0 127.0.0.1:20886   PRIMARY READ_WRITE    1.0
4b7036a9-63fc-11e4-942e-fa163e020fd0 127.0.0.1:20887 SECONDARY  READ_ONLY    1.0



このmodeの値、"READ_WRITE", "READ_ONLY"が入ってるところの取りうるバリエーションを知りたい。
マニュアルにはread_only, read_write, offlineっぽいのが書いてはあるものの、 正直信用ならないので


$ grep -r READ_WRITE /usr/lib/python2.6/site-packages/mysql/fabric/
/usr/lib/python2.6/site-packages/mysql/fabric/server.py:    :type mode: OFFLINE, READ_ONLY, READ_WRITE.
/usr/lib/python2.6/site-packages/mysql/fabric/server.py:    READ_WRITE = "READ_WRITE"
/usr/lib/python2.6/site-packages/mysql/fabric/server.py:    SERVER_MODE = [OFFLINE, READ_ONLY, WRITE_ONLY, READ_WRITE]
Binary file /usr/lib/python2.6/site-packages/mysql/fabric/server.pyo matches
/usr/lib/python2.6/site-packages/mysql/fabric/services/highavailability.py:    master.mode = _server.MySQLServer.READ_WRITE
Binary file /usr/lib/python2.6/site-packages/mysql/fabric/services/highavailability.pyc matches
/usr/lib/python2.6/site-packages/mysql/fabric/services/server.py:        (_server.MySQLServer.WRITE_ONLY, _server.MySQLServer.READ_WRITE)
Binary file /usr/lib/python2.6/site-packages/mysql/fabric/services/highavailability.pyo matches
Binary file /usr/lib/python2.6/site-packages/mysql/fabric/services/server.pyo matches
Binary file /usr/lib/python2.6/site-packages/mysql/fabric/services/server.pyc matches
Binary file /usr/lib/python2.6/site-packages/mysql/fabric/server.pyc matches

ああ、あるね、マニュアルに載ってないのあるね。Fabricクオリティだもんね。


$ mysqlfabric server set_mode 47cf54df-63fc-11e4-942e-fa163e020fd0 WRITE_ONLY
Password for admin:
Fabric UUID:  5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

                                uuid finished success result
------------------------------------ -------- ------- ------
0389370b-8f9e-4b68-ab2a-75e2940701cb        1       1      1

state success          when                                                   description
----- ------- ------------- -------------------------------------------------------------
    3       2   1.41689e+09 Triggered by <mysql .fabric.events.event="" 0x217fc90="" at="" object="">.
    4       2   1.41689e+09                          Executing action (_set_server_mode).
    5       2   1.41689e+09                           Executed action (_set_server_mode).


$ mysqlfabric group lookup_servers my_first_fabric
Password for admin:
Fabric UUID:  5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

                         server_uuid         address    status       mode weight
------------------------------------ --------------- --------- ---------- ------
47cf54df-63fc-11e4-942e-fa163e020fd0 127.0.0.1:20886   PRIMARY WRITE_ONLY    1.0
4b7036a9-63fc-11e4-942e-fa163e020fd0 127.0.0.1:20887 SECONDARY  READ_ONLY    1.0



セットできた。。


$ less test.cc
    mysql_options(&mysql, FABRIC_OPT_GROUP, "my_first_fabric");
    mysql_options(&mysql, FABRIC_OPT_DEFAULT_MODE, "ro");
    mysql_options4(&mysql, FABRIC_OPT_GROUP_CREDENTIALS, "msandbox", "msandbox");

    for (n= 0; n < 1000; n++)
    {
      //mysql_options(&mysql, FABRIC_OPT_MODE, "ro");
      mysql_query(&mysql, "SELECT @@port, @@read_only");
      res= mysql_store_result(&mysql);
      row = mysql_fetch_row(res);
      std::cout << "port: " << row[0] << ", read_only: " << row[1] << std::endl;
      mysql_free_result(res);
      mysql_query(&mysql, "COMMIT");
    }

$ gcc -I/usr/local/mysql/include -L/usr/local/mysql/lib -lmysqlclient -g test.cc

$ ./a.out | sort | uniq -c
    506 port: 20886, read_only: 0
    494 port: 20887, read_only: 1
      1 Using Fabric for MYSQL connection

だが有効にならないFabricクオリティ!
とはいえ。


$ gdb a.out
gdb> b Fabric_context::pick_connection
gdb> r
gdb> p *m_grp
..
  m_iter = {
    m_grp = 0x6221a0,
    m_srv_list = std::vector of length 2, capacity 2 = {{
        m_host = "127.0.0.1",
        m_port = 20886,
        m_mode = fabric::Server::WRITE_ONLY,
        m_status = fabric::Server::PRIMARY,
        m_weight = 1,
        m_uuid = {
          uuid = "G\317T\337c\374\021\344\224.\372\026>\002\017", 
        },
        m_parent_group = 0x6221a0,
        m_tainted = false
      }, {
        m_host = "127.0.0.1",
        m_port = 20887,
        m_mode = fabric::Server::READ_ONLY,
        m_status = fabric::Server::SECONDARY,
        m_weight = 1,
        m_uuid = {
          uuid = "Kp6\251c\374\021\344\224.\372\026>\002\017", 
        },
        m_parent_group = 0x6221a0,
        m_tainted = false
      }},
..

クライアント側にWRITE_ONLYは渡されてるから、Connector/Cがまだ対応してないってことなのかしら。labsとはいえ同じ会社が作ってるConnector/Cでこれだというのに、PHPのMySQL Fabric対応実装の mysqlnd とか本当に大丈夫かしら。。愛想尽かされない程度にやってくださいね。。

2014/11/18

MySQL Fabricつらい(Fabricサーバー上のMySQLプロトコルの口でFabricのAPIが呼べる編)

もともとは、mysqlコマンドラインクライアントにHA切り替え機能を実装する編 でいじってたクライアントにもう少しパラメーターを突っ込もうとか Fabricサーバー上でstatusコマンド叩くとmysqlコマンドラインクライアントがクラッシュする件 を直したいとかそんなことを考えてソースを読んでいたときのこと。

mysql-utilities-1.5.3/mysql/fabric/protocols/mysqlrpc.py のMySQLRPCRequestHandler.handleあたりの中で
 925             # Handle CALL
 926             if CHECK_CALL.match(data):
 927                 call_match = PARSE_CALL.match(data)
 928                 if call_match:
 929                     self._handle_call(call_match)
 930                 else:
 931                     self.send_syntax_error()
 932                 continue
 933
 934             # Handle SHOW CREATE PROCEDURE
 935             if CHECK_SHOW_CREATE_PROC.match(data):
 936                 showproc_match = PARSE_SHOW_CREATE_PROC.match(data)
 937                 if showproc_match:
 938                     self._handle_show_create_procedure(showproc_match)
 939                 else:
 940                     self.send_syntax_error()
 941                 continue
 942
 943             # Handle INFORMATION_SCHEMA.ROUTINES
 944             is_routines_match = PARSE_IS_ROUTINES.match(data)
 945             if is_routines_match:
 946                 self._handle_information_schema_routines(is_routines_match)
 947                 continue
 948
 949             # Handle SET
 950             set_match = PARSE_SET.match(data)
 951             if set_match and set_match.group(1).lower() in ('format',):
 952                 format = dequote(set_match.group(2).strip()).strip().lower()
 953                 if format not in ('json',):
 954                     self.send_error(
 955                         errorcode.ER_LOCAL_VARIABLE,
 956                         "Format '{0}' is not supported".format(format),
 957                         "42000"
 958                     )
 959                 else:
 960                     _LOGGER.debug("Format set to %s", format)
 961                     self._format = format
 962
 963             # We accept anything else without doing anything
 964             self.send_packet(self.ok_packet())


なんか特定の文字列が来た時だけ別ハンドルにしているように見える。
さかのぼってこのPARSE_*の中身を覗いてみると、
  57 # Regular expression getting group, command and the arguments as a string
  58 CHECK_CALL = re.compile(
  59     r"""^CALL\s*""", re.IGNORECASE
  60 )
  61 PARSE_CALL = re.compile(
  62     r"""CALL\s+(\w+)\.(\w+)\s*\((.*)\)"""
  63     r"""(?=(?:[^"']*["'][^"']*["'])*[^"']*$)""", re.IGNORECASE
  64 )
  65 PARSE_CALL_ARGS = re.compile(
  66     r""",(?=(?:[^"']*["'][^"']*["'])*[^"']*$)"""
  67 )
  68 PARSE_CALL_KWARG = re.compile(
  69     r"""=(?=(?:[^"']*["'][^"']*["'])*[^"']*$)"""
  70 )
  71
  72 # Regular expression for SHOW CREATE PROCEDURE
  73 CHECK_SHOW_CREATE_PROC = re.compile(
  74     r"""^SHOW\s+CREATE\s+PROCEDURE\s*""", re.IGNORECASE
  75 )
  76 PARSE_SHOW_CREATE_PROC = re.compile(
  77     r"""SHOW\s+CREATE\s+PROCEDURE\s+(\w+).(\w+)$""", re.IGNORECASE
  78 )
  79
  80 # Regular expression for INFORMATION_SCHEMA.ROUTINES
  81 PARSE_IS_ROUTINES = re.compile(
  82     r"""SELECT\s+(.*)\s+FROM\s+INFORMATION_SCHEMA.ROUTINES\s*(.*)""",
  83     re.IGNORECASE
  84 )
  85
  86 # Regular expression getting SET statements
  87 PARSE_SET = re.compile(
  88     r"""SET\s+(\w+)\s*=(.*)""", re.IGNORECASE
  89 )

このあたりのクエリーは無視せずに返してくれるらしい。

$ client/mysql -P 32275 -u admin -p --protocol=tcp
mysql> SELECT * FROM information_schema.routines;
+----------------------------+-----------------+----------------+----------------------+----------------+
| SPECIFIC_NAME              | ROUTINE_CATALOG | ROUTINE_SCHEMA | ROUTINE_NAME         | ROUTINE_TYPE   |
+----------------------------+-----------------+----------------+----------------------+----------------+
| statistics.node            | fabric          | statistics     | node                 | FABRIC_COMMAND |
| statistics.group           | fabric          | statistics     | group                | FABRIC_COMMAND |
| statistics.procedure       | fabric          | statistics     | procedure            | FABRIC_COMMAND |
| group.activate             | fabric          | group          | activate             | FABRIC_COMMAND |
| group.description          | fabric          | group          | description          | FABRIC_COMMAND |
| group.deactivate           | fabric          | group          | deactivate           | FABRIC_COMMAND |
| group.create               | fabric          | group          | create               | FABRIC_COMMAND |
| group.remove               | fabric          | group          | remove               | FABRIC_COMMAND |
| group.add                  | fabric          | group          | add                  | FABRIC_COMMAND |
| group.health               | fabric          | group          | health               | FABRIC_COMMAND |
| group.lookup_servers       | fabric          | group          | lookup_servers       | FABRIC_COMMAND |
| group.destroy              | fabric          | group          | destroy              | FABRIC_COMMAND |
| group.demote               | fabric          | group          | demote               | FABRIC_COMMAND |
| group.promote              | fabric          | group          | promote              | FABRIC_COMMAND |
| group.lookup_groups        | fabric          | group          | lookup_groups        | FABRIC_COMMAND |
| dump.fabric_nodes          | fabric          | dump           | fabric_nodes         | FABRIC_COMMAND |
| dump.shard_index           | fabric          | dump           | shard_index          | FABRIC_COMMAND |
| dump.sharding_information  | fabric          | dump           | sharding_information | FABRIC_COMMAND |
| dump.servers               | fabric          | dump           | servers              | FABRIC_COMMAND |
| dump.shard_tables          | fabric          | dump           | shard_tables         | FABRIC_COMMAND |
| dump.shard_maps            | fabric          | dump           | shard_maps           | FABRIC_COMMAND |
| manage.teardown            | fabric          | manage         | teardown             | FABRIC_COMMAND |
| manage.stop                | fabric          | manage         | stop                 | FABRIC_COMMAND |
| manage.setup               | fabric          | manage         | setup                | FABRIC_COMMAND |
| manage.ping                | fabric          | manage         | ping                 | FABRIC_COMMAND |
| manage.start               | fabric          | manage         | start                | FABRIC_COMMAND |
| manage.logging_level       | fabric          | manage         | logging_level        | FABRIC_COMMAND |
| server.set_mode            | fabric          | server         | set_mode             | FABRIC_COMMAND |
| server.clone               | fabric          | server         | clone                | FABRIC_COMMAND |
| server.list                | fabric          | server         | list                 | FABRIC_COMMAND |
| server.set_weight          | fabric          | server         | set_weight           | FABRIC_COMMAND |
| server.lookup_uuid         | fabric          | server         | lookup_uuid          | FABRIC_COMMAND |
| server.set_status          | fabric          | server         | set_status           | FABRIC_COMMAND |
| server.destroy             | fabric          | server         | destroy              | FABRIC_COMMAND |
| server.create              | fabric          | server         | create               | FABRIC_COMMAND |
| role.list                  | fabric          | role           | list                 | FABRIC_COMMAND |
| user.roles                 | fabric          | user           | roles                | FABRIC_COMMAND |
| user.usercommand           | fabric          | user           | usercommand          | FABRIC_COMMAND |
| user.list                  | fabric          | user           | list                 | FABRIC_COMMAND |
| user.add                   | fabric          | user           | add                  | FABRIC_COMMAND |
| user.password              | fabric          | user           | password             | FABRIC_COMMAND |
| user.delete                | fabric          | user           | delete               | FABRIC_COMMAND |
| threat.report_error        | fabric          | threat         | report_error         | FABRIC_COMMAND |
| threat.report_failure      | fabric          | threat         | report_failure       | FABRIC_COMMAND |
| provider.unregister        | fabric          | provider       | unregister           | FABRIC_COMMAND |
| provider.register          | fabric          | provider       | register             | FABRIC_COMMAND |
| provider.list              | fabric          | provider       | list                 | FABRIC_COMMAND |
| sharding.list_definitions  | fabric          | sharding       | list_definitions     | FABRIC_COMMAND |
| sharding.remove_definition | fabric          | sharding       | remove_definition    | FABRIC_COMMAND |
| sharding.move_shard        | fabric          | sharding       | move_shard           | FABRIC_COMMAND |
| sharding.disable_shard     | fabric          | sharding       | disable_shard        | FABRIC_COMMAND |
| sharding.remove_table      | fabric          | sharding       | remove_table         | FABRIC_COMMAND |
| sharding.split_shard       | fabric          | sharding       | split_shard          | FABRIC_COMMAND |
| sharding.create_definition | fabric          | sharding       | create_definition    | FABRIC_COMMAND |
| sharding.add_shard         | fabric          | sharding       | add_shard            | FABRIC_COMMAND |
| sharding.add_table         | fabric          | sharding       | add_table            | FABRIC_COMMAND |
| sharding.lookup_table      | fabric          | sharding       | lookup_table         | FABRIC_COMMAND |
| sharding.enable_shard      | fabric          | sharding       | enable_shard         | FABRIC_COMMAND |
| sharding.remove_shard      | fabric          | sharding       | remove_shard         | FABRIC_COMMAND |
| sharding.list_tables       | fabric          | sharding       | list_tables          | FABRIC_COMMAND |
| sharding.prune_shard       | fabric          | sharding       | prune_shard          | FABRIC_COMMAND |
| sharding.lookup_servers    | fabric          | sharding       | lookup_servers       | FABRIC_COMMAND |
| snapshot.destroy           | fabric          | snapshot       | destroy              | FABRIC_COMMAND |
| snapshot.create            | fabric          | snapshot       | create               | FABRIC_COMMAND |
| event.trigger              | fabric          | event          | trigger              | FABRIC_COMMAND |
| event.wait_for_procedures  | fabric          | event          | wait_for_procedures  | FABRIC_COMMAND |
+----------------------------+-----------------+----------------+----------------------+----------------+
66 rows in set (0.00 sec)


おお、ホントだ。
これ、バッキングストアやファームのMySQLサーバーの上ではなくFabricサーバーの上なので、たとえば他のテーブルとかは存在しない。というかたぶんクエリーを握りつぶしてる。

mysql> SELECT * FROM information_schema.tables;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW DATABASES;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW GLOBAL VARIABLES;
Query OK, 0 rows affected (0.00 sec)


SHOW CREATE PROCEDUREでプロシージャを見ると、

mysql> SHOW CREATE PROCEDURE group.health\G
*************************** 1. row ***************************
fabric_help: group health group_id

Check if any server within a group has failed and report health
information.

It returns a dictionary where keys are the servers' uuids and the
values are dictionaries which have the following keys:

* is_alive - whether it is possible to access the server or not.     *
status - PRIMARY, SECONDARY, SPARE or FAULTY.     * threads -
Information on the replication threads.
1 row in set (0.00 sec)


簡単なusageが表示される。

mysql> CALL group.health();
+--------------------------------------+-----+-----------------------------------------------+
| fabric_uuid                          | ttl | message                                       |
+--------------------------------------+-----+-----------------------------------------------+
| 5ca1ab1e-a007-feed-f00d-cab3fe13249e |   1 | execute() takes exactly 2 arguments (1 given) |
+--------------------------------------+-----+-----------------------------------------------+
1 row in set (0.01 sec)


ありゃ、引数の数が足りないらしい。や、それこそusageのSHOW CREATE PROCEDUREに書けよ。
取り敢えずたぶんFabric Groupが足りないんだと思うので渡してみようか。

mysql> CALL group.health('my_first_fabric');
+--------------------------------------+-----+---------+
| fabric_uuid                          | ttl | message |
+--------------------------------------+-----+---------+
| 5ca1ab1e-a007-feed-f00d-cab3fe13249e |   1 | NULL    |
+--------------------------------------+-----+---------+
1 row in set (0.14 sec)

+--------------------------------------+----------+-----------+----------------+-------------------+----------------+-----------------+----------+-----------+
| uuid                                 | is_alive | status    | is_not_running | is_not_configured | io_not_running | sql_not_running | io_error | sql_error |
+--------------------------------------+----------+-----------+----------------+-------------------+----------------+-----------------+----------+-----------+
| 47cf54df-63fc-11e4-942e-fa163e020fd0 |        1 | PRIMARY   |              0 |                 0 |              0 |               0 | False    | False     |
| 4b7036a9-63fc-11e4-942e-fa163e020fd0 |        1 | SECONDARY |              0 |                 0 |              0 |               0 | False    | False     |
+--------------------------------------+----------+-----------+----------------+-------------------+----------------+-----------------+----------+-----------+
2 rows in set (0.14 sec)

Empty set (0.14 sec)


うむ。
こんなこともできるんだ、ってことは認識したんだが、

ドキュメントはよ

MySQL Fabricつらい(AWS連携を試したかったけど挫折した編)

MySQL Utilities 1.5.3からMySQL FabricからAWSが叩けるよ! という噂があったので、1週間半くらいがんばっていたメモ。結論先に言うと、1.5.3では使えない気がする。
http://dev.mysql.com/doc/relnotes/mysql-utilities/en/wb-utils-news-1-5-3.html

この記事は2014/11/18現在の情報を元に書かれているので、「ちゃんと情報書いてあるんじゃん」という未来が来るかも知れません。というか来てくれないと困る。ドキュメントちゃんと書いてくれマジで。

まず、"MySQL Fabric AWS"や"MySQL Fabric EC2"でググっても何も出てこない。これがFabricクオリティ。
かろうじてOracle Open World 2014の "Elastic Scalability in MySQL Fabric with OpenStack" の資料に(教えてもらって)辿り着くものの、

Experiments with Amazon AWS
mysqlfabric provider register my_amazon
   AKIAIOSFODNN7EXAMPLE wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
   eu­west­1 ­­provider_type=AMAZON
mysqlfabric machine create my_amazon
   ­­image image­id=ami­892fe1fe
   ­­flavor name=t2.micro

Note!
Not in MySQL Fabric 1.5
Will be on MySQL Lab
ノートの部分がリリースノートと矛盾しててなんかどうなのかって感じがするけれど、providerサブコマンドを使うらしい。

$ mysqlfabric help provider

Commands available in group 'provider' are:
    provider unregister provider_id  [--synchronous]
    provider register provider_id username password url  [--tenant=NONE] [--provider_type=OPENSTACK] [--default_image=NONE] [--default_flavor=NONE] [--extra=NONE] [--synchronous]
    provider list  [--provider_id=NONE]


うん、構文だけ教えてもらっても、肝心な「何を指定すればいいのか」が全くわからない。
そんな時はマニュアル嫁ですよねわかりますん。

http://dev.mysql.com/doc/mysql-utilities/1.5/en/fabric-util.html

(  д ) ゚  ゚ 載ってねええええええ! そもそもproviderコマンドが載ってねええええええ!

さすがのFabricクオリティなので手探りでproviderコマンドを叩いてみる。

$ mysqlfabric provider list
Password for admin:
Fabric UUID:  5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

provider_id type username url tenant default_image default_flavor extra
----------- ---- -------- --- ------ ------------- -------------- -----


$ mysqlfabric provider register my_amazon AKIAIOSFODNN7EXAMPLE wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY eu-west-1 --provider_type=AMAZON
Password for admin:
Fabric UUID:  5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

ProviderError: Provider type (AMAZON) is not supported yet.




あ、ああ、うん、まだサポートされてないのね。うん。さすがFabricクオリティ。リリースノートなんて知ったこっちゃねぇぜ。

( ゚д゚) えっ

しかし更に待ち構える驚きのFabricクオリティ。
じゃあOPENSTACK(暗黙のデフォルト)ならサポートしてるのかっていうと

$ mysqlfabric provider register my_provider provider_user_name provider_passsword provider_url
Password for admin:
Fabric UUID:  5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

ProviderError: Provider type (OPENSTACK) is not supported yet.



ちょwwwwwおまwwwwwデフォルトくらいサポートしろwwwww

なお、同じスライドの別のコマンドをコピペすると

$ mysqlfabric provider register my_stack mats xyzzy http://example.net:5000/v2.0/ my_project --provider_type=OPENSTACK
Password for admin:
Usage:  provider register provider_id username password url  [--tenant=NONE] [--provider_type=OPENSTACK] [--default_image=NONE] [--default_flavor=NONE] [--extra=NONE] [--synchronous]

mysqlfabric: error: Wrong number of parameters were provided for command 'provider register'.

ちょwwwww引数の数がおかしいってwwwww
まあほらこのスライド、Oracle Open World 2014のセッションのやつですからねー。。アテになんなくても仕方ないかしらFabricクオリティだし。

( ゚д゚) えっ

取り敢えず db tech showcase 2014 東京 に来ていた 日本一MySQL Fabricに詳しそうな人 を捕まえてみたところ、開発チームに問い合わせてくれました。

返ってきた返答は
You need to install the OpenStack libraries;
- python-novaclient for Compute node
- python-neutronclient for Network node
だそうで、


$ sudo pip install python-novaclient python-neutronclient
$ mysqlfabric manage stop
$ mysqlfabric manage start --daemonize

$ mysqlfabric provider register my_provider provider_user_name provider_passsword provider_url
Password for admin:
Fabric UUID:  5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

                                uuid finished success result
------------------------------------ -------- ------- ------
dae961a0-4ec2-4e40-b554-88cb284a5a02        1       1      1

state success          when                                                   description
----- ------- ------------- -------------------------------------------------------------
    3       2   1.41628e+09 Triggered by <mysql.fabric.events.Event object at 0x260ebd0>.
    4       2   1.41628e+09                        Executing action (_register_provider).
    5       2   1.41628e+09                         Executed action (_register_provider).


$ mysqlfabric provider list
Password for admin:
Fabric UUID:  5ca1ab1e-a007-feed-f00d-cab3fe13249e
Time-To-Live: 1

provider_id      type           username          url tenant default_image default_flavor extra
----------- --------- ------------------ ------------ ------ ------------- -------------- -----
my_provider OPENSTACK provider_user_name provider_url   None          None           None  None




取り敢えず、not supported yetだけは回避できた模様。

:(;゙゚'ω゚'): なんかドキュメント書けよ。。せめてエラーに出せよ。。

ここまでで1週間半。

しかしなんかどうも、

$ ll /usr/lib/python2.6/site-packages/mysql/fabric/providers
total 96
-rw-r--r-- 1 root root 10088 Oct 21 20:13 __init__.py
-rw-r--r-- 2 root root 12202 Oct 22 01:32 __init__.pyc
-rw-r--r-- 2 root root 12202 Oct 22 01:32 __init__.pyo
-rw-r--r-- 1 root root  2951 Oct 21 20:13 null.py
-rw-r--r-- 2 root root  3873 Oct 22 01:32 null.pyc
-rw-r--r-- 2 root root  3873 Oct 22 01:32 null.pyo
-rw-r--r-- 1 root root 14317 Oct 21 20:13 openstack.py
-rw-r--r-- 2 root root 13481 Oct 22 01:32 openstack.pyc
-rw-r--r-- 2 root root 13481 Oct 22 01:32 openstack.pyo


ここを見てる限り、OpenStack以外はまだ実装されてないんじゃないかなぁって気がする。。また今度。